diff --git a/config/functions_reviews.php b/config/functions_reviews.php index edc7413..9736f2e 100644 --- a/config/functions_reviews.php +++ b/config/functions_reviews.php @@ -8,11 +8,15 @@ // google_reviews_min_rating — only reviews with at least this many stars (default 4) // google_reviews_max — how many to show (default 3) // google_reviews_cache_hours — how long a fetch is reused (default 24) +// google_reviews_sort — relevance (Google's order) | newest | longest +// google_reviews_photo_only — only reviewers with a profile photo (0/1) // // Two limits of the API shape the section: // - Place Details returns at most 5 reviews, chosen by Google ("most -// relevant"). There is no "only positive" query: we filter those 5 by -// rating, so on a bad week fewer than `max` may qualify. +// relevant"), and the New API has no sort parameter (checked 2026-09: +// places.get only takes languageCode/regionCode/sessionToken). Rating, +// photo, newest and longest are therefore applied to those 5 here, so on a +// bad week fewer than `max` may qualify. // - Reviews are a billed Enterprise + Atmosphere field. Without the cache every // page view would be one billed call; with it, one call per cache window. // @@ -30,9 +34,16 @@ function reviews_config(): array { 'min_rating' => max(1, min(5, (int) setting('google_reviews_min_rating', 4))), 'max' => max(1, min(5, (int) setting('google_reviews_max', 3))), 'cache_hours' => max(1, (int) setting('google_reviews_cache_hours', 24)), + 'sort' => in_array(setting('google_reviews_sort', 'relevance'), ['relevance', 'newest', 'longest'], true) + ? (string) setting('google_reviews_sort', 'relevance') : 'relevance', + 'photo_only' => (bool) (int) setting('google_reviews_photo_only', 0), ]; } +function reviews_sort_options(): array { + return ['relevance' => 'Relevância (ordem da Google)', 'newest' => 'Mais recentes', 'longest' => 'Mais completas (texto mais longo)']; +} + function reviews_configured(): bool { $c = reviews_config(); return $c['api_key'] !== '' && $c['place_id'] !== ''; @@ -163,9 +174,15 @@ function site_reviews(): ?array { try { $data = reviews_get(); } catch (Throwable $e) { error_log('[reviews] ' . $e->getMessage()); return null; } if (!$data || empty($data['reviews'])) return null; $c = reviews_config(); - $picked = array_values(array_filter($data['reviews'], fn($r) => $r['rating'] >= $c['min_rating'] && trim($r['text']) !== '')); - // Highest rating first; Google's relevance order breaks ties (usort is stable). - usort($picked, fn($a, $b) => $b['rating'] <=> $a['rating']); + $picked = array_values(array_filter($data['reviews'], fn($r) => + $r['rating'] >= $c['min_rating'] && trim($r['text']) !== '' + && (!$c['photo_only'] || $r['author_photo'] !== ''))); + // 'relevance' keeps Google's order. usort is stable, so ties keep it too. + if ($c['sort'] === 'newest') { + usort($picked, fn($a, $b) => strcmp($b['published'], $a['published'])); // ISO-8601 sorts as text + } elseif ($c['sort'] === 'longest') { + usort($picked, fn($a, $b) => mb_strlen($b['text']) <=> mb_strlen($a['text'])); + } $picked = array_slice($picked, 0, $c['max']); if (!$picked) return null; return [ diff --git a/site/assets/css/style.css b/site/assets/css/style.css index 289ea87..76fd956 100644 --- a/site/assets/css/style.css +++ b/site/assets/css/style.css @@ -3384,42 +3384,46 @@ section[id] { .review-card { display: flex; flex-direction: column; + align-items: center; + text-align: center; margin: 0; background: var(--fitme-dark-2); border: 1px solid var(--fitme-dark-3); border-radius: 6px; - padding: 24px; + padding: 28px 24px 24px; } -.rv-text { - flex: 1 1 auto; - margin: .8rem 0 1.2rem; - font-size: .95rem; - line-height: 1.6; - color: #fff; - display: -webkit-box; - -webkit-line-clamp: 7; - -webkit-box-orient: vertical; +.rv-avatar { + width: 84px; + height: 84px; + border-radius: 50%; overflow: hidden; + border: 3px solid var(--fitme-accent); + background: var(--fitme-dark-3); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: .8rem; + flex: 0 0 auto; +} + +.rv-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.rv-avatar span { + font-size: 2rem; + font-weight: 900; + color: var(--fitme-accent); } .rv-author { - display: flex; - align-items: center; - gap: 12px; -} - -.rv-author img { - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; -} - -.rv-author span { display: flex; flex-direction: column; - font-weight: 700; + font-size: 1.05rem; + font-weight: 800; color: #fff; } @@ -3427,8 +3431,27 @@ section[id] { .rv-author a:hover { color: var(--fitme-accent); } .rv-author small { + font-size: .78rem; font-weight: 400; color: var(--fitme-ash); + margin-top: 2px; +} + +.rv-stars-lg { + font-size: 1.3rem; + letter-spacing: 3px; + margin: .6rem 0 .2rem; +} + +.rv-text { + margin: .6rem 0 0; + font-size: .85rem; + line-height: 1.55; + color: var(--fitme-ash); + display: -webkit-box; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; + overflow: hidden; } .reviews-actions { diff --git a/site/index.php b/site/index.php index cce2034..b1005dd 100644 --- a/site/index.php +++ b/site/index.php @@ -501,15 +501,17 @@ if ($reviews):
= nl2br(htmlspecialchars($r['text'])) ?>+ +
= nl2br(htmlspecialchars($r['text'])) ?>