Files
WP-LIT-MCP/src/Integrations/Divi/Pages/DiviContentComposer.php
T
2026-09-27 16:09:49 +02:00

100 lines
3.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Pages;
use Closure;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\Outline;
use LIT\MCP\Integrations\Divi\BlockTree\Serializer;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Divi short form for lit/content-create (spec §6.2): the sections become a Divi 5 layout in
* exactly one divi/placeholder – inserted by TreeEditor like the first section of an empty page
* (ruling S9) – stored through DiviSaver's kses window, with the builder flags (DiviFlags, S11).
*
* Allow map (ruling S22) for an entry that does not exist yet: check() runs before the insert
* with the composition bound to post ID 0 – Divi's own registry for "no entry" (get_options(0)
* treats it like a post; live the same options as for a page, only the taxonomy choices of
* post_categories/post_tags differ). write() composes again, bound to the new entry – the
* layout that is stored always passed that entry's live map, like every other Divi write
* (DiviWriter::composition). Should the entry's map refuse something (an option Divi offers
* only for other types), write() returns the errors and lit/content-create deletes the draft.
*
* Every client string goes through Composer (AttrWriter / ShortformWriter → ValueCleaner) – the
* single choke point that makes the kses bypass safe (spec D7).
*/
final class DiviContentComposer implements ContentComposer
{
/** @var Closure(): bool */
private Closure $writable;
/** @param (Closure(): bool)|null $writable Tools-tab gate (Plugin: lit/divi-insert enabled); null = always. */
public function __construct(
private ContentSchema $schema,
private DiviComposition $composition,
private DiviSaver $saver,
private DesignSystemReader $design,
?Closure $writable = null
) {
$this->writable = $writable ?? static fn (): bool => true;
}
public function available(): bool
{
return DiviModules::isAvailable();
}
public function writable(): bool
{
return ($this->writable)();
}
public function check(string $postType, array $sections): ?WP_Error
{
if (! $this->schema->diviEnabled($postType)) {
return Errors::invalidInput(
'Für diesen Inhaltstyp ist der Divi-Builder nicht aktiviert; nichts wurde angelegt.',
['divi' => sprintf('Divi-Builder für "%s" nicht aktiv', $postType)]
);
}
$layout = LayoutBuilder::build($this->composition->forPost(0), $sections, 'divi');
return $layout instanceof WP_Error ? $layout : null;
}
public function write(int $postId, string $postType, array $sections): array|WP_Error
{
$composition = $this->composition->forPost($postId);
$layout = LayoutBuilder::build($composition, $sections, 'divi');
if ($layout instanceof WP_Error) {
return $layout;
}
$index = NodeIndex::build($layout['document']);
$warnings = $composition->warnings()->collect($index, $layout['blocks']);
$outline = (new Outline($this->design->read()->presetNames()))->summary($index);
if ($outline['truncated']) {
$warnings[] = DiviWriter::OUTLINE_TRUNCATED;
}
$saved = $this->saver->saveNew($postId, Serializer::document($layout['document']));
return [
'warnings' => array_merge($warnings, $saved),
'outline' => (string) $outline['text'],
'edit_url' => DiviPage::builderUrl($postId),
];
}
}