129 lines
4.8 KiB
PHP
129 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../app/bootstrap.php';
|
|
require_once __DIR__ . '/../app/database.php';
|
|
require_once __DIR__ . '/../app/legal.php';
|
|
|
|
$errors = [];
|
|
$warnings = [];
|
|
$requireValue = static function (string $name) use (&$errors): string {
|
|
$value = trim((string)app_env($name, ''));
|
|
if ($value === '') {
|
|
$errors[] = "{$name} ist nicht gesetzt.";
|
|
}
|
|
return $value;
|
|
};
|
|
|
|
if (app_env('APP_ENV', 'prod') !== 'prod') {
|
|
$errors[] = 'APP_ENV muss für die Freigabe auf prod stehen.';
|
|
}
|
|
$appHost = $requireValue('APP_HOST');
|
|
$baseUrl = $requireValue('APP_BASE_URL');
|
|
if ($baseUrl !== '' && (!str_starts_with($baseUrl, 'https://') || parse_url($baseUrl, PHP_URL_HOST) !== $appHost)) {
|
|
$errors[] = 'APP_BASE_URL muss eine HTTPS-URL auf APP_HOST sein.';
|
|
}
|
|
|
|
foreach (app_matomo_configuration_errors() as $matomoError) {
|
|
$errors[] = $matomoError;
|
|
}
|
|
foreach (['nutzungsanalyse.php', 'app/analytics-relay.php', 'assets/js/privacy-settings.js', 'assets/css/privacy-settings.css'] as $analyticsFile) {
|
|
if (!is_file(APP_ROOT . '/' . $analyticsFile)) {
|
|
$errors[] = "First-Party-Analyse-Datei {$analyticsFile} fehlt.";
|
|
}
|
|
}
|
|
|
|
try {
|
|
$dbTablePrefix = app_db_table_prefix();
|
|
if ($dbTablePrefix === '') {
|
|
$errors[] = 'DB_TABLE_PREFIX ist leer; Produktion und Test wären in derselben Datenbank nicht sicher getrennt.';
|
|
}
|
|
} catch (Throwable $e) {
|
|
$dbTablePrefix = '';
|
|
$errors[] = 'DB_TABLE_PREFIX ist ungültig: ' . $e->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', 'kaffeeliste_analytics_visitor', 'Cookie-Einstellungen', 'anonyme Aufrufzahlen'] 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', 'public_page_views'] 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(['0032_public_page_views.sql']);
|
|
if ((int)$stmt->fetchColumn() !== 1) {
|
|
$errors[] = 'Migration 0032_public_page_views.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";
|