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:
@@ -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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -21,8 +21,10 @@ defined('ABSPATH') || exit;
|
||||
* ausdrücklicher Freigabe"): Divi reads TB layouts live (F22), so a layout that a live template (attached to the
|
||||
* live Theme Builder and enabled, whatever its post status – ruling Q8) shows changes the website at once –
|
||||
* without confirm: true nothing is saved and the answer lists those templates (dry run of the fully validated
|
||||
* change, like global modules, answer F2; ruling Q9). A confirmed edit gets the restore point of every visible
|
||||
* change (VisibleEdits, ruling Q2): a backup when revisions are off, refused when it could not be faithful (P9).
|
||||
* change, like global modules, answer F2; ruling Q9) – by name only those the user may edit, the others as the
|
||||
* count hidden_templates; confirm is decided on all of them (ruling Q22). A confirmed edit gets the restore point
|
||||
* of every visible 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.
|
||||
*
|
||||
@@ -39,28 +41,49 @@ final class ThemeBuilderEdits
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return list<Template> Live templates that show layout $page in its area. */
|
||||
public function showing(DiviPage $page): array
|
||||
/**
|
||||
* The live templates split by canEditTemplate() (ThemeBuilderAccess::editableLiveTemplates()) – computed once
|
||||
* per write and passed to prepare()/notice()/fieldWarnings().
|
||||
*
|
||||
* @return array{templates: list<Template>, hidden: list<Template>}
|
||||
*/
|
||||
public function live(): array
|
||||
{
|
||||
return $this->access->editableLiveTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Live templates that show layout $page in its area (ruling Q22, Q21 pattern): "templates" the user may edit
|
||||
* (named in answers), "hidden" the number of the others (never named). Both together decide whether a write
|
||||
* needs confirm – a layout shown only by hidden templates is still visible.
|
||||
*
|
||||
* @param array{templates: list<Template>, hidden: list<Template>}|null $live live(); null = read now
|
||||
* @return array{templates: list<Template>, hidden: int}
|
||||
*/
|
||||
public function showing(DiviPage $page, ?array $live = null): array
|
||||
{
|
||||
$area = (string) $page->themeBuilderArea();
|
||||
$id = (int) $page->post->ID;
|
||||
$shows = static fn (Template $template): bool => $template->showsLayout($area, $id);
|
||||
$live ??= $this->live();
|
||||
|
||||
return array_values(array_filter(
|
||||
$this->access->store()->liveTemplates(),
|
||||
static fn (Template $template): bool => $template->showsLayout($area, $id)
|
||||
));
|
||||
return [
|
||||
'templates' => array_values(array_filter($live['templates'], $shows)),
|
||||
'hidden' => count(array_filter($live['hidden'], $shows)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @param array{templates: list<Template>, hidden: list<Template>}|null $live live(); null = read now
|
||||
* @return array{scope: SaveScope, details: array<string, mixed>}|WP_Error
|
||||
* @throws WriteFailed When the backup could not be made (nothing was saved).
|
||||
*/
|
||||
public function prepare(DiviPage $page, array $input): array|WP_Error
|
||||
public function prepare(DiviPage $page, array $input, ?array $live = null): array|WP_Error
|
||||
{
|
||||
$templates = $this->showing($page);
|
||||
$details = ['templates' => self::listing($templates)];
|
||||
if ($templates === []) {
|
||||
$showing = $this->showing($page, $live);
|
||||
$details = self::details($showing);
|
||||
if (! self::isShown($showing)) {
|
||||
return ['scope' => SaveScope::ThemeBuilderLayout, 'details' => $details];
|
||||
}
|
||||
|
||||
@@ -71,7 +94,7 @@ final class ThemeBuilderEdits
|
||||
'Theme-Builder-Layout %d ist auf der Website sichtbar (%s, siehe templates); die Änderung wirkt '
|
||||
. 'sofort. Nichts wurde gespeichert (Probelauf).',
|
||||
$id,
|
||||
self::count($templates)
|
||||
self::count($showing)
|
||||
),
|
||||
$details
|
||||
);
|
||||
@@ -95,15 +118,19 @@ final class ThemeBuilderEdits
|
||||
/** A tool's own confirm_required (lit-divi-delete) on a visible layout, extended by the templates. */
|
||||
public function notice(DiviPage $page, WP_Error $error): WP_Error
|
||||
{
|
||||
$templates = Errors::codeOf($error) === 'confirm_required' ? $this->showing($page) : [];
|
||||
if ($templates === []) {
|
||||
if (Errors::codeOf($error) !== 'confirm_required') {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$showing = $this->showing($page);
|
||||
if (! self::isShown($showing)) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return VisibleEdits::merged(
|
||||
$error,
|
||||
sprintf('Das Layout ist auf der Website sichtbar (%s, siehe templates).', self::count($templates)),
|
||||
['templates' => self::listing($templates)]
|
||||
sprintf('Das Layout ist auf der Website sichtbar (%s, siehe templates).', self::count($showing)),
|
||||
self::details($showing)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,9 +139,10 @@ final class ThemeBuilderEdits
|
||||
* 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.
|
||||
*
|
||||
* @param array{templates: list<Template>, hidden: list<Template>}|null $live live(); null = read now
|
||||
* @return list<string>
|
||||
*/
|
||||
public function fieldWarnings(DiviPage $page, DiviChange $change): array
|
||||
public function fieldWarnings(DiviPage $page, DiviChange $change, ?array $live = null): array
|
||||
{
|
||||
if ($this->fields === null || $page->themeBuilderArea() !== 'body') {
|
||||
return [];
|
||||
@@ -127,7 +155,7 @@ final class ThemeBuilderEdits
|
||||
|
||||
$id = (int) $page->post->ID;
|
||||
$uses = static fn (Template $template): bool => $template->layouts['body']['id'] === $id;
|
||||
$live = $this->access->editableLiveTemplates();
|
||||
$live ??= $this->live();
|
||||
$types = [];
|
||||
foreach (array_filter($live['templates'], $uses) as $template) {
|
||||
array_push($types, ...$template->postTypes());
|
||||
@@ -261,10 +289,38 @@ final class ThemeBuilderEdits
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<Template> $templates */
|
||||
private static function count(array $templates): string
|
||||
/** @param array{templates: list<Template>, hidden: int} $showing */
|
||||
private static function isShown(array $showing): bool
|
||||
{
|
||||
return sprintf('%d aktive %s', count($templates), count($templates) === 1 ? 'Vorlage' : 'Vorlagen');
|
||||
return $showing['templates'] !== [] || $showing['hidden'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The answer fields: "templates" always (also [] – DiviReplaceTextAbility tells a layout by it), and the
|
||||
* hidden count only when there are any.
|
||||
*
|
||||
* @param array{templates: list<Template>, hidden: int} $showing
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function details(array $showing): array
|
||||
{
|
||||
return ['templates' => self::listing($showing['templates'])]
|
||||
+ ($showing['hidden'] > 0 ? ['hidden_templates' => $showing['hidden']] : []);
|
||||
}
|
||||
|
||||
/** @param array{templates: list<Template>, hidden: int} $showing */
|
||||
private static function count(array $showing): string
|
||||
{
|
||||
$total = count($showing['templates']) + $showing['hidden'];
|
||||
$text = sprintf('%d aktive %s', $total, $total === 1 ? 'Vorlage' : 'Vorlagen');
|
||||
|
||||
return $showing['hidden'] > 0
|
||||
? sprintf(
|
||||
'%s, davon %d ohne Berechtigung – nicht aufgeführt, siehe hidden_templates',
|
||||
$text,
|
||||
$showing['hidden']
|
||||
)
|
||||
: $text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user