Seiten im Browser anlegen und löschen, Adresshierarchie und Beitragstyp

Migration 20260912000400 ergänzt page.path, parent_id, type und
created_at. Die websiteweite Eindeutigkeit wandert vom Slug auf den
vollständigen Pfad, damit gleiche Segmente unter verschiedenen Eltern
möglich sind. Nur ADD COLUMN: Index- oder Fremdschlüsseländerungen
würden SQLite die Tabelle neu aufbauen lassen, was am Fremdschlüssel
revision.page_id scheitert; die Indizes werden in postUp() gesetzt.

POST /admin/pages legt Seiten und Beiträge als privaten Entwurf an
(ROLE_EDITOR), POST /admin/pages/{id}/delete entfernt eine Seite mit
allen Revisionen (ROLE_ADMIN). Beides mit CSRF, ohne JavaScript
bedienbar, mit Schutz für Elternseiten und veraltete Versionen.
Die öffentliche Route bedient verschachtelte Pfade und lässt die
CMS-Routen unberührt.

Testumgebung: Login-Throttling zählt jetzt in einem Array-Cache,
weil die persistierten Zähler die Anmeldung über Testläufe hinweg
aussperrten.

Geprüft: 56 PHP-Tests / 212 Assertions unter PHP 8.2.12 und 8.4.22,
10 JavaScript-Modelltests, Lint/Format/Container, Migration zuerst
gegen eine Datenbankkopie. Keine Browser- oder Lighthouse-Läufe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 10:31:46 +02:00
1 parent 82b07820bc
commit a6ffb9bdfd
16 files changed
+555 -23

No files matched your search

+22
View File
@@ -49,6 +49,28 @@ final class DemoContent
];
}
/** The first draft of a newly created page: valid, empty enough to replace, with exactly one H1. */
public static function starter(string $title, string $type = 'page'): array
{
return [
'schemaVersion' => 3,
'title' => $title,
'description' => 'post' === $type
? 'Ein neuer Beitrag. Ersetze diese Beschreibung durch eine kurze Zusammenfassung.'
: 'Eine neue Seite. Ersetze diese Beschreibung durch eine kurze Zusammenfassung fuer Suchmaschinen.',
'blocks' => [
self::section('start-hero', [
self::heading('start-heading', $title, 1),
self::text('start-intro', 'Schreibe hier den ersten Absatz. Waehle links ein Element aus, um es zu bearbeiten, oder fuege ueber die Elementbibliothek weitere Bloecke hinzu.'),
], 'mint'),
self::section('start-body', [
self::heading('start-subheading', 'Ein erster Abschnitt', 2),
self::text('start-text', 'Dieser Text ist ein Platzhalter. Ersetze ihn durch deinen eigenen Inhalt.'),
], 'paper', 'narrow'),
],
];
}
private static function article(string $title, string $description, string $heading, array $sections): array
{
$blocks = [self::section('article-hero', [self::text('article-label', 'KANT CMS / ENTWICKLUNG'), self::heading('article-heading', $heading, 1)], 'mint')];