$src, 'defer' => $defer, 'async' => $async, 'type' => $type, 'version' => $version, // kann null, '' oder string sein ]; if ($pos === 'header') { $GLOBALS['page_header_scripts'][] = $data; } else { $GLOBALS['page_footer_scripts'][] = $data; } } /** * CSS registrieren * * @param string $href Pfad zur CSS * @param string $pos 'header' oder 'footer' * @param string|null $version gleiche Logik wie bei Scripts */ function tpl_add_style(string $href, string $pos = 'header', ?string $version = null): void { if ($version === null && defined('ASSET_VERSION')) { $version = ASSET_VERSION; } $GLOBALS['page_styles'][] = [ 'href' => $href, 'pos' => $pos, 'version' => $version, ]; } /** * Templating Loader * * Beispiel: * tpl('header'); // structure/header.php * tpl('hero', 'landing', 'main'); // landing/main/hero.php * tpl('faq', 'landing', 'fakecheck'); // landing/fakecheck/faq.php */ function tpl(string $file, string $type = 'structure', string $site = 'main'): void { $base = __DIR__ . '/../partials/'; // VALIDIERUNG: Nur einfache Check, kein Path-Traversal if (preg_match('/[^a-zA-Z0-9_\-]/', $file)) { echo ""; return; } if (preg_match('/[^a-zA-Z0-9_\-]/', $type)) { echo ""; return; } if (preg_match('/[^a-zA-Z0-9_\-]/', $site)) { echo ""; return; } if ($type === 'landing') { $path = $base . "landing/$site/$file.php"; } else { $path = $base . "structure/$file.php"; } extract($GLOBALS, EXTR_SKIP); if (file_exists($path)) { include $path; } else { echo ""; } } /** * Flash-Meldung setzen (wird genau einmal nach Redirect angezeigt). * * @param string $type z.B. 'success', 'error', 'info', 'warning' * @param string $message Die Meldung für den Nutzer * @param string|null $context Optionaler Kontext (z.B. 'login', 'register') */ function flash_set(string $type, string $message, ?string $context = null): void { if (session_status() !== PHP_SESSION_ACTIVE) { @session_start(); } $_SESSION['flash'] = [ 'type' => $type, 'message' => $message, 'context' => $context, ]; } /** * Flash-Meldung holen und direkt löschen (Einmal-Anzeige). * * @return array|null ['type' => 'success|error|info|warning', 'message' => '...', 'context' => '...'] */ function flash_get(): ?array { if (session_status() !== PHP_SESSION_ACTIVE) { @session_start(); } if (empty($_SESSION['flash']) || !is_array($_SESSION['flash'])) { return null; } $flash = $_SESSION['flash']; unset($_SESSION['flash']); $flash['type'] = $flash['type'] ?? 'info'; $flash['message'] = $flash['message'] ?? ''; $flash['context'] = $flash['context'] ?? null; return $flash; } /** * Interne Helper-Funktion: traversiert ein Array mit "dot notation"-Segmenten. * * @param array $data * @param string $key z.B. 'pages.landing.meta.title' * @return mixed|null */ function _i18n_traverse_array(array $data, string $key) { $segments = explode('.', $key); $node = $data; foreach ($segments as $seg) { if (!is_array($node) || !array_key_exists($seg, $node)) { return null; } $node = $node[$seg]; } return $node; } /** * Zentrale Funktion zum Laden eines i18n-Strings mit Fallback. * * - greift zuerst auf $GLOBALS['i18n']['current'] zu * - dann auf $GLOBALS['i18n']['fallback'] * - unterstützt Platzhalter {key} und {{key}} * - eingebaute Platzhalter: * {year} * {{primary_domain}} * {{primary_url}} * * @param string $path z.B. 'pages.login.meta.title' * @param mixed $default Fallback, falls nichts gefunden wird * @param array $replacements ['name' => 'Lars'] * * @return string */ function i18n_get(string $path, $default = null, array $replacements = []): string { if (!isset($GLOBALS['i18n']) || !is_array($GLOBALS['i18n'])) { return $default !== null ? (string)$default : ''; } $current = $GLOBALS['i18n']['current'] ?? []; $fallback = $GLOBALS['i18n']['fallback'] ?? []; // zuerst in der aktiven Sprache suchen $value = _i18n_traverse_array($current, $path); // wenn dort nichts → Fallback-Sprache if ($value === null && !empty($fallback)) { $value = _i18n_traverse_array($fallback, $path); } if (!is_string($value)) { return $default !== null ? (string)$default : ''; } $text = $value; // eingebaute Platzhalter $builtIn = [ '{year}' => date('Y'), '{{primary_domain}}' => function_exists('app_primary_domain') ? app_primary_domain() : '', '{{primary_url}}' => function_exists('app_primary_url') ? app_primary_url() : '', ]; foreach ($builtIn as $ph => $val) { $text = str_replace($ph, $val, $text); } // zusätzliche Platzhalter aus $replacements foreach ($replacements as $key => $val) { $val = (string)$val; // Variante {key} $text = str_replace('{' . $key . '}', $val, $text); // Variante {{key}} $text = str_replace('{{' . $key . '}}', $val, $text); } return $text; } /** * Convenience-Wrapper für i18n_get, etwas "kürzer" zu tippen * und vom bisherigen Code kompatibel genutzt. * * Beispiel: * i18n_get_fmt('fake_ui.log_capacity_probe_result', 'Fallback', ['size' => '2 GB']); */ function i18n_get_fmt(string $path, $default = '', array $vars = []): string { // Falls $default explizit null ist → leere String als Basis $def = $default ?? ''; return i18n_get($path, $def, $vars); }