Zeitzonen-Angleich PHP/MySQL + veralteten Settings-Test aktualisiert
Zwei vorbestehende, bislang rote Pruefungen behoben.
1) PHP lief in UTC, MySQL in der System-Zeitzone (hier CEST, +2h). Ueberall,
wo ein in PHP berechneter Zeitstempel gegen MySQL NOW() verglichen wird,
liefen die Uhren dadurch gegeneinander:
- Hinweise mit kurzer Restlaufzeit galten sofort als abgelaufen
(notices: valid_from = NOW() aus MySQL, valid_until aus PHP date()).
- Auth-Token (Passwort-Reset, E-Mail-Verifikation) liefen bis zu 2h zu
frueh ab (expires_at aus PHP date(), Pruefung gegen NOW()).
bootstrap.php pinnt die PHP-Zeitzone jetzt deterministisch aus
APP_TIMEZONE (Standard Europe/Berlin), app_db_pdo() setzt die DB-Session
per numerischem Offset auf dieselbe Zeit. Damit stimmen beide Uhren
ueberein. Behebt check-m8-tenant-isolation (11/1 -> 12/0).
2) check-m3-settings-flow stammte aus M3 und lieferte nicht die spaeter
hinzugekommenen Pflichtfelder (pdf_row_height_px,
payment_reminder_interval_days). Dadurch schlug bereits das Update fehl und
alle Folge-Assertions kippten. Der Test sendet jetzt den vollstaendigen
Feldsatz wie das Einstellungsformular und prueft die beiden Felder mit.
Die Update-Funktion selbst war korrekt (7/6 -> 15/0).
Voller Regressionslauf gruen: http-smoke 33/0, role-matrix 55/0,
tenant-isolation 12/0, m3-auth 9/0, password-email 14/0, settings 15/0,
tenant-resolution 12/0, billing 10/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,16 @@ function app_env(string $name, ?string $default = null): ?string
|
|||||||
return $value;
|
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
|
function app_is_dev(): bool
|
||||||
{
|
{
|
||||||
return app_env('APP_ENV', 'prod') === 'dev';
|
return app_env('APP_ENV', 'prod') === 'dev';
|
||||||
|
|||||||
@@ -29,5 +29,12 @@ function app_db_pdo(): PDO
|
|||||||
PDO::ATTR_EMULATE_PREPARES => true,
|
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;
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
putenv('APP_ENV=prod');
|
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
|
// Eigene App-Domain. Nur Traffic mit genau diesem Host-Header bekommt das
|
||||||
// Dashboard/Login/etc.; jeder andere Host (z. B. kaffeeliste.de) zeigt
|
// Dashboard/Login/etc.; jeder andere Host (z. B. kaffeeliste.de) zeigt
|
||||||
// stattdessen die Landingpage. Siehe index.php.
|
// stattdessen die Landingpage. Siehe index.php.
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ if ($registration['ok']) {
|
|||||||
settings_check_assert('default mark price is 20 cents', $settings['mark_price_cents'] === 20, $failures, $passes);
|
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, [
|
$update = saas_update_tenant_settings($pdo, $tenantId, [
|
||||||
'tenant_name' => 'M3 Settings Flow Updated',
|
'tenant_name' => 'M3 Settings Flow Updated',
|
||||||
'timezone' => 'Europe/Berlin',
|
'timezone' => 'Europe/Berlin',
|
||||||
@@ -85,6 +88,9 @@ if ($registration['ok']) {
|
|||||||
'paypal_url_template' => 'https://paypal.example/m3/',
|
'paypal_url_template' => 'https://paypal.example/m3/',
|
||||||
'self_entry_enabled' => '1',
|
'self_entry_enabled' => '1',
|
||||||
'paypal_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);
|
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('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('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('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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user