Umut: "Beim Startseitenbild wird der Text & umgewandelt, aber in der Ansicht nach dem Speichern wird & daraus." RichText::sanitise() treated text without a single tag as plain text and only escaped it. What the editor sends is HTML, tag or no tag - so "Freudenstadt & Umgebung" gained one level of encoding on every save (&amp;, &amp;amp; ...) until the entity stood on the page for everyone to read. The draft of the kant-it.de home page had already reached three levels. The same branch carried a second fault of the same kind: nl2br() left the newline behind the <br>, and the next pass turned it into a second <br> via breakLines() - one paragraph became two. The branch now decodes first and replaces the line break instead of adding to it, so it ends up with the same representation as the DOM branch and both are stable under repeated saves. scripts/repair-doubled-entities.php cleans up what is already stored: the published and draft revision of every page and the areas, with a dry run, each change checked against the validator. Older revisions stay as history. DEEG: favicon is now its own square mark - the roof gable from the logo, white on the logo blue, rendered from logo-deeg.png - instead of the centre crop of the wide logo, which showed the "EE" of the wordmark. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
5.3 KiB
PHP
128 lines
5.3 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Repariert Texte, die durch das doppelte Kodieren in `RichText::sanitise()` verschachtelt wurden.
|
||
*
|
||
* Bis 1.20.0 hat der Zweig für Text ohne Tags die Eingabe nur escaped, statt sie vorher zu
|
||
* dekodieren. Aus „Freudenstadt & Umgebung“ wurde bei jedem Speichern eine Stufe mehr
|
||
* (`&amp;`, `&amp;amp;` …), bis die Entity sichtbar auf der Seite stand. Der Fehler ist
|
||
* behoben; dieses Skript räumt auf, was schon gespeichert ist.
|
||
*
|
||
* Angefasst werden die Dokumente, die tatsächlich in Gebrauch sind: die veröffentlichte und die
|
||
* Entwurfsfassung jeder Seite sowie die Bereiche. Ältere Fassungen bleiben, wie sie sind – sie
|
||
* sind Historie; wer eine davon zurückholt, speichert sie danach einmal, und der reparierte Kern
|
||
* schreibt sie richtig.
|
||
*
|
||
* Aufruf: php scripts/repair-doubled-entities.php [--schreiben]
|
||
* Ohne `--schreiben` wird nur gezeigt, was sich ändern würde.
|
||
*/
|
||
require dirname(__DIR__).'/config/bootstrap.php';
|
||
|
||
use App\Content\DocumentValidator;
|
||
use App\Infrastructure\ConnectionFactory;
|
||
|
||
$root = dirname(__DIR__);
|
||
$schreiben = in_array('--schreiben', $argv, true);
|
||
$url = str_replace('%kernel.project_dir%', str_replace('\\', '/', $root), (string) $_SERVER['DATABASE_URL']);
|
||
$db = ConnectionFactory::create($url);
|
||
$validator = new DocumentValidator();
|
||
|
||
/** Entschachtelt eine doppelt kodierte Entity, so oft wie nötig: &amp;amp; wird zu &. */
|
||
$entschachteln = static function (string $text): string {
|
||
$vorher = null;
|
||
// Nur eine Entity hinter einem & ist ein Fehler - ein einzelnes & ist richtig.
|
||
while ($vorher !== $text) {
|
||
$vorher = $text;
|
||
$text = preg_replace('/&(amp|lt|gt|quot|apos|nbsp|#\d{1,6}|#x[0-9a-fA-F]{1,6});/', '&$1;', $text) ?? $text;
|
||
}
|
||
|
||
return $text;
|
||
};
|
||
|
||
/** Läuft durch die Bausteine und korrigiert die Texte; zählt die Änderungen. */
|
||
$bausteine = function (array $liste, int &$zahl, array &$beispiele) use (&$bausteine, $entschachteln): array {
|
||
foreach ($liste as $index => $block) {
|
||
foreach (['text', 'caption', 'label', 'title', 'hint', 'provider'] as $feld) {
|
||
$wert = $block['props'][$feld] ?? null;
|
||
if (!is_string($wert) || '' === $wert) {
|
||
continue;
|
||
}
|
||
$neu = $entschachteln($wert);
|
||
if ($neu !== $wert) {
|
||
$liste[$index]['props'][$feld] = $neu;
|
||
++$zahl;
|
||
if (count($beispiele) < 6) {
|
||
$beispiele[] = mb_substr($wert, 0, 60).' → '.mb_substr($neu, 0, 60);
|
||
}
|
||
}
|
||
}
|
||
if (isset($block['children']) && is_array($block['children'])) {
|
||
$liste[$index]['children'] = $bausteine($block['children'], $zahl, $beispiele);
|
||
}
|
||
}
|
||
|
||
return $liste;
|
||
};
|
||
|
||
$gesamt = 0;
|
||
$berichte = [];
|
||
|
||
// Die aktiven Fassungen jeder Seite: veröffentlicht und Entwurf.
|
||
$aktive = [];
|
||
foreach ($db->fetchAllAssociative('SELECT id, path, published_revision_id, draft_revision_id FROM page') as $seite) {
|
||
foreach (['published_revision_id' => 'veröffentlicht', 'draft_revision_id' => 'Entwurf'] as $spalte => $was) {
|
||
if (!empty($seite[$spalte])) {
|
||
$aktive[(string) $seite[$spalte]] = '/'.$seite['path'].' ('.$was.')';
|
||
}
|
||
}
|
||
}
|
||
foreach ($aktive as $revision => $bezeichnung) {
|
||
$roh = $db->fetchOne('SELECT document FROM revision WHERE id = ?', [$revision]);
|
||
if (!is_string($roh)) {
|
||
continue;
|
||
}
|
||
$dokument = json_decode($roh, true, 512, JSON_THROW_ON_ERROR);
|
||
$zahl = 0;
|
||
$beispiele = [];
|
||
$dokument['blocks'] = $bausteine($dokument['blocks'] ?? [], $zahl, $beispiele);
|
||
if (0 === $zahl) {
|
||
continue;
|
||
}
|
||
$gesamt += $zahl;
|
||
$berichte[] = ['was' => $bezeichnung, 'zahl' => $zahl, 'beispiele' => $beispiele];
|
||
if ($schreiben) {
|
||
// Geprüft wird wie beim Speichern - eine Korrektur darf kein ungültiges Dokument hinterlassen.
|
||
$validator->validate($dokument, writing: true);
|
||
$db->executeStatement('UPDATE revision SET document = ? WHERE id = ?', [json_encode($dokument, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $revision]);
|
||
}
|
||
}
|
||
|
||
// Und die Bereiche (Kopf, Fuß, verknüpfte Sektionen).
|
||
foreach ($db->fetchAllAssociative('SELECT id, kind, name, document FROM partial') as $bereich) {
|
||
$dokument = json_decode((string) $bereich['document'], true, 512, JSON_THROW_ON_ERROR);
|
||
$zahl = 0;
|
||
$beispiele = [];
|
||
$dokument['blocks'] = $bausteine($dokument['blocks'] ?? [], $zahl, $beispiele);
|
||
if (0 === $zahl) {
|
||
continue;
|
||
}
|
||
$gesamt += $zahl;
|
||
$berichte[] = ['was' => 'Bereich '.$bereich['kind'].' „'.$bereich['name'].'“', 'zahl' => $zahl, 'beispiele' => $beispiele];
|
||
if ($schreiben) {
|
||
$validator->validate($dokument, writing: true);
|
||
$db->executeStatement('UPDATE partial SET document = ? WHERE id = ?', [json_encode($dokument, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $bereich['id']]);
|
||
}
|
||
}
|
||
|
||
foreach ($berichte as $bericht) {
|
||
echo $bericht['was'], ': ', $bericht['zahl'], ' Stelle(n)', "\n";
|
||
foreach ($bericht['beispiele'] as $beispiel) {
|
||
echo ' ', $beispiel, "\n";
|
||
}
|
||
}
|
||
if (0 === $gesamt) {
|
||
echo "Nichts zu reparieren.\n";
|
||
exit(0);
|
||
}
|
||
echo $schreiben ? "$gesamt Stelle(n) repariert.\n" : "$gesamt Stelle(n) gefunden. Mit --schreiben anwenden.\n";
|