feat: Pods fields in Theme Builder bodies checked against the template's post types (E12)
Both forms Divi writes are recognized (ruling Q11): custom_meta_<key> and the dialog form post_meta_key + select_meta_key. Choice fields get a note about the stored value, relation fields name IDs (F4). Only templates the user may edit are checked or named; others are counted (Q21 pattern). Warnings only, also in the dry run of a live body. DynamicFields extracted from lit/divi-dynamic-sources. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,8 @@ final class TbCreateTemplateAbility implements Ability
|
||||
private TemplateConditions $conditions,
|
||||
private DiviComposition $composition,
|
||||
private DiviSaver $saver,
|
||||
private ChangeLog $log
|
||||
private ChangeLog $log,
|
||||
private ?ThemeBuilderEdits $edits = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -286,6 +287,14 @@ final class TbCreateTemplateAbility implements Ability
|
||||
SaveScope::ThemeBuilderLayout
|
||||
)
|
||||
);
|
||||
if ($area === 'body' && $this->edits !== null) {
|
||||
// E12: fields of the new body against the post types of the new conditions – warnings only.
|
||||
$warnings = array_merge($warnings, $this->edits->checkFields(
|
||||
$layout['blocks'],
|
||||
Template::postTypesOf($useOn),
|
||||
'Das neue Body-Layout'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$themeBuilderId = $build->themeBuilder();
|
||||
|
||||
@@ -92,9 +92,18 @@ final class Template
|
||||
|
||||
/** @return list<string> Post types of its singular conditions (singular:post_type:<type>:…). */
|
||||
public function postTypes(): array
|
||||
{
|
||||
return self::postTypesOf($this->useOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $useOn
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function postTypesOf(array $useOn): array
|
||||
{
|
||||
$types = [];
|
||||
foreach ($this->useOn as $condition) {
|
||||
foreach ($useOn as $condition) {
|
||||
$parts = explode(':', $condition);
|
||||
if (($parts[0] ?? '') === 'singular' && ($parts[1] ?? '') === 'post_type' && ($parts[2] ?? '') !== '') {
|
||||
$types[] = $parts[2];
|
||||
|
||||
@@ -6,7 +6,10 @@ namespace LIT\MCP\Integrations\Divi\ThemeBuilder;
|
||||
|
||||
use LIT\MCP\Abilities\Errors;
|
||||
use LIT\MCP\Abilities\WriteFailed;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviChange;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPage;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DynamicFields;
|
||||
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
|
||||
use LIT\MCP\Integrations\Divi\Pages\VisibleEdits;
|
||||
use WP_Error;
|
||||
@@ -22,12 +25,17 @@ defined('ABSPATH') || exit;
|
||||
* change (VisibleEdits, ruling Q2): a backup when revisions are off, refused when it could not be faithful (P9).
|
||||
* Layouts of disabled or detached templates save at once. The save scope sets the Divi flags and clears every
|
||||
* page's CSS and the Theme Builder cache.
|
||||
*
|
||||
* Fields in a body (E12, answer F4): the field references of the changed nodes – both forms Divi writes (ruling
|
||||
* Q11) – are checked against the post types of the templates that use the body; warnings only, nothing is
|
||||
* refused. Templates the user may not edit are only counted (ruling Q21 pattern). Read-only analysis.
|
||||
*/
|
||||
final class ThemeBuilderEdits
|
||||
{
|
||||
public function __construct(
|
||||
private ThemeBuilderAccess $access,
|
||||
private VisibleEdits $visible = new VisibleEdits()
|
||||
private VisibleEdits $visible = new VisibleEdits(),
|
||||
private ?DynamicFields $fields = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -99,6 +107,160 @@ final class ThemeBuilderEdits
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* E12: in a body layout, the field references of the changed nodes checked against the post types of the
|
||||
* templates that use the layout (their singular conditions). Only templates the user may edit are named or
|
||||
* checked; others add a count (ruling Q21 pattern). Warnings only – the layout is saved anyway.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function fieldWarnings(DiviPage $page, DiviChange $change): array
|
||||
{
|
||||
if ($this->fields === null || $page->themeBuilderArea() !== 'body') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = self::keysIn($change->touched());
|
||||
if ($keys === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$id = (int) $page->post->ID;
|
||||
$uses = static fn (Template $template): bool => $template->layouts['body']['id'] === $id;
|
||||
$live = $this->access->editableLiveTemplates();
|
||||
$types = [];
|
||||
foreach (array_filter($live['templates'], $uses) as $template) {
|
||||
array_push($types, ...$template->postTypes());
|
||||
}
|
||||
|
||||
$types = array_values(array_unique($types));
|
||||
$hidden = count(array_filter($live['hidden'], $uses));
|
||||
if ($types === [] && $hidden > 0) {
|
||||
return [sprintf(
|
||||
'Body-Layout %d nutzen nur Vorlagen ohne Berechtigung (%d) oder keine mit Bedingung '
|
||||
. '„singular:post_type:…“; ob die Felder %s dort existieren, ist ungeprüft.',
|
||||
$id,
|
||||
$hidden,
|
||||
implode(', ', $keys)
|
||||
)];
|
||||
}
|
||||
|
||||
$warnings = $this->checkKeys($keys, $types, sprintf('Body-Layout %d', $id));
|
||||
if ($hidden > 0) {
|
||||
$warnings[] = sprintf(
|
||||
'Weitere Vorlagen ohne Berechtigung nutzen dieses Layout: %d; ob die Felder %s für deren '
|
||||
. 'Inhaltstypen passen, ist ungeprüft.',
|
||||
$hidden,
|
||||
implode(', ', $keys)
|
||||
);
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field references ({{field:<key>}} = custom_meta_<key>, and Divi's dialog form post_meta_key with
|
||||
* select_meta_key – ruling Q11) in $blocks and their descendants against $postTypes.
|
||||
*
|
||||
* @param list<Block> $blocks
|
||||
* @param list<string> $postTypes
|
||||
* @param string $what Names the layout in the warning ("Das neue Body-Layout").
|
||||
* @return list<string>
|
||||
*/
|
||||
public function checkFields(array $blocks, array $postTypes, string $what): array
|
||||
{
|
||||
if ($this->fields === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = self::keysIn($blocks);
|
||||
|
||||
return $keys === [] ? [] : $this->checkKeys($keys, $postTypes, $what);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keys
|
||||
* @param list<string> $postTypes
|
||||
* @return list<string>
|
||||
*/
|
||||
private function checkKeys(array $keys, array $postTypes, string $what): array
|
||||
{
|
||||
if ($this->fields === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($postTypes === []) {
|
||||
return [sprintf(
|
||||
'%s gehört zu keiner Vorlage mit Bedingung „singular:post_type:…“; ob die Felder %s dort existieren, '
|
||||
. 'ist ungeprüft (Bedingungen mit lit/tb-assign setzen).',
|
||||
$what,
|
||||
implode(', ', $keys)
|
||||
)];
|
||||
}
|
||||
|
||||
$warnings = [];
|
||||
foreach ($keys as $key) {
|
||||
$problems = [];
|
||||
$notes = [];
|
||||
foreach ($postTypes as $type) {
|
||||
$reason = $this->fields->reason($type, $key);
|
||||
if ($reason !== null) {
|
||||
$problems[] = $type . ': ' . $reason;
|
||||
continue;
|
||||
}
|
||||
$note = $this->fields->note($type, $key);
|
||||
if ($note !== null) {
|
||||
$notes[] = $type . ': ' . $note;
|
||||
}
|
||||
}
|
||||
if ($problems !== []) {
|
||||
$warnings[] = sprintf(
|
||||
'Feld „%s“ zeigt Divi womöglich nicht als Text (%s); passende Felder mit lit/divi-dynamic-sources.',
|
||||
$key,
|
||||
implode('; ', array_merge($problems, $notes))
|
||||
);
|
||||
} elseif ($notes !== []) {
|
||||
$warnings[] = sprintf('Feld „%s“: %s.', $key, implode('; ', $notes));
|
||||
}
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Block> $blocks
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function keysIn(array $blocks): array
|
||||
{
|
||||
$keys = [];
|
||||
foreach ($blocks as $block) {
|
||||
self::collectKeys($block, $keys);
|
||||
}
|
||||
|
||||
return array_values(array_unique($keys));
|
||||
}
|
||||
|
||||
/** @param list<string> $keys */
|
||||
private static function collectKeys(Block $block, array &$keys): void
|
||||
{
|
||||
if ($block->isOpaque()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attrs = $block->attrs();
|
||||
array_walk_recursive($attrs, static function (mixed $value) use (&$keys): void {
|
||||
$key = is_string($value) ? DynamicFields::metaKeyOf($value) : null;
|
||||
if ($key !== null) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($block->children() as $child) {
|
||||
self::collectKeys($child, $keys);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<Template> $templates */
|
||||
private static function count(array $templates): string
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\ThemeBuilder;
|
||||
use LIT\MCP\ChangeLog\ChangeLog;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviComposition;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DynamicFields;
|
||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||
use LIT\MCP\Integrations\Integration;
|
||||
|
||||
@@ -26,7 +27,8 @@ final class ThemeBuilderIntegration implements Integration
|
||||
private ThemeBuilderAccess $access,
|
||||
private DiviComposition $composition,
|
||||
private DiviSaver $saver,
|
||||
private ChangeLog $log
|
||||
private ChangeLog $log,
|
||||
private DynamicFields $fields
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -51,7 +53,14 @@ final class ThemeBuilderIntegration implements Integration
|
||||
|
||||
return [
|
||||
new TbListAbility($this->access, $conditions),
|
||||
new TbCreateTemplateAbility($this->access, $conditions, $this->composition, $this->saver, $this->log),
|
||||
new TbCreateTemplateAbility(
|
||||
$this->access,
|
||||
$conditions,
|
||||
$this->composition,
|
||||
$this->saver,
|
||||
$this->log,
|
||||
new ThemeBuilderEdits($this->access, fields: $this->fields)
|
||||
),
|
||||
new TbAssignAbility($this->access, $conditions, $this->log),
|
||||
new TbActivateAbility($this->access, $conditions, $this->log),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user