fix: layout-edit dry runs name only editable live templates (ruling Q22)

ThemeBuilderEdits::showing() splits the live templates that show a layout
into editable ones (named under templates) and a hidden_templates count;
confirm is still decided on all of them, so a layout shown only by
uneditable templates still needs confirm. The live split is read once per
write for the confirm check and the field warnings, and field warnings now
also survive a non-confirm refusal such as the P9 backup refusal (M-12 a).
replace-text passes hidden_templates on; the write schema lists it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-27 22:57:45 +02:00
co-authored by Claude Opus 5.5
parent 0b7a1c28ec
commit 7590da4d96
8 changed files with 221 additions and 36 deletions
@@ -406,7 +406,7 @@ final class DiviReplaceTextAbility implements Ability
'error' => Errors::clientMessage($error) . ' Sichtbare Theme-Builder-Layouts nicht mit '
. 'lit/divi-replace-text, sondern nach Rückfrage mit lit-divi-update (confirm: true) ändern.',
'templates' => $data['templates'],
];
] + (isset($data['hidden_templates']) ? ['hidden_templates' => $data['hidden_templates']] : []);
}
return [
@@ -41,8 +41,10 @@ abstract class DiviWriteAbility implements Ability
'affected_count' => ['type' => 'integer'],
'hidden_count' => ['type' => 'integer'],
'backup_id' => ['type' => 'integer'],
// Theme Builder layouts: the enabled templates that show the layout.
// Theme Builder layouts: the enabled templates that show the layout – those the user may edit; the
// others only as a count (ruling Q22).
'templates' => ['type' => 'array', 'items' => ['type' => 'object']],
'hidden_templates' => ['type' => 'integer'],
], ['tree_rev']);
}
+29 -6
View File
@@ -218,22 +218,45 @@ final class DiviWriter
return $this->save($ability, $page, $change, $input, SaveScope::ThemeBuilderLayout);
}
// E12: fields in a body checked against the templates' post types – warnings only, also in the dry run.
$fieldWarnings = $this->themeBuilder->fieldWarnings($page, $change);
// E12: fields in a body checked against the templates' post types – warnings only, also in the dry run
// and in any refusal (review M-12 a). The live templates are read once for both checks.
$live = $this->themeBuilder->live();
$fieldWarnings = $this->themeBuilder->fieldWarnings($page, $change, $live);
foreach ($fieldWarnings as $warning) {
$change->warning($warning);
}
$prepared = $this->themeBuilder->prepare($page, $input);
$prepared = $this->themeBuilder->prepare($page, $input, $live);
if ($prepared instanceof WP_Error) {
return $fieldWarnings !== [] && Errors::codeOf($prepared) === 'confirm_required'
? VisibleEdits::merged($prepared, 'Hinweise zu Feldern siehe warnings.', ['warnings' => $fieldWarnings])
: $prepared;
return $fieldWarnings === [] ? $prepared : self::withWarnings($prepared, $fieldWarnings);
}
return $this->savePrepared($ability, $page, $change, $input, $prepared, 'des Theme-Builder-Layouts');
}
/**
* $error with the field warnings of the change (key warnings): a confirm_required also says so in its message.
*
* @param list<string> $warnings
*/
private static function withWarnings(WP_Error $error, array $warnings): WP_Error
{
if (Errors::codeOf($error) === 'confirm_required') {
return VisibleEdits::merged($error, 'Hinweise zu Feldern siehe warnings.', ['warnings' => $warnings]);
}
$data = $error->get_error_data();
$data = is_array($data) ? $data : [];
return Errors::make(
Errors::codeOf($error),
Errors::clientMessage($error),
(string) ($data['hint'] ?? ''),
(int) ($data['status'] ?? 400),
array_merge(array_diff_key($data, ['status' => true, 'hint' => true]), ['warnings' => $warnings])
);
}
/**
* save() with the scope GlobalEdits/ThemeBuilderEdits prepared; their details join the result, and a
* failure after their backup names it (ruling P14, VisibleEdits). $entry names the entry in that error.