From 6cf0a565e6cc9a2af477aa4a0760a4ea5e2dc9d5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sat, 26 Sep 2026 18:15:28 +0200 Subject: [PATCH] refactor: DiviSaver writes content through one guarded writeContent() (M4) writeContent($postId, $content, ?$before) holds the kses window plus wp_update_post(wp_slash([ID, post_content]), true); save() passes the restore point capture as $before, so the window still holds exactly RevisionPoint:: capture() and wp_update_post() (S37), saveNew() and the restore() rollback use it without. refusal() turns WP_Error / 0 into the message ("returned 0" as before) for save() and saveNew(); restore() keeps its own RuntimeException text. The class docblock (the M-5 window description) is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7 --- src/Integrations/Divi/Pages/DiviSaver.php | 54 ++++++++++++++++------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Integrations/Divi/Pages/DiviSaver.php b/src/Integrations/Divi/Pages/DiviSaver.php index 9e643cf..36dd9f2 100644 --- a/src/Integrations/Divi/Pages/DiviSaver.php +++ b/src/Integrations/Divi/Pages/DiviSaver.php @@ -68,20 +68,19 @@ class DiviSaver $revisionId = null; try { - $result = $this->withoutKses(static function () use ($postId, $content, &$revisionId): int|WP_Error { + // Ruling S37: the restore point is captured inside the same kses window. + $result = $this->writeContent($postId, $content, static function () use ($postId, &$revisionId): void { $revisionId = RevisionPoint::capture($postId); - - return wp_update_post(wp_slash(['ID' => $postId, 'post_content' => $content]), true); }); } catch (Throwable $e) { $this->restore($postId, (string) $post->post_content, $revisionId, $e); } - if ($result instanceof WP_Error || $result === 0) { - $message = $result instanceof WP_Error ? $result->get_error_message() : 'returned 0'; + $refusal = self::refusal($result); + if ($refusal !== null) { throw WriteFailed::rolledBack( self::REASON, - detail: sprintf('wp_update_post(%d) refused: %s', $postId, $message), + detail: sprintf('wp_update_post(%d) refused: %s', $postId, $refusal), state: self::keeping($revisionId), postId: $postId, revisionId: $revisionId @@ -129,12 +128,9 @@ class DiviSaver */ public function saveNew(int $postId, string $content): array { - $result = $this->withoutKses( - static fn (): int|WP_Error => wp_update_post(wp_slash(['ID' => $postId, 'post_content' => $content]), true) - ); - if ($result instanceof WP_Error || $result === 0) { - $message = $result instanceof WP_Error ? $result->get_error_message() : 'returned 0'; - throw new RuntimeException(sprintf('wp_update_post(%d) refused the Divi layout: %s', $postId, $message)); + $refusal = self::refusal($this->writeContent($postId, $content)); + if ($refusal !== null) { + throw new RuntimeException(sprintf('wp_update_post(%d) refused the Divi layout: %s', $postId, $refusal)); } DiviFlags::set($postId); @@ -144,6 +140,34 @@ class DiviSaver return $stored instanceof WP_Post && (string) $stored->post_content !== $content ? [self::CHANGED_ON_SAVE] : []; } + /** + * The one guarded content write: wp_update_post() (wp_slash, status unchanged) of $content in + * the kses window. $before runs first inside the same window – save() captures the restore + * point there (ruling S37); nothing else ever runs in it (see the class docblock). + * + * @param (callable(): void)|null $before + */ + private function writeContent(int $postId, string $content, ?callable $before = null): int|WP_Error + { + return $this->withoutKses(static function () use ($postId, $content, $before): int|WP_Error { + if ($before !== null) { + $before(); + } + + return wp_update_post(wp_slash(['ID' => $postId, 'post_content' => $content]), true); + }); + } + + /** Why wp_update_post() refused ($result WP_Error or 0), null when it saved. */ + private static function refusal(int|WP_Error $result): ?string + { + if ($result instanceof WP_Error) { + return $result->get_error_message(); + } + + return $result === 0 ? 'returned 0' : null; + } + /** * Runs $write with kses' filters off for users without unfiltered_html (D7); restored in * finally, right when $write returns or throws. @@ -174,11 +198,7 @@ class DiviSaver $failures = Rollback::attempt(function () use ($postId, $before): void { PostGuard::assertNoMissedSchedule($postId, 'restoring would publish a missed schedule'); - $restored = $this->withoutKses( - static fn (): int|WP_Error - => wp_update_post(wp_slash(['ID' => $postId, 'post_content' => $before]), true) - ); - if ($restored instanceof WP_Error || $restored === 0) { + if (self::refusal($this->writeContent($postId, $before)) !== null) { throw new RuntimeException('previous content could not be written back'); } });