diff --git a/Readme.md b/Readme.md index 8cc7143..46a8a84 100644 --- a/Readme.md +++ b/Readme.md @@ -10,7 +10,7 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)** ![MySQL](https://img.shields.io/badge/MySQL-5.7%2B%20%2F%20MariaDB-4479A1?logo=mysql&logoColor=white) ![UniFi OS](https://img.shields.io/badge/UniFi%20OS-7.0%2B-0559C9?logo=ubiquiti&logoColor=white) ![License](https://img.shields.io/badge/Lizenz-MIT-green) -![Version](https://img.shields.io/badge/Version-2.7.0-blueviolet) +![Version](https://img.shields.io/badge/Version-2.8.0-blueviolet) ![Tests](https://img.shields.io/badge/Tests-PHPUnit%20%2B%20PHPStan-brightgreen) [![Repository](https://img.shields.io/badge/Code-git.loheide.cloud-4b3ec4)](https://git.loheide.cloud/friloo/Unifi-Voucher-Tool) @@ -29,6 +29,7 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)** - 🎟️ **Voucher-Erstellung** mit sofortiger QR-Code-Anzeige, Druckvorlage und E-Mail-Versand - 🖥️ **Display-Seiten (Kiosk)** – öffentliche Seite je Site, an der Gäste sich mit einem Klick selbst einen Zugang holen +- 🎨 **Jede Display-Seite eigenständig gestaltbar** – Logo, Hintergrundbild, Akzentfarbe, helle oder dunkle Karte - 📦 **Bulk-Erstellung** – bis zu 20 Vouchers auf einmal, inkl. Sammeldruck-Layout - 🧩 **Voucher-Profile/Templates** – vordefinierte Laufzeiten & Gerätelimits per Schnellauswahl - 🏢 **Multi-Site-Support** – beliebig viele UniFi-Standorte zentral verwalten @@ -88,7 +89,12 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)**
Display-Seite im Ruhezustand + Display-Seite mit eigenem Bild und Farben +
+ +
Ausgegebener Zugangscode auf dem Display + Display-Seite einrichten
@@ -253,6 +259,23 @@ Auf dem Startbildschirm steht zusätzlich ein QR-Code, der auf dieselbe Seite zeigt. Gäste können sie damit **am eigenen Handy** öffnen – praktisch bei Bildschirmen ohne Touch. +### Jede Seite eigenständig gestalten + +Jede Display-Seite bringt ihr eigenes Erscheinungsbild mit – das Hotel am +Empfang sieht anders aus als der Tagungsraum nebenan: + +| Einstellung | Wirkung | +|---|---| +| Logo | eigenes Logo auf der Karte (leer = Logo aus den Einstellungen) | +| Hintergrundbild | formatfüllend hinter der Karte, z. B. ein Foto des Hauses | +| Abdunklung | 0–90 % dunkle Ebene über dem Bild, damit die Karte lesbar bleibt | +| Akzentfarbe | färbt den Knopf dieser Seite (leer = Farbe aus dem Design-Tab) | +| Karte | hell oder dunkel – auf Fotos wirkt die dunkle Karte meist ruhiger | + +Logo und Hintergrund lassen sich direkt hochladen (PNG, JPG, WEBP, GIF, SVG bis +3 MB) oder als URL hinterlegen; beim Löschen einer Display-Seite verschwinden +die hochgeladenen Dateien mit. + Die ausgegebenen Codes erscheinen normal in *Live Vouchers*, im *Reporting* und im *Audit-Log* (Aktion „Voucher am Display geholt"), sodass jederzeit nachvollziehbar bleibt, woher ein Zugang stammt. @@ -660,7 +683,7 @@ dem Git-Hoster.
-**Version 2.7.0** · Autor: **Friederich Loheide** · Lizenz: **MIT** +**Version 2.8.0** · Autor: **Friederich Loheide** · Lizenz: **MIT** Entwickelt von **[Loheide.eu](https://loheide.eu)** diff --git a/VERSION b/VERSION index 24ba9a3..834f262 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.0 +2.8.0 diff --git a/admin/kiosks.php b/admin/kiosks.php index 5597422..50019af 100644 --- a/admin/kiosks.php +++ b/admin/kiosks.php @@ -16,6 +16,7 @@ require_once __DIR__ . '/../includes/Auth.php'; require_once __DIR__ . '/../includes/I18n.php'; require_once __DIR__ . '/../includes/Ui.php'; require_once __DIR__ . '/../includes/Kiosk.php'; +require_once __DIR__ . '/../includes/Upload.php'; $auth = new Auth(); $auth->requireAdmin(); @@ -40,9 +41,20 @@ function kioskInput(): array 'cooldown_seconds' => max(0, min(3600, (int)($_POST['cooldown_seconds'] ?? Kiosk::DEFAULT_COOLDOWN))), 'display_seconds' => max(10, min(600, (int)($_POST['display_seconds'] ?? Kiosk::DEFAULT_DISPLAY_SECONDS))), 'is_active' => isset($_POST['is_active']) ? 1 : 0, + 'bg_overlay' => max(0, min(90, (int)($_POST['bg_overlay'] ?? 45))), + 'accent_color' => self_accent($_POST['accent_color'] ?? ''), + 'card_style' => ($_POST['card_style'] ?? 'light') === 'dark' ? 'dark' : 'light', ]; } +/** Nur echte Hex-Farben durchlassen – der Wert landet in einem style-Attribut. */ +function self_accent($value): ?string +{ + $value = strtolower(trim((string)$value)); + + return preg_match('/^#[0-9a-f]{6}$/', $value) ? $value : null; +} + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_kiosk'])) { if (!$auth->validateCsrfToken($_POST['csrf_token'] ?? '')) { $error = __('error_csrf'); @@ -52,12 +64,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_kiosk'])) { if ($in['name'] === '') throw new Exception(__('error_name_req')); if ($in['site_id'] <= 0) throw new Exception(__('error_site_req')); + $logo = Upload::resolveField('logo_url', '', 'image'); + $bg = Upload::resolveField('background_url', '', 'image'); + $db->execute( - "INSERT INTO kiosks (site_id, template_id, name, token, headline, subline, daily_limit, cooldown_seconds, display_seconds, is_active, created_by) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)", + "INSERT INTO kiosks (site_id, template_id, name, token, headline, subline, + logo_url, background_url, bg_overlay, accent_color, card_style, + daily_limit, cooldown_seconds, display_seconds, is_active, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)", [$in['site_id'], $in['template_id'], $in['name'], Kiosk::newToken(), - $in['headline'], $in['subline'], $in['daily_limit'], $in['cooldown_seconds'], - $in['display_seconds'], $_SESSION['user_id']] + $in['headline'], $in['subline'], $logo, $bg, $in['bg_overlay'], + $in['accent_color'], $in['card_style'], $in['daily_limit'], + $in['cooldown_seconds'], $in['display_seconds'], $_SESSION['user_id']] ); $auth->writeAuditLog($_SESSION['user_id'], 'kiosk_created', 'kiosk', null, $in['name']); $success = __('kiosks_added'); @@ -77,11 +95,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_kiosk'])) { if ($in['name'] === '') throw new Exception(__('error_name_req')); if ($in['site_id'] <= 0) throw new Exception(__('error_site_req')); + $current = $db->fetchOne("SELECT logo_url, background_url FROM kiosks WHERE id = ?", [$id]) ?: []; + $logo = Upload::resolveField('logo_url', (string)($current['logo_url'] ?? ''), 'image'); + $bg = Upload::resolveField('background_url', (string)($current['background_url'] ?? ''), 'image'); + $db->execute( "UPDATE kiosks SET site_id=?, template_id=?, name=?, headline=?, subline=?, + logo_url=?, background_url=?, bg_overlay=?, accent_color=?, card_style=?, daily_limit=?, cooldown_seconds=?, display_seconds=?, is_active=? WHERE id=?", [$in['site_id'], $in['template_id'], $in['name'], $in['headline'], $in['subline'], + $logo, $bg, $in['bg_overlay'], $in['accent_color'], $in['card_style'], $in['daily_limit'], $in['cooldown_seconds'], $in['display_seconds'], $in['is_active'], $id] ); $auth->writeAuditLog($_SESSION['user_id'], 'kiosk_updated', 'kiosk', $id, $in['name']); @@ -106,6 +130,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['renew_token'])) { if (isset($_GET['delete'], $_GET['token'])) { if ($auth->validateCsrfToken($_GET['token'])) { + $old = $db->fetchOne("SELECT logo_url, background_url FROM kiosks WHERE id = ?", [(int)$_GET['delete']]); + if ($old) { + Upload::delete((string)($old['logo_url'] ?? '')); + Upload::delete((string)($old['background_url'] ?? '')); + } $db->execute("DELETE FROM kiosks WHERE id = ?", [(int)$_GET['delete']]); $auth->writeAuditLog($_SESSION['user_id'], 'kiosk_deleted', 'kiosk', (int)$_GET['delete'], ''); $success = __('kiosks_deleted'); @@ -223,6 +252,9 @@ $adminBase = ''; "id" => (int)$k["id"], "site_id" => (int)$k["site_id"], "template_id" => (int)$k["template_id"], "name" => $k["name"], "headline" => $k["headline"], "subline" => $k["subline"], + "logo_url" => $k["logo_url"], "background_url" => $k["background_url"], + "bg_overlay" => (int)$k["bg_overlay"], "accent_color" => $k["accent_color"], + "card_style" => $k["card_style"], "daily_limit" => (int)$k["daily_limit"], "cooldown_seconds" => (int)$k["cooldown_seconds"], "display_seconds" => (int)$k["display_seconds"], @@ -257,7 +289,7 @@ $adminBase = '';
-
+