feat: Presets als Stapel, Gruppen-Presets und clear_overrides im Composer

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 16:48:58 +02:00
co-authored by Claude Sonnet 5.5
parent fafa140546
commit be6ca5bee0
13 changed files with 549 additions and 74 deletions
@@ -18,6 +18,9 @@ use LIT\MCP\Integrations\Divi\Compose\ValueSanitizer;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
use LIT\MCP\Integrations\Divi\Compose\Warnings;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
use LIT\MCP\Integrations\Divi\Design\PresetBook;
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
defined('ABSPATH') || exit;
@@ -29,6 +32,11 @@ defined('ABSPATH') || exit;
* and ONE ValueCleaner instance are shared by AttrWriter, ShortformWriter and Composer, and the
* same AttrWriter serves Composer and the abilities.
*
* Presets (D7) reach blocks through ONE PresetAssignment per composition: its book is Divi's stored preset
* inventory (DesignSystemWriter::presets()), read lazily and at most once – only when a write names a preset or a
* warning looks at a block that has one – and Divi's option groups come from DesignSystemWriter::groupsOf().
* Without a preset source (null, unit tests) there are no presets.
*
* forPost() binds the composition to the entry being written (DiviWriter::composition() does it
* for the abilities); unbound, VariableRef's default map applies. Not final so abilities can be
* tested with a mock.
@@ -42,6 +50,7 @@ class DiviComposition
private ?ValueCleaner $cleaner = null;
private ?AttrWriter $attrWriter = null;
private ?Composer $composer = null;
private ?PresetAssignment $assignment = null;
public function __construct(
private DiviModules $modules,
@@ -49,7 +58,8 @@ class DiviComposition
private ValueSanitizer $sanitizer = new WpValueSanitizer(),
private MediaResolver $media = new WpMediaResolver(),
private DynamicContentMap $dynamicContent = new DynamicContentMap(),
private LinkTargets $links = new WpLinkTargets()
private LinkTargets $links = new WpLinkTargets(),
private ?DesignSystemWriter $presetSource = null
) {
}
@@ -64,6 +74,7 @@ class DiviComposition
$bound->cleaner = null;
$bound->attrWriter = null;
$bound->composer = null;
$bound->assignment = null;
return $bound;
}
@@ -102,6 +113,21 @@ class DiviComposition
return $this->attrWriter ??= new AttrWriter($this->modules->catalog(), $this->placeholders(), $this->cleaner());
}
/**
* The one PresetAssignment of this composition (D7, rulings R2, R3): the book comes lazily from Divi's stored
* presets, ids must pass the choke point unchanged.
*
* @internal Public for tests; code uses composer() / warnings().
*/
public function presets(): PresetAssignment
{
return $this->assignment ??= new PresetAssignment(
fn (): PresetBook => PresetBook::fromDivi($this->presetSource?->presets() ?? []),
fn (string $module): array => $this->presetSource?->groupsOf($module) ?? [],
$this->cleaner()->keepsId(...)
);
}
public function composer(): Composer
{
return $this->composer ??= new Composer(
@@ -110,7 +136,7 @@ class DiviComposition
new ShortformWriter($this->placeholders(), $this->media, $this->cleaner()),
$this->attrWriter(),
$this->cleaner(),
$this->design->read(),
$this->presets(),
$this->modules->builderVersion()
);
}