feat: Divi composer – short/full form to blocks with structure rules, presets and labels
Labels, background_color and preset ids go through the choke point (S13); ambiguous preset names fail with their ids (S13a); fullwidth + rows is an error. Carry-ins: control characters stripped before the marker check (S32), kind override may only tighten, reference alt texts dropped, color check via VariableRef::typeOf (S33). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Compose;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\Schema\ModuleCatalog;
|
||||
|
||||
/**
|
||||
* Which node may sit where (spec §5.4 "Struktur"): page → sections; regular section → rows;
|
||||
* fullwidth section → fullwidth modules; column/group → modules; parent module → its child
|
||||
* modules (catalog). Columns only come with their row; specialty sections and read-only
|
||||
* nodes (opaque, non-Divi) are not rebuilt. Pure PHP.
|
||||
*/
|
||||
final class StructureRules
|
||||
{
|
||||
private const COLUMNS = ['divi/column', 'divi/column-inner'];
|
||||
private const ROWS = ['divi/row', 'divi/row-inner'];
|
||||
private const FULLWIDTH = 'divi/fullwidth-';
|
||||
|
||||
public function __construct(private ModuleCatalog $catalog)
|
||||
{
|
||||
}
|
||||
|
||||
/** Null when $child may be placed into $parent (null = top level of the page), else the German reason. */
|
||||
public function violation(?Block $parent, string $child): ?string
|
||||
{
|
||||
if (! $this->catalog->has($child)) {
|
||||
return sprintf('unbekanntes Modul "%s" (siehe lit-divi-catalog)', $child);
|
||||
}
|
||||
|
||||
if ($parent === null) {
|
||||
return $child === 'divi/section' ? null : 'auf oberster Ebene sind nur Abschnitte (section) erlaubt';
|
||||
}
|
||||
|
||||
if (! $parent->isDivi() || $parent->isOpaque()) {
|
||||
return 'der Zielknoten ist nur lesbar (globales Modul oder fremder Block)';
|
||||
}
|
||||
|
||||
$parentName = $parent->name();
|
||||
if ($parentName === 'divi/section') {
|
||||
return $this->inSection($parent, $child);
|
||||
}
|
||||
|
||||
if (in_array($parentName, self::ROWS, true)) {
|
||||
return 'Spalten entstehen mit der Zeile ("columns"); eine neue Zeile mit der gewünschten Aufteilung '
|
||||
. 'einfügen';
|
||||
}
|
||||
|
||||
if (in_array($parentName, self::COLUMNS, true) || $parentName === 'divi/group') {
|
||||
return $this->isPlainModule($child) || $child === 'divi/group'
|
||||
? null
|
||||
: sprintf('%s passt nicht in eine Spalte', $child);
|
||||
}
|
||||
|
||||
$children = $this->catalog->childrenOf($parentName);
|
||||
if ($children === []) {
|
||||
return sprintf('%s kann keine Kind-Module enthalten', $parentName);
|
||||
}
|
||||
|
||||
return in_array($child, $children, true)
|
||||
? null
|
||||
: sprintf('%s erlaubt nur %s als Kind', $parentName, implode(', ', $children));
|
||||
}
|
||||
|
||||
/** A module that goes into a column: category "module", not a fullwidth module. */
|
||||
public function isPlainModule(string $name): bool
|
||||
{
|
||||
return $this->catalog->category($name) === 'module' && ! str_starts_with($name, self::FULLWIDTH);
|
||||
}
|
||||
|
||||
private function inSection(Block $section, string $child): ?string
|
||||
{
|
||||
$type = AttrMap::desktop($section->attrs(), 'module.advanced.type');
|
||||
if ($type === 'specialty') {
|
||||
return 'Spezialabschnitte (specialty) nur im Divi-Builder umbauen; Module in ihre Spalten einfügen';
|
||||
}
|
||||
|
||||
if ($type === 'fullwidth') {
|
||||
return str_starts_with($child, self::FULLWIDTH)
|
||||
? null
|
||||
: 'in einen Vollbreite-Abschnitt passen nur fullwidth-Module';
|
||||
}
|
||||
|
||||
return $child === 'divi/row' ? null : 'ein Abschnitt enthält Zeilen (row)';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user