kaffeeliste.de). In der * lokalen Entwicklung ohne APP_HOST bleibt der aktuelle Host uebrig, im * CLI-Betrieb ein leerer String. */ function app_marketing_url(): string { $configured = app_env('APP_MARKETING_URL'); if ($configured !== null) { return rtrim($configured, '/'); } $scheme = app_is_https() ? 'https' : 'http'; $host = app_primary_host(); if ($host !== null) { return $scheme . '://' . preg_replace('/^app\./i', '', $host); } $requestHost = (string)($_SERVER['HTTP_HOST'] ?? ''); return $requestHost !== '' ? $scheme . '://' . $requestHost : ''; } function app_start_session(): void { if (PHP_SAPI === 'cli' || session_status() === PHP_SESSION_ACTIVE) { return; } $sessionPath = app_env('APP_SESSION_PATH', APP_ROOT . '/var/sessions'); if ($sessionPath !== null && !is_dir($sessionPath)) { @mkdir($sessionPath, 0700, true); } if ($sessionPath !== null && is_dir($sessionPath) && is_writable($sessionPath)) { session_save_path($sessionPath); } session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', 'domain' => '', 'secure' => app_is_https(), 'httponly' => true, 'samesite' => 'Lax', ]); session_name(app_env('APP_SESSION_NAME', 'kaffeeliste_session') ?? 'kaffeeliste_session'); session_start(); } function app_csrf_token(): string { app_start_session(); if (!isset($_SESSION) || !is_array($_SESSION)) { $_SESSION = []; } if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function app_csrf_field(): string { return ''; } function app_verify_csrf(?string $token): bool { app_start_session(); if (!isset($_SESSION) || !is_array($_SESSION)) { $_SESSION = []; } return is_string($token) && isset($_SESSION['csrf_token']) && is_string($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } function app_require_csrf(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { return; } if (!app_verify_csrf($_POST['csrf_token'] ?? null)) { http_response_code(419); die('CSRF validation failed.'); } } app_send_security_headers(); // Der Analyse-Baustein wird zentral geladen, damit alle HTML-Templates nur // noch die beiden bewusst platzierten Head-/Body-Fragmente ausgeben muessen. // analytics.php bindet bootstrap.php seinerseits per require_once ein. require_once __DIR__ . '/analytics.php';