Switch homepage review stat from Facebook to Google

- Replace inc/facebook-stats.php with inc/google-reviews.php: fetches
  average star rating + review count from the Places API (New) instead
  of Facebook's Graph API, same cache-to-JSON/fallback-on-failure shape
  as before.
- index.php: stat card now shows the native star rating ("4.8★ average
  rating") instead of a recommend %, sourced from Google. Drops the
  Facebook-followers line entirely — Google's API has no equivalent and
  showing a stale/static number wasn't worth keeping. "Follow on
  Facebook" links elsewhere on the site are untouched.
- config.example.php: swap FB_PAGE_ID/FB_PAGE_ACCESS_TOKEN/
  FB_STATS_CACHE_TTL/FB_FALLBACK_* for GOOGLE_PLACE_ID/
  GOOGLE_PLACES_API_KEY/GOOGLE_STATS_CACHE_TTL/GOOGLE_FALLBACK_RATING/
  GOOGLE_FALLBACK_REVIEW_COUNT (set to the pub's actual 4.8★/71 reviews),
  with setup instructions in comments. config.php (gitignored) updated
  to match.
- Remove the now-dead .stat-row CSS that only existed for the dropped
  followers line; rebuild styles.css.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
David DyerandClaude Sonnet 5 committed 2026-09-01 18:41:28 -04:00
1 parent 2145094e55
commit 5bed636cca
6 files changed
+117 -150

No files matched your search

-94
View File
@@ -1,94 +0,0 @@
<?php
/**
* Pulls "recommend %", review count, and follower count from The Old
* Fitz's Facebook Page via the Graph API, cached to a local JSON file so
* we don't hit Facebook on every page load. Falls back to the numbers in
* config.php whenever there's no access token yet, or the API call fails
* for any reason — the homepage never breaks because of this.
*
* Known limitation: Facebook's newer thumbs-up/down "recommendation"
* system (the "X% recommend (Y reviews)" wording used on the Page today)
* doesn't have a confirmed, stable Graph API field the way star ratings
* do. This code requests overall_star_rating/rating_count and converts
* a star rating to a percentage if Facebook returns one; if the Page's
* review data isn't exposed that way, recommend_pct/review_count will
* silently stay at the config.php fallback while followers_count still
* goes live. That's expected, not a bug — Meta's review-related API
* access has gotten more restricted over time.
*/
function fritz_facebook_stats(): array {
$fallback = [
'recommend_pct' => FB_FALLBACK_RECOMMEND_PCT,
'review_count' => FB_FALLBACK_REVIEW_COUNT,
'followers' => FB_FALLBACK_FOLLOWERS,
'source' => 'fallback',
'fetched_at' => null,
];
if (FB_PAGE_ACCESS_TOKEN === '') {
return $fallback;
}
$cacheFile = __DIR__ . '/../cache/facebook-stats.json';
if (is_file($cacheFile)) {
$cached = json_decode((string) file_get_contents($cacheFile), true);
if (is_array($cached) && (time() - ($cached['fetched_at'] ?? 0)) < FB_STATS_CACHE_TTL) {
$cached['source'] = 'cache';
return $cached;
}
}
$fresh = fritz_fetch_facebook_stats($fallback);
$fresh['fetched_at'] = time();
$cacheDir = dirname($cacheFile);
if (!is_dir($cacheDir)) {
@mkdir($cacheDir, 0775, true);
}
@file_put_contents($cacheFile, json_encode($fresh));
return $fresh;
}
function fritz_fetch_facebook_stats(array $fallback): array {
if (!function_exists('curl_init')) {
return $fallback;
}
$url = sprintf(
'https://graph.facebook.com/v21.0/%s?fields=followers_count,overall_star_rating,rating_count&access_token=%s',
FB_PAGE_ID,
urlencode(FB_PAGE_ACCESS_TOKEN)
);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $httpCode !== 200) {
return $fallback;
}
$data = json_decode($body, true);
if (!is_array($data) || isset($data['error'])) {
return $fallback;
}
$recommendPct = isset($data['overall_star_rating'])
? (int) round(($data['overall_star_rating'] / 5) * 100)
: $fallback['recommend_pct'];
return [
'recommend_pct' => $recommendPct,
'review_count' => $data['rating_count'] ?? $fallback['review_count'],
'followers' => $data['followers_count'] ?? $fallback['followers'],
'source' => 'live',
];
}
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* Pulls the average star rating and review count from The Old Fitz's
* Google Business Profile via the Places API (New), cached to a local
* JSON file so we don't hit Google on every page load. Falls back to
* the numbers in config.php whenever there's no API key/Place ID yet,
* or the API call fails for any reason — the homepage never breaks
* because of this.
*/
function fritz_google_reviews(): array {
$fallback = [
'rating' => GOOGLE_FALLBACK_RATING,
'review_count' => GOOGLE_FALLBACK_REVIEW_COUNT,
'source' => 'fallback',
'fetched_at' => null,
];
if (GOOGLE_PLACES_API_KEY === '' || GOOGLE_PLACE_ID === '') {
return $fallback;
}
$cacheFile = __DIR__ . '/../cache/google-reviews.json';
if (is_file($cacheFile)) {
$cached = json_decode((string) file_get_contents($cacheFile), true);
if (is_array($cached) && (time() - ($cached['fetched_at'] ?? 0)) < GOOGLE_STATS_CACHE_TTL) {
$cached['source'] = 'cache';
return $cached;
}
}
$fresh = fritz_fetch_google_reviews($fallback);
$fresh['fetched_at'] = time();
$cacheDir = dirname($cacheFile);
if (!is_dir($cacheDir)) {
@mkdir($cacheDir, 0775, true);
}
@file_put_contents($cacheFile, json_encode($fresh));
return $fresh;
}
function fritz_fetch_google_reviews(array $fallback): array {
if (!function_exists('curl_init')) {
return $fallback;
}
$url = sprintf(
'https://places.googleapis.com/v1/places/%s',
rawurlencode(GOOGLE_PLACE_ID)
);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'X-Goog-Api-Key: ' . GOOGLE_PLACES_API_KEY,
// Field mask is required by the New Places API (400 without
// it) and also determines the billing SKU — rating and
// userRatingCount are "Place Details (New) - Atmosphere".
'X-Goog-FieldMask: rating,userRatingCount',
],
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $httpCode !== 200) {
return $fallback;
}
$data = json_decode($body, true);
if (!is_array($data) || isset($data['error'])) {
return $fallback;
}
return [
'rating' => $data['rating'] ?? $fallback['rating'],
'review_count' => $data['userRatingCount'] ?? $fallback['review_count'],
'source' => 'live',
];
}