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:
@@ -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=""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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user