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:
1 parent
2145094e55
commit
5bed636cca
6 files changed
+117
-150
No files matched your search
+27
-26
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Site config template — tracked in git, safe to commit (no real secret
|
||||
* here). Copy this file to config.php (gitignored) and fill in your own
|
||||
* FB_PAGE_ACCESS_TOKEN there.
|
||||
* GOOGLE_PLACES_API_KEY there.
|
||||
*/
|
||||
|
||||
// Base URL the site is currently reachable at. Used for social sharing
|
||||
@@ -11,33 +11,34 @@
|
||||
// this to the permanent domain once the site moves there.
|
||||
define('SITE_URL', 'https://theoldfitz.daviddyer.me');
|
||||
|
||||
// The Old Fitz's Facebook Page ID (from the facebook.com/profile.php?id=... links already on the site).
|
||||
define('FB_PAGE_ID', '100086635646712');
|
||||
// The Old Fitz's Google Place ID — identifies the business for the
|
||||
// Places API. Look it up with Google's Place ID Finder:
|
||||
// https://developers.google.com/maps/documentation/places/web-service/place-id
|
||||
// (search "The Old Fitz Public House, Marion, IN" and copy the ID shown).
|
||||
define('GOOGLE_PLACE_ID', '');
|
||||
|
||||
// Paste a Page Access Token here to make the homepage stats live.
|
||||
// Until this is filled in, the site quietly uses the fallback numbers below.
|
||||
// API key for the Places API (New), used to fetch the live star rating
|
||||
// and review count for the homepage stat card. Until this is filled in,
|
||||
// the site quietly uses the fallback numbers below.
|
||||
//
|
||||
// To generate one:
|
||||
// 1. Go to https://developers.facebook.com/apps and create an app
|
||||
// (type: "Business"), logged in as an admin of The Old Fitz Page.
|
||||
// 2. In Graph API Explorer (developers.facebook.com/tools/explorer),
|
||||
// pick that app, select "Page Access Token" for The Old Fitz,
|
||||
// requesting the pages_read_engagement permission.
|
||||
// 3. Use the "Access Token Debugger" (developers.facebook.com/tools/debug/accesstoken)
|
||||
// to extend it to a long-lived token (~60 days). Long-lived Page
|
||||
// tokens generally don't expire on their own with regular use, but
|
||||
// Meta can still invalidate one (password change, permission
|
||||
// changes, app review) — if that happens, this just falls back to
|
||||
// the numbers below instead of breaking the page.
|
||||
define('FB_PAGE_ACCESS_TOKEN', '');
|
||||
// 1. Go to https://console.cloud.google.com, create (or pick) a
|
||||
// project, and enable "Places API (New)" under APIs & Services.
|
||||
// 2. Enable billing on the project — Google requires a card on file,
|
||||
// but the free monthly credit comfortably covers a rating/review
|
||||
// lookup cached for hours at a time on a low-traffic site like
|
||||
// this one.
|
||||
// 3. Create an API key (APIs & Services → Credentials) and restrict
|
||||
// it to "Places API (New)" only.
|
||||
define('GOOGLE_PLACES_API_KEY', '');
|
||||
|
||||
// How often to re-fetch from Facebook, in seconds. 6 hours is plenty for
|
||||
// numbers that move a few times a month.
|
||||
define('FB_STATS_CACHE_TTL', 6 * 60 * 60);
|
||||
// How often to re-fetch from Google, in seconds. 6 hours is plenty for
|
||||
// a number that moves a few times a month, and stays well inside
|
||||
// Google's caching allowance for Place Details data.
|
||||
define('GOOGLE_STATS_CACHE_TTL', 6 * 60 * 60);
|
||||
|
||||
// Fallback numbers — used whenever there's no token yet, or the Facebook
|
||||
// API call fails for any reason. Update these by hand if you're not
|
||||
// using the live token.
|
||||
define('FB_FALLBACK_RECOMMEND_PCT', 92);
|
||||
define('FB_FALLBACK_REVIEW_COUNT', 11);
|
||||
define('FB_FALLBACK_FOLLOWERS', 2600);
|
||||
// Fallback numbers — used whenever there's no API key/Place ID yet, or
|
||||
// the Google API call fails for any reason. Update these by hand if
|
||||
// you're not using the live lookup.
|
||||
define('GOOGLE_FALLBACK_RATING', 4.8);
|
||||
define('GOOGLE_FALLBACK_REVIEW_COUNT', 71);
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
require __DIR__ . '/config.php';
|
||||
require __DIR__ . '/inc/facebook-stats.php';
|
||||
require __DIR__ . '/inc/google-reviews.php';
|
||||
require __DIR__ . '/inc/announcements.php';
|
||||
require __DIR__ . '/inc/hours.php';
|
||||
$fbStats = fritz_facebook_stats();
|
||||
$googleReviews = fritz_google_reviews();
|
||||
$activeAnnouncements = get_active_announcements();
|
||||
$hoursByDay = get_all_hours();
|
||||
$siteUrl = rtrim(SITE_URL, '/');
|
||||
@@ -125,14 +125,9 @@ $siteUrl = rtrim(SITE_URL, '/');
|
||||
</div>
|
||||
<div class="stat-card reveal">
|
||||
<span class="eyebrow">As the Regulars See It</span>
|
||||
<div class="stat-big"><?= (int) $fbStats['recommend_pct'] ?>%</div>
|
||||
<p class="stat-label">recommend The Old Fitz</p>
|
||||
<p class="stat-source">Source: Facebook reviews (<?= (int) $fbStats['review_count'] ?>)</p>
|
||||
<div class="stat-row">
|
||||
<div class="stat-big"><?= number_format((int) $fbStats['followers']) ?>+</div>
|
||||
<p class="stat-label">people following along on Facebook</p>
|
||||
<p class="stat-source">Source: Facebook followers</p>
|
||||
</div>
|
||||
<div class="stat-big"><?= number_format((float) $googleReviews['rating'], 1) ?>★</div>
|
||||
<p class="stat-label">average rating</p>
|
||||
<p class="stat-source">Source: Google reviews (<?= (int) $googleReviews['review_count'] ?>)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -665,16 +665,6 @@
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.stat-card .stat-row {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.stat-card .stat-row .stat-big {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.stat-source {
|
||||
margin-top: 1.5rem;
|
||||
font-family: var(--font-mono);
|
||||
|
||||
-10
@@ -655,16 +655,6 @@ section {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.stat-card .stat-row {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.stat-card .stat-row .stat-big {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.stat-source {
|
||||
margin-top: 1.5rem;
|
||||
font-family: var(--font-mono);
|
||||
|
||||
Reference in new issue
Block a user