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:
@@ -12,7 +12,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\MalformedMarkup;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPage;
|
||||
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
|
||||
use LIT\MCP\Integrations\WordPress\Status\PostCloner;
|
||||
use LIT\MCP\Integrations\Divi\Pages\VisibleEdits;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
@@ -27,25 +27,22 @@ defined('ABSPATH') || exit;
|
||||
* confirm: true nothing is saved and the answer lists those pages (a dry run of the fully
|
||||
* validated change); with it the
|
||||
* save scope is GlobalItem (site-wide cache) and, when revisions are off for the entry, a backup
|
||||
* copy is made first – the revision itself is DiviSaver's restore point, as for every page.
|
||||
* copy is made first – the revision itself is DiviSaver's restore point, as for every page
|
||||
* (VisibleEdits, shared with live Theme Builder layouts, ruling Q2).
|
||||
* Confirm stays honour-based (ruling P9, like D10). A confirmed edit is refused while the user may
|
||||
* not edit_post one of the published, private or scheduled posts that show the entry (rulings
|
||||
* P8/P8a, stricter than Divi); the dry run names them already (blocked_ids – posts the user may
|
||||
* not read only as counts, audit L4). One GlobalUsages report per call (audit L1); above
|
||||
* GlobalUsages::HARD_CAP live posts a confirmed edit is refused (Divi builder instead).
|
||||
* The backup (PostCloner, wp_insert_post with kses) is refused when it could not be faithful: for
|
||||
* users without unfiltered_html, and when the trash is disabled (ruling P9, S37).
|
||||
* users without unfiltered_html, and when the trash is disabled (ruling P9, S37; VisibleEdits).
|
||||
*/
|
||||
final class GlobalEdits
|
||||
{
|
||||
/** Refusals name at most this many page IDs. */
|
||||
private const LISTED_IDS = 10;
|
||||
|
||||
/** @var Closure(WP_Post): int */
|
||||
private Closure $backup;
|
||||
|
||||
/** @var Closure(): bool */
|
||||
private Closure $trashEnabled;
|
||||
private VisibleEdits $visible;
|
||||
|
||||
/**
|
||||
* @param (Closure(WP_Post): int)|null $backup Backup copy of the entry; null = PostCloner::backup().
|
||||
@@ -58,9 +55,7 @@ final class GlobalEdits
|
||||
private Parser $parser = new Parser(),
|
||||
?Closure $trashEnabled = null
|
||||
) {
|
||||
$this->backup = $backup ?? static fn (WP_Post $item): int => (new PostCloner())->backup($item);
|
||||
$this->trashEnabled = $trashEnabled
|
||||
?? static fn (): bool => ! defined('EMPTY_TRASH_DAYS') || (bool) EMPTY_TRASH_DAYS;
|
||||
$this->visible = new VisibleEdits($backup, $trashEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,17 +103,21 @@ final class GlobalEdits
|
||||
);
|
||||
}
|
||||
|
||||
$refusal = $this->pagesRefusal(self::label($id, $global), $report) ?? $this->backupRefusal($item);
|
||||
$refusal = $this->pagesRefusal(self::label($id, $global), $report)
|
||||
?? $this->visible->backupRefusal(
|
||||
$item,
|
||||
sprintf('Bibliothekseintrag %d', $id),
|
||||
'Revisionen für die Divi-Bibliothek aktivieren (WP_POST_REVISIONS) oder den Eintrag im Divi-Builder '
|
||||
. 'bearbeiten.'
|
||||
);
|
||||
if ($refusal !== null) {
|
||||
return $refusal;
|
||||
}
|
||||
|
||||
$details = $report->listing();
|
||||
if (! wp_revisions_enabled($item)) {
|
||||
$details['backup_id'] = ($this->backup)($item);
|
||||
}
|
||||
|
||||
return ['scope' => SaveScope::GlobalItem, 'details' => $details];
|
||||
return [
|
||||
'scope' => SaveScope::GlobalItem,
|
||||
'details' => array_merge($report->listing(), $this->visible->backup($item)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,20 +136,15 @@ final class GlobalEdits
|
||||
return $error;
|
||||
}
|
||||
|
||||
$data = $error->get_error_data();
|
||||
$details = is_array($data) ? array_diff_key($data, ['status' => true, 'hint' => true]) : [];
|
||||
|
||||
return Errors::confirmRequired(
|
||||
Errors::clientMessage($error)
|
||||
. sprintf(
|
||||
' %s: wirkt auf %d %s (siehe affected).',
|
||||
$global ? 'Globaler Bibliothekseintrag' : 'Bibliothekseintrag (als globales Modul verwendet)',
|
||||
$report->count,
|
||||
self::pages($report->count)
|
||||
)
|
||||
. self::hiddenNote($report)
|
||||
. self::blockedNote($report),
|
||||
array_merge($details, $report->details())
|
||||
return VisibleEdits::merged(
|
||||
$error,
|
||||
sprintf(
|
||||
'%s: wirkt auf %d %s (siehe affected).',
|
||||
$global ? 'Globaler Bibliothekseintrag' : 'Bibliothekseintrag (als globales Modul verwendet)',
|
||||
$report->count,
|
||||
self::pages($report->count)
|
||||
) . self::hiddenNote($report) . self::blockedNote($report),
|
||||
$report->details()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,39 +248,4 @@ final class GlobalEdits
|
||||
{
|
||||
return $count === 1 ? 'Seite' : 'Seiten';
|
||||
}
|
||||
|
||||
/** Ruling P9: a backup (only made when revisions are off) must be faithful and must survive. */
|
||||
private function backupRefusal(WP_Post $item): ?WP_Error
|
||||
{
|
||||
if (wp_revisions_enabled($item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hint = 'Revisionen für die Divi-Bibliothek aktivieren (WP_POST_REVISIONS) oder den Eintrag im Divi-Builder '
|
||||
. 'bearbeiten.';
|
||||
if (! current_user_can('unfiltered_html')) {
|
||||
return Errors::unsupported(
|
||||
sprintf(
|
||||
'Revisionen sind für Bibliothekseintrag %d abgeschaltet, und die Sicherungskopie wäre für dieses '
|
||||
. 'Konto nicht verlustfrei (WordPress filtert HTML); nichts wurde gespeichert.',
|
||||
(int) $item->ID
|
||||
),
|
||||
$hint
|
||||
);
|
||||
}
|
||||
|
||||
if (! ($this->trashEnabled)()) {
|
||||
return Errors::unsupported(
|
||||
sprintf(
|
||||
'Revisionen sind für Bibliothekseintrag %d abgeschaltet, und der Papierkorb ist deaktiviert '
|
||||
. '(EMPTY_TRASH_DAYS = 0) – die Sicherungskopie würde sofort gelöscht; nichts wurde '
|
||||
. 'gespeichert.',
|
||||
(int) $item->ID
|
||||
),
|
||||
$hint
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user