Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
166 lines
6.3 KiB
PHP
166 lines
6.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations\Divi\Pages;
|
||
|
||
use LIT\MCP\Integrations\Divi\Compose\AttrWriter;
|
||
use LIT\MCP\Integrations\Divi\Compose\Composer;
|
||
use LIT\MCP\Integrations\Divi\Compose\LinkTargets;
|
||
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\StoredNodeGuard;
|
||
use LIT\MCP\Integrations\Divi\Compose\StructureRules;
|
||
use LIT\MCP\Integrations\Divi\Compose\TreeEditor;
|
||
use LIT\MCP\Integrations\Divi\Compose\ValueCleaner;
|
||
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;
|
||
|
||
/**
|
||
* Wires the pure Compose classes with the live catalog, design system, Divi's dynamic content
|
||
* registry and the WordPress sanitizer/media lookup – once per request and entry. Ruling S31:
|
||
* ONE VariableRef (carrying the live dynamic content map of the entry, S22), ONE Placeholders
|
||
* 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.
|
||
*/
|
||
class DiviComposition
|
||
{
|
||
private ?int $postId = null;
|
||
private ?string $themeBuilderArea = null;
|
||
private ?VariableRef $references = null;
|
||
private ?Placeholders $placeholders = null;
|
||
private ?ValueCleaner $cleaner = null;
|
||
private ?AttrWriter $attrWriter = null;
|
||
private ?Composer $composer = null;
|
||
private ?PresetAssignment $assignment = null;
|
||
|
||
public function __construct(
|
||
private DiviModules $modules,
|
||
private DesignSystemReader $design,
|
||
private ValueSanitizer $sanitizer = new WpValueSanitizer(),
|
||
private MediaResolver $media = new WpMediaResolver(),
|
||
private DynamicContentMap $dynamicContent = new DynamicContentMap(),
|
||
private LinkTargets $links = new WpLinkTargets(),
|
||
private ?DesignSystemWriter $presetSource = null
|
||
) {
|
||
}
|
||
|
||
/** A fresh composition for entry $postId (own VariableRef with its live map; TB area for StructureRules). */
|
||
public function forPost(int $postId, ?string $themeBuilderArea = null): static
|
||
{
|
||
$bound = clone $this;
|
||
$bound->postId = $postId;
|
||
$bound->themeBuilderArea = $themeBuilderArea;
|
||
$bound->references = null;
|
||
$bound->placeholders = null;
|
||
$bound->cleaner = null;
|
||
$bound->attrWriter = null;
|
||
$bound->composer = null;
|
||
$bound->assignment = null;
|
||
|
||
return $bound;
|
||
}
|
||
|
||
/**
|
||
* The one checker of $variable(…)$ values of this composition, with the entry's live map (S22, S31).
|
||
*
|
||
* @internal Public for tests; code uses cleaner() / composer().
|
||
*/
|
||
public function references(): VariableRef
|
||
{
|
||
return $this->references ??= new VariableRef(
|
||
$this->design->read(),
|
||
$this->postId === null ? null : $this->dynamicContent->forPost($this->postId)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* The one Placeholders instance of this composition (ruling S31).
|
||
*
|
||
* @internal Public for tests; code uses attrWriter() / composer().
|
||
*/
|
||
public function placeholders(): Placeholders
|
||
{
|
||
return $this->placeholders ??= new Placeholders($this->design->read(), $this->links);
|
||
}
|
||
|
||
/** The one choke point of this composition (ValueCleaner, rulings S1–S3, S7, S13, S28, S32). */
|
||
public function cleaner(): ValueCleaner
|
||
{
|
||
return $this->cleaner ??= new ValueCleaner($this->modules->catalog(), $this->references(), $this->sanitizer);
|
||
}
|
||
|
||
public function attrWriter(): AttrWriter
|
||
{
|
||
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(
|
||
$this->modules->catalog(),
|
||
$this->rules(),
|
||
new ShortformWriter($this->placeholders(), $this->media, $this->cleaner()),
|
||
$this->attrWriter(),
|
||
$this->cleaner(),
|
||
$this->presets(),
|
||
$this->modules->builderVersion()
|
||
);
|
||
}
|
||
|
||
/** @internal Public for tests; code uses composer() / editor(). */
|
||
public function rules(): StructureRules
|
||
{
|
||
return new StructureRules($this->modules->catalog(), $this->themeBuilderArea);
|
||
}
|
||
|
||
/** E6: the check for stored nodes that move into this composition's entry (same cleaner and references). */
|
||
public function storedGuard(): StoredNodeGuard
|
||
{
|
||
return new StoredNodeGuard($this->cleaner(), $this->references(), $this->sanitizer);
|
||
}
|
||
|
||
public function editor(): TreeEditor
|
||
{
|
||
return new TreeEditor($this->rules());
|
||
}
|
||
|
||
public function warnings(): Warnings
|
||
{
|
||
return new Warnings($this->design->read());
|
||
}
|
||
}
|