feat: Divi save pipeline – tree_rev lock, kses-safe save, revision, flags, cache, ToolResult

DiviSaver/DiviWriter/DiviComposition/DiviChange, WpValueSanitizer (S4),
WpMediaResolver, DiviFlags (S11), DynamicContentMap from Divi's live
registry (S22), S31 wiring, S35 control strip at apply() entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 14:56:47 +02:00
co-authored by Claude Opus 5.5
parent ea1fd607b2
commit 7762b76305
20 changed files with 1716 additions and 2 deletions
@@ -193,7 +193,6 @@ final class ShortformWriterTest extends TestCase
'url: line-break octet marker' => ['url', '$variable%0a(y)$'],
'url: nested line-break octet marker' => ['url', '$variable%0%0aa(y)$'],
'color: tag-split marker' => ['color', '$variable<x>(' . substr($color, 10)],
'color: NUL-split marker' => ['color', '$variable' . chr(0) . '(' . substr($color, 10)],
'color: octet-split marker' => ['color', '$variable%41(' . substr($color, 10)],
];
}
@@ -318,4 +317,38 @@ final class ShortformWriterTest extends TestCase
self::assertSame([], $admin['errors']);
self::assertSame('Hallo', $admin['attrs']['module']['advanced']['html']['desktop']['value']['htmlBefore']);
}
/**
* Ruling S35: controls go once at the entry of apply(), before placeholder resolution and
* trim – "{\0{post:title}}" is the placeholder, checked like any other.
*/
public function testControlCharactersGoBeforePlaceholderResolution(): void
{
$writer = ComposeFixture::shortform();
foreach ([chr(0), chr(1), chr(11), chr(127)] as $control) {
$result = $writer->apply('heading', [], ['text' => '{' . $control . '{post:title}}'], 'n');
self::assertSame([], $result['errors'], (string) ord($control));
self::assertSame(
'$variable({"type":"content","value":{"name":"post_title","settings":{"before":"","after":""}}})$',
$result['attrs']['title']['innerContent']['desktop']['value']
);
$embedded = $writer->apply('heading', [], ['text' => 'Hallo {' . $control . '{post:title}}'], 'n');
self::assertStringContainsString('allein im Wert', $embedded['errors']['n.text']);
self::assertSame([], $embedded['attrs']);
// A control-split whole reference IS the reference (S32a/S35): validated and re-serialized.
$reference = '$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{}}})$';
$split = $writer->apply('divider', [], ['color' => '$variable' . $control . substr($reference, 9)], 'n');
self::assertSame([], $split['errors']);
self::assertSame($reference, $split['attrs']['divider']['advanced']['line']['desktop']['value']['color']);
// Colors: the placeholder resolves after the strip (and trim), then the color check.
$color = $writer->apply('divider', [], ['color' => ' {{color:Nacht' . $control . 'blau}} '], 'n');
self::assertSame([], $color['errors']);
self::assertStringContainsString('gcid-a1b2', $color['attrs']['divider']['advanced']['line']['desktop']['value']['color']);
}
}
}