ShortformWriter routes every short-form string through AttrWriter::cleanValue() with the curated field kind (new optional kind override), exposes the color check for background_color and guards builder-only paths on removal too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
117 lines
4.3 KiB
PHP
117 lines
4.3 KiB
PHP
<?php
|
|
|
|
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\MediaResolver;
|
|
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
|
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
|
|
use LIT\MCP\Integrations\Divi\Compose\ValueSanitizer;
|
|
use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest;
|
|
use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest;
|
|
|
|
/**
|
|
* Compose classes wired with the real Divi 5.2.1 sample catalog, the sample design system and
|
|
* predictable fakes for sanitizing and media (no WordPress). $unfiltered switches the fake
|
|
* sanitizer between a user with and without unfiltered_html (ruling S7).
|
|
*/
|
|
final class ComposeFixture
|
|
{
|
|
public static function sanitizer(bool $unfiltered = false): ValueSanitizer
|
|
{
|
|
return new class ($unfiltered) implements ValueSanitizer {
|
|
public function __construct(private bool $unfiltered)
|
|
{
|
|
}
|
|
|
|
/** Like sanitize_text_field: NUL bytes, tags and %XX octets go, then trim. */
|
|
public function text(string $value): string
|
|
{
|
|
$value = strip_tags(str_replace(chr(0), '', $value));
|
|
|
|
return trim((string) preg_replace('/%[a-f0-9]{2}/i', '', $value));
|
|
}
|
|
|
|
/**
|
|
* Like WpValueSanitizer (ruling S4): raw for users with unfiltered_html; otherwise like
|
|
* wp_kses_post: NUL bytes, <script> blocks and tags outside a small allow list go.
|
|
*/
|
|
public function html(string $value): string
|
|
{
|
|
if ($this->unfiltered) {
|
|
return $value;
|
|
}
|
|
|
|
$value = (string) preg_replace('#<script\b[^>]*>.*?</script>#is', '', str_replace(chr(0), '', $value));
|
|
|
|
return (string) preg_replace(
|
|
'#</?(?!(?:p|b|i|em|strong|a|br|span|h[1-6]|ul|ol|li)\b)[a-z][^>]*>#i',
|
|
'',
|
|
$value
|
|
);
|
|
}
|
|
|
|
public function code(string $value): string
|
|
{
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Like esc_url_raw: spaces become %20, characters outside its allow list (tags, quotes,
|
|
* braces, NUL) go, %0d/%0a are removed repeatedly, javascript: gives ''.
|
|
*/
|
|
public function url(string $value): string
|
|
{
|
|
$value = str_replace(' ', '%20', trim($value));
|
|
$value = (string) preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\x80-\xff]|i', '', $value);
|
|
do {
|
|
$before = $value;
|
|
$value = str_replace(['%0d', '%0a', '%0D', '%0A'], '', $value);
|
|
} while ($value !== $before);
|
|
|
|
return str_starts_with(strtolower($value), 'javascript:') ? '' : $value;
|
|
}
|
|
|
|
public function allowsUnfilteredHtml(): bool
|
|
{
|
|
return $this->unfiltered;
|
|
}
|
|
};
|
|
}
|
|
|
|
public static function media(): MediaResolver
|
|
{
|
|
return new class implements MediaResolver {
|
|
public function image(int $id): ?array
|
|
{
|
|
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' => ''],
|
|
default => null,
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
public static function placeholders(): Placeholders
|
|
{
|
|
return new Placeholders(DesignSystemTest::sample());
|
|
}
|
|
|
|
public static function attrWriter(bool $unfiltered = false): AttrWriter
|
|
{
|
|
return new AttrWriter(ModuleCatalogTest::sampleCatalog(), self::placeholders(), self::sanitizer($unfiltered));
|
|
}
|
|
|
|
/**
|
|
* @param array<string, array<string, array<string, mixed>>>|null $modules Short form per
|
|
* type instead of ShortformMap (to reach paths the curated map never names).
|
|
*/
|
|
public static function shortform(bool $unfiltered = false, ?array $modules = null): ShortformWriter
|
|
{
|
|
return new ShortformWriter(self::placeholders(), self::media(), self::attrWriter($unfiltered), $modules);
|
|
}
|
|
}
|