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:
2026-06-09 13:50:15 +02:00
parent c53e219cca
commit 765996e6df
9 changed files with 107 additions and 116 deletions
-1
View File
@@ -12,7 +12,6 @@ RUN chmod +x /entrypoint.sh
COPY assets /var/www/html/assets/
COPY src /var/www/html/src/
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 500.html /var/www/html/500.html
COPY .env /var/www/html/.env
+7 -1
View File
@@ -42,7 +42,13 @@ http {
client_max_body_size 16k;
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 = /500.html { internal; }
-105
View File
@@ -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>
+8 -6
View File
@@ -37,12 +37,14 @@
<div class="footer-col">
<h5>Locations</h5>
<ul>
<li><a href="/locations/buffalo/">Buffalo</a></li>
<li><a href="/locations/amherst/">Amherst</a></li>
<li><a href="/locations/williamsville/">Williamsville</a></li>
<li><a href="/locations/clarence/">Clarence</a></li>
<li><a href="/locations/east-amherst/">East Amherst</a></li>
<li><a href="/locations/lancaster/">Lancaster</a></li>
<?php
$ldb = new SQLite3(__DIR__ . '/../data/locations.sqlite', SQLITE3_OPEN_READONLY);
$ldb->busyTimeout(3000);
$locs = $ldb->query("SELECT slug, city FROM locations ORDER BY city");
while ($loc = $locs->fetchArray(SQLITE3_ASSOC)):
?><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>
</div>
<div class="footer-col">
Binary file not shown.
Binary file not shown.
Binary file not shown.
+58
View File
@@ -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";
+34 -3
View File
@@ -20,7 +20,7 @@ $page_title = $page['title'];
$page_meta = $page['meta_description'];
$sections = json_decode($page['sections_json'], true) ?? [];
$schema_json = json_encode([
$schema_data = [
'@context' => 'https://schema.org',
'@type' => 'HomeAndConstructionBusiness',
'name' => 'Floor It Hardwood Floors',
@@ -32,9 +32,35 @@ $schema_json = json_encode([
'addressRegion' => 'NY',
'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'],
], 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';
@@ -355,6 +381,11 @@ foreach ($sections as $s) {
</div>
<?php endforeach; ?>
</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>
</section>
<?php