diff --git a/app/bootstrap.php b/app/bootstrap.php index de8f55f..92b9f74 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -27,6 +27,16 @@ function app_env(string $name, ?string $default = null): ?string return $value; } +// PHP und MySQL muessen dieselbe Uhr sehen. Sonst laufen alle Stellen, die +// einen in PHP berechneten Zeitstempel gegen MySQL NOW() vergleichen, +// gegeneinander - z. B. Token-Ablaeufe (Passwort-Reset, E-Mail-Verifikation) +// und die Gueltigkeit von Hinweisen. Wir pinnen die PHP-Zeitzone +// deterministisch; app_db_pdo() setzt die DB-Session auf denselben Offset. +$appTimezone = app_env('APP_TIMEZONE', 'Europe/Berlin'); +if (@date_default_timezone_set((string)$appTimezone) === false) { + date_default_timezone_set('UTC'); +} + function app_is_dev(): bool { return app_env('APP_ENV', 'prod') === 'dev'; diff --git a/app/database.php b/app/database.php index 921944c..72b86d8 100644 --- a/app/database.php +++ b/app/database.php @@ -29,5 +29,12 @@ function app_db_pdo(): PDO PDO::ATTR_EMULATE_PREPARES => true, ]); + // DB-Session auf denselben Offset wie die (in bootstrap.php gepinnte) + // PHP-Zeitzone setzen, damit MySQL NOW() und PHP-Zeitstempel + // uebereinstimmen. Numerischer Offset, damit keine MySQL-Zeitzonentabellen + // noetig sind. + $offset = (new DateTimeImmutable('now'))->format('P'); + $pdo->exec("SET time_zone = '{$offset}'"); + return $pdo; } diff --git a/env.local.example.php b/env.local.example.php index 4c0e391..64874fb 100644 --- a/env.local.example.php +++ b/env.local.example.php @@ -14,6 +14,11 @@ putenv('APP_ENV=prod'); +// Zeitzone der Anwendung. Wird sowohl fuer PHP als auch fuer die DB-Session +// verwendet, damit in PHP berechnete Zeitstempel und MySQL NOW() dieselbe Uhr +// sehen (Token-Ablaeufe, Hinweis-Gueltigkeiten usw.). Standard: Europe/Berlin. +putenv('APP_TIMEZONE=Europe/Berlin'); + // Eigene App-Domain. Nur Traffic mit genau diesem Host-Header bekommt das // Dashboard/Login/etc.; jeder andere Host (z. B. kaffeeliste.de) zeigt // stattdessen die Landingpage. Siehe index.php. diff --git a/scripts/check-m3-settings-flow.php b/scripts/check-m3-settings-flow.php index 1b0f8bd..aa24661 100644 --- a/scripts/check-m3-settings-flow.php +++ b/scripts/check-m3-settings-flow.php @@ -74,6 +74,9 @@ if ($registration['ok']) { settings_check_assert('default mark price is 20 cents', $settings['mark_price_cents'] === 20, $failures, $passes); } + // Vollstaendiger Feldsatz wie ihn das Einstellungsformular sendet - die + // Update-Funktion erwartet auch die spaeter hinzugekommenen Pflichtfelder + // (pdf_row_height_px, payment_reminder_interval_days). $update = saas_update_tenant_settings($pdo, $tenantId, [ 'tenant_name' => 'M3 Settings Flow Updated', 'timezone' => 'Europe/Berlin', @@ -85,6 +88,9 @@ if ($registration['ok']) { 'paypal_url_template' => 'https://paypal.example/m3/', 'self_entry_enabled' => '1', 'paypal_enabled' => '1', + 'pdf_split_mode' => 'alphabetical', + 'pdf_row_height_px' => '18', + 'payment_reminder_interval_days' => '14', ]); settings_check_assert('settings update succeeds', $update['ok'] === true, $failures, $passes); @@ -98,6 +104,8 @@ if ($registration['ok']) { settings_check_assert('negative warning is stored negative', $updated['negative_warning_cents'] === -1250, $failures, $passes); settings_check_assert('paypal is enabled', $updated['paypal_enabled'] === 1, $failures, $passes); settings_check_assert('self entry is enabled', $updated['self_entry_enabled'] === 1, $failures, $passes); + settings_check_assert('pdf row height is updated', (int)$updated['pdf_row_height_px'] === 18, $failures, $passes); + settings_check_assert('reminder interval is updated', (int)$updated['payment_reminder_interval_days'] === 14, $failures, $passes); } }