feat: LoopContext – Schleifen-Kontext aus dem Blockbaum, innerste gewinnt (X1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
39dfb0f490
commit
617ad2888d
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Compose;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* The loop context of a node from the block tree (ruling X1), pure PHP. A block runs a loop exactly when
|
||||
* attrs.module.advanced.loop.desktop.value.enable is the string "on" – Divi's render gate
|
||||
* (LoopUtils::_is_block_loop_enabled, LoopUtils.php:4720-4724) and the Visual Builder read only that;
|
||||
* tablet, phone, hover/sticky values and true/1 do not count. A missing queryType is post_types
|
||||
* (LoopUtils.php:730-732). The nearest enabled ancestor-or-self wins whatever its kind: Divi resolves
|
||||
* nested loops innermost first (LoopUtils.php:4576-4605).
|
||||
*
|
||||
* Limits (rulings X1, X7): loop settings that come only from a preset (LoopPresetAttrsMap) are not seen,
|
||||
* and opaque blocks (global modules, foreign blocks) are not read – both leave the node without a loop,
|
||||
* so loop_* options are refused there (fail closed).
|
||||
*/
|
||||
final class LoopContext
|
||||
{
|
||||
private const PATH = 'module.advanced.loop';
|
||||
|
||||
/** @param array<string, mixed> $attrs Attributes of a block */
|
||||
public static function enabled(array $attrs): bool
|
||||
{
|
||||
return (self::settings($attrs)['enable'] ?? null) === 'on';
|
||||
}
|
||||
|
||||
/**
|
||||
* The loop of exactly this block; null when it runs none.
|
||||
*
|
||||
* @param array<string, mixed> $attrs Attributes of a block
|
||||
*/
|
||||
public static function ownScope(array $attrs): ?LoopScope
|
||||
{
|
||||
if (! self::enabled($attrs)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$queryType = self::settings($attrs)['queryType'] ?? 'post_types';
|
||||
|
||||
// Divi's sanitize_key() makes a non-string "" – a loop without a kind the tools support.
|
||||
return LoopScope::ofQueryType(is_string($queryType) ? $queryType : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $attrChain Attributes from the root down to the node (inclusive)
|
||||
*/
|
||||
public static function fromChain(array $attrChain): LoopScope
|
||||
{
|
||||
foreach (array_reverse($attrChain) as $attrs) {
|
||||
$scope = self::ownScope($attrs);
|
||||
if ($scope !== null) {
|
||||
return $scope;
|
||||
}
|
||||
}
|
||||
|
||||
return LoopScope::none();
|
||||
}
|
||||
|
||||
/** The loop of the node $nodeId (path ID) or its nearest ancestor; none() for an unknown ID. */
|
||||
public static function of(NodeIndex $index, string $nodeId): LoopScope
|
||||
{
|
||||
for ($node = $index->find($nodeId); $node !== null; $node = $index->parentBlock($node)) {
|
||||
$scope = $node->isOpaque() ? null : self::ownScope($node->attrs());
|
||||
if ($scope !== null) {
|
||||
return $scope;
|
||||
}
|
||||
}
|
||||
|
||||
return LoopScope::none();
|
||||
}
|
||||
|
||||
/**
|
||||
* desktop.value of module.advanced.loop; [] when it is no object.
|
||||
*
|
||||
* @param array<string, mixed> $attrs
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
private static function settings(array $attrs): array
|
||||
{
|
||||
$value = AttrMap::desktop($attrs, self::PATH);
|
||||
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user