Expand locations to 14, update service slugs, dynamic sitemap and footer
- Add 8 new location pages: Cheektowaga, Depew, Grand Island, Hamburg, Kenmore, Orchard Park, Tonawanda, West Seneca (locations.sqlite) - Update service slugs to floor-* pattern; repair -> restoration (services.sqlite) - Replace static sitemap.xml with dynamic sitemap.php (35 URLs, all routes) - Dynamic footer location list from SQLite (no more hardcoded links) - Reviews page: schema.org AggregateRating + Review from testimonials.sqlite - Expand schema.org areaServed to 14 cities on all pages - Add Elfsight Google Reviews widget to reviews page - Update blog.sqlite content - Drop Dockerfile COPY for deleted sitemap.xml
This commit is contained in:
@@ -12,7 +12,6 @@ RUN chmod +x /entrypoint.sh
|
|||||||
COPY assets /var/www/html/assets/
|
COPY assets /var/www/html/assets/
|
||||||
COPY src /var/www/html/src/
|
COPY src /var/www/html/src/
|
||||||
COPY robots.txt /var/www/html/robots.txt
|
COPY robots.txt /var/www/html/robots.txt
|
||||||
COPY sitemap.xml /var/www/html/sitemap.xml
|
|
||||||
COPY 404.html /var/www/html/404.html
|
COPY 404.html /var/www/html/404.html
|
||||||
COPY 500.html /var/www/html/500.html
|
COPY 500.html /var/www/html/500.html
|
||||||
COPY .env /var/www/html/.env
|
COPY .env /var/www/html/.env
|
||||||
|
|||||||
+7
-1
@@ -42,7 +42,13 @@ http {
|
|||||||
client_max_body_size 16k;
|
client_max_body_size 16k;
|
||||||
|
|
||||||
location = /robots.txt { access_log off; try_files $uri =404; }
|
location = /robots.txt { access_log off; try_files $uri =404; }
|
||||||
location = /sitemap.xml { access_log off; try_files $uri =404; }
|
location = /sitemap.xml {
|
||||||
|
access_log off;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /var/www/html/src/api/sitemap.php;
|
||||||
|
fastcgi_param QUERY_STRING "";
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
}
|
||||||
location = /404.html { internal; }
|
location = /404.html { internal; }
|
||||||
location = /500.html { internal; }
|
location = /500.html { internal; }
|
||||||
|
|
||||||
|
|||||||
-105
@@ -1,105 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>1.0</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/about/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/contact/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/reviews/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/blog/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/services/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/services/floor-installation/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.85</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/services/floor-refinishing/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.85</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/services/floor-restoration/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.85</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/services/floor-sanding/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.85</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/buffalo/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.85</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/amherst/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/williamsville/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/clarence/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/east-amherst/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://floorithardwoodfloors.com/locations/lancaster/</loc>
|
|
||||||
<lastmod>2026-05-31</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
</urlset>
|
|
||||||
@@ -37,12 +37,14 @@
|
|||||||
<div class="footer-col">
|
<div class="footer-col">
|
||||||
<h5>Locations</h5>
|
<h5>Locations</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/locations/buffalo/">Buffalo</a></li>
|
<?php
|
||||||
<li><a href="/locations/amherst/">Amherst</a></li>
|
$ldb = new SQLite3(__DIR__ . '/../data/locations.sqlite', SQLITE3_OPEN_READONLY);
|
||||||
<li><a href="/locations/williamsville/">Williamsville</a></li>
|
$ldb->busyTimeout(3000);
|
||||||
<li><a href="/locations/clarence/">Clarence</a></li>
|
$locs = $ldb->query("SELECT slug, city FROM locations ORDER BY city");
|
||||||
<li><a href="/locations/east-amherst/">East Amherst</a></li>
|
while ($loc = $locs->fetchArray(SQLITE3_ASSOC)):
|
||||||
<li><a href="/locations/lancaster/">Lancaster</a></li>
|
?><li><a href="/locations/<?= htmlspecialchars($loc['slug'], ENT_QUOTES) ?>/"><?= htmlspecialchars($loc['city'], ENT_QUOTES) ?>, NY</a></li>
|
||||||
|
<?php endwhile; $ldb->close(); ?>
|
||||||
|
<li><a href="/locations/">View all service areas</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-col">
|
<div class="footer-col">
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$base = 'https://floorithardwoodfloors.com';
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$data_dir = __DIR__ . '/data';
|
||||||
|
|
||||||
|
function open_db(string $path): SQLite3 {
|
||||||
|
$db = new SQLite3($path, SQLITE3_OPEN_READONLY);
|
||||||
|
$db->busyTimeout(3000);
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
$urls = [];
|
||||||
|
|
||||||
|
$urls[] = ['loc' => $base . '/', 'lastmod' => $today, 'changefreq' => 'weekly', 'priority' => '1.0'];
|
||||||
|
|
||||||
|
$pages_db = open_db($data_dir . '/pages.sqlite');
|
||||||
|
$res = $pages_db->query("SELECT slug FROM pages WHERE in_nav=1 AND slug != 'home' ORDER BY nav_order");
|
||||||
|
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$urls[] = ['loc' => $base . '/' . $row['slug'] . '/', 'lastmod' => $today, 'changefreq' => 'monthly', 'priority' => '0.8'];
|
||||||
|
}
|
||||||
|
$pages_db->close();
|
||||||
|
|
||||||
|
$svc_db = open_db($data_dir . '/services.sqlite');
|
||||||
|
$res = $svc_db->query("SELECT slug FROM services ORDER BY slug");
|
||||||
|
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$urls[] = ['loc' => $base . '/services/' . $row['slug'] . '/', 'lastmod' => $today, 'changefreq' => 'monthly', 'priority' => '0.85'];
|
||||||
|
}
|
||||||
|
$svc_db->close();
|
||||||
|
|
||||||
|
$loc_db = open_db($data_dir . '/locations.sqlite');
|
||||||
|
$res = $loc_db->query("SELECT slug FROM locations ORDER BY slug");
|
||||||
|
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$urls[] = ['loc' => $base . '/locations/' . $row['slug'] . '/', 'lastmod' => $today, 'changefreq' => 'monthly', 'priority' => '0.8'];
|
||||||
|
}
|
||||||
|
$loc_db->close();
|
||||||
|
|
||||||
|
$blog_db = open_db($data_dir . '/blog.sqlite');
|
||||||
|
$res = $blog_db->query("SELECT slug FROM posts WHERE published=1 ORDER BY created_at DESC");
|
||||||
|
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$urls[] = ['loc' => $base . '/blog/' . $row['slug'] . '/', 'lastmod' => $today, 'changefreq' => 'monthly', 'priority' => '0.7'];
|
||||||
|
}
|
||||||
|
$blog_db->close();
|
||||||
|
|
||||||
|
header('Content-Type: application/xml; charset=UTF-8');
|
||||||
|
header('X-Robots-Tag: noindex');
|
||||||
|
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||||
|
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||||
|
foreach ($urls as $u) {
|
||||||
|
echo " <url>\n";
|
||||||
|
echo ' <loc>' . htmlspecialchars($u['loc'], ENT_XML1) . "</loc>\n";
|
||||||
|
echo ' <lastmod>' . $u['lastmod'] . "</lastmod>\n";
|
||||||
|
echo ' <changefreq>' . $u['changefreq'] . "</changefreq>\n";
|
||||||
|
echo ' <priority>' . $u['priority'] . "</priority>\n";
|
||||||
|
echo " </url>\n";
|
||||||
|
}
|
||||||
|
echo '</urlset>' . "\n";
|
||||||
@@ -20,7 +20,7 @@ $page_title = $page['title'];
|
|||||||
$page_meta = $page['meta_description'];
|
$page_meta = $page['meta_description'];
|
||||||
$sections = json_decode($page['sections_json'], true) ?? [];
|
$sections = json_decode($page['sections_json'], true) ?? [];
|
||||||
|
|
||||||
$schema_json = json_encode([
|
$schema_data = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'HomeAndConstructionBusiness',
|
'@type' => 'HomeAndConstructionBusiness',
|
||||||
'name' => 'Floor It Hardwood Floors',
|
'name' => 'Floor It Hardwood Floors',
|
||||||
@@ -32,9 +32,35 @@ $schema_json = json_encode([
|
|||||||
'addressRegion' => 'NY',
|
'addressRegion' => 'NY',
|
||||||
'addressCountry' => 'US',
|
'addressCountry' => 'US',
|
||||||
],
|
],
|
||||||
'areaServed' => ['Buffalo','Amherst','Williamsville','Clarence','East Amherst','Lancaster'],
|
'areaServed' => ['Buffalo','Amherst','Williamsville','Clarence','East Amherst','Lancaster','Cheektowaga','Tonawanda','West Seneca','Orchard Park','Hamburg','Grand Island','Kenmore','Depew'],
|
||||||
'description' => $page['meta_description'],
|
'description' => $page['meta_description'],
|
||||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
];
|
||||||
|
if ($slug === 'reviews') {
|
||||||
|
$rdb = new SQLite3(__DIR__ . '/../data/testimonials.sqlite', SQLITE3_OPEN_READONLY);
|
||||||
|
$rdb->busyTimeout(3000);
|
||||||
|
$r_result = $rdb->query("SELECT quote, author, rating FROM testimonials ORDER BY id");
|
||||||
|
$reviews = [];
|
||||||
|
while ($row = $r_result->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$reviews[] = [
|
||||||
|
'@type' => 'Review',
|
||||||
|
'reviewBody' => $row['quote'],
|
||||||
|
'author' => ['@type' => 'Person', 'name' => $row['author']],
|
||||||
|
'reviewRating' => ['@type' => 'Rating', 'ratingValue' => (string)$row['rating'], 'bestRating' => '5'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$rdb->close();
|
||||||
|
$schema_data['aggregateRating'] = [
|
||||||
|
'@type' => 'AggregateRating',
|
||||||
|
'ratingValue' => '4.9',
|
||||||
|
'bestRating' => '5',
|
||||||
|
'worstRating' => '1',
|
||||||
|
'reviewCount' => (string)max(count($reviews), 1),
|
||||||
|
];
|
||||||
|
if ($reviews) {
|
||||||
|
$schema_data['review'] = $reviews;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$schema_json = json_encode($schema_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
require __DIR__ . '/../components/_header.php';
|
require __DIR__ . '/../components/_header.php';
|
||||||
|
|
||||||
@@ -355,6 +381,11 @@ foreach ($sections as $s) {
|
|||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Elfsight Google Reviews | floor-it -->
|
||||||
|
<div class="elfsight-reviews-wrap" style="margin-top:2rem">
|
||||||
|
<script src="https://elfsightcdn.com/platform.js" async></script>
|
||||||
|
<div class="elfsight-app-b303800c-1d16-47ee-a5fc-8eaacfe67ca0" data-elfsight-app-lazy></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
Reference in New Issue
Block a user