Unbekannte Blöcke erhalten statt abweisen (Dokumentschema v4)
Der Lesevalidator wies bisher jeden unbekannten Blocktyp ab, womit der Fallback im Renderer unerreichbar war: eine Seite mit Bausteinen aus einer deaktivierten Erweiterung wäre nicht mehr ladbar gewesen. Ab Schema v4 gilt ein Typ mit Namensraum (acme.slider) als Baustein einer Erweiterung und bleibt erhalten. Der Kern prüft weiterhin ID, Tiefe, Blockanzahl und Dokumentgröße, aber bewusst nicht die Props - er kennt das Schema der Erweiterung nicht. Damit ungeprüfte Werte nie ausgegeben werden, rendert der Kern aus einem solchen Block nichts: öffentlich nur ein HTML-Kommentar, in der Editorvorschau ein Platzhalter. Im Editor bleiben die Blöcke auswählbar, verschiebbar, duplizierbar und löschbar, aber nicht bearbeitbar, und es lässt sich nichts in sie einfügen. Testumgebung: Twig lädt Vorlagen jetzt automatisch neu. Ohne Debug konnte eine kompilierte Vorlage im Cache den Ausgang eines Testlaufs bestimmen. Geprüft: 64 PHP-Tests / 229 Assertions unter PHP 8.2.12 und 8.4.22, 11 JavaScript-Modelltests, Lint/Format/Container. Keine Browser- oder Lighthouse-Läufe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a6ffb9bdfd
commit
8e0006f883
13 files changed
+157
-26
No files matched your search
@@ -9,7 +9,7 @@ final class DocumentValidator
|
||||
public function validate(array $document): void
|
||||
{
|
||||
$schemaVersion = $document['schemaVersion'] ?? null;
|
||||
$this->require(in_array($schemaVersion, [1, 2, 3], true), 'Unsupported document schema.');
|
||||
$this->require(in_array($schemaVersion, [1, 2, 3, 4], true), 'Unsupported document schema.');
|
||||
$this->text($document['title'] ?? null, 160);
|
||||
$this->text($document['description'] ?? null, 320);
|
||||
$this->require(is_array($document['blocks'] ?? null) && array_is_list($document['blocks']), 'Blocks must be a list.');
|
||||
@@ -29,12 +29,17 @@ final class DocumentValidator
|
||||
$this->require(is_string($id) && 1 === preg_match('/^[a-zA-Z0-9_-]{1,64}$/D', $id) && !isset($ids[$id]), 'Block IDs must be stable and unique.');
|
||||
$ids[$id] = true;
|
||||
$type = $block['type'] ?? null;
|
||||
$this->require(in_array($type, ['section', 'row', 'column', 'heading', 'text', 'image', 'button'], true) || (3 === $schemaVersion && 'gallery' === $type), 'Unknown block type. Preserve source until its plugin is available.');
|
||||
// From schema v4 on, a namespaced type belongs to a plugin. The core does not know its
|
||||
// property schema, so it keeps the block untouched instead of rejecting or dropping the
|
||||
// page: a deactivated plugin must never make stored content unreadable. Nothing from
|
||||
// props is rendered for such a block, so unvalidated values never reach the output.
|
||||
$plugin = $schemaVersion >= 4 && is_string($type) && 1 === preg_match('/^[a-z][a-z0-9]{1,30}\.[a-z][a-z0-9-]{1,40}$/D', $type);
|
||||
$this->require(in_array($type, ['section', 'row', 'column', 'heading', 'text', 'image', 'button'], true) || ($schemaVersion >= 3 && 'gallery' === $type) || $plugin, 'Unknown block type. Preserve source until its plugin is available.');
|
||||
$props = $block['props'] ?? [];
|
||||
$layout = $block['layout'] ?? [];
|
||||
$this->require(is_array($props) && is_array($layout), 'Props and layout must be objects.');
|
||||
foreach ($layout as $key => $value) {
|
||||
if ('effects' === $key && 3 === $schemaVersion) {
|
||||
if ('effects' === $key && $schemaVersion >= 3) {
|
||||
$this->require(is_array($value), 'Effects must be an object.');
|
||||
$effects = ['shadow' => ['none', 'soft', 'strong'], 'hover' => ['none', 'lift', 'zoom'], 'entrance' => ['none', 'fade', 'rise']];
|
||||
foreach ($value as $effect => $token) {
|
||||
@@ -69,7 +74,7 @@ final class DocumentValidator
|
||||
}
|
||||
foreach (['caption', 'mediaId', 'sources'] as $extra) {
|
||||
if (isset($props[$extra])) {
|
||||
$this->require(3 === $schemaVersion, 'Media properties require schema v3.');
|
||||
$this->require($schemaVersion >= 3, 'Media properties require schema v3.');
|
||||
}
|
||||
}
|
||||
if (isset($props['caption'])) {
|
||||
@@ -91,7 +96,8 @@ final class DocumentValidator
|
||||
}
|
||||
$children = $block['children'] ?? [];
|
||||
$this->require(is_array($children) && array_is_list($children), 'Children must be a list.');
|
||||
$container = in_array($type, ['section', 'row', 'column', 'gallery'], true);
|
||||
// A plugin block may nest content; keeping its children is part of preserving the page.
|
||||
$container = $plugin || in_array($type, ['section', 'row', 'column', 'gallery'], true);
|
||||
$this->require($container || [] === $children, 'Leaf blocks cannot contain children.');
|
||||
foreach ($children as $child) {
|
||||
if ('gallery' === $type) {
|
||||
|
||||
@@ -138,7 +138,7 @@ final class EditorController extends AbstractController
|
||||
}
|
||||
$this->validator->validate($document);
|
||||
|
||||
return $this->render('page.html.twig', ['page' => [...$page, 'document' => $document], 'canonical' => '']);
|
||||
return $this->render('page.html.twig', ['page' => [...$page, 'document' => $document], 'canonical' => '', 'editing' => true]);
|
||||
}
|
||||
if (!isset($body['expectedVersion']) || !is_int($body['expectedVersion']) || $body['expectedVersion'] < 0) {
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
Reference in new issue
Block a user