From 8f475d1374d2f72f96d202e37f22c7ae98089d77 Mon Sep 17 00:00:00 2001 From: Umut Kantarci Date: Mon, 14 Sep 2026 12:30:13 +0200 Subject: [PATCH] fix: remove automatically appended footer legal links (1.8.1) --- README.md | 2 ++ VERSION | 2 +- docs/ARCHITECTURE.md | 8 ++++++++ docs/STATUS.md | 12 ++++++++++++ public/assets/editor.js | 2 +- public/assets/site.css | 6 ------ src/Controller/PageController.php | 5 ----- templates/archive.html.twig | 11 ----------- templates/not-found.html.twig | 11 ----------- templates/page.html.twig | 12 ------------ tests/CmsTest.php | 23 ++++++++++++----------- tests/EditorSecurityTest.php | 8 ++++---- 12 files changed, 40 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index c309bdf..a946ecc 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Ohne veröffentlichte Auswahl erscheint automatisch eine Standard-404 im Website ## Anmelden und bearbeiten +**Footer-Links (ab 1.8.1):** Datenschutz, Impressum und weitere Links werden selbst im Footer oder Seiteninhalt platziert. Das CMS hängt keine zusätzliche Rechtszeile an. Der optionale Baustein „Rechtliches“ verwendet die Adressen aus den Einstellungen und erscheint nur an seiner eingefügten Position. + Für eine neue Installation im interaktiven Terminal ein Konto anlegen: ```powershell diff --git a/VERSION b/VERSION index 27f9cd3..a8fdfda 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0 +1.8.1 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e95a526..7f73a69 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,6 +2,14 @@ Stand: 14.09.2026. Arbeitstitel KantSites; Produktname offen. +## ADR 083 – Footer-Links werden redaktionell platziert (14.09.2026) + +Auf ausdrücklichen Wunsch von Umut entfällt die automatisch angehängte Rechtszeile vollständig. Seiten, Beiträge, Archive, Fehlerseiten und Editorvorschauen geben ausschließlich die selbst platzierten Footer-/Inhaltslinks aus. Die frühere Erkennung des speziellen `legal`-Blocks konnte normale Text- oder Menülinks nicht berücksichtigen; dadurch erschien unter einem vollständig gestalteten Footer ein zusätzlicher heller Streifen mit doppelten Links. Die Template-Ausgabe, ihre Erkennung im Controller und ungenutztes CSS sind entfernt. + +Normale Links, Menüs und bewusst eingefügte „Rechtliches“-Blöcke bleiben erhalten. Der optionale Block nutzt weiterhin die konfigurierten Seitenadressen und den Link zur Einwilligung. Einwilligungsdialog, Einstellungen und der Endpunkt `/einwilligung` behalten ihre Funktion. Keine Inhalte werden umgeschrieben, keine neue Schema- oder Datenbankmigration. Bei einem Rückweg auf ältere Versionen erscheint deren automatische Leiste wieder. + +Diese Entscheidung ersetzt die automatische Verlinkung aus ADR 033 und die bedingte Zusatzleiste aus ADR 062; Position und Gestaltung der Links liegen beim Autor. + ## ADR 082 – Zuweisbare 404-Seite im gemeinsamen Seitenrenderer (14.09.2026) `website.settings.notFoundPageId` verweist auf eine normale Seite über deren stabile ID. Eine eigene Einstellung mit Livesuche weist sie zu oder entfernt die Zuordnung; sie benötigt Veröffentlichungsrechte und CSRF-Schutz. Der erwartete bisherige Wert schützt gegen ein veraltetes Formular. Die Änderung liest die aktuellen Einstellungen innerhalb einer Transaktion, unter MySQL/MariaDB mit Zeilensperre, und schreibt nur die geänderte Zuordnung in den erhaltenen JSON-Inhalt. Das normale Unternehmensformular erhält diesen Wert unabhängig von seinen eigenen Feldern. Startseite und 404-Seite dürfen nicht dieselbe Seite sein. diff --git a/docs/STATUS.md b/docs/STATUS.md index 889e10a..a155abd 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,5 +1,17 @@ # Projektstatus +## 1.8.1 – Keine automatische Rechtszeile unter dem Footer (14.09.2026) + +**Behoben:** Das CMS hängt Datenschutz, Impressum und Einwilligungslink nicht mehr automatisch an den Footer an. Entfernt aus Seiten-/Archiv-/Fehlervorlagen und damit auch aus Seiten- und Bereichsvorschauen. Normale Textlinks, Menüs und ausdrücklich eingefügte „Rechtliches“-Bausteine bleiben erhalten; der Editorhinweis beschreibt die freie Platzierung. Keine Inhaltsänderung oder Migration (ADR 083). + +**Geprüft:** PHP-Vollsuite 477 Tests / 3815 Assertions und 32 JavaScript-Tests erfolgreich. Vorhandene Tests an die redaktionelle Platzierung angepasst; freiwilliger Rechtsblock und Einwilligungsablauf weiterhin geprüft. 47 Twig-Dateien und Produktionscontainer gültig, PHP-Stilprüfung der drei betroffenen PHP-Dateien ohne Änderungen, Git-Diff ohne Whitespacefehler. + +**Browser:** Isolierte Kant-IT-Kopie mit bestehenden Rechtsblöcken sowie einer Variante mit normalen Footer-Textlinks wie im Screenshot. Acht Prüfgruppen erfolgreich: Startseite, Beitragsarchiv und 404 auf Desktop 1600/Mobil 390 sowie Seiten- und Footer-Editor. Eigene Links erhalten, keine Zusatzleiste, kein horizontaler Überlauf. Screenshots unter `reports/footer-links/` visuell geprüft. Gegenprobe mit dem unveränderten Release 1.8.0 reproduziert die doppelte Leiste auf allen sechs öffentlichen Ansichten (`reports/footer-links-before/`). Originaldaten bleiben unverändert. + +**Bekannte Prüfbefunde:** Die Variante mit gewöhnlichen, unformatierten Textlinks meldet Kontrastbefunde an diesen Links; bei sichtbarem Nach-oben-Knopf zusätzlich einen Landmark-Befund. Das Archiv meldet außerdem Überschriftenreihenfolge und doppelte Landmark-Benennung. Alle Befunde betreffen dieselben Elemente wie in der Gegenprobe mit 1.8.0. Keine fehlerfreie Gesamt-Barrierefreiheit oder neuen Lighthouse-Werte behauptet. Keine Wiederholung der gesamten Editor-Bedienprüfung für diese Template-Entfernung. + +**Veröffentlichung:** Release 1.8.1 wird aus dem geprüften Commit gebaut und auf dem vorhandenen Git bereitgestellt. Danach über die Systemverwaltung aktualisieren; Kundenhostings werden hier nicht verändert. + ## 1.8.0 – Eigene 404-Seite mit Website-Design (14.09.2026) **Umgesetzt:** Einstellungen → 404-Seite mit Livesuche, bestehende Seite zuweisen, Auswahl zurücksetzen, direktem Bearbeiten und öffentlicher 404-Vorschau. „404-Vorlage anlegen“ erstellt und verknüpft einen bearbeitbaren Entwurf, ohne vorhandene Seiten zu überschreiben. Bis zur Veröffentlichung erscheint eine automatische Standard-404. Normaler Seitenrenderer übernimmt Standard-Header/-Footer, Theme, Schriften, Menüs und verknüpfte Sektionen. Die ID-Verknüpfung übersteht Umbenennen; Löschen führt auf den Standard zurück. Nur veröffentlichte Revisionen werden öffentlich ausgeliefert. diff --git a/public/assets/editor.js b/public/assets/editor.js index e969b8b..945c5e5 100644 --- a/public/assets/editor.js +++ b/public/assets/editor.js @@ -328,7 +328,7 @@ function renderProperties() { field('Beim Laden geöffnet', 'open', block.props.open, { checkbox: true }); } if (block.type === 'legal') { - field('Beschriftung für Vorlesesoftware', 'label', block.props?.label || 'Rechtliches', { maxLength: 60, help: 'Der Baustein zeigt Impressum, Datenschutz und – sobald jemand gefragt wurde – den Weg zurück zur Einwilligung. Die Adressen stehen in den Einstellungen. Steht er im Fußbereich, entfällt die eingebaute Zeile am Seitenende.' }); + field('Beschriftung für Vorlesesoftware', 'label', block.props?.label || 'Rechtliches', { maxLength: 60, help: 'Der Baustein zeigt Impressum, Datenschutz und – sobald jemand gefragt wurde – den Weg zurück zur Einwilligung. Die Adressen stehen in den Einstellungen. Platziere ihn dort, wo die Links erscheinen sollen, zum Beispiel im Fußbereich.' }); } if (block.type === 'postnav') { field('Beschriftung zurück', 'previousLabel', block.props.previousLabel, { maxLength: 60 }); diff --git a/public/assets/site.css b/public/assets/site.css index c8a406b..163e572 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -162,12 +162,6 @@ body.consent-open{padding-bottom:260px} .site-footer .block-column{border:0;border-radius:0;padding:0} .site-header .block-text, .site-footer .block-text{margin:0} -/* One landmark for the three links that must be on every page. Loose text outside a landmark is - also what axe objects to, and rightly: a screen reader then has no way to skip to it. */ -.legal-bar{padding:16px 20px;text-align:center;font-size:12px;color:var(--muted);background:color-mix(in srgb,var(--ink) 6%,var(--paper))} -.legal-bar a{color:var(--accent);text-decoration:none} -.legal-bar a:hover{text-decoration:underline} - /* An image takes the rounding of its figure. Without this a radius on an image block rounded the figure and left the picture square - which looked like the setting did nothing. */ .block-image img, .block-gallery img, .block-logos img { border-radius: inherit } diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index 8cc130e..cec2cb8 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -131,11 +131,6 @@ final class PageController extends AbstractController 'googleFonts' => $googleFonts, 'localFonts' => $localFonts, 'styleBundle' => $this->bundle->url([] !== $localFonts), - // Steht die Rechtszeile als Baustein im Dokument oder in einem Bereich, entfaellt - // die eingebaute Leiste am Seitenende - zweimal dasselbe waere schlechter als gar - // nichts, und die Pflicht ist mit dem Baustein erfuellt. - 'hasLegalBlock' => $this->hasType($page['document'], 'legal') - || (bool) array_filter(array_values($areas), fn (array $area): bool => $this->hasType($area, 'legal')), ], new Response(status: $status)); // Die Pruefung braucht ein Skript von Cloudflare und einen Rahmen dafuer. Beides wird nur diff --git a/templates/archive.html.twig b/templates/archive.html.twig index cd1ef09..b379fb7 100644 --- a/templates/archive.html.twig +++ b/templates/archive.html.twig @@ -53,17 +53,6 @@ {% for block in areas.footer.blocks %}{% include 'blocks/block.html.twig' with {block: block, postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: true, richHeadings: areas.footer.schemaVersion|default(1) >= 17, eagerImage: null} only %}{% endfor %} {% endif %} - {# Privacy statement, imprint and the way back to the consent decision. All three have to be - reachable from every page, so they are part of the template and not of the footer area, - which someone could delete. One labelled landmark, so they are not loose text either. #} - {% set legal = consentGate.legalLinks() %} - {% set revocable = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %} - {% if legal is not empty or revocable %} - - {% endif %} {% if not editing|default(false) and consentGate.needsBanner(app.request) %} {% include 'consent-banner.html.twig' with {consent: consentGate.state(app.request), back: app.request.pathInfo} only %} {% endif %} diff --git a/templates/not-found.html.twig b/templates/not-found.html.twig index 8e431ba..c9541ba 100644 --- a/templates/not-found.html.twig +++ b/templates/not-found.html.twig @@ -2,17 +2,6 @@ Seite nicht gefunden · KantSites{% include 'favicon.html.twig' %} {% set consentOpen = not editing|default(false) and consentGate.needsBanner(app.request) %}

404 · KantSites

Hier ist noch keine Seite.

Die Adresse existiert nicht oder die Seite ist noch nicht veröffentlicht.

Zur Startseite
- {# Privacy statement, imprint and the way back to the consent decision. All three have to be - reachable from every page, so they are part of the template and not of the footer area, - which someone could delete. One labelled landmark, so they are not loose text either. #} - {% set legal = consentGate.legalLinks() %} - {% set revocable = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %} - {% if legal is not empty or revocable %} - - {% endif %} {% if not editing|default(false) and consentGate.needsBanner(app.request) %} {% include 'consent-banner.html.twig' with {consent: consentGate.state(app.request), back: app.request.pathInfo} only %} {% endif %} diff --git a/templates/page.html.twig b/templates/page.html.twig index d056d0d..d456b6d 100644 --- a/templates/page.html.twig +++ b/templates/page.html.twig @@ -91,18 +91,6 @@ {% for block in areas.footer.blocks %}{% include 'blocks/block.html.twig' with {block: block, editing: editing|default(false), postLists: {}, forms: {}, breadcrumbs: [], navigation: navigation|default({}), sections: sections|default({}), richText: areas.footer.schemaVersion|default(1) >= 12, richHeadings: areas.footer.schemaVersion|default(1) >= 17, eagerImage: null} only %}{% endfor %} {% endif %} - {# Privacy statement, imprint and the way back to the consent decision. All three have to be - reachable from every page, so they are part of the template and not of the footer area, - which someone could delete. One labelled landmark, so they are not loose text either. #} - {% set legal = consentGate.legalLinks() %} - {% set revocable = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %} - {# Nur, wenn die Seite die Zeile nicht selbst enthaelt: sonst stuende sie zweimal da. #} - {% if (legal is not empty or revocable) and not hasLegalBlock|default(false) %} - - {% endif %} {% set oben = siteSettings.load() %} {% if oben.scrollTop|default(false) and not editing|default(false) %} {# Ein Anker, kein Skript. Er springt an den Anfang - das kann ein Browser allein. #} diff --git a/tests/CmsTest.php b/tests/CmsTest.php index 98afc94..58a1e69 100644 --- a/tests/CmsTest.php +++ b/tests/CmsTest.php @@ -1118,12 +1118,9 @@ final class CmsTest extends TestCase self::assertStringContainsString("script-src 'none'", (string) $zweite->headers->get('Content-Security-Policy')); } - public function testTheLegalLineCanLiveInsideTheFooterInsteadOfUnderneathIt(): void + public function testLegalLinksAppearOnlyWhereAnAuthorPlacesThem(): void { - // Umut: „ganz unten Einwilligung ändern, Datenschutz usw. ist links, komplett bitte besser - // lösen." Die eingebaute Leiste klebte als heller Streifen unter einem dunklen Fuß. Als - // Baustein steht sie dort, wo sie hingehört, und erbt die Farben ihres Bereichs. - // Zwei verschiedene Seiten: die Zeile fuehrt zu Datenschutz und Impressum. + // Die hinterlegten Adressen erzeugen keine zusätzliche Leiste unter dem Footer. $datenschutz = $this->pages->createPage('datenschutz', null, 'page', DemoContent::starter('Datenschutz')); $seite = $this->pages->findForEditor($datenschutz); $this->pages->publish($datenschutz, $seite['draft_revision_id'], $seite['version']); @@ -1132,9 +1129,10 @@ final class CmsTest extends TestCase $document['schemaVersion'] = 19; $this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1); - // Ohne Baustein: die eingebaute Leiste am Seitenende. + // Ohne selbst platzierte Links bleibt die Seite ohne Rechtszeile. $html = $this->request('/')->getContent(); - self::assertStringContainsString('