fix: anchor Divi name/id patterns at string end, number-advanced stays unknown, doc wording

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 13:16:20 +02:00
co-authored by Claude Opus 5.5
parent 4356e69bb9
commit c24b91d8a5
8 changed files with 13 additions and 8 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ Breakpoints: `desktop`, `tablet`, `phone` (je nach Website auch `tabletWide`, `p
gültigen Pfade dieses Moduls auf dieser Website (live aus Divi) und die Kurzform-Felder mit ihrem Pfad. gültigen Pfade dieses Moduls auf dieser Website (live aus Divi) und die Kurzform-Felder mit ihrem Pfad.
`path_kinds` nennt die Feldart eines Pfads (`text`, `richtext`, `code`, `url`, `image`, `color`, `enum`, `path_kinds` nennt die Feldart eines Pfads (`text`, `richtext`, `code`, `url`, `image`, `color`, `enum`,
`onoff`, `number`, `list`, `icon`), soweit aus den Divi-Metadaten ableitbar. Bei einem zusammengesetzten `onoff`, `number`, `list`, `icon`), soweit aus den Divi-Metadaten ableitbar. Bei einem zusammengesetzten
Pfad (mehrere benannte Unterfelder unter einem Pfad, z. B. `buttonOne.innerContent` mit `text` und Pfad (ein oder mehrere benannte Unterfelder unter einem Pfad, z. B. `buttonOne.innerContent` mit `text` und
`linkUrl`) ist der Eintrag stattdessen eine Zuordnung Unterfeld → Feldart (`{"text": "text", "linkUrl": `linkUrl`) ist der Eintrag stattdessen eine Zuordnung Unterfeld → Feldart (`{"text": "text", "linkUrl":
"url"}`), soweit für die einzelnen Unterfelder ableitbar; fehlende Pfade oder Unterfelder gelten als "url"}`), soweit für die einzelnen Unterfelder ableitbar; fehlende Pfade oder Unterfelder gelten als
unbekannt, statt eine Feldart zu raten. unbekannt, statt eine Feldart zu raten.
+1 -1
View File
@@ -20,7 +20,7 @@ use stdClass;
final class Block extends Container final class Block extends Container
{ {
/** A valid block name: "namespace/block-name" (WordPress' own block name convention). */ /** A valid block name: "namespace/block-name" (WordPress' own block name convention). */
private const NAME_PATTERN = '/^[a-z][a-z0-9_-]*\/[a-z][a-z0-9_-]*$/'; private const NAME_PATTERN = '/^[a-z][a-z0-9_-]*\/[a-z][a-z0-9_-]*$/D';
/** /**
* @param array<string, mixed> $attrs * @param array<string, mixed> $attrs
@@ -14,7 +14,7 @@ namespace LIT\MCP\Integrations\Divi\BlockTree;
*/ */
final class NodeIndex final class NodeIndex
{ {
public const ID_PATTERN = '/^[srcmgx]\d+(\.[srcmgx]\d+)*$/'; public const ID_PATTERN = '/^[srcmgx]\d+(\.[srcmgx]\d+)*$/D';
/** @var array<string, Block> */ /** @var array<string, Block> */
private array $byId = []; private array $byId = [];
@@ -9,7 +9,7 @@ namespace LIT\MCP\Integrations\Divi\BlockTree;
* delimiters; changed or new blocks get delimiters generated exactly like WordPress' * delimiters; changed or new blocks get delimiters generated exactly like WordPress'
* get_comment_delimited_block_content() / serialize_block_attributes() do, which also * get_comment_delimited_block_content() / serialize_block_attributes() do, which also
* yields the escaping Divi expects inside $variable(…)$ payloads: json_encode()'s own * yields the escaping Divi expects inside $variable(…)$ payloads: json_encode()'s own
* \" (an escaped quote inside a JSON string) is replaced by ", see ESCAPES below. * \" (an escaped quote inside a JSON string) is replaced by \u0022, see ESCAPES below.
*/ */
final class Serializer final class Serializer
{ {
@@ -69,7 +69,10 @@ final class AttrPathIndex
* type still tells them apart reliably. Only used for composite (subName-bearing) * type still tells them apart reliably. Only used for composite (subName-bearing)
* candidates; a plain path keeps trusting its field component alone (COMPONENT_KINDS), * candidates; a plain path keeps trusting its field component alone (COMPONENT_KINDS),
* so an already more specific kind there (richtext, code, …) is never downgraded to the * so an already more specific kind there (richtext, code, …) is never downgraded to the
* generic "text" dynamicContent commonly names for both. * generic "text" dynamicContent commonly names for both. number-advanced (e.g. border
* radius) holds an object of corner values and therefore stays unknown. A path that has
* both a plain field and named sub-fields keeps only the sub-field map (the plain field
* then counts as unknown).
*/ */
private const DYNAMIC_CONTENT_KINDS = [ private const DYNAMIC_CONTENT_KINDS = [
'text' => 'text', 'text' => 'text',
@@ -77,7 +80,6 @@ final class AttrPathIndex
'image' => 'image', 'image' => 'image',
'color' => 'color', 'color' => 'color',
'number' => 'number', 'number' => 'number',
'number-advanced' => 'number',
]; ];
private const ATTR_NAME_PATTERN = '/^[A-Za-z0-9]+(\.[A-Za-z0-9]+)+$/'; private const ATTR_NAME_PATTERN = '/^[A-Za-z0-9]+(\.[A-Za-z0-9]+)+$/';
@@ -84,8 +84,9 @@ final class DiviCatalogAbility implements Ability
'type' => 'object', 'type' => 'object',
'description' => 'Bekannter Attributpfad => Feldart (text, richtext, code, url, ' 'description' => 'Bekannter Attributpfad => Feldart (text, richtext, code, url, '
. 'image, color, enum, onoff, number, list, icon), soweit aus den Divi-Metadaten ' . 'image, color, enum, onoff, number, list, icon), soweit aus den Divi-Metadaten '
. 'ableitbar. Bei einem zusammengesetzten Pfad (mehrere benannte Unterfelder unter ' . 'ableitbar. Bei einem zusammengesetzten Pfad (ein oder mehrere benannte '
. 'einem Pfad, z. B. buttonOne.innerContent mit "text" und "linkUrl") ist der Wert ' . 'Unterfelder unter einem Pfad, z. B. buttonOne.innerContent mit "text" und '
. '"linkUrl") ist der Wert '
. 'stattdessen eine Zuordnung Unterfeld => Feldart, soweit für die einzelnen ' . 'stattdessen eine Zuordnung Unterfeld => Feldart, soweit für die einzelnen '
. 'Unterfelder ableitbar. Fehlende Pfade oder Unterfelder gelten als unbekannt.', . 'Unterfelder ableitbar. Fehlende Pfade oder Unterfelder gelten als unbekannt.',
], ],
@@ -19,6 +19,7 @@ final class BlockTest extends TestCase
return [ return [
'no namespace' => ['button'], 'no namespace' => ['button'],
'trailing markup injection' => ['divi/x --><script>'], 'trailing markup injection' => ['divi/x --><script>'],
'trailing newline' => ["divi/x\n"],
'uppercase' => ['Divi/Button'], 'uppercase' => ['Divi/Button'],
'empty' => [''], 'empty' => [''],
]; ];
@@ -42,6 +42,7 @@ final class NodeIndexTest extends TestCase
); );
self::assertSame(1, preg_match(NodeIndex::ID_PATTERN, 's1.g2')); self::assertSame(1, preg_match(NodeIndex::ID_PATTERN, 's1.g2'));
self::assertSame(0, preg_match(NodeIndex::ID_PATTERN, 's1..g2')); self::assertSame(0, preg_match(NodeIndex::ID_PATTERN, 's1..g2'));
self::assertSame(0, preg_match(NodeIndex::ID_PATTERN, "s1\n"));
} }
public function testDocumentWithoutPlaceholderIsItsOwnRoot(): void public function testDocumentWithoutPlaceholderIsItsOwnRoot(): void