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:
@@ -6,6 +6,7 @@ namespace LIT\MCP\Integrations\Divi\Compose;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\GlobalInstance;
|
||||
use LIT\MCP\Integrations\Divi\Schema\ModuleCatalog;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
@@ -18,7 +19,8 @@ defined('ABSPATH') || exit;
|
||||
* rules limit the depth); parent module → its child modules (catalog). Columns only come with
|
||||
* their row; specialty sections, specialty columns (module.advanced.specialtyColumns or
|
||||
* row-inner children, ColumnModule.php:306-324) and read-only nodes (opaque, non-Divi) are not
|
||||
* rebuilt. Pure PHP.
|
||||
* rebuilt. F22: in Theme Builder layouts only modules Divi renders there (appearance.themeBuilderLayouts,
|
||||
* e.g. divi/post-content only in the body). Pure PHP.
|
||||
*/
|
||||
final class StructureRules
|
||||
{
|
||||
@@ -26,7 +28,8 @@ final class StructureRules
|
||||
private const ROWS = ['divi/row', 'divi/row-inner'];
|
||||
private const FULLWIDTH = 'fullwidth-module';
|
||||
|
||||
public function __construct(private ModuleCatalog $catalog)
|
||||
/** @param ?string $themeBuilderArea "header", "body" or "footer" when the entry is a Theme Builder layout. */
|
||||
public function __construct(private ModuleCatalog $catalog, private ?string $themeBuilderArea = null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -37,6 +40,11 @@ final class StructureRules
|
||||
return sprintf('unbekanntes Modul "%s" (siehe lit-divi-catalog)', $child);
|
||||
}
|
||||
|
||||
$area = $this->areaViolation($child);
|
||||
if ($area !== null) {
|
||||
return $area;
|
||||
}
|
||||
|
||||
if ($parent === null) {
|
||||
return $child === 'divi/section' ? null : 'auf oberster Ebene sind nur Abschnitte (section) erlaubt';
|
||||
}
|
||||
@@ -116,4 +124,41 @@ final class StructureRules
|
||||
|
||||
return $child === 'divi/row' ? null : 'ein Abschnitt enthält Zeilen (row)';
|
||||
}
|
||||
|
||||
/**
|
||||
* F22 for stored nodes that arrive with their children (copy, library entry, duplicate): the first module
|
||||
* below $block that Divi does not render in this Theme Builder area; null for pages.
|
||||
*/
|
||||
public function nestedAreaViolation(Block $block): ?string
|
||||
{
|
||||
if ($this->themeBuilderArea === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($block->children() as $child) {
|
||||
$violation = $this->areaViolation(GlobalInstance::effectiveName($child))
|
||||
?? $this->nestedAreaViolation($child);
|
||||
if ($violation !== null) {
|
||||
return $violation;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** F22: in a Theme Builder layout, a module Divi renders only in other areas (Divi's own render rule). */
|
||||
private function areaViolation(string $child): ?string
|
||||
{
|
||||
$areas = $this->catalog->themeBuilderAreas($child);
|
||||
if ($areas === null || $this->themeBuilderArea === null || in_array($this->themeBuilderArea, $areas, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s zeigt Divi im Bereich „%s“ nicht (nur %s)',
|
||||
$child,
|
||||
$this->themeBuilderArea,
|
||||
implode(', ', $areas)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user