catalog->has($child)) { 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'; } if (! $parent->isDivi() || $parent->isOpaque()) { return 'der Zielknoten ist nur lesbar (globales Modul oder fremder Block)'; } $parentName = $parent->name(); if ($parentName === 'divi/section') { return $this->inSection($parent, $child); } if (in_array($parentName, self::ROWS, true)) { return 'Spalten entstehen mit der Zeile ("columns"); eine neue Zeile mit der gewünschten Aufteilung ' . 'einfügen'; } if (in_array($parentName, self::COLUMNS, true) && self::isSpecialtyColumn($parent)) { return 'Spezialspalten (specialty) nur im Divi-Builder umbauen; Module in ihre inneren Spalten einfügen'; } if (in_array($parentName, self::COLUMNS, true) || $parentName === 'divi/group') { // A nested row: only straight into a (Divi 5) column – like the builder's drop target. $nestedRow = $child === 'divi/row' && $parentName === 'divi/column'; return $this->isPlainModule($child) || $child === 'divi/group' || $nestedRow ? null : sprintf('%s passt nicht in eine Spalte', $child); } $children = $this->catalog->childrenOf($parentName); if ($children === []) { return sprintf('%s kann keine Kind-Module enthalten', $parentName); } return in_array($child, $children, true) ? null : sprintf('%s erlaubt nur %s als Kind', $parentName, implode(', ', $children)); } /** A module that goes into a column: catalog category "module" (not "fullwidth-module"). */ public function isPlainModule(string $name): bool { return $this->catalog->category($name) === 'module'; } /** A column of a specialty section: it holds inner rows, not modules. */ private static function isSpecialtyColumn(Block $column): bool { $specialty = AttrMap::desktop($column->attrs(), 'module.advanced.specialtyColumns'); if ($specialty !== null && $specialty !== '') { return true; } foreach ($column->children() as $child) { if ($child->name() === 'divi/row-inner') { return true; } } return false; } private function inSection(Block $section, string $child): ?string { $type = AttrMap::desktop($section->attrs(), 'module.advanced.type'); if ($type === 'specialty') { return 'Spezialabschnitte (specialty) nur im Divi-Builder umbauen; Module in ihre Spalten einfügen'; } if ($type === 'fullwidth') { return $this->catalog->category($child) === self::FULLWIDTH ? null : 'in einen Vollbreite-Abschnitt passen nur fullwidth-Module'; } 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) ); } }