fix(audit M1, L2): Meta-Schlüssel-Optionen gegen alle Schleifen der Vorfahrenkette prüfen; queryType wie sanitize_key normalisieren, terms/users als Term-/Benutzer-Schleife
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
52208764f0
commit
522fc8ff4d
@@ -45,8 +45,10 @@ final class LoopContext
|
||||
|
||||
$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 : '');
|
||||
// Divi's sanitize_key() (LoopUtils.php:729-731) makes a non-string "" and lowercases and strips the rest.
|
||||
return LoopScope::ofQueryType(
|
||||
is_string($queryType) ? preg_replace('/[^a-z0-9_\-]/', '', strtolower($queryType)) ?? '' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,14 +56,20 @@ final class LoopContext
|
||||
*/
|
||||
public static function fromChain(array $attrChain): LoopScope
|
||||
{
|
||||
$scopes = [];
|
||||
foreach (array_reverse($attrChain) as $attrs) {
|
||||
$scope = self::ownScope($attrs);
|
||||
if ($scope !== null) {
|
||||
return $scope;
|
||||
$scopes[] = $scope;
|
||||
}
|
||||
}
|
||||
if ($scopes === []) {
|
||||
return LoopScope::none();
|
||||
}
|
||||
|
||||
return LoopScope::none();
|
||||
$outerKinds = array_map(static fn (LoopScope $outer): ?string => $outer->kind(), array_slice($scopes, 1));
|
||||
|
||||
return $scopes[0]->inside($outerKinds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,14 +111,12 @@ final class LoopContext
|
||||
/** 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
|
||||
{
|
||||
$chain = [];
|
||||
for ($node = $index->find($nodeId); $node !== null; $node = $index->parentBlock($node)) {
|
||||
$scope = $node->isOpaque() ? null : self::ownScope($node->attrs());
|
||||
if ($scope !== null) {
|
||||
return $scope;
|
||||
}
|
||||
array_unshift($chain, $node->isOpaque() ? [] : $node->attrs());
|
||||
}
|
||||
|
||||
return LoopScope::none();
|
||||
return self::fromChain($chain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,10 +11,15 @@ defined('ABSPATH') || exit;
|
||||
* queryType: post_types and current_page → post, post_taxonomies → term, user_roles → user, menus → menu
|
||||
* (the five values the Visual Builder offers; LoopUtils.php:730-739, QueryTypeController.php:89-96).
|
||||
*
|
||||
* Any other queryType – repeater_<ACF field>, the aliases "terms"/"users", "" – is a loop the tools do
|
||||
* not support: queryType() names it, but it has no kind and is not active(), so every loop_* option is
|
||||
* refused inside it (fail closed). ACF repeater rows would need the setting acf_type, which the content
|
||||
* rules refuse (ruling X7e); Divi's content renderer knows no aliases (LoopUtils.php:3130-3148).
|
||||
* "terms" and "users" are Divi's aliases for term and user loops (LoopUtils.php:881-898; LoopContext
|
||||
* normalises the queryType like sanitize_key first). Any other queryType – repeater_<ACF field>, "" – is a
|
||||
* loop the tools do not support: queryType() names it, but it has no kind and is not active(), so every
|
||||
* loop_* option is refused inside it (fail closed). ACF repeater rows would need the setting acf_type,
|
||||
* which the content rules refuse (ruling X7e).
|
||||
*
|
||||
* Nested loops (audit M1): an empty inner loop hands its block unchanged to the outer loop, which then
|
||||
* resolves the references with ITS object. So kinds() lists the kinds of all enabled loops of the chain,
|
||||
* and a meta key option has to fit every one of them (fits()).
|
||||
*
|
||||
* kindOfOption() is the kind an option NAME belongs to: Divi takes the meta type of
|
||||
* loop_<post|user|term>_meta_key_* from the name and reads the meta of the loop object's ID, whatever the
|
||||
@@ -35,6 +40,8 @@ final class LoopScope
|
||||
'post_taxonomies' => self::TERM,
|
||||
'user_roles' => self::USER,
|
||||
'menus' => self::MENU,
|
||||
'terms' => self::TERM,
|
||||
'users' => self::USER,
|
||||
];
|
||||
|
||||
private const LABELS = [
|
||||
@@ -52,7 +59,8 @@ final class LoopScope
|
||||
'loop_acf_' => null,
|
||||
];
|
||||
|
||||
private function __construct(private ?string $queryType)
|
||||
/** @param list<?string> $outerKinds Kinds of the enclosing enabled loops, innermost first */
|
||||
private function __construct(private ?string $queryType, private array $outerKinds = [])
|
||||
{
|
||||
}
|
||||
|
||||
@@ -66,6 +74,26 @@ final class LoopScope
|
||||
return new self($queryType);
|
||||
}
|
||||
|
||||
/**
|
||||
* The same loop inside the loops of $outerKinds (innermost first; null = an unsupported loop).
|
||||
*
|
||||
* @param list<?string> $outerKinds
|
||||
*/
|
||||
public function inside(array $outerKinds): self
|
||||
{
|
||||
return new self($this->queryType, $outerKinds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kinds of all enabled loops of the chain, innermost (own) first; null for an unsupported loop; [] without.
|
||||
*
|
||||
* @return list<?string>
|
||||
*/
|
||||
public function kinds(): array
|
||||
{
|
||||
return $this->queryType === null ? [] : [$this->kind(), ...$this->outerKinds];
|
||||
}
|
||||
|
||||
/** Whether there is a loop whose loop_* options the tools allow. */
|
||||
public function active(): bool
|
||||
{
|
||||
@@ -92,10 +120,19 @@ final class LoopScope
|
||||
return $kind === null ? (string) $this->queryType : self::LABELS[$kind];
|
||||
}
|
||||
|
||||
/** Whether the loop_* option $option belongs to this loop's kind. */
|
||||
/**
|
||||
* Whether the loop_* option $option belongs to this loop's kind – a meta key option (loop_<kind>_meta_key_*)
|
||||
* to the kind of every loop of the chain (audit M1), since Divi reads meta by the ID of whatever loop
|
||||
* object resolves it.
|
||||
*/
|
||||
public function fits(string $option): bool
|
||||
{
|
||||
return $this->active() && self::kindOfOption($option) === $this->kind();
|
||||
$kind = self::kindOfOption($option);
|
||||
if (! $this->active() || $kind !== $this->kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! str_contains($option, '_meta_key_') || array_unique($this->kinds()) === [$kind];
|
||||
}
|
||||
|
||||
/** Kind of a loop_* option by its name; null for other names and ACF repeater options. */
|
||||
|
||||
Reference in New Issue
Block a user