// === Google Reviews (ACF local group on Options) === add_action('acf/init', function () { if( function_exists('acf_add_local_field_group') ){ acf_add_local_field_group([ 'key' => 'group_spotts_google_reviews', 'title' => 'Footer: Google Reviews', 'fields' => [ [ 'key' => 'field_gr_enable', 'label' => 'Enable Google Reviews', 'name' => 'gr_enable', 'type' => 'true_false', 'ui' => 1, 'default_value' => 0, ], [ 'key' => 'field_gr_title', 'label' => 'Section Title', 'name' => 'gr_title', 'type' => 'text', 'placeholder' => 'What clients say on Google', ], [ 'key' => 'field_gr_place_id', 'label' => 'Google Place ID', 'name' => 'gr_place_id', 'type' => 'text', 'instructions' => 'e.g. ChIJ... (Place ID for spottsgardens.com business)', ], [ 'key' => 'field_gr_api_key', 'label' => 'Google API Key (Places)', 'name' => 'gr_api_key', 'type' => 'text', 'instructions' => 'Must have Places API enabled. If left blank, the site-wide ACF Google key will NOT be used automatically—put a key here.', ], [ 'key' => 'field_gr_max', 'label' => 'Max Reviews to Show', 'name' => 'gr_max', 'type' => 'number', 'default_value' => 3, 'min' => 1, 'max' => 5 ], [ 'key' => 'field_gr_min_rating', 'label' => 'Minimum Star Rating', 'name' => 'gr_min_rating', 'type' => 'number', 'default_value' => 4, 'min' => 1, 'max' => 5, 'step' => 0.1 ], ], 'location' => [[[ 'param' => 'options_page', 'operator' => '==', 'value' => 'acf-options', ]]], ]); } }); // === Fetch Google Reviews with caching === function spotts_get_google_reviews() { if (!function_exists('get_field')) return []; $enabled = get_field('gr_enable', 'options'); if (!$enabled) return []; $place_id = trim((string) get_field('gr_place_id', 'options')); $api_key = trim((string) get_field('gr_api_key', 'options')); $max_show = (int) (get_field('gr_max', 'options') ?: 3); $min_rating = (float) (get_field('gr_min_rating', 'options') ?: 4); if (!$place_id || !$api_key) return []; $transient_key = 'spotts_gr_' . md5($place_id . '|' . $max_show . '|' . $min_rating); $cached = get_transient($transient_key); if ($cached !== false) return $cached; $endpoint = add_query_arg([ 'place_id' => $place_id, 'fields' => 'name,url,rating,user_ratings_total,reviews', 'reviews_sort' => 'newest', 'key' => $api_key, ], 'https://maps.googleapis.com/maps/api/place/details/json'); $resp = wp_remote_get($endpoint, ['timeout' => 12]); if (is_wp_error($resp)) return []; $code = wp_remote_retrieve_response_code($resp); if ($code !== 200) return []; $data = json_decode(wp_remote_retrieve_body($resp), true); if (!is_array($data) || empty($data['result'])) return []; $result = $data['result']; $reviews = isset($result['reviews']) && is_array($result['reviews']) ? $result['reviews'] : []; // Filter & trim $reviews = array_values(array_filter($reviews, function($r) use ($min_rating){ return isset($r['rating']) && (float)$r['rating'] >= $min_rating; })); $reviews = array_slice($reviews, 0, max(1, min(5, $max_show))); // Normalize fields we need $normalized = []; foreach ($reviews as $r) { $normalized[] = [ 'author_name' => $r['author_name'] ?? 'Google User', 'profile_photo_url' => $r['profile_photo_url'] ?? '', 'rating' => (float) ($r['rating'] ?? 0), 'text' => $r['text'] ?? '', 'relative_time' => $r['relative_time_description'] ?? '', 'author_url' => $r['author_url'] ?? '', 'place_name' => $result['name'] ?? '', 'place_url' => $result['url'] ?? '', 'overall_rating' => (float) ($result['rating'] ?? 0), 'total_ratings' => (int) ($result['user_ratings_total'] ?? 0), ]; } // Cache 12 hours set_transient($transient_key, $normalized, 12 * HOUR_IN_SECONDS); return $normalized; }