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

+72 -4
View File
@@ -174,13 +174,14 @@ final class SystemController extends AbstractController
return $this->redirectToRoute('admin_system');
}
// Alles, was nach dem Dateitausch noch gebraucht wird, steht vorher fest: Danach darf diese
// Anfrage keinen Code mehr nachladen (siehe sendAndStop).
$abschluss = '/'.trim($this->adminPath, '/').'/system/abschliessen?token=';
$ergebnis = $this->archive->run($request->request->getString('target') ?: null);
$request->getSession()->set('update_steps', $ergebnis['steps']);
if ($ergebnis['migrate'] ?? false) {
$this->addFlash('success', 'Die Dateien sind ersetzt. Jetzt noch die Migrationen - der Knopf steht oben.');
return $this->redirectToRoute('admin_system');
$this->sendAndStop((string) $ergebnis['fassung'], $abschluss.$ergebnis['token'], $ergebnis['steps']);
}
$request->getSession()->set('update_steps', $ergebnis['steps']);
$this->addFlash($ergebnis['ok'] ? 'success' : 'error', $ergebnis['ok']
? 'Es war nichts einzuspielen.'
: 'Das Update ist nicht durchgelaufen. Die Schritte stehen unten.');
@@ -188,6 +189,73 @@ final class SystemController extends AbstractController
return $this->redirectToRoute('admin_system');
}
/**
* Der zweite Schritt des Archivupdates, aufgerufen von der Zwischenseite selbst (ADR 112).
*
* Läuft in einer eigenen Anfrage und damit mit dem Code der neuen Fassung: Migrationen,
* Aufräumen, Wartungszustand aus. Der Einmalschlüssel aus var/update/offen.json ersetzt das
* CSRF-Token, das die Zwischenseite nicht mehr erzeugen kann; ohne Administratorrolle geht es
* ohnehin nicht. Ein zweiter Aufruf (Neuladen) findet nichts mehr offen und meldet das.
*/
#[Route('/%kant.admin_path%/system/abschliessen', name: 'admin_system_finish', methods: ['GET'])]
public function finishArchive(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$offen = $this->archive->pending();
if (null === $offen) {
$this->addFlash('success', 'Das Update ist bereits abgeschlossen.');
return $this->redirectToRoute('admin_system');
}
if (!is_string($offen['token'] ?? null) || !hash_equals($offen['token'], $request->query->getString('token'))) {
return $this->back('Dieser Link gehört zu keinem offenen Update. Schließe es oben mit „Migrationen ausführen“ ab.');
}
@set_time_limit(0);
$ergebnis = $this->archive->finish($this->installation);
$request->getSession()->set('update_steps', $ergebnis['steps']);
$this->addFlash($ergebnis['ok'] ? 'success' : 'error', $ergebnis['ok']
? sprintf('Update auf %s abgeschlossen. Die Website ist wieder erreichbar.', $offen['fassung'])
: 'Die Migrationen sind fehlgeschlagen. Die Website bleibt im Wartungszustand.');
return $this->redirectToRoute('admin_system');
}
/**
* Beendet die Anfrage, die gerade die Dateien getauscht hat, mit einer schlichten Seite.
*
* Dieser Prozess hat den alten Code im Speicher, auf der Platte liegt der neue. Alles, was
* jetzt noch nachgeladen würde - Antwort-Listener, Sitzung, Vorlagen -, käme aus der neuen
* Fassung und passte nicht zu dem, was schon geladen ist. Genau das endete vorher mit einem
* 500er, obwohl das Update gelungen war. Deshalb hier nur noch PHP selbst: Kopfzeilen, Text,
* Ende. Die Seite ruft den Abschluss sofort selbst auf, ein Link steht für den Fall daneben,
* dass der Browser das Weiterleiten unterdrückt.
*
* @param list<array{step: string, ok: bool, detail: string}> $schritte
*/
private function sendAndStop(string $fassung, string $weiter, array $schritte): never
{
while (ob_get_level() > 0) {
ob_end_clean();
}
$e = static fn (string $text): string => htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$liste = '';
foreach ($schritte as $schritt) {
$liste .= '<li>'.($schritt['ok'] ? '✓ ' : '! ').$e($schritt['step']).'</li>';
}
http_response_code(200);
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store');
header('X-Robots-Tag: noindex');
header("Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'");
echo '<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">'
.'<meta http-equiv="refresh" content="1;url='.$e($weiter).'"><title>Update wird abgeschlossen</title>'
.'<style>body{font:16px/1.5 system-ui,sans-serif;background:#f3f5f2;color:#203b34;margin:0;display:grid;place-items:center;min-height:100vh}main{background:#fff;border:1px solid #dfe5e1;border-radius:12px;padding:28px 32px;max-width:460px}h1{font-size:20px;margin:0 0 8px}ul{padding-left:18px;color:#46514c;font-size:14px}a{color:#1f5f45}</style></head>'
.'<body><main><h1>Update auf '.$e($fassung).' wird abgeschlossen …</h1>'
.'<p role="status">Die neuen Dateien sind da. Jetzt laufen die Migrationen, danach geht es von selbst zur Systemseite.</p>'
.'<ul>'.$liste.'</ul><p><a href="'.$e($weiter).'">Weiter, falls nichts passiert</a></p></main></body></html>';
exit;
}
#[Route('/%kant.admin_path%/system/update', name: 'admin_system_update', methods: ['POST'])]
public function update(Request $request): Response
{