Seitenbreite wirkt, vier Menueplaetze, Seiten ohne Kopfbereich
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Drei Fragen, drei unterschiedliche Antworten.

Die globale Seitenbreite war einstellbar und wirkte nur in eine Richtung:
site.css schrieb width:min(1180px,...), also schluckte die 1180 jeden
groesseren Wert aus dem Designbereich. Schmaler ging, breiter nicht, ohne
jede Meldung. Die Zahl kommt jetzt aus dem Theme. Das ist der dritte Fehler
dieser Art an einem Tag - nach der Rahmenstaerke und der Bildrundung -, und
alle drei hatten dieselbe Ursache: CSS, das nie gegen eine Erwartung geprueft
wurde.

Menueplaetze und Menueart waren vermischt. Wo ein Menue steht, bestimmt der
Block und war immer frei. Welche Eintraege es zeigt, bestimmt der Platz -
davon gab es zwei, jetzt vier (Haupt, zweite, Fusszeile, Rechtliches). Wie es
aussieht, war gar nicht einstellbar: jetzt waagerecht, untereinander oder
aufklappbar als Burger. Der Burger ist ein details-Element, denn ein Menue,
das ein Skript zum Oeffnen braucht, geht auf einer Seite ohne Skript nicht auf.

"Seiten ohne Menue" war nicht moeglich - der Kopfbereich war ein Dokument fuer
alle Seiten. Jetzt waehlt jede Seite: Standard, ein anderer Bereich, oder
keiner. Dafuer musste die Regel "es gibt genau einen Kopf- und Fussbereich"
fallen; sie war richtig, solange niemand waehlen konnte, und wurde falsch,
sobald man waehlen kann. Ein bestehender Test, der diese Regel zusicherte, ist
deshalb umgeschrieben und prueft jetzt die neue.

Gespeichert wird die Wahl, nicht das Ergebnis: leer heisst Standard, none
heisst keiner, sonst die Adresse. Ein Verweis auf einen geloeschten Bereich
faellt auf den Standard zurueck, statt die Seite stillschweigend ohne
Navigation auszuliefern.

Neu im Ausbauplan: ein einzelnes install.php, das das Releasearchiv
herunterlaedt und entpackt, damit niemand tausende Dateien per FTP hochladen
muss. Mit den Regeln, die darueber entscheiden, ob das eine Hilfe oder eine
Einladung ist: Adresse und SHA-256 stehen in der Datei und nicht im Formular,
HTTPS mit Zertifikatspruefung, Schutz gegen ZIP-Slip, Selbstloeschung danach.

336 PHP-Tests / 1354 Assertions, 17 JS-Modelltests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 21:15:03 +02:00
1 parent 67b85751cf
commit af3589c95c
22 files changed
+357 -26

No files matched your search

+26
View File
@@ -154,6 +154,32 @@
</div>
{% endif %}
{% if is_granted('ROLE_EDITOR') %}
<div class="sheet-part">
<h4>Kopf- und Fußbereich</h4>
<p>Standard ist der gemeinsame Bereich. „Kein Bereich“ liefert die Seite ohne Kopf beziehungsweise Fuß aus – für eine Landingpage ohne Navigation. Weitere Bereiche legst du unter Bereiche an.</p>
<form action="{{ path('admin_page_areas', {id: page.id}) }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('pages') }}">
<input type="hidden" name="expectedVersion" value="{{ page.version }}">
<div class="fields">
{% for kind, label in {header: 'Kopfbereich', footer: 'Fußbereich'} %}
<div>
<label for="{{ kind }}-{{ page.id }}">{{ label }}</label>
<select id="{{ kind }}-{{ page.id }}" name="{{ kind }}Area">
<option value=""{{ page[kind ~ '_area'] is null ? ' selected' : '' }}>Standard</option>
{% for slug, name in areaChoices[kind] %}{% if slug != 'default' %}
<option value="{{ slug }}"{{ page[kind ~ '_area'] == slug ? ' selected' : '' }}>{{ name }}</option>
{% endif %}{% endfor %}
<option value="none"{{ page[kind ~ '_area'] == 'none' ? ' selected' : '' }}>Kein Bereich</option>
</select>
</div>
{% endfor %}
</div>
<button class="secondary" type="submit">Bereiche speichern</button>
</form>
</div>
{% endif %}
{% if is_granted('ROLE_PUBLISHER') %}
<div class="sheet-part">
<h4>Veröffentlichung planen</h4>
+1
View File
@@ -88,6 +88,7 @@
<div>
<label for="base-container">Inhaltsbreite</label>
<input id="base-container" name="base[containerWidth]" value="{{ theme.base.containerWidth }}" placeholder="1240px">
<p class="field-help">Die Breite des Inhalts auf allen Seiten. Abschnitte mit der Inhaltsbreite „Breit“ oder „Randlos“ halten sich nicht daran – das ist der Zweck dieser beiden.</p>
</div>
<div>
<label for="base-color">Textfarbe</label>
+11 -1
View File
@@ -68,8 +68,18 @@
{% elseif type == 'menu' %}
{% set links = navigation[props.slot]|default([]) %}
{% if links is not empty %}
<nav id="{{ block.id }}" class="block-menu{{ designClasses }}" aria-label="{{ props.slot == 'footer' ? 'Fußzeile' : 'Hauptnavigation' }}">
{# The layout is a class, and the burger variant is a details element: a menu that needs a
script to open would be a menu that does not open on a page without one. #}
<nav id="{{ block.id }}" class="block-menu menu-{{ props.layout|default('horizontal') }}{{ designClasses }}"
aria-label="{{ {header: 'Hauptnavigation', secondary: 'Zweite Navigation', footer: 'Fußzeile', legal: 'Rechtliches'}[props.slot]|default('Navigation') }}">
{% if props.layout|default('horizontal') == 'burger' %}
<details class="menu-burger">
<summary aria-label="Menü öffnen"><span class="burger-lines" aria-hidden="true"></span><span class="burger-text">Menü</span></summary>
<div class="menu-burger-body">{% include 'nav.html.twig' with {links: links} only %}</div>
</details>
{% else %}
{% include 'nav.html.twig' with {links: links} only %}
{% endif %}
</nav>
{% endif %}
{% elseif type == 'partialref' %}