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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 18:15:28 +02:00
co-authored by Claude Opus 5.5
parent 3eeb7eee90
commit 6cf0a565e6
+37 -17
View File
@@ -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');
}
});