getMessage(); } $phone = app_legal_phone(); if (strlen(preg_replace('/\D/', '', $phone) ?? '') < 7) { $errors[] = 'LEGAL_PHONE fehlt oder ist unplausibel; B2C darf so nicht freigeschaltet werden.'; } if (!filter_var(app_legal_email(), FILTER_VALIDATE_EMAIL)) { $errors[] = 'LEGAL_EMAIL ist ungültig.'; } if (!str_starts_with(app_legal_ticket_url(), 'https://')) { $errors[] = 'LEGAL_TICKET_URL muss HTTPS verwenden.'; } if (app_env('APP_MAIL_TRANSPORT', 'mail') === 'log') { $errors[] = 'APP_MAIL_TRANSPORT darf in Produktion nicht log sein.'; } if (!filter_var((string)app_env('APP_MAIL_FROM', ''), FILTER_VALIDATE_EMAIL)) { $errors[] = 'APP_MAIL_FROM ist nicht als gültige E-Mail-Adresse konfiguriert.'; } foreach (['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET', 'DOLIBARR_API_KEY'] as $secretName) { if (strlen(trim((string)app_env($secretName, ''))) < 16) { $errors[] = "{$secretName} fehlt oder ist unplausibel."; } } foreach ([APP_ROOT . '/env.local.php', APP_ROOT . '/.env.local'] as $secretFile) { if (!is_file($secretFile)) { continue; } $mode = fileperms($secretFile) & 0777; if (($mode & 0077) !== 0) { $errors[] = basename($secretFile) . ' ist zu weit lesbar; Dateirechte auf 0600 setzen.'; } } $mainCss = file_get_contents(APP_ROOT . '/assets/css/main.css') ?: ''; if (preg_match('~@import\s+url\(["\']?https?://|url\(["\']?https?://~i', $mainCss)) { $errors[] = 'assets/css/main.css lädt noch externe Ressourcen.'; } $privacyText = app_legal_document_text('privacy_notice'); foreach (['Matomo', 'kaffeeliste_analytics_consent', 'Cookie-Einstellungen'] as $privacyRequirement) { if (!str_contains($privacyText, $privacyRequirement)) { $errors[] = "Datenschutzerklärung nennt {$privacyRequirement} nicht."; } } try { $pdo = app_db_pdo(); foreach (['legal_acceptances', 'legal_requests', 'tenant_billing', 'rate_limit_attempts'] as $table) { $stmt = $pdo->prepare( 'SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?' ); $stmt->execute([app_db_table_name($table)]); if ($stmt->fetchColumn() === false) { $errors[] = "Datenbanktabelle " . app_db_table_name($table) . ' fehlt; Migrationen ausführen.'; } } $stmt = $pdo->prepare('SELECT COUNT(*) FROM schema_migrations WHERE version = ?'); $stmt->execute(['0030_contract_termination.sql']); if ((int)$stmt->fetchColumn() !== 1) { $errors[] = 'Migration 0030_contract_termination.sql ist nicht angewendet.'; } $unknownTenants = (int)$pdo->query("SELECT COUNT(*) FROM tenants WHERE customer_type = 'unknown'")->fetchColumn(); if ($unknownTenants > 0) { $warnings[] = "{$unknownTenants} bestehende Mandant(en) haben noch keinen Vertragstyp; vor kostenpflichtiger Buchung festlegen."; } } catch (Throwable $e) { $errors[] = 'Datenbankprüfung fehlgeschlagen: ' . $e->getMessage(); } foreach ($warnings as $warning) { echo "WARNUNG: {$warning}\n"; } if ($errors !== []) { foreach ($errors as $error) { echo "FEHLER: {$error}\n"; } echo "\nProduktionsfreigabe: NICHT BEREIT\n"; exit(1); } echo "Produktionsfreigabe: technische Pflichtprüfungen bestanden.\n";