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
129 lines
4.4 KiB
PHP
129 lines
4.4 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\MediaResolver;
|
||
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
||
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
|
||
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\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.
|
||
*
|
||
* 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 ?VariableRef $references = null;
|
||
private ?Placeholders $placeholders = null;
|
||
private ?ValueCleaner $cleaner = null;
|
||
private ?AttrWriter $attrWriter = null;
|
||
private ?Composer $composer = 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()
|
||
) {
|
||
}
|
||
|
||
/** A fresh composition for entry $postId (own VariableRef with that entry's live map). */
|
||
public function forPost(int $postId): static
|
||
{
|
||
$bound = clone $this;
|
||
$bound->postId = $postId;
|
||
$bound->references = null;
|
||
$bound->placeholders = null;
|
||
$bound->cleaner = null;
|
||
$bound->attrWriter = null;
|
||
$bound->composer = 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());
|
||
}
|
||
|
||
/** 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());
|
||
}
|
||
|
||
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->design->read(),
|
||
$this->modules->builderVersion()
|
||
);
|
||
}
|
||
|
||
/** @internal Public for tests; code uses composer() / editor(). */
|
||
public function rules(): StructureRules
|
||
{
|
||
return new StructureRules($this->modules->catalog());
|
||
}
|
||
|
||
public function editor(): TreeEditor
|
||
{
|
||
return new TreeEditor($this->rules());
|
||
}
|
||
|
||
public function warnings(): Warnings
|
||
{
|
||
return new Warnings($this->design->read());
|
||
}
|
||
}
|