Seitenbreite wirkt, vier Menueplaetze, Seiten ohne Kopfbereich
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:
1 parent
67b85751cf
commit
af3589c95c
22 files changed
+357
-26
No files matched your search
@@ -45,6 +45,8 @@ final class EditorController extends AbstractController
|
||||
'pages' => $pages,
|
||||
'trash' => $this->pages->trash(),
|
||||
'parents' => array_values(array_filter($pages, static fn (array $p): bool => 'page' === $p['type'] && $p['depth'] + 2 <= PageRepository::MAX_DEPTH)),
|
||||
// Which header and footer areas a page can choose from.
|
||||
'areaChoices' => ['header' => $this->partials->choices('header'), 'footer' => $this->partials->choices('footer')],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -148,6 +150,30 @@ final class EditorController extends AbstractController
|
||||
return $this->back('Die Beitragsdaten wurden gespeichert.', 'success');
|
||||
}
|
||||
|
||||
/** Which header and footer this page uses, including none at all. */
|
||||
#[Route('/pages/{id}/bereiche', name: 'admin_page_areas', methods: ['POST'])]
|
||||
public function areas(Request $request, string $id): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_EDITOR');
|
||||
if (!$this->isCsrfTokenValid('pages', $request->request->getString('_csrf_token'))) {
|
||||
return $this->back('Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.');
|
||||
}
|
||||
// An empty field means "the standard area"; that is stored as null, not as an address.
|
||||
$choice = static fn (string $value): ?string => '' === $value ? null : $value;
|
||||
try {
|
||||
$this->pages->saveAreas(
|
||||
$id,
|
||||
$choice($request->request->getString('headerArea')),
|
||||
$choice($request->request->getString('footerArea')),
|
||||
$request->request->getInt('expectedVersion', -1),
|
||||
);
|
||||
} catch (InvalidPage|StaleWrite $e) {
|
||||
return $this->back($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->back('Kopf- und Fußbereich dieser Seite wurden gespeichert.', 'success');
|
||||
}
|
||||
|
||||
#[Route('/pages/{id}/move', name: 'admin_move', methods: ['POST'])]
|
||||
public function move(Request $request, string $id): Response
|
||||
{
|
||||
|
||||
@@ -41,7 +41,8 @@ final class PageController extends AbstractController
|
||||
$page = $this->pages->findPublished($path);
|
||||
$styles = '';
|
||||
$googleFonts = null;
|
||||
$areas = $this->areas();
|
||||
// The page decides which header and footer it gets - including none.
|
||||
$areas = $this->areas($page);
|
||||
if (null !== $page) {
|
||||
$theme = $this->theme->load();
|
||||
// Theme first, block styles after, so a block always wins over the site defaults.
|
||||
@@ -374,11 +375,21 @@ final class PageController extends AbstractController
|
||||
*
|
||||
* @return array<string, array>
|
||||
*/
|
||||
private function areas(): array
|
||||
private function areas(?array $page = null): array
|
||||
{
|
||||
$areas = [];
|
||||
foreach (['header', 'footer'] as $kind) {
|
||||
$partial = $this->partials->findByKind($kind);
|
||||
// A page may ask for another area or for none at all: a landing page without a
|
||||
// navigation is a normal wish, and it must not need a second header document that
|
||||
// happens to be empty.
|
||||
$choice = $page[$kind.'_area'] ?? null;
|
||||
if ('none' === $choice) {
|
||||
continue;
|
||||
}
|
||||
$partial = $this->partials->findByKind($kind, is_string($choice) && '' !== $choice ? $choice : 'default');
|
||||
// A page pointing at an area that no longer exists falls back to the standard one
|
||||
// rather than losing its header without saying so.
|
||||
$partial ??= $this->partials->findByKind($kind);
|
||||
if (null !== $partial) {
|
||||
$areas[$kind] = $partial['document'];
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user