Unifi-Voucher-Tool/tools/demo/overlay/includes/Database.php
Friederich Loheide 6da46f040a Alle Frontend-Assets lokal ausliefern statt über CDNs
Inter, Font Awesome, Chart.js, qrcodejs und TinyMCE liegen jetzt unter
assets/vendor/ und werden vom eigenen Server ausgeliefert.

Warum:
- Datenschutz: bisher ging bei jedem Seitenaufruf die IP der Nutzer an
  Google Fonts, cdnjs, jsDelivr und Tiny Cloud
- Funktion: UniFi-Installationen stehen oft in abgeschotteten Netzen –
  dort fehlten bisher Schrift, Icons, Diagramme und Editor

Neu: includes/Ui.php
- Ui::head()/Ui::script() binden die Assets ein und hängen einen
  Versionsstempel an (?v=filemtime), damit Browser nach einem Update
  nicht das alte CSS aus dem Cache nehmen
- Ui::themeScript() setzt das Theme aus der gespeicherten Auswahl oder
  – wenn keine vorliegt – aus prefers-color-scheme; global.js folgt
  Systemwechseln live, solange nichts manuell gewählt wurde
- <meta name="color-scheme"> ergänzt, damit Formularelemente passen

Der TinyMCE-API-Key entfällt: der Editor läuft immer lokal (GPL-Variante),
inklusive deutscher Oberfläche, wenn die App auf Deutsch steht.

Neu: tools/demo/build.py – baut aus dem Projekt eine Demo-Instanz ohne
Datenbank (Stubs für Database/Auth, feste Beispieldaten). Damit lassen
sich Screenshots reproduzierbar erzeugen und alle Seiten einmal rendern
(Smoke-Test), ohne eine MySQL-Instanz aufzusetzen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 06:23:44 +00:00

164 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/** Demo-Stub: liefert feste Beispieldaten statt echter DB-Zugriffe. */
class Database {
private static $instance = null;
private $settings = [
'app_title' => 'UniFi Voucher System',
'logo_url' => '', 'favicon_url' => '',
'public_access' => '1', 'smtp_enabled' => '1', 'sms_enabled' => '1',
'm365_enabled' => '1', 'oidc_enabled' => '0', 'oidc_name' => 'Keycloak',
'default_expire_minutes' => '480', 'default_max_uses' => '2', 'max_uses_limit' => '10',
'instruction_header' => 'So verbinden Sie sich',
'instruction_text' => 'WLAN „Gast-WiFi" wählen, Code eingeben und bestätigen. Bei Fragen hilft der Empfang gerne weiter.',
'last_cron_sync' => '2026-09-22 14:35:00',
'session_driver' => 'db', 'captcha_mode' => 'math',
'enforce_2fa_admins' => '1', 'user_daily_voucher_limit' => '25',
'webhook_enabled' => '1', 'webhook_url' => 'https://hooks.slack.com/services/T024/B0197/XYZ',
'twilio_sid' => 'AC8f2c1d94e77b40a2', 'twilio_from' => '+4915112345678',
'trusted_proxy' => '10.0.0.1, 172.18.0.1',
'cleanup_expired_days' => '90', 'cleanup_audit_days' => '365', 'cleanup_login_days' => '30',
'last_cleanup' => '2026-09-22 03:00:00',
'print_template' => '<div style="padding:20px;border:1px dashed #999;"><h2>{APP_TITLE}</h2><p>{SITE_NAME}</p><h1>{VOUCHER_CODE}</h1></div>',
];
public function __construct()
{
// Screenshot-Varianten: ?brand=custom|gradient|nopanel
$brand = $_GET['brand'] ?? '';
if ($brand === 'custom') {
$this->settings = array_merge($this->settings, [
'app_title' => 'Hotel Seeblick',
'login_brand_name' => 'Hotel Seeblick',
'login_logo_url' => '/demo-assets/logo.svg',
'login_claim_title' => 'Willkommen im Hotel Seeblick.',
'login_claim_text' => 'Gäste-WLAN für Zimmer, Tagungsräume und Restaurant Zugangscodes direkt an der Rezeption erstellen.',
'login_features' => "Code direkt beim Check-in ausdrucken\nTagungsgäste per E-Mail versorgen\nAuswertung je Haus und Etage",
'login_footer' => '© 2026 Hotel Seeblick GmbH · Datenschutz · Impressum',
'login_bg_image' => '/demo-assets/background.svg',
'login_bg_overlay' => '45',
'brand_accent' => '#0f766e',
'brand_accent_dark' => '#2dd4bf',
'brand_gradient_from' => '#0f766e',
'brand_gradient_to' => '#0ea5e9',
]);
} elseif ($brand === 'nopanel') {
$this->settings = array_merge($this->settings, [
'login_panel_enabled' => '0',
'login_brand_name' => 'Stadtwerke Nordheim',
]);
}
}
public static function getInstance() { return self::$instance ??= new self(); }
public function getConnection() { return null; }
public function getSetting($key, $default = null) { return $this->settings[$key] ?? $default; }
public function setSetting($key, $value) { $this->settings[$key] = $value; }
public function execute($sql, $params = []) { return 1; }
public function query($sql, $params = []) { return new DemoStmt(); }
public function fetchOne($sql, $params = []) { $r = $this->fetchAll($sql, $params); return $r[0] ?? false; }
public function fetchAll($sql, $params = []) {
$s = preg_replace('/\s+/', ' ', strtolower($sql));
if (str_contains($s, 'count(*) as count from sites')) return [['count'=>4]];
if (str_contains($s, 'count(*) as count from users')) return [['count'=>12]];
if (str_contains($s, 'count(*) as count from vouchers where date(created_at)=curdate()')) return [['count'=>18]];
if (str_contains($s, 'count(*) as count from vouchers where date(created_at)=')) {
$day = $params[0] ?? '';
$map = [6,9,5,12,8,4,7,14,11,16,9,6,13,18];
$idx = abs((int)((strtotime($day) - strtotime('today')) / 86400));
return [['count' => $map[13 - min($idx, 13)]]];
}
if (str_contains($s, 'from vouchers where site_id=?') && str_contains($s, 'count(*) as total')) {
$per = [1=>['total'=>128,'valid'=>74,'used'=>39,'expired'=>15],
2=>['total'=>63,'valid'=>41,'used'=>18,'expired'=>4],
3=>['total'=>22,'valid'=>14,'used'=>6,'expired'=>2],
4=>['total'=>0,'valid'=>0,'used'=>0,'expired'=>0]];
return [$per[(int)($params[0] ?? 1)] ?? $per[4]];
}
if (str_contains($s, 'count(*) as total') && str_contains($s, 'from vouchers')) {
return [['total'=>213,'valid'=>129,'used'=>63,'expired'=>21]];
}
if (str_contains($s, 'from sites')) {
return [
['id'=>1,'name'=>'Hauptstandort Nord','site_id'=>'default','unifi_controller_url'=>'https://unifi.example.com:8443','unifi_username'=>'voucher-api','unifi_password'=>'x','is_active'=>1,'use_ssl'=>1,'created_at'=>'2026-01-12 09:00:00'],
['id'=>2,'name'=>'Campus West','site_id'=>'campus-west','unifi_controller_url'=>'https://unifi.example.com:8443','unifi_username'=>'voucher-api','unifi_password'=>'x','is_active'=>1,'use_ssl'=>1,'created_at'=>'2026-02-03 11:30:00'],
['id'=>3,'name'=>'Showroom Berlin','site_id'=>'showroom','unifi_controller_url'=>'https://unifi-b.example.com:8443','unifi_username'=>'voucher-api','unifi_password'=>'x','is_active'=>1,'use_ssl'=>1,'created_at'=>'2026-04-21 15:10:00'],
['id'=>4,'name'=>'Lager Süd','site_id'=>'lager','unifi_controller_url'=>'https://unifi-s.example.com:8443','unifi_username'=>'voucher-api','unifi_password'=>'x','is_active'=>1,'use_ssl'=>0,'created_at'=>'2026-06-08 08:45:00'],
];
}
if (str_contains($s, 'count(v.id) as voucher_count') || str_contains($s, 'from users u left join vouchers')) {
return [
['name'=>'Marie Sander','email'=>'marie.sander@example.com','voucher_count'=>48],
['name'=>'Jonas Weber','email'=>'j.weber@example.com','voucher_count'=>31],
['name'=>'Alina Brandt','email'=>'alina.brandt@example.com','voucher_count'=>27],
['name'=>'Tim Kurz','email'=>'tim.kurz@example.com','voucher_count'=>19],
['name'=>'Sara Vogt','email'=>'sara.vogt@example.com','voucher_count'=>12],
];
}
if (str_contains($s, 'from vouchers')) {
if (str_contains($s, 'group by date')) {
$out = []; $vals = [6,9,5,12,8,4,7,14,11,16,9,6,13,18];
foreach ($vals as $i => $c) { $out[] = ['date'=>date('d.m', strtotime('-'.(13-$i).' days')), 'count'=>$c]; }
return $out;
}
$codes = [['H7K2-M9QF',1,'Hauptstandort Nord','valid'],['P3RT-8ZXC',2,'Campus West','used'],
['QW4E-7TYU',1,'Hauptstandort Nord','valid'],['LM2N-5BVD',3,'Showroom Berlin','expired'],
['ZX9C-3PLO',2,'Campus West','valid'],['FG6H-1JKL',1,'Hauptstandort Nord','used']];
$names = ['Besuch Agentur Nordlicht','Workshop Raum 2','Empfang Tagesgast','Messe-Stand','Handwerker Haustechnik','Bewerbungsgespräch'];
$out = [];
foreach ($codes as $i => [$c,$sid,$sname,$st]) {
$out[] = ['id'=>$i+1,'voucher_code'=>$c,'site_id'=>$sid,'site_name'=>$sname,'user_name'=>'Marie Sander',
'status'=>$st,'created_at'=>date('Y-m-d H:i:s', strtotime("-{$i} hours")),
'voucher_name'=>$names[$i],'unifi_voucher_id'=>'65f1a'.$i,
'expires_at'=>date('Y-m-d H:i:s', strtotime('+'.(8-$i).' hours')),
'max_uses'=>[2,5,1,3,2,1][$i],'used_count'=>[2,1,0,3,0,1][$i],'expire_minutes'=>480];
}
return $out;
}
if (str_contains($s, 'from users')) {
return [
['id'=>1,'name'=>'Marie Sander','email'=>'marie.sander@example.com','is_admin'=>1,'is_active'=>1,'created_at'=>'2026-01-10 09:00:00','last_login'=>'2026-09-22 08:12:00','totp_enabled'=>1,'auth_source'=>'local','voucher_count'=>48,'password_hash'=>'x'],
['id'=>2,'name'=>'Jonas Weber','email'=>'j.weber@example.com','is_admin'=>0,'is_active'=>1,'created_at'=>'2026-03-02 13:20:00','last_login'=>'2026-09-21 17:40:00','totp_enabled'=>0,'auth_source'=>'m365','voucher_count'=>31,'password_hash'=>''],
['id'=>3,'name'=>'Alina Brandt','email'=>'alina.brandt@example.com','is_admin'=>0,'is_active'=>1,'created_at'=>'2026-05-18 10:05:00','last_login'=>'2026-09-20 09:31:00','totp_enabled'=>1,'auth_source'=>'local','voucher_count'=>27,'password_hash'=>'x'],
];
}
if (str_contains($s, 'from api_keys')) {
return [
['id'=>1,'name'=>'Buchungssystem','key_prefix'=>'3f9a2c','scope'=>'write','rate_limit'=>60,'is_active'=>1,'last_used_at'=>'2026-09-22 09:12:00','creator'=>'Marie Sander','created_at'=>'2026-06-01 10:00:00'],
['id'=>2,'name'=>'Terminal Foyer','key_prefix'=>'b71e04','scope'=>'read','rate_limit'=>0,'is_active'=>1,'last_used_at'=>'2026-09-21 17:40:00','creator'=>'Marie Sander','created_at'=>'2026-07-14 16:20:00'],
['id'=>3,'name'=>'Altes Kassensystem','key_prefix'=>'c0d5f8','scope'=>'write','rate_limit'=>30,'is_active'=>0,'last_used_at'=>null,'creator'=>'Jonas Weber','created_at'=>'2026-02-09 08:05:00'],
];
}
if (str_contains($s, 'from audit_log')) {
if (str_contains($s, 'count(')) return [['total'=>1284,'c'=>1284,'count'=>1284]];
if (str_contains($s, 'distinct action')) {
return [['action'=>'voucher_created'],['action'=>'user_login'],['action'=>'settings_saved'],['action'=>'user_created'],['action'=>'template_updated']];
}
$rows = [['2026-09-22 14:32:00','Marie Sander','voucher_created','Hauptstandort Nord · H7K2-M9QF','192.168.10.42'],
['2026-09-22 13:58:00','Jonas Weber','user_login','Microsoft 365','83.112.4.17'],
['2026-09-22 11:04:00','Marie Sander','settings_saved','Tab: E-Mail (SMTP)','192.168.10.42'],
['2026-09-21 17:40:00','Alina Brandt','user_created','tim.kurz@example.com','192.168.10.88'],
['2026-09-21 09:12:00','Marie Sander','template_updated','Tagesgast','192.168.10.42'],
['2026-09-20 16:03:00','Jonas Weber','voucher_bulk','5 Vouchers · Campus West','83.112.4.17']];
$out = [];
foreach ($rows as $i => [$d,$u,$a,$det,$ip]) {
$out[] = ['id'=>$i+1,'created_at'=>$d,'user_name'=>$u,'user_email'=>'demo@example.com','action'=>$a,'details'=>$det,'ip_address'=>$ip,'entity_type'=>'voucher','entity_id'=>$i+1];
}
return $out;
}
if (str_contains($s, 'templates')) {
return [
['id'=>1,'name'=>'Tagesgast','description'=>'Standardprofil für Besucher am Empfang','max_uses'=>2,'expire_minutes'=>480,'qos_rate_max_down'=>20000,'qos_rate_max_up'=>5000,'qos_usage_quota'=>0,'is_active'=>1,'is_default'=>1,'created_at'=>'2026-01-15 10:00:00'],
['id'=>2,'name'=>'Konferenz','description'=>'Mehrtägige Veranstaltungen','max_uses'=>5,'expire_minutes'=>4320,'qos_rate_max_down'=>50000,'qos_rate_max_up'=>10000,'qos_usage_quota'=>0,'is_active'=>1,'is_default'=>0,'created_at'=>'2026-02-20 11:30:00'],
['id'=>3,'name'=>'Handwerker','description'=>'Kurzzugang für Dienstleister','max_uses'=>1,'expire_minutes'=>240,'qos_rate_max_down'=>10000,'qos_rate_max_up'=>2000,'qos_usage_quota'=>1024,'is_active'=>1,'is_default'=>0,'created_at'=>'2026-03-08 09:15:00'],
];
}
return [];
}
}
class DemoStmt {
public function fetch($m = null) { return false; }
public function fetchAll($m = null) { return []; }
public function rowCount() { return 1; }
public function execute($p = []) { return true; }
}