fix: name the WordPress revision screen for library and layout revisions (audit L3)

lit/revision-restore takes only content types, so a write to a library entry
or a Theme Builder layout now answers with restore_url (wp-admin
revision.php for its revision_id), also per page in lit/divi-replace-text.
The guides and readme no longer present lit/revision-restore as the way back
for these types.

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 23:05:05 +02:00
co-authored by Claude Opus 5.5
parent 79f2fdbda6
commit dcae2d4b91
11 changed files with 48 additions and 12 deletions
+2 -1
View File
@@ -75,7 +75,8 @@ Die Divi-Bibliothek speichert Layouts, Abschnitte, Zeilen und Module zur Wiederv
4. Dem Nutzer die betroffenen Seiten nennen und fragen. Erst nach Zustimmung dieselbe Änderung mit 4. Dem Nutzer die betroffenen Seiten nennen und fragen. Erst nach Zustimmung dieselbe Änderung mit
`confirm: true` wiederholen (bei `lit-divi-delete` gilt dasselbe `confirm`). `confirm: true` wiederholen (bei `lit-divi-delete` gilt dasselbe `confirm`).
5. Vorher entsteht eine Revision des Eintrags (`revision_id`); sind Revisionen abgeschaltet, eine 5. Vorher entsteht eine Revision des Eintrags (`revision_id`); sind Revisionen abgeschaltet, eine
Sicherungskopie im Papierkorb (`backup_id`). Schlägt das Speichern danach fehl, nennt der Fehler die Sicherungskopie im Papierkorb (`backup_id`). `lit/revision-restore` gilt nicht für Bibliothekseinträge:
Zurück geht es über `restore_url` in der Antwort (Revisionsvergleich in WordPress, dort wiederherstellen). Schlägt das Speichern danach fehl, nennt der Fehler die
`backup_id`. Nach dem Speichern wird Divis CSS-Cache für alle Seiten geleert. `backup_id`. Nach dem Speichern wird Divis CSS-Cache für alle Seiten geleert.
Die Bestätigung wird abgelehnt (`forbidden`), wenn das Konto einen veröffentlichten, privaten oder geplanten Die Bestätigung wird abgelehnt (`forbidden`), wenn das Konto einen veröffentlichten, privaten oder geplanten
+3 -1
View File
@@ -56,7 +56,9 @@ Für den Einstieg liefert `lit-guide` mit dem Thema „all“ alle Themen in ein
- Erfolg: `ok`, `summary`, `changed`, `warnings`, `rev`, `revision_id` (Revision mit dem Stand vorher, nur bei - Erfolg: `ok`, `summary`, `changed`, `warnings`, `rev`, `revision_id` (Revision mit dem Stand vorher, nur bei
Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs. Divi-Werkzeuge zusätzlich `tree_rev` (Stand Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs. Divi-Werkzeuge zusätzlich `tree_rev` (Stand
des Seiteninhalts, Pflicht für die nächste Divi-Änderung) und `outline` (betroffener Abschnitt); des Seiteninhalts, Pflicht für die nächste Divi-Änderung) und `outline` (betroffener Abschnitt);
`lit/divi-replace-text` nennt `tree_rev` je Seite in `results` (Probelauf: in `matches`). `lit/divi-replace-text` nennt `tree_rev` je Seite in `results` (Probelauf: in `matches`). Bei
Bibliothekseinträgen und Theme-Builder-Layouts kommt `restore_url` hinzu (Revisionsvergleich in WordPress) –
`lit/revision-restore` gilt nur für Inhaltstypen.
- Fehler: JSON mit `code` (z. B. `validation_failed`, `rev_mismatch`, `write_failed`, `partial_write`), - Fehler: JSON mit `code` (z. B. `validation_failed`, `rev_mismatch`, `write_failed`, `partial_write`),
`message`, `hint` (und ggf. `fields`). Dem `hint` folgen. `message`, `hint` (und ggf. `fields`). Dem `hint` folgen.
- `confirm_required`: Nutzer fragen, dann mit `confirm: true` wiederholen. - `confirm_required`: Nutzer fragen, dann mit `confirm: true` wiederholen.
+7 -4
View File
@@ -15,8 +15,8 @@ Kopf- und Fußzeile heißen „global“, weil andere Vorlagen sie mitbenutzen.
`confirm: true`: Bedingungen einer aktiven Vorlage ändern (`lit/tb-assign`) und ein Layout ändern, das eine `confirm: true`: Bedingungen einer aktiven Vorlage ändern (`lit/tb-assign`) und ein Layout ändern, das eine
aktive Vorlage zeigt (die `lit-divi-…`-Werkzeuge; die Antwort nennt die Vorlagen unter `templates`, solche aktive Vorlage zeigt (die `lit-divi-…`-Werkzeuge; die Antwort nennt die Vorlagen unter `templates`, solche
ohne Berechtigung nur als Anzahl `hidden_templates`). Vor dem ohne Berechtigung nur als Anzahl `hidden_templates`). Vor dem
Speichern eines sichtbaren Layouts entsteht eine Revision; sind Revisionen abgeschaltet, eine Speichern eines sichtbaren Layouts entsteht eine Revision (wiederherstellbar über `restore_url`, siehe
Sicherungskopie im Papierkorb (`backup_id`). „Rückgängig machen“); sind Revisionen abgeschaltet, eine Sicherungskopie im Papierkorb (`backup_id`).
- `lit/divi-replace-text` ändert sichtbare Layouts nicht; solche Änderungen mit `lit-divi-update` und - `lit/divi-replace-text` ändert sichtbare Layouts nicht; solche Änderungen mit `lit-divi-update` und
`confirm: true` vornehmen. `confirm: true` vornehmen.
- Die Standardvorlage ändert LIT MCP nicht (nur im Theme Builder). - Die Standardvorlage ändert LIT MCP nicht (nur im Theme Builder).
@@ -100,8 +100,11 @@ bleiben die Felder ungeprüft (die Antwort sagt das). Kopf- und Fußzeile werden
## Rückgängig machen ## Rückgängig machen
Vorlagen haben keine Revisionen: `lit/tb-assign` nennt die bisherigen Bedingungen (`previous`), `lit/tb-activate` Vorlagen haben keine Revisionen: `lit/tb-assign` nennt die bisherigen Bedingungen (`previous`), `lit/tb-activate`
schaltet zurück; jede Änderung steht im Protokoll. Layouts erhalten wie Divi-Seiten eine Revision. Neu angelegte schaltet zurück; jede Änderung steht im Protokoll. Layouts erhalten wie Divi-Seiten eine Revision (`revision_id`),
Vorlagen im Divi Theme Builder löschen. aber `lit/revisions-list` und `lit/revision-restore` gelten nur für Seiten, Beiträge und andere Inhaltstypen,
nicht für Theme-Builder-Layouts: Die Antwort nennt deshalb `restore_url`, den Revisionsvergleich in WordPress,
in dem der Nutzer die Revision wiederherstellt. Schneller Weg zurück bei einem sichtbaren Layout: die Vorlage mit
`lit/tb-activate` (`enabled: false`) ausblenden. Neu angelegte Vorlagen im Divi Theme Builder löschen.
## Ablauf: Vorlage für einen Inhaltstyp (custom-post-type-vorlage) ## Ablauf: Vorlage für einen Inhaltstyp (custom-post-type-vorlage)
+7 -5
View File
@@ -125,12 +125,14 @@ Es gibt keine allgemeine "Rückgängig"-Funktion; was sich wie zurücksetzen lä
* Divi-Seiten: jede Divi-Änderung legt vorher eine Revision an (revision_id in der Antwort); zurück über * Divi-Seiten: jede Divi-Änderung legt vorher eine Revision an (revision_id in der Antwort); zurück über
lit/revision-restore. Beim seitenübergreifenden Ersetzen erhält jede geänderte Seite eine eigene Revision. lit/revision-restore. Beim seitenübergreifenden Ersetzen erhält jede geänderte Seite eine eigene Revision.
* Bibliothekseinträge: jede Änderung legt vorher eine Revision an (revision_id), bei abgeschalteten Revisionen * Bibliothekseinträge: jede Änderung legt vorher eine Revision an (revision_id), bei abgeschalteten Revisionen
eine Sicherungskopie im Papierkorb (backup_id); zurück über WordPress (Eintrag der Divi-Bibliothek eine Sicherungskopie im Papierkorb (backup_id); zurück über den Revisionsvergleich in WordPress (restore_url
bearbeiten → Revisionen). Neu angelegte Einträge (lit/library-save, lit/library-make-global) in der in der Antwort; lit/revision-restore gilt nur für Inhaltstypen, nicht für Bibliothekseinträge). Neu
Divi-Bibliothek löschen; durch Instanzen ersetzte Knoten über die Revision der jeweiligen Seite zurückholen. angelegte Einträge (lit/library-save, lit/library-make-global) in der Divi-Bibliothek löschen; durch
Instanzen ersetzte Knoten über die Revision der jeweiligen Seite zurückholen.
* Theme Builder: Vorlagen haben keine Revisionen. lit/tb-assign nennt die bisherigen Bedingungen (previous) und * Theme Builder: Vorlagen haben keine Revisionen. lit/tb-assign nennt die bisherigen Bedingungen (previous) und
schreibt sie ins Protokoll; lit/tb-activate schaltet zurück. Layouts erhalten wie Divi-Seiten eine Revision. schreibt sie ins Protokoll; lit/tb-activate schaltet zurück. Layouts erhalten wie Divi-Seiten eine Revision,
Neu angelegte Vorlagen und Layouts im Divi Theme Builder löschen. wiederherstellbar im Revisionsvergleich von WordPress (restore_url in der Antwort; nicht über
lit/revision-restore). Neu angelegte Vorlagen und Layouts im Divi Theme Builder löschen.
* Original durch eine Kopie ersetzen (lit/post-replace-with-copy): vorher legt das Werkzeug eine Revision * Original durch eine Kopie ersetzen (lit/post-replace-with-copy): vorher legt das Werkzeug eine Revision
und eine vollständige Sicherung des bisherigen Originals im Papierkorb an (backup_id in der Antwort) – und eine vollständige Sicherung des bisherigen Originals im Papierkorb an (backup_id in der Antwort) –
von dort in WordPress wiederherstellbar. von dort in WordPress wiederherstellbar.
@@ -383,7 +383,7 @@ final class DiviReplaceTextAbility implements Ability
'revision_id' => $saved['revision_id'] ?? null, 'revision_id' => $saved['revision_id'] ?? null,
'log_id' => $saved['log_id'] ?? null, 'log_id' => $saved['log_id'] ?? null,
'warnings' => is_array($saved['warnings'] ?? null) ? $saved['warnings'] : [], 'warnings' => is_array($saved['warnings'] ?? null) ? $saved['warnings'] : [],
]; ] + (isset($saved['restore_url']) ? ['restore_url' => $saved['restore_url']] : []);
} }
/** /**
@@ -41,6 +41,9 @@ abstract class DiviWriteAbility implements Ability
'affected_count' => ['type' => 'integer'], 'affected_count' => ['type' => 'integer'],
'hidden_count' => ['type' => 'integer'], 'hidden_count' => ['type' => 'integer'],
'backup_id' => ['type' => 'integer'], 'backup_id' => ['type' => 'integer'],
// Library entries and Theme Builder layouts: their revision in WordPress' revision screen (audit L3 –
// lit/revision-restore takes only content types).
'restore_url' => ['type' => 'string'],
// Theme Builder layouts: the enabled templates that show the layout – those the user may edit; the // Theme Builder layouts: the enabled templates that show the layout – those the user may edit; the
// others only as a count (ruling Q22). // others only as a count (ruling Q22).
'templates' => ['type' => 'array', 'items' => ['type' => 'object']], 'templates' => ['type' => 'array', 'items' => ['type' => 'object']],
@@ -155,10 +155,28 @@ final class DiviWriter
foreach ($change->entries() as $entry) { foreach ($change->entries() as $entry) {
$tool->changed(DiviChange::idOf($entry, $index) ?? '?', $entry['type'], $entry['action'], $entry['label']); $tool->changed(DiviChange::idOf($entry, $index) ?? '?', $entry['type'], $entry['action'], $entry['label']);
} }
$restore = self::restoreUrl($page, $saved['revision_id']);
if ($restore !== null) {
$tool->with('restore_url', $restore);
}
return $tool->toArray(); return $tool->toArray();
} }
/**
* Audit L3: lit/revision-restore takes only the content types (ContentSchema) – not library entries or Theme
* Builder layouts. Their revision is restored in WordPress' revision screen; null for everything else.
*/
private static function restoreUrl(DiviPage $page, mixed $revisionId): ?string
{
$noRestoreTool = $page->isLibraryItem() || $page->isThemeBuilderLayout();
if (! $noRestoreTool || ! is_int($revisionId) || $revisionId <= 0) {
return null;
}
return admin_url('revision.php?revision=' . $revisionId);
}
/** /**
* save() for the Divi tools (E2): pages as before; a library entry keeps its shape, a global one * save() for the Divi tools (E2): pages as before; a library entry keeps its shape, a global one
* needs confirm (dry run first), gets a backup when revisions are off and clears every page's * needs confirm (dry run first), gets a backup when revisions are off and clears every page's
+1
View File
@@ -85,6 +85,7 @@ final class LibraryFixtures
static fn (string $key, string $value, string $url): string => $url . '&' . $key . '=' . $value static fn (string $key, string $value, string $url): string => $url . '&' . $key . '=' . $value
); );
Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true'); Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true');
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.test/wp-admin/' . $path);
} }
/** A store that answers isGlobal()/layoutType() from ITEMS (no database). */ /** A store that answers isGlobal()/layoutType() from ITEMS (no database). */
@@ -206,6 +206,8 @@ final class GlobalEditsTest extends TestCase
self::assertTrue($result['ok']); self::assertTrue($result['ok']);
self::assertSame(SaveScope::GlobalItem, $this->saved[0][1]); self::assertSame(SaveScope::GlobalItem, $this->saved[0][1]);
self::assertSame(811, $result['revision_id']); self::assertSame(811, $result['revision_id']);
// Audit L3: lit/revision-restore does not take library entries – the WordPress revision screen does.
self::assertSame('https://kunde.test/wp-admin/revision.php?revision=811', $result['restore_url']);
self::assertSame(2, $result['affected_count']); self::assertSame(2, $result['affected_count']);
self::assertArrayNotHasKey('backup_id', $result); self::assertArrayNotHasKey('backup_id', $result);
self::assertSame(0, $this->backups); self::assertSame(0, $this->backups);
@@ -537,6 +539,7 @@ final class GlobalEditsTest extends TestCase
self::assertSame(1, $result['results'][0]['count']); self::assertSame(1, $result['results'][0]['count']);
self::assertSame(SaveScope::LibraryItem, $this->saved[0][1]); self::assertSame(SaveScope::LibraryItem, $this->saved[0][1]);
self::assertStringContainsString('Bibliotheksmodul', $this->saved[0][0]); self::assertStringContainsString('Bibliotheksmodul', $this->saved[0][0]);
self::assertSame('https://kunde.test/wp-admin/revision.php?revision=811', $result['results'][0]['restore_url']);
} }
/** Ruling P1 / answer F2: a global entry is never changed without the dry run that names its pages. */ /** Ruling P1 / answer F2: a global entry is never changed without the dry run that names its pages. */
@@ -23,6 +23,7 @@ final class DiviInsertAbilityTest extends DiviWriteAbilityTestCase
self::assertSame(Rev::of($this->saved[0]), $result['tree_rev']); self::assertSame(Rev::of($this->saved[0]), $result['tree_rev']);
self::assertSame(Rev::ofPost($this->post), $result['rev']); self::assertSame(Rev::ofPost($this->post), $result['rev']);
self::assertSame(811, $result['revision_id']); self::assertSame(811, $result['revision_id']);
self::assertArrayNotHasKey('restore_url', $result, 'pages: lit/revision-restore');
self::assertSame(4711, $result['log_id']); self::assertSame(4711, $result['log_id']);
self::assertStringStartsWith('s0 section "Hero"', (string) $result['outline']); self::assertStringStartsWith('s0 section "Hero"', (string) $result['outline']);
self::assertSame('https://kunde.test/?page_id=7&et_fb=1', $result['edit_url']); self::assertSame('https://kunde.test/?page_id=7&et_fb=1', $result['edit_url']);
@@ -86,6 +86,8 @@ final class DiviWriterThemeBuilderTest extends TestCase
self::assertTrue($result['ok']); self::assertTrue($result['ok']);
self::assertCount(1, $this->saved); self::assertCount(1, $this->saved);
self::assertSame(1066, $result['templates'][0]['id']); self::assertSame(1066, $result['templates'][0]['id']);
// Audit L3: lit/revision-restore does not take Theme Builder layouts – the WordPress revision screen does.
self::assertSame('https://kunde.test/wp-admin/revision.php?revision=811', $result['restore_url']);
} }
public function testLayoutOfADisabledTemplateSavesAtOnce(): void public function testLayoutOfADisabledTemplateSavesAtOnce(): void