diff --git a/config.example.php b/config.example.php index a9ff940..ff59474 100644 --- a/config.example.php +++ b/config.example.php @@ -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); diff --git a/inc/facebook-stats.php b/inc/facebook-stats.php deleted file mode 100644 index 3df9cac..0000000 --- a/inc/facebook-stats.php +++ /dev/null @@ -1,94 +0,0 @@ - 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', - ]; -} diff --git a/inc/google-reviews.php b/inc/google-reviews.php new file mode 100644 index 0000000..83fafe4 --- /dev/null +++ b/inc/google-reviews.php @@ -0,0 +1,85 @@ + 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', + ]; +} diff --git a/index.php b/index.php index 87cc6a2..da0c904 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,9 @@
As the Regulars See It -
%
-

recommend The Old Fitz

-

Source: Facebook reviews ()

-
-
+
-

people following along on Facebook

-

Source: Facebook followers

-
+
★
+

average rating

+

Source: Google reviews ()

diff --git a/src/scss/styles.scss b/src/scss/styles.scss index eaeec2d..dcdc102 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -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); diff --git a/styles.css b/styles.css index 2d14b3a..cc4a6b1 100644 --- a/styles.css +++ b/styles.css @@ -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);