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', ]; }