feat: Divi tools edit Theme Builder layouts; visible layouts need confirm

TB layouts load through ThemeBuilderAccess (P12), live layouts (attached +
enabled, Q8) need a dry run and confirm (Q9) and get the shared restore point
of VisibleEdits, extracted from GlobalEdits (Q2: backup when revisions are
off, P9 refusals, P14 naming). divi/post-content only in body layouts (F22),
also for nested stored nodes. SaveScope::ThemeBuilderLayout sets Divi flags
and clears site CSS + TB cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 15:45:07 +02:00
co-authored by Claude Opus 5.5
parent 4ff1c1e2a5
commit b1dd35abd8
27 changed files with 1106 additions and 132 deletions
+16 -7
View File
@@ -9,6 +9,8 @@ use LIT\MCP\Integrations\Divi\BlockTree\MalformedMarkup;
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\Library\LibraryAccess;
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
use LIT\MCP\Integrations\Divi\ThemeBuilder\ThemeBuilderAccess;
use LIT\MCP\Integrations\WordPress\PostGuard;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Log;
@@ -18,7 +20,8 @@ use WP_Post;
defined('ABSPATH') || exit;
/**
* Loads an entry for the Divi tools: Divi Library entries via LibraryAccess (E1/E3), every other
* Loads an entry for the Divi tools: Divi Library entries via LibraryAccess (E1/E3), Theme Builder
* layouts via ThemeBuilderAccess (Etappe 3b; hidden ones answer like a missing ID, P12), every other
* entry with its rights and status via PostGuard (spec §4.5), then the block tree. Divi 4
* shortcode content and unbalanced markup are refused with a hint – spec §3.4: Divi 4 content
* is kept, never edited.
@@ -31,7 +34,8 @@ final class DiviPageLoader
public function __construct(
private ContentSchema $schema,
private Parser $parser = new Parser(),
private ?LibraryAccess $library = null
private ?LibraryAccess $library = null,
private ?ThemeBuilderAccess $themeBuilder = null
) {
}
@@ -50,9 +54,10 @@ final class DiviPageLoader
/** Readers pass $forWrite = false (trashed entries may be read, like lit-content-get). */
public function load(mixed $postId, bool $forWrite): DiviPage|WP_Error
{
// Library entries (E1): with a library access, for reading and writing.
// Library entries (E1) and Theme Builder layouts (Etappe 3b) through their access, everything else PostGuard.
$item = $this->library?->item($postId, $forWrite);
$post = $item ?? PostGuard::editable($postId, $this->schema, 'post_id', ! $forWrite);
$layout = $item === null ? $this->themeBuilder?->layout($postId, $forWrite) : null;
$post = $item ?? $layout ?? PostGuard::editable($postId, $this->schema, 'post_id', ! $forWrite);
if ($post instanceof WP_Error) {
return $post;
}
@@ -84,7 +89,9 @@ final class DiviPageLoader
);
}
return new DiviPage($post, $document, NodeIndex::build($document), $item instanceof WP_Post);
$area = $layout instanceof WP_Post ? DiviThemeBuilder::areaOf((string) $layout->post_type) : null;
return new DiviPage($post, $document, NodeIndex::build($document), $item instanceof WP_Post, $area);
}
/** The entry loaded for writing and checked by writable() – the refusal of either step. */
@@ -99,14 +106,16 @@ final class DiviPageLoader
}
/**
* Additional checks for writing: the type must use the Divi builder (library entries always do),
* Additional checks for writing: the type must use the Divi builder (library entries and Theme Builder
* layouts always do),
* the content must be a Divi 5 layout (or empty) and saving must not publish a missed schedule
* (spec §2).
*/
public function writable(DiviPage $page): ?WP_Error
{
$post = $page->post;
if (! $page->isLibraryItem() && ! $this->schema->diviEnabled((string) $post->post_type)) {
$builderPost = $page->isLibraryItem() || $page->isThemeBuilderLayout();
if (! $builderPost && ! $this->schema->diviEnabled((string) $post->post_type)) {
return Errors::unsupported(
sprintf('Für den Inhaltstyp "%s" ist der Divi-Builder nicht aktiviert.', $post->post_type),
'In Divi → Theme-Optionen → Builder den Builder für diesen Inhaltstyp aktivieren (Administrator).'