fix: UsageMatcher reads Divi 4 shortcodes like WordPress and Divi, everywhere (ruling P24)

- G1: every decoded string value of a Divi 5 opener is scanned for shortcodes;
  outside the openers only the remaining content is.
- G2: attributes are parsed like shortcode_parse_atts() and global_module is
  cast with (int), as Divi does.
- G3: a non-empty global_module other than "0" that casts to 0 makes Divi
  render the newest layout, so it counts for every entry (fail closed).
- The prefilter LIKEs use LOWER(post_content), so they are case-insensitive
  under any collation.
- The guide describes the fail-closed rule without an absolute claim.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 04:53:54 +02:00
co-authored by Claude Opus 5.5
parent 8f87ec6163
commit b6d791d46a
5 changed files with 172 additions and 33 deletions
@@ -50,8 +50,9 @@ final class GlobalUsagesTest extends TestCase
self::assertSame([1053, 1054, 1055, 1056, 1061], array_column($rows, 'id'), '1061: undecodable, fail closed');
self::assertStringStartsWith('SELECT ID, post_status, post_type FROM', $queries[0]);
self::assertStringNotContainsString('post_content,', $queries[0]);
self::assertStringContainsString("post_content LIKE '%globalModule%'", $queries[0]);
self::assertStringContainsString("post_content LIKE '%global" . $bs . "_module%'", $queries[0]);
// Ruling P24: case-insensitive whatever the column collation.
self::assertStringContainsString("LOWER(post_content) LIKE '%globalmodule%'", $queries[0]);
self::assertStringContainsString("LOWER(post_content) LIKE '%global" . $bs . "_module%'", $queries[0]);
self::assertStringContainsString("post_content REGEXP '" . $bs . $bs . 'u00[4-7][0-9a-fA-F]' . "'", $queries[0]);
self::assertStringContainsString("post_status NOT IN ('trash', 'auto-draft', 'inherit')", $queries[0]);
self::assertStringContainsString("post_type <> 'revision'", $queries[0]);
@@ -61,9 +61,54 @@ final class UsageMatcherTest extends TestCase
false,
],
'plain page' => ['<!-- wp:divi/section --><!-- /wp:divi/section -->', false],
// Ruling P24 G1: a Divi 4 shortcode inside a Divi 5 attribute value (quotes serialized as escapes).
'shortcode in a divi 5 text value' => [self::inText('[et_pb_section global_module=' . self::u('0022') . '123'
. self::u('0022') . '][/et_pb_section]'), true],
'shortcode in a divi 5 text value, another entry' => [self::inText('[et_pb_section global_module='
. self::u('0022') . '124' . self::u('0022') . '][/et_pb_section]'), false],
'shortcode in a divi 5 text value, escaped digits' => [self::inText('[et_pb_section global_module='
. self::u('0022') . self::u('0031') . self::u('0032') . '3' . self::u('0022') . '][/et_pb_section]'), true],
// G2: parsed like shortcode_parse_atts, cast with (int) like Divi.
'divi 4 leading space' => ['[et_pb_section global_module=" 123"][/et_pb_section]', true],
'divi 4 exponent' => ['[et_pb_section global_module="1.23e2"][/et_pb_section]', true],
'divi 4 upper-case attribute' => ['[et_pb_section GLOBAL_MODULE="123"][/et_pb_section]', true],
'divi 4 zero renders nothing' => ['[et_pb_section global_module="0"][/et_pb_section]', false],
'divi 4 empty renders nothing' => ['[et_pb_section global_module=""][/et_pb_section]', false],
'global_module as plain text' => ['<p>global_module="123" steht hier nur als Text</p>', false],
];
}
/** A Divi 5 text module whose content value holds $text, JSON-encoded like WordPress does. */
private static function inText(string $text): string
{
return '<!-- wp:divi/text {"content":{"innerContent":{"desktop":{"value":"' . $text . '"}}}} /-->';
}
/**
* Ruling P24 G3: a global_module Divi casts to 0 renders the newest library or TB layout
* (et_pb_load_global_module, WP_Query p=0) – a reference to every entry.
*
* @return array<string, array{string}>
*/
public static function wildcards(): array
{
return [
'entity before the id' => ['[et_pb_section global_module="&quot;123"][/et_pb_section]'],
'letters' => ['[et_pb_row global_module="abc"][/et_pb_row]'],
'typographic quotes' => ['[et_pb_row global_module=' . "\u{201D}" . '123' . "\u{201D}" . '][/et_pb_row]'],
'double zero' => ['[et_pb_row global_module="00"][/et_pb_row]'],
'inside a divi 5 value' => [self::inText('[et_pb_text global_module=' . self::u('0022') . 'x'
. self::u('0022') . ']a[/et_pb_text]')],
];
}
/** @dataProvider wildcards */
public function testAValueThatCastsToZeroShowsEveryEntry(string $content): void
{
self::assertTrue(UsageMatcher::shows($content, 123));
self::assertTrue(UsageMatcher::shows($content, 98765));
}
/** @dataProvider contents */
public function testFindsEveryReferenceDiviRenders(string $content, bool $shows): void
{