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>
112 lines
8.1 KiB
PHP
112 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace App\Content;
|
|
|
|
final class DemoContent
|
|
{
|
|
public static function pages(): array
|
|
{
|
|
return [
|
|
'home' => [
|
|
'schemaVersion' => 1,
|
|
'title' => 'Kant CMS · Raum für deine Website',
|
|
'description' => 'Ein eigenes CMS für individuelle Kundenwebsites. Entdecke die technische Grundlage für strukturierte Inhalte, schnelle Seiten und einen visuellen Editor.',
|
|
'blocks' => [
|
|
self::section('hero', [
|
|
self::text('eyebrow', 'KANT CMS / DER ANFANG IST GEMACHT'),
|
|
self::heading('headline', 'Deine Inhalte.\nDein Gestaltungsspielraum.', 1),
|
|
self::text('intro', 'Eine Website sollte zu dir passen. Wir entwickeln die Grundlage dafür: klar strukturierte Inhalte, unabhängiges Hosting und einen Editor, der Gestaltung einfach macht.'),
|
|
self::button('discover', 'Die Grundlage entdecken', '/grundlagen'),
|
|
]),
|
|
self::section('principles', [
|
|
self::text('principles-label', '01 / WAS UNS WICHTIG IST'),
|
|
self::heading('principles-heading', 'Ein guter Auftritt beginnt\nunter der Oberfläche.', 2),
|
|
['id' => 'principles-grid', 'type' => 'row', 'children' => [
|
|
self::card('content', '01 — INHALTE', 'Klar aufgebaut.', 'Texte und Gestaltung bleiben getrennt. Ein versionierter Blockbaum bildet die Grundlage, damit sich Inhalte später weiterentwickeln lassen.'),
|
|
self::card('delivery', '02 — AUSGABE', 'Direkt lesbar.', 'Der Hauptinhalt kommt als vollständiges HTML beim Browser an. Zum Lesen braucht diese Seite kein JavaScript und keine Editor-Bibliothek.'),
|
|
self::card('control', '03 — KONTROLLE', 'Bewusst veröffentlicht.', 'Entwurf und Veröffentlichung haben eigene Stände. Eine gespeicherte Änderung erscheint erst dann öffentlich, wenn sie veröffentlicht wird.'),
|
|
]],
|
|
], 'mint'),
|
|
self::section('next', [
|
|
self::text('next-label', '02 / VON DER GRUNDLAGE ZUM WERKZEUG'),
|
|
self::heading('next-heading', 'Schritt für Schritt.\nMit einem klaren Ziel.', 2),
|
|
self::text('next-text', 'Diese Seite ist der erste technische Prototyp von Kant CMS. Als Nächstes entsteht der visuelle Editor mit Strukturbaum, Vorschau und Eigenschaften. Das fertige CMS wächst an einer echten Pilotwebsite.'),
|
|
self::button('progress', 'Den Projektstand ansehen', '/projektstand'),
|
|
], 'ink'),
|
|
],
|
|
],
|
|
'grundlagen' => self::article('Die Grundlage · Kant CMS', 'Wie Kant CMS Inhalte als Blöcke speichert und Seiten als HTML ausliefert.', 'Ein Fundament für\nveränderbare Websites.', [
|
|
['Inhalte mit Struktur', 'Eine Seite besteht aus einem versionierten Dokument. Sektionen, Zeilen und Spalten ordnen Überschriften, Texte, Bilder und Links. Jeder Block hat eine stabile Kennung. Text wird bei der Ausgabe automatisch maskiert; frei ausführbares HTML ist kein Inhaltsformat.'],
|
|
['Ein Entwurf bleibt ein Entwurf', 'Speichern erzeugt einen eigenen Stand. Die öffentliche Route liest ausschließlich die veröffentlichte Revision. Veraltete Schreibstände werden abgewiesen, damit eine ältere Bearbeitung neue Inhalte nicht unbemerkt überschreibt.'],
|
|
['PHP-Webhosting als Ziel', 'Der Prototyp nutzt Symfony, Twig und Doctrine. Die lokale Installation verwendet SQLite. Für den späteren Hostingbetrieb ist MySQL oder MariaDB vorgesehen. Der öffentlich erreichbare Dokumentenstamm ist ausschließlich das Verzeichnis public.'],
|
|
['Qualität von Anfang an', 'Semantische Elemente, sichtbarer Tastaturfokus und ein Sprunglink sind bereits Teil der Ausgabe. Automatische Prüfungen begleiten die Entwicklung. Belastbare Qualitätswerte für Kundenwebsites benötigen repräsentative Inhalte und Messungen auf dem tatsächlichen Hosting.'],
|
|
]),
|
|
'projektstand' => self::article('Projektstand · Kant CMS', 'Der aktuelle Entwicklungsstand von Kant CMS und die nächsten Schritte zum visuellen Editor.', 'Der erste Schritt\nist eine solide Basis.', [
|
|
['Jetzt: technische Grundlage', 'Projektregeln und Entwicklungsplan sind abgelegt. Das Grundgerüst speichert Seiten in einer Datenbank und liefert veröffentlichte Blockdokumente als HTML aus. Diese Demonstrationsinhalte gehören zum Prototyp; sie sind keine Kundenwebsite.'],
|
|
['Als Nächstes: visuell bearbeiten', 'Geplant sind eine Elementbibliothek, ein synchronisierter Strukturbaum, Live-Vorschau und ein Eigenschaftenpanel. Verschieben, Duplizieren, Löschen und Undo/Redo werden zusammen mit dem Speichern und Laden geprüft.'],
|
|
['Noch zu konkretisieren', 'Die Pilotwebsite, konkrete UX-Builder-Referenzen und die Ausstattung des PHP-Webhostings stehen noch aus. Anmeldung, Medienverwaltung, Formulare, Templates und Plugins folgen in den dafür vorgesehenen Ausbauschritten.'],
|
|
]),
|
|
];
|
|
}
|
|
|
|
/** 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')];
|
|
$children = [];
|
|
foreach ($sections as $i => [$label, $text]) {
|
|
$children[] = self::heading('heading-'.$i, $label, 2);
|
|
$children[] = self::text('text-'.$i, $text);
|
|
}
|
|
$blocks[] = self::section('article-body', $children, 'paper', 'narrow');
|
|
|
|
return ['schemaVersion' => 1, 'title' => $title, 'description' => $description, 'blocks' => $blocks];
|
|
}
|
|
|
|
private static function section(string $id, array $children, string $tone = 'paper', string $width = 'normal'): array
|
|
{
|
|
return ['id' => $id, 'type' => 'section', 'layout' => ['tone' => $tone, 'width' => $width], 'children' => $children];
|
|
}
|
|
|
|
private static function heading(string $id, string $text, int $level): array
|
|
{
|
|
return ['id' => $id, 'type' => 'heading', 'props' => ['text' => str_replace('\\n', ' ', $text), 'level' => $level]];
|
|
}
|
|
|
|
private static function text(string $id, string $text): array
|
|
{
|
|
return ['id' => $id, 'type' => 'text', 'props' => ['text' => $text]];
|
|
}
|
|
|
|
private static function button(string $id, string $text, string $href): array
|
|
{
|
|
return ['id' => $id, 'type' => 'button', 'props' => ['text' => $text, 'href' => $href]];
|
|
}
|
|
|
|
private static function card(string $id, string $label, string $title, string $text): array
|
|
{
|
|
return ['id' => $id, 'type' => 'column', 'children' => [self::text($id.'-label', $label), self::heading($id.'-heading', $title, 3), self::text($id.'-text', $text)]];
|
|
}
|
|
}
|