mit Versionsstempel. */ public static function script(string $path, string $base = '', bool $defer = false): string { return ''; } /** * Theme-Bootstrap: gespeicherte Auswahl, sonst Systemeinstellung. * Muss im stehen, damit nichts hell aufblitzt. */ public static function themeScript(): string { return ''; } /** Gueltige Hex-Farbe oder Fallback. */ private static function color(?string $value, string $fallback): string { $value = trim((string)$value); return preg_match('/^#[0-9a-fA-F]{6}$/', $value) ? strtolower($value) : $fallback; } /** * CSS-Overrides fuer die Markenfarben. Gibt einen leeren String zurueck, * wenn nichts vom Standard abweicht. */ public static function brandingStyle($db = null): string { if (!$db) { return ''; } $accent = self::color($db->getSetting('brand_accent', ''), self::DEFAULT_ACCENT); $accentDark = self::color($db->getSetting('brand_accent_dark', ''), self::DEFAULT_ACCENT_DARK); $from = self::color($db->getSetting('brand_gradient_from', ''), self::DEFAULT_GRADIENT_FROM); $to = self::color($db->getSetting('brand_gradient_to', ''), self::DEFAULT_GRADIENT_TO); $radius = (int)$db->getSetting('brand_radius', (string)self::DEFAULT_RADIUS); $radius = max(0, min(28, $radius)); $isDefault = $accent === self::DEFAULT_ACCENT && $accentDark === self::DEFAULT_ACCENT_DARK && $from === self::DEFAULT_GRADIENT_FROM && $to === self::DEFAULT_GRADIENT_TO && $radius === self::DEFAULT_RADIUS; if ($isDefault) { return ''; } // Abgeleitete Töne über color-mix – so genügt eine einzige Grundfarbe. return ''; } /** * Version aus der Datei VERSION im Projektstamm. * Damit tragen Oberfläche und Release-Paket dieselbe Nummer. */ public static function version(): string { static $version = null; if ($version === null) { $file = self::root() . '/VERSION'; $version = is_file($file) ? trim((string)file_get_contents($file)) : ''; } return $version; } /** Entwicklerhinweis – bewusst an einer Stelle gepflegt. */ public const CREDIT_NAME = 'Loheide.eu'; public const CREDIT_URL = 'https://loheide.eu'; /** * Dezenter Hinweis auf den Entwickler, wie er im Seitenfuß erscheint. */ public static function credit(bool $withVersion = false): string { $label = function_exists('__') ? __('credit_by') : 'Entwickelt von'; $prefix = ''; if ($withVersion && self::version() !== '') { $prefix = 'v' . htmlspecialchars(self::version()) . ' · '; } return '

' . $prefix . htmlspecialchars($label) . ' ' . '' . self::CREDIT_NAME . '

'; } /** * Standard-Druckvorlage (wird nur verwendet, solange keine eigene * Vorlage gespeichert ist). {QR_CODE} fuellt der Browser. */ public static function defaultPrintTemplate(): string { $validUntil = function_exists('__') ? __('print_valid_until') : 'Gültig bis'; $devices = function_exists('__') ? __('print_devices') : 'Geräte'; return '
' . '
{APP_TITLE}
' . '
{SITE_NAME}
' . '{QR_CODE}' . '
{VOUCHER_CODE}
' . '
' . $validUntil . ' {EXPIRY_DATE} {EXPIRY_TIME} · {MAX_USES} ' . $devices . '
' . '
{INSTRUCTIONS}
' . '
'; } /** * URL eines Bildes aus den Einstellungen. * Hochgeladene Dateien liegen relativ zur Projektwurzel (uploads/…), * externe Adressen bleiben unveraendert. */ public static function mediaUrl(string $value, string $base = ''): string { $value = trim($value); if ($value === '') { return ''; } if (preg_match('#^(https?:)?//#i', $value) || strncmp($value, 'data:', 5) === 0 || $value[0] === '/') { return $value; } return $base . $value; } /** * Kompletter Standard-Kopf: Favicon, Schrift, Icons, Design-System, * Theme-Bootstrap und Branding. */ public static function head($db = null, string $base = ''): string { $out = []; $favicon = $db ? self::mediaUrl((string)$db->getSetting('favicon_url', ''), $base) : ''; if ($favicon !== '') { $out[] = ''; } $out[] = ''; $out[] = ''; $out[] = ''; $out[] = ''; $out[] = self::themeScript(); $branding = self::brandingStyle($db); if ($branding !== '') { $out[] = $branding; } return implode("\n ", $out); } }