Display-Seiten: Gäste holen sich den Zugang selbst

Für Empfang, Lobby oder Tagungsraum lässt sich je Site eine öffentliche
Seite anlegen (kiosk.php), die auf einem Bildschirm oder Tablet läuft:
ein großer Knopf, ein Klick, ein Zugangscode mit QR-Code. Nach einer
einstellbaren Anzeigedauer springt der Bildschirm zurück, damit der
nächste Gast nicht den Code seines Vorgängers sieht.

Verwaltung unter Administration → Display-Seiten:
- Site und optionales Voucher-Profil (bestimmt Laufzeit, Geräte, QoS)
- eigene Überschrift und Text für den Bildschirm
- Codes pro Tag, Wartezeit zwischen zwei Codes, Anzeigedauer
- geheimer Link zum Kopieren, als QR-Code anzeigbar und jederzeit
  erneuerbar (der alte Link gilt dann sofort nicht mehr)

Absicherung: der Link ist der Zugang, deshalb Tageslimit und Wartezeit
je Display, CSRF-Token am Formular, `noindex` im Kopf und ein Eintrag im
Audit-Log für jeden ausgegebenen Code. Webhooks werden für Kiosk-Codes
bewusst nicht ausgelöst – ein Empfangsdisplay würde den Kanal fluten.

Technik:
- neue Tabelle `kiosks`, `vouchers.kiosk_id` hält die Herkunft fest
  (Migration 0005, database.sql nachgezogen)
- includes/Kiosk.php kapselt Token, Limits und Profil-Auflösung
- includes/VoucherService.php bündelt die Voucher-Erstellung, die vorher
  in index.php lag und für den Kiosk ein zweites Mal nötig gewesen wäre
- Startbildschirm zeigt zusätzlich einen QR auf sich selbst, damit Gäste
  die Seite am eigenen Handy öffnen können

Tests: 9 neue Fälle für Token-Prüfung, Wartezeit, Tageslimit und
Profil-Auflösung (38 Tests gesamt), PHPStan deckt Kiosk.php mit ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Friederich Loheide 2026-09-23 16:51:50 +00:00
parent 5d72febadc
commit 61810eb050
30 changed files with 1360 additions and 29 deletions

View file

@ -89,6 +89,16 @@ document.addEventListener('DOMContentLoaded', function () {
patch(os.path.join(target, 'admin', 'api_keys.php'), '$keys = $db->fetchAll(',
"if (($_GET['demo'] ?? '') === 'new') { $newKey = 'uvt_3f9a2c7d41e8b60592af18cc4d7e0b3a95f2617c'; }\n$keys = $db->fetchAll(")
# Kiosk: ausgegebenen Code zeigen, ohne echten Controller (?demo=code)
patch(os.path.join(target, 'kiosk.php'),
"$headline = trim((string)($kiosk['headline'] ?? ''))",
'''if (($_GET['demo'] ?? '') === 'code' && $kiosk) {
$voucher = ['code' => '4829-17364', 'site_name' => $kiosk['site_name'], 'max_uses' => 2,
'expire_min' => 480, 'expiry_date' => '24.09.2026', 'expiry_time' => '08:00'];
}
$headline = trim((string)($kiosk['headline'] ?? ''))''')
# Theme per Query-Parameter erzwingen (fuer Dark-Mode-Screenshots)
ui = os.path.join(target, 'includes', 'Ui.php')
patch(ui, 'var s=localStorage.getItem("theme");',