feat: Divi composer – short/full form to blocks with structure rules, presets and labels
Labels, background_color and preset ids go through the choke point (S13); ambiguous preset names fail with their ids (S13a); fullwidth + rows is an error. Carry-ins: control characters stripped before the marker check (S32), kind override may only tighten, reference alt texts dropped, color check via VariableRef::typeOf (S33). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -5,10 +5,13 @@ declare(strict_types=1);
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Compose\AttrWriter;
|
||||
use LIT\MCP\Integrations\Divi\Compose\Composer;
|
||||
use LIT\MCP\Integrations\Divi\Compose\MediaResolver;
|
||||
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
||||
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
|
||||
use LIT\MCP\Integrations\Divi\Compose\StructureRules;
|
||||
use LIT\MCP\Integrations\Divi\Compose\ValueSanitizer;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystem;
|
||||
use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest;
|
||||
use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest;
|
||||
|
||||
@@ -19,6 +22,9 @@ use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest;
|
||||
*/
|
||||
final class ComposeFixture
|
||||
{
|
||||
/** A valid content reference as an attachment's alt text (media ids 44 and 45). */
|
||||
public const REFERENCE_ALT = '$variable({"type":"content","value":{"name":"post_title","settings":{}}})$';
|
||||
|
||||
public static function sanitizer(bool $unfiltered = false): ValueSanitizer
|
||||
{
|
||||
return new class ($unfiltered) implements ValueSanitizer {
|
||||
@@ -26,12 +32,26 @@ final class ComposeFixture
|
||||
{
|
||||
}
|
||||
|
||||
/** Like sanitize_text_field: NUL bytes, tags and %XX octets go, then trim. */
|
||||
/**
|
||||
* Like sanitize_text_field (_sanitize_text_fields): tags only go when there is a "<"
|
||||
* (wp_strip_all_tags; PHP's strip_tags also drops NUL then), whitespace runs become one
|
||||
* space, trim, %XX octets go repeatedly. Without a "<", NUL and other control
|
||||
* characters stay – as in WordPress (ruling S32: AttrWriter strips them).
|
||||
*/
|
||||
public function text(string $value): string
|
||||
{
|
||||
$value = strip_tags(str_replace(chr(0), '', $value));
|
||||
if (str_contains($value, '<')) {
|
||||
$value = strip_tags((string) preg_replace('@<(script|style)[^>]*?>.*?</\\1>@si', '', $value));
|
||||
}
|
||||
|
||||
return trim((string) preg_replace('/%[a-f0-9]{2}/i', '', $value));
|
||||
$value = trim((string) preg_replace('/[\r\n\t ]+/', ' ', $value));
|
||||
$found = false;
|
||||
while (preg_match('/%[a-f0-9]{2}/i', $value, $match) === 1) {
|
||||
$value = str_replace($match[0], '', $value);
|
||||
$found = true;
|
||||
}
|
||||
|
||||
return $found ? trim((string) preg_replace('/ +/', ' ', $value)) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +109,10 @@ final class ComposeFixture
|
||||
return match ($id) {
|
||||
33 => ['url' => 'https://kunde.test/wp-content/uploads/team.jpg', 'alt' => 'Unser Team'],
|
||||
12 => ['url' => 'https://kunde.test/wp-content/uploads/halle.jpg', 'alt' => ''],
|
||||
// Alt texts carrying a reference, whole or NUL-split (ruling S33b: dropped).
|
||||
44 => ['url' => 'https://kunde.test/wp-content/uploads/a.jpg', 'alt' => ComposeFixture::REFERENCE_ALT],
|
||||
45 => ['url' => 'https://kunde.test/wp-content/uploads/b.jpg', 'alt' => '$variable' . chr(0)
|
||||
. substr(ComposeFixture::REFERENCE_ALT, strlen('$variable'))],
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@@ -113,4 +137,29 @@ final class ComposeFixture
|
||||
{
|
||||
return new ShortformWriter(self::placeholders(), self::media(), self::attrWriter($unfiltered), $modules);
|
||||
}
|
||||
|
||||
public static function rules(): StructureRules
|
||||
{
|
||||
return new StructureRules(ModuleCatalogTest::sampleCatalog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder version 5.2.1. $design replaces the sample design system (one Placeholders instance
|
||||
* shared by AttrWriter and ShortformWriter, like the live wiring – ruling S31).
|
||||
*/
|
||||
public static function composer(bool $unfiltered = false, ?DesignSystem $design = null): Composer
|
||||
{
|
||||
$design ??= DesignSystemTest::sample();
|
||||
$placeholders = new Placeholders($design);
|
||||
$attrWriter = new AttrWriter(ModuleCatalogTest::sampleCatalog(), $placeholders, self::sanitizer($unfiltered));
|
||||
|
||||
return new Composer(
|
||||
ModuleCatalogTest::sampleCatalog(),
|
||||
self::rules(),
|
||||
new ShortformWriter($placeholders, self::media(), $attrWriter),
|
||||
$attrWriter,
|
||||
$design,
|
||||
'5.2.1'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user