Skip to content

Rewrite & Republish corrupts HTML/newlines in block content (missing wp_slash() before wp_update_post()) #537

Description

@nikhilumaretiya

Using Rewrite & Republish to publish an edited copy back over the original post corrupts the original post's post_content whenever a block's saved attributes contain HTML tags or newlines that WordPress's block serializer has escaped (< → <, > → >, & → &, -- → --, and real newlines → \r\n).

After republishing, those escape sequences lose their backslash and get saved as literal text instead of being interpreted correctly. A block attribute that should contain:

"body":"

Some heading

\r\nSome paragraph text."
is saved instead as:

"body":"u003ch3u003eSome headingu003c/h3u003ernSome paragraph text."
which then renders on the front end as literal garbage text (u003ch3u003eSome heading...) instead of an actual

heading, with rn sitting in place of every line break.

Root cause: republish_post_elements() in src/post-republisher.php clones the copy's WP_Post object (which holds correctly-escaped, unslashed content straight from get_post()) and passes it directly to wp_update_post():

$post_to_be_rewritten = clone $post;
...
$rewritten_post_id = \wp_update_post( $post_to_be_rewritten );
wp_update_post()/wp_insert_post() always call wp_unslash() internally on the data passed in, on the assumption the caller has already slashed it (the same convention $_POST data follows). Since $post_to_be_rewritten->post_content is unslashed here, that internal wp_unslash() strips a real layer of backslash-escaping out of the content, corrupting it.

For comparison, the plugin's own basic Clone/New Draft duplication in admin-functions.php does this correctly:

$new_post_id = wp_insert_post( wp_slash( $new_post ), true );
The identical bug is also present in the revision migration step, migrate_revisions() in src/revisions-migrator.php:

foreach ( $copy_revisions as $revision ) {
$revision->post_parent = $original_post->ID;
$revision->post_name = "$original_post->ID-revision-v1";
\wp_update_post( $revision );
}
which corrupts the migrated revision history the same way.

To Reproduce
Step-by-step reproduction instructions
Create/publish a page containing a block whose saved attributes include HTML markup with a heading tag and at least one real line break (e.g. any block field with rich text — including ACF block fields — that WordPress's block serializer needs to escape with </>/\r\n).
Use Duplicate Post → Rewrite & Republish to create a copy, make any small edit, and click Republish.
Inspect the original post's post_content in the database, or view the page.
Expected results
The original post's content is preserved exactly as it was in the copy — HTML tags and line breaks intact.
Actual results
The HTML tags and line breaks in the affected field are replaced with literal text (u003ch3u003e..., rn) instead of real markup/line breaks, and this renders as broken text on the front end.

The identical bug is also present in the revision migration step, migrate_revisions() in src/revisions-migrator.php:

foreach ( $copy_revisions as $revision ) {
$revision->post_parent = $original_post->ID;
$revision->post_name = "$original_post->ID-revision-v1";
\wp_update_post( $revision );
}
which corrupts the migrated revision history the same way.

To Reproduce
Step-by-step reproduction instructions
Create/publish a page containing a block whose saved attributes include HTML markup with a heading tag and at least one real line break (e.g. any block field with rich text — including ACF block fields — that WordPress's block serializer needs to escape with </>/\r\n).
Use Duplicate Post → Rewrite & Republish to create a copy, make any small edit, and click Republish.
Inspect the original post's post_content in the database, or view the page.
Expected results
The original post's content is preserved exactly as it was in the copy — HTML tags and line breaks intact.
Actual results
The HTML tags and line breaks in the affected field are replaced with literal text (u003ch3u003e..., rn) instead of real markup/line breaks, and this renders as broken text on the front end.

Technical info
If relevant, which editor is affected (or editors):
done
Block Editor
not done
Gutenberg Editor
not done
Classic Editor
not done
Other:
Which browser is affected (or browsers):
not done
Chrome
not done
Firefox
not done
Safari
done
Other: Not browser-specific — this is a server-side PHP bug in the Rewrite & Republish save path, independent of browser.
Used versions
Device you are using: N/A (server-side bug)
Operating system: Linux (production server)
PHP version: 8.1.34
WordPress version: 7.1
WordPress Theme: Koobr Core Theme (custom Sage/Acorn-based theme)
Yoast Duplicate Post version: 4.7
Gutenberg plugin version: N/A (using WordPress core Block Editor, no separate Gutenberg plugin)
Classic Editor plugin version: N/A
Relevant plugins in case of a bug: Advanced Custom Fields PRO 6.8.9 (the corrupted content lives in ACF block field values)
Suggested fix
Wrap the post data in wp_slash() before calling wp_update_post() in both republish_post_elements() (src/post-republisher.php) and migrate_revisions() (src/revisions-migrator.php), matching what admin-functions.php's Clone/New Draft path already does correctly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions