sayamaapps / kirby-noren
Opening hours for Kirby CMS with an open-now status, closures and German public holidays.
Package info
github.com/sayamaapps/kirby-noren
Type:kirby-plugin
pkg:composer/sayamaapps/kirby-noren
Requires
- php: ^8.2
- ext-mbstring: *
- getkirby/composer-installer: ^1.2
Requires (Dev)
- ergebnis/composer-normalize: ^2.54
- getkirby/cms: ^5.0
- icanhazstring/composer-unused: ^0.9.7
- maglnet/composer-require-checker: ^4.0
- phpmd/phpmd: ^2.15
- phpunit/phpunit: 11.5.55
- vimeo/psalm: ^6.20
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Opening hours for Kirby CMS 5, with an "open now" status and German public holidays.
Versions: Kirby 5 · PHP 8.2 to 8.5 · MIT · Changes · Reference
Noren (暖簾) is the curtain hung in a shop's doorway while it is open. On a website, the plugin answers the same question: are you open right now, and if not, when again? It prints unstyled markup and provides the data for your own markup and schema.org.
Features
- Several blocks per day, including overnight blocks such as Friday 18:00 to 2:00.
- Areas and locations: one plan per shop, café or site.
- Closures and special hours, with an automatic "back on" date.
- German public holidays for all 16 states, calculated locally.
- "Open now" status in the business's time zone, computed on each request.
- Unstyled snippets, plus arrays for your own markup and schema.org JSON-LD.
Requirements
- Kirby 5 (
getkirby/cms5.0 or newer) - PHP 8.2, 8.3, 8.4 or 8.5
Installation
Pick one of three ways. The plugin always ends up in site/plugins/noren/.
Composer (the plugin goes to vendor/ and is linked into site/plugins/):
composer require sayamaapps/kirby-noren
Add site/plugins/noren/ to .gitignore and deploy it like vendor/.
ZIP: download the release archive from the releases page, unpack it and copy the folder to site/plugins/noren/.
Git submodule:
git submodule add https://github.com/sayamaapps/kirby-noren.git site/plugins/noren
Usage
Setup
Switch Noren on in site/config/config.php:
return [ 'sayamaapps.noren.active' => true, 'sayamaapps.noren.language' => 'de', // German texts (optional) 'sayamaapps.noren.holidays' => 'BY', // Bavarian public holidays (optional) ];
Texts are English unless sayamaapps.noren.language, the site language or Kirby's locale says otherwise. A German single-language site without a locale must set sayamaapps.noren.language. Noren keeps its texts in its own tables (translations/), registered with Kirby as noren.* keys, so Sayamaapps\Noren\I18n::t() returns them without a Kirby app too. The class name I18n is also used by Kirby (Kirby\Toolkit\I18n). The two are unrelated; refer to Noren's class by its full name.
Give the location pages this blueprint, for example site/blueprints/pages/betrieb.yml:
title: noren.panel.page.title extends: noren/pages/location
Keep the title line, because extends: does not carry it over. Pages with the template betrieb are locations (see sayamaapps.noren.template).
Snippets
Call snippet('noren/status') for the "open now" line, snippet('noren/table') for the full week and snippet('noren/compact') for short lines.
| Snippet | Variables (all optional) |
|---|---|
noren/status |
$only (a unit, area or location id), $link (page with all hours), $units, $now |
noren/table |
$area or $schedule, $now |
noren/compact |
$area or $schedule, $mode (grouped or consecutive), $title, $now |
To change the markup, create a file with the same name in site/snippets/noren/. The markup classes are listed in docs/reference.md.
Own markup and schema.org
Site and page methods return the data behind the snippets, for example site()->norenStatus('laden') or page()->norenTable(). When Noren is off or there is no plan, they return null or []. All methods and return shapes: docs/reference.md.
<script type="application/ld+json"> <?= json_encode(site()->norenSchema(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?> </script>
Content field contract
The German field names are the defaults; rename them with sayamaapps.noren.fields. One location with several areas (a shop and a café, say) lists them in bereiche and gives each hours row its bereich. Several sites are several location pages; sayamaapps.noren.locations selects them explicitly.
| Field (option key) | Content |
|---|---|
bereiche (areas) |
Areas: schluessel (required), name, kurzname |
zeiten (hours) |
Weekly rows: bereich, tag, von1, bis1, von2, bis2, hinweis |
schliessungen (closures) |
Closures and special days: von, bis (Y-m-d, inclusive), bereich, anlass, hinweis, oeffnet, schliesst (short hours instead of closed) |
zeitenschluessel (areaRef) |
On a non-location page: the area whose hours apply |
kurzname (short) |
On a location page: the short name shown as status label |
tagisMontagtoSonntag, the English names, or1to7(Monday = 1). A day without rows is closed.- Rows for the same weekday add up, and blocks that touch or overlap form one opening. A block needs both times; a half-filled pair is ignored.
- An end after midnight belongs to the start day: Friday 18:00 to 2:00 goes into Friday's row, up to
overnightUntil. An end of00:00means midnight, and00:00to00:00is open around the clock. - The legacy columns
vm_von,vm_bis,nm_von,nm_bisandnm_hinweisare read when their new column is empty.
Configuration
Every option is sayamaapps.noren.<name>, written flat: 'sayamaapps.noren.active' => true. The nested form 'noren' => ['active' => true] works for the 1.x name only (see below). null means "not set". Booleans must be real true or false. A broken value is logged once and the default is used; with Kirby's debug option on, it throws instead.
The 1.x name noren.<name> still works as a fallback until 2.0, flat or nested, and is read without a warning. If both names are set, the new one wins. See Deprecations.
| Option | Default | Description |
|---|---|---|
active |
false |
Master switch. Off: methods return null or [], snippets print nothing. |
template |
'betrieb' |
Template of the location pages: a string or a list. |
locations |
null |
Closure or list of page ids for the locations; overrides template. |
fields |
see Content field contract | Field names as a map. Give only the keys you change. |
timezone |
'Europe/Berlin' |
Time zone of all date logic. |
language |
site language | en or de. |
holidays |
false |
Public holidays: a state code, or an array with state and flags. See Public holidays. |
holidayEve |
false |
Adds a notice to the status when tomorrow is a public holiday. |
holidayRow |
true |
"Public holidays" row in the table (needs holidays). |
holidayNote |
true |
Note on the next public holiday under the table (needs holidays). |
holidayNoteDays |
14 |
How many days ahead that note looks. |
leadDays |
14 |
Closures and special days show on the site this many days ahead. schema.org always gets all of them. |
lookahead |
90 |
How many days the status searches for the next opening. |
overnightUntil |
'06:00' |
Latest end of a block running past midnight, as 'HH:MM'. false turns overnight blocks off. |
closureLabel |
Betriebsferien / Closed for holidays |
Name of multi-day closures, for example 'Praxisurlaub'. Translated by default. |
compact |
'grouped' |
Default mode of noren/compact: grouped or consecutive. |
status |
true |
false hides the noren/status snippet. |
mergeAreas |
false |
Areas with identical hours become one unit, such as "Hofladen und Hofcafé". site()->norenUnits(null, false) still returns one unit per area. |
Example with several options:
return [ 'sayamaapps.noren.active' => true, 'sayamaapps.noren.template' => ['betrieb', 'praxis'], 'sayamaapps.noren.timezone' => 'Europe/Berlin', 'sayamaapps.noren.language' => 'de', 'sayamaapps.noren.holidays' => ['state' => 'BY', 'augsburg' => true], 'sayamaapps.noren.holidayEve' => true, 'sayamaapps.noren.overnightUntil' => '04:00', 'sayamaapps.noren.closureLabel' => 'Praxisurlaub', 'sayamaapps.noren.mergeAreas' => true, ];
Deprecations
Deprecated in 1.4, removal planned for 2.0. Nothing changes in 1.x: the old names keep working, without a warning.
| 1.x | Now | Notes |
|---|---|---|
noren.<option> in config.php |
sayamaapps.noren.<option> |
Still read as fallback. Rename the keys in your site config. |
noren.<name> translation keys |
sayamaapps.noren.<name> |
Both are registered in 1.4. |
noren_option('name', $default) |
Sayamaapps\Noren\Options::get(kirby(), 'name', $default) |
Global function, kept as a guarded wrapper. |
Noren\Schedule, Noren\I18n, … (all classes) |
Sayamaapps\Noren\Schedule, Sayamaapps\Noren\I18n, … |
Class aliases. instanceof and type hints keep working. |
Noren\Log::forget() |
none | Test hook, kept for existing callers. |
In warnings and logs, a broken option is named as the site wrote it: noren.timezone for the 1.x name, sayamaapps.noren.timezone otherwise. Log searches for noren. keep matching.
The site and page methods (norenStatus(), norenTable() and so on) and the snippet names keep their names. Their return values do not change.
Planned for 2.0
- Runtime deprecation notices for the 1.x option names and
noren_option(). In debug mode, Kirby shows them as an error page. - Invalid options throw
Kirby\Exception\InvalidArgumentExceptionin debug mode instead of\InvalidArgumentException. Acatch (\InvalidArgumentException)does not catch it. - Removal of the 1.x option names,
noren_option(), theNoren\class aliases andLog::forget().
Public holidays
Off by default. With holidays set, the holidays of a state are calculated for any year, Easter included. Pass flags in an array, for example ['state' => 'BY', 'augsburg' => true, 'extra' => ['06-24' => 'Stadtfest']]. The regional days of each state are listed in docs/reference.md.
| Flag | Default | Applies to |
|---|---|---|
assumption |
true |
Bavaria: Assumption Day, in Catholic municipalities only. Always in Saarland. |
augsburg |
false |
Bavaria: Augsburg Peace Festival, in the city of Augsburg only. |
corpusChristi |
false |
Saxony and Thuringia: Corpus Christi, in some Catholic municipalities. |
sundays |
true |
Brandenburg: Easter Sunday and Whit Sunday, which are statutory holidays there. |
- Nine days apply in every state: New Year's Day, Good Friday, Easter Monday, Labour Day, Ascension Day, Whit Monday, German Unity Day, and both Christmas days.
extrakeys areMM-DD(every year) orY-m-d(once). A day that is already a statutory holiday keeps its name.
Privacy
Noren processes no visitor data. It sets no cookies, uses no browser storage and makes no request to a third party, so a site's privacy policy needs no entry for it.
Maintenance
Tests: composer install, then composer test. Contributing and the checks: .github/CONTRIBUTING.md. Issues and pull requests are welcome, without a support promise. Report security problems privately, see .github/SECURITY.md. Changes: CHANGELOG.md.
License
MIT, see LICENSE.