.json) mit Request-weitem Cache. * Wirft bei kaputtem JSON — Datenfehler sollen laut scheitern, nicht leise. */ function json_load(string $name): array { static $cache = []; if (!array_key_exists($name, $cache)) { $file = DATA_PATH . '/' . $name . '.json'; if (!is_file($file)) { return []; } $cache[$name] = json_decode((string) file_get_contents($file), true, 512, JSON_THROW_ON_ERROR); } return $cache[$name]; } /** * Komponente rendern: component('hero', ['title' => …]). * Props werden als lokale Variablen extrahiert; Komponenten sind dumme Includes. */ function component(string $name, array $props = []): void { extract($props, EXTR_SKIP); require APP_PATH . '/components/' . $name . '.php'; } /** * Page-Datei ausführen: sie setzt $meta und emittiert ihren Body. * Rückgabe: [$meta, $html]. */ function render_page(string $file): array { $meta = []; ob_start(); require $file; return [$meta, (string) ob_get_clean()]; } /** * Signierten Zeitstempel für die Formular-Time-Trap erzeugen. */ function form_token(): string { $ts = (string) time(); return $ts . '.' . hash_hmac('sha256', $ts, (string) config('app_secret')); } /** * Time-Trap prüfen: Signatur gültig, älter als $min Sekunden, jünger als $max. */ function form_token_valid(string $token, int $min = 3, int $max = 7200): bool { $parts = explode('.', $token); if (count($parts) !== 2) { return false; } [$ts, $sig] = $parts; if (!hash_equals(hash_hmac('sha256', $ts, (string) config('app_secret')), $sig)) { return false; } $age = time() - (int) $ts; return $age >= $min && $age <= $max; }