From 6b5de199f97f57b7484ddbd83529b9114f04cdc5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 18 Oct 2026 17:28:19 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Texte=20zu=20post=5Fids=20und=20Bearbeit?= =?UTF-8?q?ungsrecht=20nennen=20forbidden=20f=C3=BCr=20genannte=20Live-Sei?= =?UTF-8?q?ten;=20Test=20f=C3=BCr=20genannte=20blockierte=20Live-Seite;=20?= =?UTF-8?q?Smoke=20pr=C3=BCft=20Seiten-IDs=20numerisch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- docs/guides/divi-design-system.md | 10 +++++--- ...6-09-28-etappe-4-entscheidungen-entwurf.md | 2 +- .../Divi/Design/DiviDesignDetachAbility.php | 6 +++-- .../Divi/Design/DiviPresetsAssignAbility.php | 6 +++-- .../Integration/divi-presets-assign-smoke.sh | 2 +- .../Divi/Design/PageRewriterTest.php | 23 +++++++++++++++++++ 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/docs/guides/divi-design-system.md b/docs/guides/divi-design-system.md index 7146b45..5002cfd 100644 --- a/docs/guides/divi-design-system.md +++ b/docs/guides/divi-design-system.md @@ -84,9 +84,13 @@ Einzelne Module bekommen Presets mit `lit-divi-insert`, `lit-divi-update` und `l umgestellte Module ändern sich nicht noch einmal. Jede Seite bekommt ihre eigene WordPress-Revision; einen Snapshot gibt es für Seiteninhalte nicht. - Blockaden: Ohne `post_ids` verweigert der Aufruf alles und schreibt nichts: mit `forbidden` (und `blocked_ids`), - wenn das Konto eine veröffentlichte Fundstelle nicht bearbeiten darf, mit `unsupported`, wenn es mehr als 2000 - veröffentlichte Fundstellen gibt. Mit `post_ids` können nur die genannten Seiten blockieren, die Obergrenze gilt nicht, und eine genannte - Seite ohne Bearbeitungsrecht steht unter `skipped` (nicht `blocked_ids`). Scheitert das Speichern mitten im Lauf, + wenn das Konto eine veröffentlichte, private oder geplante Fundstelle nicht bearbeiten darf, mit `unsupported`, + wenn es mehr als 2000 solcher Fundstellen gibt. +- Mit `post_ids` blockieren nur die genannten Seiten, die Obergrenze von 2000 gilt nicht. Eine genannte + veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen Aufruf (`forbidden` + mit `blocked_ids`, nichts wird geschrieben; der Probelauf listet sie unter `blocked_ids`). Eine genannte + Seite, die nicht live ist (Entwurf usw.) und nicht bearbeitet werden darf, steht unter `skipped`. +- Scheitert das Speichern mitten im Lauf, meldet `partial_write` die bis dahin gespeicherten Seiten (`saved`); die übrigen mit einem neuen Probelauf weiterführen. - Globale Module und die Bibliothek: Eine globale Instanz (`[global ]` in `lit-divi-outline`) wird auf der Seite diff --git a/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md b/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md index 8cd213f..c12e3f8 100644 --- a/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md +++ b/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md @@ -47,7 +47,7 @@ Ansätze vorschlagen → Entwurf abschnittsweise freigeben → Spec schreiben - R6 Outline: group_presets je Zeile und in node-get; Fingerprint unverändert. - R7 clear_overrides beim Einfügen wirkt nach set/attrs desselben Knotens; nicht-boolesch → Fehler. - R8 Seiten-Umschreiber: globale Instanzen nie umgeschrieben (skipped; Bibliotheks-ID nur bei sichtbaren Bibliotheks-IDs, P13), Bibliothekseintrag ist eigene Fundstelle; nicht ladbare Seiten skipped; remaining zählt nur Seiten mit Änderungen. -- P8a mit post_ids: nur die genannten Seiten können das Schreiben blockieren, HARD_CAP gilt nicht, eine genannte Seite ohne Bearbeitungsrecht steht in skipped. Ohne post_ids verweigert jede blockierte Live-Fundstelle oder zu viele Fundstellen den ganzen Aufruf, nichts wird geschrieben. +- P8a mit post_ids: nur die genannten Seiten können das Schreiben blockieren, HARD_CAP gilt nicht (die Rechteprüfung der Live-Seiten entfällt dann vorab, loadWritable() prüft jede Seite einzeln und meldet skipped). Eine genannte veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen Aufruf (forbidden, blocked_ids, nichts geschrieben; der Probelauf listet sie in blocked_ids); eine genannte Seite, die nicht live ist (Entwurf usw.) und nicht bearbeitet werden darf, steht in skipped. Ohne post_ids verweigert jede blockierte Live-Fundstelle oder zu viele Fundstellen den ganzen Aufruf, nichts wird geschrieben. - R9 Kein unfiltered_html-Zwang: der Umschreiber ändert nur Preset-Verweise und entfernt lokale Werte, Speichern über DiviSaver. - R10 detach nur Presets: Eingabe preset = Objekt {module|group, preset}; variable → unsupported (4c); ohne post_ids zuerst verschachtelte Verweise über DesignChange (Snapshot, design_rev), danach Seiten (≤ 50); scheitert die erste Seite danach, nennt der Fehler Snapshot und diff; Standard-Presets nur ausdrückliche Verweise. - lit/divi-presets-assign: preset: null und null in group_presets werden abgewiesen (Hinweis auf lit/divi-design-detach); ein leerer Probelauf ist ein normales „nichts zu tun“. diff --git a/src/Integrations/Divi/Design/DiviDesignDetachAbility.php b/src/Integrations/Divi/Design/DiviDesignDetachAbility.php index 23b8fbc..99a6b6c 100644 --- a/src/Integrations/Divi/Design/DiviDesignDetachAbility.php +++ b/src/Integrations/Divi/Design/DiviDesignDetachAbility.php @@ -66,8 +66,10 @@ final class DiviDesignDetachAbility implements Ability . 'diese Seiten (Verweise in Presets bleiben dann). Zuerst Probelauf (pages; bei Verweisen in Presets diff ' . 'und design_rev), nach Rückfrage beim Nutzer confirm: true (mit design_rev, wenn der Probelauf eine ' . 'nennt); je Aufruf höchstens 50 Seiten, remaining nennt den Rest. Mit post_ids können nur die ' - . 'genannten Seiten das Schreiben blockieren, die Obergrenze für sehr viele Seiten gilt dann nicht, und ' - . 'eine genannte Seite ohne Bearbeitungsrecht steht in skipped. Danach kann lit/divi-presets-delete ' + . 'genannten Seiten das Schreiben blockieren und die Obergrenze für sehr viele Seiten gilt nicht; eine ' + . 'genannte veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen ' + . 'Aufruf (forbidden, blocked_ids), eine genannte andere Seite ohne ' + . 'Bearbeitungsrecht steht in skipped. Danach kann lit/divi-presets-delete ' . 'das Preset löschen. ' . self::VARIABLES; } diff --git a/src/Integrations/Divi/Design/DiviPresetsAssignAbility.php b/src/Integrations/Divi/Design/DiviPresetsAssignAbility.php index a3a7a1c..cde4ce7 100644 --- a/src/Integrations/Divi/Design/DiviPresetsAssignAbility.php +++ b/src/Integrations/Divi/Design/DiviPresetsAssignAbility.php @@ -58,8 +58,10 @@ final class DiviPresetsAssignAbility implements Ability . 'only_with_preset; clear_overrides: true entfernt lokale Werte, die das Preset überdecken. Zuerst ' . 'Probelauf (betroffene Seiten und Module), nach Rückfrage beim Nutzer confirm: true; je Aufruf höchstens ' . '50 Seiten, jede mit Revision; remaining nennt den Rest. Mit post_ids können nur die genannten Seiten ' - . 'das Schreiben blockieren, die Obergrenze für sehr viele Seiten gilt dann nicht, und eine genannte ' - . 'Seite ohne Bearbeitungsrecht steht in skipped. Verweise entfernen: lit/divi-design-detach.'; + . 'das Schreiben blockieren und die Obergrenze für sehr viele Seiten gilt nicht; eine genannte ' + . 'veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen Aufruf ' + . '(forbidden, blocked_ids), eine genannte andere Seite ohne ' + . 'Bearbeitungsrecht steht in skipped. Verweise entfernen: lit/divi-design-detach.'; } public function inputSchema(): array diff --git a/tests/Integration/divi-presets-assign-smoke.sh b/tests/Integration/divi-presets-assign-smoke.sh index b2c69c3..53a101b 100755 --- a/tests/Integration/divi-presets-assign-smoke.sh +++ b/tests/Integration/divi-presets-assign-smoke.sh @@ -120,7 +120,7 @@ tree_rev() { # $1 = post ID # 1. Two draft pages with one button without preset each. PAGE1=$(create_page "$PREFIX Seite 1") PAGE2=$(create_page "$PREFIX Seite 2") -[ -n "$PAGE1" ] && [ -n "$PAGE2" ] || fail "pages not created" +[[ $PAGE1 =~ ^[1-9][0-9]*$ && $PAGE2 =~ ^[1-9][0-9]*$ ]] || fail "pages not created" for id in "$PAGE1" "$PAGE2"; do button_attrs "$id" | jq -e '(.modulePreset // null) == null and (.groupPreset // null) == null' >/dev/null \ || fail "page $id starts with a preset" diff --git a/tests/Unit/Integrations/Divi/Design/PageRewriterTest.php b/tests/Unit/Integrations/Divi/Design/PageRewriterTest.php index a22b3f3..97532f6 100644 --- a/tests/Unit/Integrations/Divi/Design/PageRewriterTest.php +++ b/tests/Unit/Integrations/Divi/Design/PageRewriterTest.php @@ -268,6 +268,29 @@ final class PageRewriterTest extends TestCase self::assertSame([[101, SaveScope::Page]], $this->saved); } + /** A named live post the caller may not edit blocks the whole call (forbidden), also with post_ids. */ + public function testNamedBlockedLivePageRefusesTheCallEvenWithPostIds(): void + { + $rewriter = $this->rewriter( + [ + self::page(101, self::layout(self::BUTTON), ['post_status' => 'publish']), + self::page(102, self::layout(self::BUTTON), ['post_status' => 'publish']), + ], + self::report(2, [102], 2) + ); + + $preview = $rewriter->preview(self::query(), self::assign(), [101, 102]); + self::assertSame([102], $preview['blocked_ids']); + + $error = self::assertToolError( + $rewriter->apply(self::ABILITY, self::query(), self::assign(), [101, 102], self::SUMMARY), + 'forbidden' + ); + self::assertSame([102], $error['blocked_ids']); + self::assertSame([], $this->saved); + self::assertSame([], $this->log->records); + } + public function testTooManyLiveUsagesAreUnsupported(): void { $rewriter = $this->rewriter(