Finish an archive update in one go and translate Kantarci Reisen into Turkish

The request that swaps the files now ends with a plain page instead of running on through the
framework with old code in memory and new code on disk - that is what answered with a 500 after
a successful update. The page calls the second step itself with a one-time key: migrations,
clean-up, maintenance off, back to the system screen (ADR 112).

Turkish is complete in the fixed template texts and form messages. Two language switches on one
page get distinct landmark names. Kantarci Reisen gets a Turkish version of every page, the
header, footer, menu and enquiry form, with a DE · TR switch; the reset script applies it.
The DM Sans 600 files are needed after all and are committed with fonts.css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-10-09 17:36:25 +02:00
1 parent 8aee38bde8
commit 47292c7266
14 files changed
+617 -11

No files matched your search

+4
View File
@@ -109,6 +109,10 @@ final class ArchiveUpdateTest extends TestCase
$offen = $this->updater()->pending();
self::assertNotNull($offen, 'Ohne Marke findet niemand mehr den zweiten Schritt.');
self::assertSame('1.1.0', $offen['fassung']);
// Mit dem Einmalschlüssel ruft die Zwischenseite den Abschluss selbst auf (ADR 112).
self::assertMatchesRegularExpression('/^[a-f0-9]{32}$/D', (string) ($offen['token'] ?? ''));
self::assertSame($offen['token'], $ergebnis['token']);
self::assertSame('1.1.0', $ergebnis['fassung']);
}
public function testNothingIsPendingBeforeAnUpdateAndAfterAFailedOne(): void
+41
View File
@@ -265,6 +265,47 @@ final class SystemScreenTest extends WebTestCase
self::assertStringContainsString('Fassung ermitteln', (string) $client->getResponse()->getContent());
}
public function testTheSecondUpdateStepRunsByItselfWithItsOneTimeKey(): void
{
// Umut am 09.10.2026: Update eingespielt, „direkt danach kam 500“, zurück, dann migriert.
// Die Anfrage, die die Dateien tauscht, stirbt am gemischten Code. Seit ADR 112 endet sie
// mit einer schlichten Seite, die den Abschluss über diesen Weg selbst aufruft.
$marke = dirname(__DIR__).'/'.ArchiveUpdate::MARKE;
@mkdir(dirname($marke), 0o775, true);
file_put_contents($marke, json_encode(['fassung' => '9.9.9', 'seit' => gmdate('c'), 'alt' => 'x', 'token' => str_repeat('a', 32)]));
// Die Testdatenbank entsteht aus dem Schema, nicht aus Migrationen. Eine echte
// Installation kennt ihre ausgeführten Migrationen - dasselbe Protokoll hier.
$this->db->executeStatement('CREATE TABLE IF NOT EXISTS migration_versions (version VARCHAR(191) NOT NULL PRIMARY KEY, executed_at DATETIME DEFAULT NULL, execution_time INTEGER DEFAULT NULL)');
foreach (glob(dirname(__DIR__).'/migrations/Version*.php') ?: [] as $datei) {
$this->db->executeStatement('INSERT OR IGNORE INTO migration_versions (version, executed_at, execution_time) VALUES (?, ?, 0)', ['DoctrineMigrations\\'.basename($datei, '.php'), gmdate('Y-m-d H:i:s')]);
}
try {
$redaktion = $this->login('editor');
$redaktion->request('GET', '/admin/system/abschliessen?token='.str_repeat('a', 32));
self::assertSame(403, $redaktion->getResponse()->getStatusCode(), 'Nur ein Administrator schließt ein Update ab.');
self::assertFileExists($marke);
$admin = $this->login();
$admin->request('GET', '/admin/system/abschliessen?token=falsch');
$admin->followRedirect();
self::assertStringContainsString('gehört zu keinem offenen Update', (string) $admin->getResponse()->getContent());
self::assertFileExists($marke, 'Ein falscher Schlüssel schließt nichts ab.');
$admin->request('GET', '/admin/system/abschliessen?token='.str_repeat('a', 32));
self::assertSame('/admin/system', parse_url((string) $admin->getResponse()->headers->get('Location'), PHP_URL_PATH));
$admin->followRedirect();
self::assertStringContainsString('Update auf 9.9.9 abgeschlossen', (string) $admin->getResponse()->getContent());
self::assertFileDoesNotExist($marke, 'Nach dem Abschluss ist nichts mehr offen.');
// Neu laden ist harmlos.
$admin->request('GET', '/admin/system/abschliessen?token='.str_repeat('a', 32));
$admin->followRedirect();
self::assertStringContainsString('bereits abgeschlossen', (string) $admin->getResponse()->getContent());
} finally {
@unlink($marke);
}
}
public function testNobodyButAnAdministratorCanStartAMigration(): void
{
// Ein Knopf, der die Datenbank umbaut, ist das lohnendste Ziel auf diesem Bildschirm.