feat: ReferenceUsageQuery findet Seiten, die globale Farben oder Variablen nennen (fail closed)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
68248da648
commit
a4ea487174
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Design;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Container;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\MalformedMarkup;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use LIT\MCP\Integrations\Divi\Library\UsageQuery;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* The posts that reference any of the global colors or variables $ids (GlobalUsages::reportFor(), ruling V8). Divi
|
||||
* writes a reference as $variable({"type":"color"|"content","value":{"name":"<id>",…}})$ into block attributes, the
|
||||
* quotes JSON-escaped in post_content (formats §1, §2). Fail closed (ruling P23): candidates contain an ID, a
|
||||
* JSON-escaped hyphen after gcid/gvid or an escaped letter; a candidate matches when its raw content names an ID
|
||||
* (also Divi 4 shortcodes), when the decoded attributes of any block do – the whole tree, also inside global modules,
|
||||
* which NodeIndex does not descend into –, when a block's attributes cannot be read or when the parser rejects the
|
||||
* markup.
|
||||
*/
|
||||
final class ReferenceUsageQuery implements UsageQuery
|
||||
{
|
||||
/** MySQL REGEXP for "gcid"/"gvid" followed by a JSON-escaped hyphen ("\\u002d"), which LIKE does not find. */
|
||||
public const ESCAPED_HYPHEN_REGEX = 'g[cv]id\\\\u002[dD]';
|
||||
|
||||
/** @var list<string> */
|
||||
private array $ids;
|
||||
|
||||
/**
|
||||
* @param list<string> $ids gcid-/gvid- IDs.
|
||||
* @throws InvalidArgumentException Without an ID.
|
||||
*/
|
||||
public function __construct(array $ids, private Parser $parser = new Parser())
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(
|
||||
$ids,
|
||||
static fn (mixed $id): bool => is_string($id) && $id !== ''
|
||||
)));
|
||||
if ($ids === []) {
|
||||
throw new InvalidArgumentException('ReferenceUsageQuery needs at least one ID.');
|
||||
}
|
||||
$this->ids = $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a string inside $value (nested arrays included, keys not) contains one of $ids.
|
||||
*
|
||||
* @param list<string> $ids
|
||||
*/
|
||||
public static function mentions(mixed $value, array $ids): bool
|
||||
{
|
||||
if (is_string($value)) {
|
||||
foreach ($ids as $id) {
|
||||
if (str_contains($value, $id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $inner) {
|
||||
if (self::mentions($inner, $ids)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function candidateCondition(\wpdb $db): string
|
||||
{
|
||||
$likes = array_map(static fn (string $id): string => '%' . $db->esc_like($id) . '%', $this->ids);
|
||||
|
||||
return (string) $db->prepare(
|
||||
'(' . str_repeat('post_content LIKE %s OR ', count($likes))
|
||||
. 'post_content REGEXP %s OR post_content REGEXP %s)',
|
||||
...array_merge($likes, [self::ESCAPED_HYPHEN_REGEX, self::ESCAPED_LETTER_REGEX])
|
||||
);
|
||||
}
|
||||
|
||||
public function matches(string $content): bool
|
||||
{
|
||||
if (self::mentions($content, $this->ids)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->inBlocks($this->parser->parse($content));
|
||||
} catch (MalformedMarkup) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function inBlocks(Container $container): bool
|
||||
{
|
||||
foreach ($container->children() as $block) {
|
||||
// The parser keeps attributes that are no JSON object as [] on an opaque block: unreadable, so it counts.
|
||||
$unreadable = $block->isOpaque() && $block->attrs() === [] && str_contains((string) $block->rawOpen(), '{');
|
||||
if ($unreadable || self::mentions($block->attrs(), $this->ids) || $this->inBlocks($block)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user