feat: lit-divi-insert prüft Schleifen-Inhalte im Kontext des Ziels; neue Schleife plus Inhalt in einem Aufruf fail closed (X3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
2f484b77ae
commit
249bc7b932
@@ -6,6 +6,9 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
|
||||
use LIT\MCP\Abilities\Annotations;
|
||||
use LIT\MCP\Abilities\Errors;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LoopContext;
|
||||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use WP_Error;
|
||||
|
||||
@@ -17,6 +20,9 @@ defined('ABSPATH') || exit;
|
||||
* validated first and every error is returned together; the blocks come only from Composer
|
||||
* (client values pass its choke point) and enter the tree only through TreeEditor::insert(),
|
||||
* which also wraps the first section of an empty page in divi/placeholder (ruling S9).
|
||||
*
|
||||
* Loop content (ruling X3): all new nodes share the loop scope of the target parent. A loop a new node of this
|
||||
* call switches on counts only once it is saved – with loop content in the same call the insert fails closed.
|
||||
*/
|
||||
final class DiviInsertAbility extends DiviWriteAbility
|
||||
{
|
||||
@@ -97,7 +103,18 @@ final class DiviInsertAbility extends DiviWriteAbility
|
||||
]);
|
||||
}
|
||||
|
||||
$composed = $composition->composer()->compose($nodes, $position['parent'], 'nodes', $context);
|
||||
$scope = self::loopAt($page->index, $position['parent']);
|
||||
$composed = $composition->inLoop($scope)->composer()->compose($nodes, $position['parent'], 'nodes', $context);
|
||||
if (self::startsLoop($composed['blocks']) && self::hasLoopContent($nodes)) {
|
||||
return Errors::invalidInput(
|
||||
'Schleifen-Inhalte unter einer neuen Divi-Schleife gehen nicht in einem Aufruf; nichts wurde '
|
||||
. 'gespeichert.',
|
||||
['nodes' => 'Die Schleife eines neuen Elements gilt erst nach dem Speichern: zuerst das Element mit '
|
||||
. 'Schleife einfügen, dann die Schleifen-Inhalte mit lit-divi-insert (parent = Pfad-ID des '
|
||||
. 'Elements) ergänzen']
|
||||
);
|
||||
}
|
||||
|
||||
if ($composed['errors'] !== []) {
|
||||
return DiviErrors::composed($composed, 'Knoten sind ungültig; nichts wurde gespeichert.');
|
||||
}
|
||||
@@ -117,4 +134,38 @@ final class DiviInsertAbility extends DiviWriteAbility
|
||||
|
||||
return $change;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether one of $blocks or a descendant switches a loop on.
|
||||
*
|
||||
* @param list<Block> $blocks
|
||||
*/
|
||||
private static function startsLoop(array $blocks): bool
|
||||
{
|
||||
foreach ($blocks as $block) {
|
||||
if (LoopContext::enabled($block->attrs()) || self::startsLoop($block->children())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the input names loop content: a {{loop:…}} placeholder or a reference to a loop_* option.
|
||||
*
|
||||
* @param array<array-key, mixed> $nodes
|
||||
*/
|
||||
private static function hasLoopContent(array $nodes): bool
|
||||
{
|
||||
$found = false;
|
||||
array_walk_recursive($nodes, static function (mixed $value) use (&$found): void {
|
||||
$found = $found || (is_string($value) && (
|
||||
preg_match('/^\s*\{\{\s*loop\s*:/', $value) === 1
|
||||
|| str_starts_with((string) VariableRef::nameOf($value), 'loop_')
|
||||
));
|
||||
});
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
|
||||
use LIT\MCP\Abilities\Ability;
|
||||
use LIT\MCP\Abilities\ToolResult;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LoopContext;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LoopScope;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Integrations\Divi\Release\FeatureSets;
|
||||
use LIT\MCP\Integrations\Divi\Release\RequiresDiviFeatures;
|
||||
@@ -114,4 +118,22 @@ abstract class DiviWriteAbility implements Ability, RequiresDiviFeatures
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling X3: the loop scope at $block – its nearest loop-enabled ancestor-or-self (LoopContext, X1); none at
|
||||
* the top level. $newAttrs (node ID => attrs) replaces the stored attrs of nodes this call changes. The index
|
||||
* is the tree loaded for this call, never a client value. Opaque blocks are never entered by NodeIndex, so one
|
||||
* can only be $block itself – and every tool refuses to write into or onto it.
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $newAttrs
|
||||
*/
|
||||
protected static function loopAt(NodeIndex $index, ?Block $block, array $newAttrs = []): LoopScope
|
||||
{
|
||||
$chain = [];
|
||||
for ($node = $block; $node !== null; $node = $index->parentBlock($node)) {
|
||||
array_unshift($chain, $newAttrs[(string) $index->idOf($node)] ?? $node->attrs());
|
||||
}
|
||||
|
||||
return LoopContext::fromChain($chain);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user