Rebuild DEEG as a fifth installation, add stepped forms and a plugin system, and clear the review backlog
Forms gained steps, conditions, image choices, number fields with units and a summary step; the DEEG price finder uses all of it. Plugins are manifest plus Twig: blocks, settings, containers, routes, presets and theme packs, switched per installation. The review backlog is done: archive pages render through the normal page pipeline, forms and post lists work inside areas and sections, repeated sections get their own ids, breakpoints and colours come from the palette, backup and update work on MySQL and lock each other out. The task worker no longer shortens a process that has no time limit: set_time_limit() sets the limit instead of extending it, which turned an unlimited CLI run into eighty seconds and killed the full test suite far away from the scheduler. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
66ff68abf8
commit
c8a8fa7460
74 files changed
+5352
-243
No files matched your search
@@ -127,6 +127,23 @@ final class ArchiveUpdate
|
||||
return ['ok' => true, 'steps' => $schritte];
|
||||
}
|
||||
|
||||
$lock = $this->updater->lock();
|
||||
if (null === $lock) {
|
||||
$melden($schritte, 'Sperre', false, 'Ein anderes Update läuft gerade (var/update.lock). Bitte warten.');
|
||||
|
||||
return ['ok' => false, 'steps' => $schritte];
|
||||
}
|
||||
try {
|
||||
return $this->apply($schritte, $melden, $ziel, $neueste);
|
||||
} finally {
|
||||
flock($lock, LOCK_UN);
|
||||
fclose($lock);
|
||||
}
|
||||
}
|
||||
|
||||
/** Der Teil des Updates, der die Installation veraendert - unter der Sperre. */
|
||||
private function apply(array &$schritte, callable $melden, string $ziel, array $neueste): array
|
||||
{
|
||||
$arbeit = $this->root.'/var/update/'.gmdate('Ymd-His');
|
||||
if (!@mkdir($arbeit, 0o775, true) && !is_dir($arbeit)) {
|
||||
$melden($schritte, 'Arbeitsordner', false, $arbeit.' ließ sich nicht anlegen. Fehlt das Schreibrecht auf var/?');
|
||||
@@ -145,14 +162,14 @@ final class ArchiveUpdate
|
||||
}
|
||||
|
||||
$archiv = $arbeit.'/kantsites-'.$ziel.'.zip';
|
||||
$inhalt = $this->download->get(sprintf('%s/v%s/kantsites-%s.zip', $this->releaseBase, $ziel, $ziel));
|
||||
if (null === $inhalt || '' === $inhalt) {
|
||||
// Direkt in die Datei, nicht erst als Zeichenkette in den Speicher: ein Archiv von einigen
|
||||
// Megabyte passte in memory_limit, ein groesseres nicht mehr.
|
||||
if (!$this->download->toFile(sprintf('%s/v%s/kantsites-%s.zip', $this->releaseBase, $ziel, $ziel), $archiv)) {
|
||||
$melden($schritte, 'Archiv laden', false, trim('Das Archiv ließ sich nicht herunterladen. '.$this->download->reason()));
|
||||
|
||||
return ['ok' => false, 'steps' => $schritte];
|
||||
}
|
||||
file_put_contents($archiv, $inhalt);
|
||||
$melden($schritte, 'Archiv laden', true, sprintf('%d KB geladen.', (int) round(strlen($inhalt) / 1024)));
|
||||
$melden($schritte, 'Archiv laden', true, sprintf('%d KB geladen.', (int) round((int) filesize($archiv) / 1024)));
|
||||
|
||||
$erwartet = $ziel === $neueste['version'] ? $neueste['checksum'] : $this->checksumOf(sprintf('%s/v%s/kantsites-%s.zip.sha256', $this->releaseBase, $ziel, $ziel));
|
||||
if (null === $erwartet) {
|
||||
@@ -204,6 +221,9 @@ final class ArchiveUpdate
|
||||
// public/ wird Datei für Datei aktualisiert: Schriften und erzeugte Bündel gehören dem
|
||||
// Betreiber und wären sonst weg.
|
||||
$this->mergePublic($quelle.'/public', $this->root.'/public');
|
||||
// Ebenso plugins/: die mitgelieferten Plugins werden aktualisiert, ein vom Betreiber
|
||||
// installiertes Plugin bleibt, weil sein Ordner im Archiv nicht vorkommt.
|
||||
$this->mergePublic($quelle.'/plugins', $this->root.'/plugins');
|
||||
} catch (\Throwable $fehler) {
|
||||
foreach (self::ERSETZEN as $eintrag) {
|
||||
if (is_dir($alt.'/'.$eintrag) || is_file($alt.'/'.$eintrag)) {
|
||||
@@ -286,8 +306,9 @@ final class ArchiveUpdate
|
||||
@unlink($this->root.'/'.self::MARKE);
|
||||
$this->updater->maintenance(false);
|
||||
$this->protokoll('ok Migrationen: '.$schritte[0]['detail']);
|
||||
$schritte[] = ['step' => 'Aufräumen', 'ok' => true, 'detail' => $this->aufraeumen()];
|
||||
$schritte[] = ['step' => 'Fertig', 'ok' => true, 'detail' => sprintf(
|
||||
'Fassung %s läuft. Der vorherige Stand liegt unter var/update/, bis du ihn löschst.',
|
||||
'Fassung %s läuft. Der vorherige Stand liegt unter var/update/ (die zwei jüngsten bleiben).',
|
||||
$this->updater->version(),
|
||||
)];
|
||||
|
||||
@@ -327,10 +348,69 @@ final class ArchiveUpdate
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('.htaccess' === $eintrag && is_file($ziel)) {
|
||||
$this->mergeHtaccess($quelle, $ziel);
|
||||
|
||||
continue;
|
||||
}
|
||||
@copy($quelle, $ziel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Die neue .htaccess uebernehmen, aber die Zeilen, die der Betreiber eingeschaltet hat,
|
||||
* eingeschaltet lassen: HTTPS-Zwang und HSTS. Vorher kam jedes Update mit den auskommentierten
|
||||
* Zeilen des Releases zurueck, und die Website lief nach dem Update wieder ohne Umleitung.
|
||||
*/
|
||||
private function mergeHtaccess(string $neu, string $alt): void
|
||||
{
|
||||
$vorher = (string) file_get_contents($alt);
|
||||
$inhalt = (string) file_get_contents($neu);
|
||||
foreach (['RewriteCond %{HTTPS} !=on', 'RewriteCond %{HTTP:X-Forwarded-Proto} !=https', 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]', 'Header always set Strict-Transport-Security'] as $zeile) {
|
||||
$aktiv = 1 === preg_match('/^\s*'.preg_quote($zeile, '/').'/m', $vorher);
|
||||
if ($aktiv) {
|
||||
$inhalt = (string) preg_replace('/^(\s*)#\s*('.preg_quote($zeile, '/').')/m', '$1$2', $inhalt);
|
||||
}
|
||||
}
|
||||
file_put_contents($alt, $inhalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raeumt nach einem gelungenen Update auf: die Arbeitsordner unter var/update/ bis auf die
|
||||
* zwei juengsten, und die Zwischenspeicher frueherer Fassungen unter var/cache/. Beides wuchs
|
||||
* vorher mit jedem Update, bis der Speicherplatz des Hostings voll war.
|
||||
*/
|
||||
private function aufraeumen(): string
|
||||
{
|
||||
$entfernt = 0;
|
||||
$ordner = glob($this->root.'/var/update/*', GLOB_ONLYDIR) ?: [];
|
||||
rsort($ordner);
|
||||
foreach (array_slice($ordner, 2) as $pfad) {
|
||||
$this->loeschen($pfad);
|
||||
++$entfernt;
|
||||
}
|
||||
$aktuell = 'release-'.substr(hash_file('sha256', $this->root.'/VERSION') ?: '', 0, 16);
|
||||
foreach (glob($this->root.'/var/cache/*/release-*', GLOB_ONLYDIR) ?: [] as $pfad) {
|
||||
if (basename($pfad) !== $aktuell) {
|
||||
$this->loeschen($pfad);
|
||||
++$entfernt;
|
||||
}
|
||||
}
|
||||
|
||||
return 0 === $entfernt ? 'Nichts aufzuräumen.' : sprintf('%d alte Ordner unter var/update und var/cache entfernt.', $entfernt);
|
||||
}
|
||||
|
||||
private function loeschen(string $pfad): void
|
||||
{
|
||||
foreach (scandir($pfad) ?: [] as $eintrag) {
|
||||
if ('.' === $eintrag || '..' === $eintrag) {
|
||||
continue;
|
||||
}
|
||||
is_dir($pfad.'/'.$eintrag) && !is_link($pfad.'/'.$eintrag) ? $this->loeschen($pfad.'/'.$eintrag) : @unlink($pfad.'/'.$eintrag);
|
||||
}
|
||||
@rmdir($pfad);
|
||||
}
|
||||
|
||||
/** Liest die Prüfsumme aus der Datei neben dem Archiv. */
|
||||
private function checksumOf(string $url): ?string
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user