diff --git a/docs/guides/divi-library.md b/docs/guides/divi-library.md index cf7b9ab..fea451e 100644 --- a/docs/guides/divi-library.md +++ b/docs/guides/divi-library.md @@ -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 `confirm: true` wiederholen (bei `lit-divi-delete` gilt dasselbe `confirm`). 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. Die Bestätigung wird abgelehnt (`forbidden`), wenn das Konto einen veröffentlichten, privaten oder geplanten diff --git a/docs/guides/overview.md b/docs/guides/overview.md index b32d854..e974322 100644 --- a/docs/guides/overview.md +++ b/docs/guides/overview.md @@ -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 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); - `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`), `message`, `hint` (und ggf. `fields`). Dem `hint` folgen. - `confirm_required`: Nutzer fragen, dann mit `confirm: true` wiederholen. diff --git a/docs/guides/theme-builder.md b/docs/guides/theme-builder.md index 1e7e5f4..4ac7eb0 100644 --- a/docs/guides/theme-builder.md +++ b/docs/guides/theme-builder.md @@ -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 aktive Vorlage zeigt (die `lit-divi-…`-Werkzeuge; die Antwort nennt die Vorlagen unter `templates`, solche ohne Berechtigung nur als Anzahl `hidden_templates`). Vor dem - Speichern eines sichtbaren Layouts entsteht eine Revision; sind Revisionen abgeschaltet, eine - Sicherungskopie im Papierkorb (`backup_id`). + Speichern eines sichtbaren Layouts entsteht eine Revision (wiederherstellbar über `restore_url`, siehe + „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 `confirm: true` vornehmen. - 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 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 -Vorlagen im Divi Theme Builder löschen. +schaltet zurück; jede Änderung steht im Protokoll. Layouts erhalten wie Divi-Seiten eine Revision (`revision_id`), +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) diff --git a/readme.txt b/readme.txt index 513602d..3ad93a2 100644 --- a/readme.txt +++ b/readme.txt @@ -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 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 - eine Sicherungskopie im Papierkorb (backup_id); zurück über WordPress (Eintrag der Divi-Bibliothek - bearbeiten → Revisionen). Neu 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. + eine Sicherungskopie im Papierkorb (backup_id); zurück über den Revisionsvergleich in WordPress (restore_url + in der Antwort; lit/revision-restore gilt nur für Inhaltstypen, nicht für Bibliothekseinträge). Neu + 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 - 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. + schreibt sie ins Protokoll; lit/tb-activate schaltet zurück. Layouts erhalten wie Divi-Seiten eine Revision, + 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 und eine vollständige Sicherung des bisherigen Originals im Papierkorb an (backup_id in der Antwort) – von dort in WordPress wiederherstellbar. diff --git a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php index 4d69fab..673f424 100644 --- a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php +++ b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php @@ -383,7 +383,7 @@ final class DiviReplaceTextAbility implements Ability 'revision_id' => $saved['revision_id'] ?? null, 'log_id' => $saved['log_id'] ?? null, 'warnings' => is_array($saved['warnings'] ?? null) ? $saved['warnings'] : [], - ]; + ] + (isset($saved['restore_url']) ? ['restore_url' => $saved['restore_url']] : []); } /** diff --git a/src/Integrations/Divi/Pages/DiviWriteAbility.php b/src/Integrations/Divi/Pages/DiviWriteAbility.php index 22f331f..3f6a965 100644 --- a/src/Integrations/Divi/Pages/DiviWriteAbility.php +++ b/src/Integrations/Divi/Pages/DiviWriteAbility.php @@ -41,6 +41,9 @@ abstract class DiviWriteAbility implements Ability 'affected_count' => ['type' => 'integer'], 'hidden_count' => ['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 // others only as a count (ruling Q22). 'templates' => ['type' => 'array', 'items' => ['type' => 'object']], diff --git a/src/Integrations/Divi/Pages/DiviWriter.php b/src/Integrations/Divi/Pages/DiviWriter.php index 09850db..af1cad6 100644 --- a/src/Integrations/Divi/Pages/DiviWriter.php +++ b/src/Integrations/Divi/Pages/DiviWriter.php @@ -155,10 +155,28 @@ final class DiviWriter foreach ($change->entries() as $entry) { $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(); } + /** + * 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 * needs confirm (dry run first), gets a backup when revisions are off and clears every page's diff --git a/tests/Support/LibraryFixtures.php b/tests/Support/LibraryFixtures.php index f22d86c..605eb7d 100644 --- a/tests/Support/LibraryFixtures.php +++ b/tests/Support/LibraryFixtures.php @@ -85,6 +85,7 @@ final class LibraryFixtures 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('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.test/wp-admin/' . $path); } /** A store that answers isGlobal()/layoutType() from ITEMS (no database). */ diff --git a/tests/Unit/Integrations/Divi/Library/GlobalEditsTest.php b/tests/Unit/Integrations/Divi/Library/GlobalEditsTest.php index 29424d8..b0e046e 100644 --- a/tests/Unit/Integrations/Divi/Library/GlobalEditsTest.php +++ b/tests/Unit/Integrations/Divi/Library/GlobalEditsTest.php @@ -206,6 +206,8 @@ final class GlobalEditsTest extends TestCase self::assertTrue($result['ok']); self::assertSame(SaveScope::GlobalItem, $this->saved[0][1]); 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::assertArrayNotHasKey('backup_id', $result); self::assertSame(0, $this->backups); @@ -537,6 +539,7 @@ final class GlobalEditsTest extends TestCase self::assertSame(1, $result['results'][0]['count']); self::assertSame(SaveScope::LibraryItem, $this->saved[0][1]); 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. */ diff --git a/tests/Unit/Integrations/Divi/Pages/DiviInsertAbilityTest.php b/tests/Unit/Integrations/Divi/Pages/DiviInsertAbilityTest.php index 705c674..f65ec87 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviInsertAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviInsertAbilityTest.php @@ -23,6 +23,7 @@ final class DiviInsertAbilityTest extends DiviWriteAbilityTestCase self::assertSame(Rev::of($this->saved[0]), $result['tree_rev']); self::assertSame(Rev::ofPost($this->post), $result['rev']); self::assertSame(811, $result['revision_id']); + self::assertArrayNotHasKey('restore_url', $result, 'pages: lit/revision-restore'); self::assertSame(4711, $result['log_id']); self::assertStringStartsWith('s0 section "Hero"', (string) $result['outline']); self::assertSame('https://kunde.test/?page_id=7&et_fb=1', $result['edit_url']); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviWriterThemeBuilderTest.php b/tests/Unit/Integrations/Divi/Pages/DiviWriterThemeBuilderTest.php index ec2ba37..7b134de 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviWriterThemeBuilderTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviWriterThemeBuilderTest.php @@ -86,6 +86,8 @@ final class DiviWriterThemeBuilderTest extends TestCase self::assertTrue($result['ok']); self::assertCount(1, $this->saved); 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