sayamaapps / kirby-koe
Google reviews for Kirby CMS, cached on your server; visitors' browsers never contact Google.
Requires
- php: ^8.2
- ext-mbstring: *
- getkirby/composer-installer: ^1.2
Requires (Dev)
- ergebnis/composer-normalize: 2.54.0
- getkirby/cms: ^5.0
- icanhazstring/composer-unused: 0.9.7
- maglnet/composer-require-checker: 4.17.0
- phpmd/phpmd: 2.15.0
- phpunit/phpunit: 11.5.55
- vimeo/psalm: 6.20.1
Suggests
- ext-curl: Faster Places API requests; without it the streams wrapper is used.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-31 18:03:10 UTC
README
Google reviews for Kirby CMS, served from your own server.
Kirby 5 · PHP 8.2–8.5 · sayamaapps/kirby-koe 2.x
Koe shows a business's Google rating, its review count and a few selected reviews on a Kirby site. The server fetches them from the Places API (New) and keeps them in a short-lived cache, so visitors' browsers never contact Google.
Features
- Server-side fetch from the Places API (New), held in a short-lived cache (24 hours by default).
- Rule-based selection: by default five-star reviews of at least 80 characters, the three newest, not older than 24 months.
- Optional approved list, so the business decides which reviews appear.
- Google attribution built in: author with profile picture, links to the review, "Google Maps" attribution, a notice on the selection.
- API key kept on the server; profile pictures served from your own site.
- English and German; no database, no cookies.
Requirements
- PHP 8.2–8.5 with
mbstring;curlrecommended. Outgoing HTTPS toplaces.googleapis.comand*.googleusercontent.com. - Kirby 5 with a writable cache directory (
site/cache/by default). - A Google Cloud project with Places API (New) enabled and an API key restricted to that API. Use one project and key per site, and cap the daily quota: a fetch is one request, so 30 per day is enough at any
sayamaapps.koe.cacheHours.
Installation
With Composer (recommended):
composer require sayamaapps/kirby-koe
Composer installs to site/plugins/koe/ and links the CLI as vendor/bin/koe-fetch. Add that path to .gitignore and deploy it like vendor/.
As a ZIP: download the release from GitHub and unzip it to site/plugins/koe/. The CLI is then site/plugins/koe/bin/koe-fetch.
As a Git submodule: git submodule add https://github.com/sayamaapps/kirby-koe.git site/plugins/koe.
Ignore site/cache/ in git too, and leave it out of backups.
Usage
Configure in site/config/config.php. Koe is off by default:
return [ 'sayamaapps.koe.active' => true, 'sayamaapps.koe.placeId' => 'ChIJ…', // from koe-fetch, see below ];
Keep the key out of git: set sayamaapps.koe.key in a file your deploy writes, or the environment variable GOOGLE_PLACES_API_KEY on the web server.
Show the reviews in a template:
<?php snippet('koe/reviews') ?> <?php snippet('koe/reviews', ['title' => 'What our guests say', 'logo' => 'assets/GoogleMaps_Logo_Gray.svg']) ?>
The section is omitted while Koe is off, misconfigured or the cache is empty. The first view after the cache expires starts the fetch after its response; later views show the reviews. A failed fetch is logged and retried after 15 minutes at the earliest.
Finding the place id
GOOGLE_PLACES_API_KEY=… KOE_QUERY="Business name, Street 1, 12345 Town" vendor/bin/koe-fetch
koe-fetch prints the place (Found: Name (ChIJ…), the value for koe.placeId) and its reviews with their ids (for koe.approved). It writes nothing. Use the name and address exactly as in the Google profile. Options: --query= or KOE_QUERY; --language= or KOE_LANGUAGE (default en). Exit codes: 0 done, 1 error, 3 no key. Google returns at most five reviews per place; reviews without text are skipped.
Your own markup. Create site/snippets/koe/reviews.php (Kirby takes it before the package's snippet), or call the site method from any snippet:
$r = site()->koeReviews(); // null: show nothing // rating, count, limit, profileUrl, notice, reviews[] with name, rating, text, date, month, url, avatar
$site->koeProfileUrl() returns the profile link. Site snippets read options with Sayamaapps\Koe\Options::get(kirby(), 'name', $default).
Google attribution. Google's Places policies require the author with profile picture, both linked to url, the "Google Maps" attribution and the selection notice ($r['notice'], e.g. "Selection: 5 stars, 80+ characters, at most 24 months old, newest first."). The default snippet does all of it; your own markup must do the same.
Options
Keys are sayamaapps.koe.<name>, written flat ('sayamaapps.koe.active' => true). The names of 2.3 (koe.<name>) still work, see Names of earlier versions. null means "not set".
Option (after sayamaapps.koe.) |
Default | Description |
|---|---|---|
active |
false |
Master switch. Only true turns Koe on; otherwise nothing is shown and nothing is fetched. |
placeId |
null |
Required. The Google place id, from koe-fetch. If Google answers for another id, nothing is kept. |
key |
GOOGLE_PLACES_API_KEY |
Places API key, a string or a closure. Without it, the environment variable is read. |
cacheHours |
24 |
Whole hours, 1 to 24: how long reviews and pictures stay cached. |
minRating |
5 |
Minimum stars, 1 to 5. |
minLength |
80 |
Minimum text length in characters; 0 = no limit. |
maxAgeMonths |
24 |
Maximum age in months, counted from today (Europe/Berlin); 0 = no limit. |
maxCount |
3 |
Maximum number of single reviews shown. |
truncate |
280 |
Longer texts are cut at a word boundary and get "…"; 0 = never. |
approved |
null |
null: rules only. A list of review ids: only those, still subject to the rules. []: no single reviews. |
title |
translated | Heading of the default snippet (Reviews on Google / Bewertungen auf Google). |
logo |
null |
Path under public/ to the official Google Maps logo. Without it, the attribution is the text "Google Maps". |
language |
site language | en or de. Otherwise the site's default language, then Kirby's locale, then English. |
Changing placeId, language, cacheHours or a selection rule makes the next view fetch anew. Kirby's debug option makes the error log say why nothing is shown; failed fetches are always logged. Other cache drivers work too, e.g. 'sayamaapps.koe.cache' => ['type' => 'apcu'].
Names of earlier versions
| Name | Now | Notes |
|---|---|---|
koe.<option> in config.php |
sayamaapps.koe.<option> |
Still read as fallback, without notice. If both are set, the new one wins. Rename the keys in your site config. |
Koe\* classes |
Sayamaapps\Koe\* |
Class aliases, loaded with the plugin. |
koe_option($key, $default) |
Sayamaapps\Koe\Options::get(kirby(), $key, $default) |
Deprecated. Keeps working and raises no notice. |
Koe\PlacesException |
unchanged | Not an alias: the exception is declared under this name. |
Planned for the next major version
The option names koe.<name> are read until the next major version, then no longer. Translation keys koe.<key> are removed then too; sayamaapps.koe.<key> stays. The names above will be removed. Sites must rename their option keys with that update.
Tests: composer test; all checks: composer ci (after composer install --ignore-platform-req=ext-gd). Issues and pull requests are welcome, without a support promise. Report security problems privately through GitHub's vulnerability reporting. Changes: CHANGELOG.md.
Implementation notes
The Places client (Sayamaapps\Koe\Places, with its own transport: curl, or streams without curl) and the translation table (Sayamaapps\Koe\I18n) do not use Kirby's Remote and I18n. The Places client throws Koe\PlacesException, the name it had before, a plain RuntimeException, and not one of Kirby's exceptions, because the core also runs without Kirby in the koe-fetch CLI. The snippet texts must match what the CLI prints. Everything else follows the Kirby standard: options, translations, site methods, routes and the cache.
Privacy
- No request from the visitor's browser. No script, embed, font or image comes from Google. Profile pictures and the optional "Google Maps" logo are served from your site; Google opens only when a visitor clicks a link.
- Cache only. Reviews and pictures stay in Kirby's cache for
sayamaapps.koe.cacheHoursat most, then they are fetched anew. Nothing else from Google is kept. The plugin sets no cookies and uses no browser storage.
See PRIVACY.md (English) and docs/DATENSCHUTZ.md (German text for privacy policies). They are not legal advice.
License
MIT, see LICENSE.md.