Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
108 lines
3.5 KiB
PHP
108 lines
3.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations\Divi\BlockTree;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* post_content of Divi Library entries (F4, fixtures stage3): the nodes sit in a divi/placeholder
|
||
* frame; a section/row/module entry has exactly one root, a layout entry sections. Pure PHP.
|
||
*/
|
||
final class LibraryContent
|
||
{
|
||
public const WRAPPER = 'divi/placeholder';
|
||
|
||
/**
|
||
* Parses library content for copying and unifies its line breaks (Container::unifyLineBreaks()).
|
||
* Never used for the content a tool edits in place – that keeps its bytes.
|
||
*
|
||
* @throws MalformedMarkup
|
||
*/
|
||
public static function load(Parser $parser, string $content): Document
|
||
{
|
||
$document = $parser->parse($content);
|
||
$document->unifyLineBreaks();
|
||
|
||
return $document;
|
||
}
|
||
|
||
/** post_content of a library entry holding $node, framed like the builder's "add to library". */
|
||
public static function wrap(Block $node): string
|
||
{
|
||
return '<!-- wp:' . self::WRAPPER . ' -->' . Serializer::block($node) . '<!-- /wp:' . self::WRAPPER . ' -->';
|
||
}
|
||
|
||
/** @return list<Block> Top-level nodes: the children of the first divi/placeholder, else of the document. */
|
||
public static function roots(Document $document): array
|
||
{
|
||
foreach ($document->children() as $block) {
|
||
if ($block->isPlaceholder()) {
|
||
return $block->children();
|
||
}
|
||
}
|
||
|
||
return $document->children();
|
||
}
|
||
|
||
/** Whether $block is or contains a global module. */
|
||
public static function containsGlobal(Block $block): bool
|
||
{
|
||
if ($block->isGlobal()) {
|
||
return true;
|
||
}
|
||
|
||
foreach ($block->children() as $child) {
|
||
if (self::containsGlobal($child)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Why the roots $after may not replace $before, or null. Instances on pages expect their
|
||
* entry's root type (blockName), so a section/row/module entry keeps exactly one root of the
|
||
* same type; a layout entry (or one with several roots) holds sections only. A global entry
|
||
* contains no global modules (F10: Divi forbids nesting).
|
||
*
|
||
* @param list<Block> $before
|
||
* @param list<Block> $after
|
||
*/
|
||
public static function shapeViolation(array $before, array $after, ?string $layoutType, bool $global): ?string
|
||
{
|
||
if ($after === []) {
|
||
return 'Der Bibliothekseintrag darf nicht leer werden.';
|
||
}
|
||
|
||
if ($layoutType === 'layout' || count($before) !== 1) {
|
||
foreach ($after as $root) {
|
||
if (GlobalInstance::effectiveName($root) !== 'divi/section') {
|
||
return 'Ein Layout-Eintrag der Bibliothek enthält nur Abschnitte.';
|
||
}
|
||
}
|
||
} else {
|
||
$expected = GlobalInstance::effectiveName($before[0]);
|
||
if (count($after) !== 1 || GlobalInstance::effectiveName($after[0]) !== $expected) {
|
||
return sprintf(
|
||
'Der Bibliothekseintrag muss genau ein %s bleiben (Instanzen auf den Seiten erwarten diesen Typ).',
|
||
$expected
|
||
);
|
||
}
|
||
}
|
||
|
||
if ($global) {
|
||
foreach ($after as $root) {
|
||
if (self::containsGlobal($root)) {
|
||
return 'Ein globaler Bibliothekseintrag darf keine globalen Module enthalten (Divi erlaubt keine '
|
||
. 'Verschachtelung).';
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|