Files
WP-LIT-MCP/src/Integrations/Divi/BlockTree/Serializer.php
T
janniclas.loosenandClaude Opus 5.5 b24eb6c3e9 fix(security): no direct HTTP access to plugin PHP files (audit L2, cheap part)
Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:32:12 +02:00

77 lines
2.2 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\BlockTree;
defined('ABSPATH') || exit;
/**
* Turns a Document back into block markup. Unchanged blocks reuse their original
* delimiters; changed or new blocks get delimiters generated exactly like WordPress'
* get_comment_delimited_block_content() / serialize_block_attributes() do, which also
* yields the escaping Divi expects inside $variable(…)$ payloads: json_encode()'s own
* \" (an escaped quote inside a JSON string) is replaced by \u0022, see ESCAPES below.
*/
final class Serializer
{
/** serialize_block_attributes() replacements (WordPress 7.0). */
private const ESCAPES = [
'\\\\' => '\\u005c',
'--' => '\\u002d\\u002d',
'<' => '\\u003c',
'>' => '\\u003e',
'&' => '\\u0026',
'\\"' => '\\u0022',
];
public static function document(Document $document): string
{
return self::parts($document->parts());
}
public static function block(Block $block): string
{
$open = $block->rawOpen() ?? self::opener($block);
if ($block->isVoid()) {
return $open;
}
return $open . self::parts($block->parts()) . ($block->rawClose() ?? self::closer($block));
}
/** @param array<string, mixed> $attrs */
public static function attributes(array $attrs): string
{
return strtr(Json::encode($attrs), self::ESCAPES);
}
private static function opener(Block $block): string
{
$attrs = $block->attrs() === [] ? '' : self::attributes($block->attrs()) . ' ';
return '<!-- wp:' . self::serializedName($block->name()) . ' ' . $attrs . ($block->isVoid() ? '/-->' : '-->');
}
private static function closer(Block $block): string
{
return '<!-- /wp:' . self::serializedName($block->name()) . ' -->';
}
private static function serializedName(string $name): string
{
return str_starts_with($name, 'core/') ? substr($name, 5) : $name;
}
/** @param list<string|Block> $parts */
private static function parts(array $parts): string
{
$out = '';
foreach ($parts as $part) {
$out .= $part instanceof Block ? self::block($part) : $part;
}
return $out;
}
}