Skip to content

Force visibility to password when editing a post in quick edit. - #11180

Open
Hug0-Drelon wants to merge 3 commits into
WordPress:trunkfrom
Hug0-Drelon:fix-64810
Open

Hug0-Drelon wants to merge 3 commits into
WordPress:trunkfrom
Hug0-Drelon:fix-64810

Conversation

@Hug0-Drelon

@Hug0-Drelon Hug0-Drelon commented Mar 5, 2026 •

Copy link
Copy Markdown

Visibility is never set for password when using quick edit, thus edit_post() cannot unset sticky status in https://github.com/WordPress/wordpress-develop/blob/6.9.1/src/wp-admin/includes/post.php#L308-L322.

Looking at it, I thought that wp_ajax_inline_save() would handle that like it does for private status. But it might be dead code around https://github.com/WordPress/wordpress-develop/blob/6.9.1/src/wp-admin/includes/ajax-actions.php#L2117-L2122.
Removing it doesn't affect the feature (remove sticky status when a post is private).

Trac ticket: https://core.trac.wordpress.org/ticket/64810


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions

github-actions Bot commented Mar 5, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props hugod, wildworks, abcd95, motylanogha.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

github-actions Bot commented Mar 5, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copilot AI review requested due to automatic review settings July 27, 2026 12:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures Quick Edit requests correctly treat posts with a post_password as “password-protected” visibility so that edit_post() applies the existing visibility handling (notably unsetting sticky) for password-protected posts.

Changes:

  • In edit_post(), infer visibility = 'password' when post_password is present in the incoming post data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +304 to +307
// Password visibility.
if ( ! empty( $post_data['post_password'] ) ) {
$post_data['visibility'] = 'password';
}
Comment on lines +304 to +307
// Password visibility.
if ( ! empty( $post_data['post_password'] ) ) {
$post_data['visibility'] = 'password';
}
@himanshupathak95

Copy link
Copy Markdown

Hi @Hug0-Drelon, do you have the bandwidth to address the reviews and add some test coverage for this PR?

@wppoland

wppoland commented Jul 29, 2026 •

Copy link
Copy Markdown

The ticket carries needs-unit-tests, added by @wildworks in comment:6 ("a PR review is needed, but it appears to be missing unit tests at a minimum"), and this PR is fix-only, so here's a regression test for it.

It reproduces Quick Edit's actual payload: sticky is posted, visibility is not. On trunk the case 'password' branch in edit_post() is never reached, so the unset( $post_data['sticky'] ) never happens and the post stays sticky with a password set. With this PR the inferred visibility reaches that branch and the post is unstuck.

	/**
	 * Ensures that giving a sticky post a password through edit_post() unsticks it,
	 * even when the caller never sends an explicit `visibility` value.
	 *
	 * The block editor sends `visibility`, so `edit_post()` reaches the
	 * `case 'password'` branch and drops the `sticky` flag. Quick Edit never sends
	 * it, so a post could end up both sticky and password protected, a combination
	 * the editor itself forbids.
	 *
	 * @ticket 64810
	 *
	 * @covers ::edit_post
	 */
	public function test_edit_post_unsticks_a_post_when_a_password_is_set_without_visibility() {
		wp_set_current_user( self::$admin_id );

		$post_id = self::factory()->post->create(
			array(
				'post_status' => 'publish',
				'post_author' => self::$admin_id,
			)
		);

		stick_post( $post_id );
		$this->assertTrue( is_sticky( $post_id ), 'The post was not sticky to begin with: check test setup.' );

		// Mirrors Quick Edit, which posts `sticky` but no `visibility`.
		edit_post(
			array(
				'post_ID'       => $post_id,
				'post_title'    => 'Protected and sticky',
				'post_status'   => 'publish',
				'post_password' => 'secret',
				'sticky'        => 'sticky',
			)
		);

		$this->assertSame(
			'secret',
			get_post( $post_id )->post_password,
			'The password was not saved: check test setup.'
		);
		$this->assertFalse(
			is_sticky( $post_id ),
			'A password protected post was left sticky when no explicit visibility was sent.'
		);
	}

It goes in tests/phpunit/tests/admin/includesPost.php, which had no coverage of the visibility/sticky/password interaction at all.

Verified against a local build:

trunk:            Failures: 1  ("A password protected post was left sticky when no explicit visibility was sent.")
trunk + this PR:  OK (1 test, 3 assertions)

Whole file still green with the patch applied: 52 tests, 118 assertions, 0 failures (6 warnings, all pre-existing PHPUnit 10 deprecation notices in test_post_exists_*).

Also attached to the ticket as 64810-tests.diff. Take it into this branch if it helps, no attribution needed.

@wppoland

Copy link
Copy Markdown

Rather than leave the test as a paste, I've opened it as a commit against this branch: Hug0-Drelon#1.

Merging it there brings the test into this PR so the fix and its coverage land as one changeset, and nothing else on your side changes. The fix stays yours; I only wrote the test.

@wppoland

Copy link
Copy Markdown

@Hug0-Drelon this is the unit test @wildworks asked for in comment:6 when they marked #64810 needs-unit-tests. Bringing it here rather than leaving it on a fork branch, so it is visible to whoever picks this up.

It reproduces what Quick Edit actually posts, sticky with no visibility. On trunk edit_post() never reaches case 'password', so unset( $post_data['sticky'] ) never runs and the post stays sticky with a password set. With your fix the inferred visibility reaches that branch and the post is unstuck. tests/phpunit/tests/admin/includesPost.php had no coverage of the visibility and sticky interaction at all before this.

Ready to cherry-pick onto this branch as-is:

diff --git a/tests/phpunit/tests/admin/includesPost.php b/tests/phpunit/tests/admin/includesPost.php
index d9d39d8da727d..dac5c8bc86dfa 100644
--- a/tests/phpunit/tests/admin/includesPost.php
+++ b/tests/phpunit/tests/admin/includesPost.php
@@ -1371,4 +1371,52 @@ public function test_user_get_refreshed_metabox_nonce() {
 		$this->assertNotEmpty( $response['wp-refresh-metabox-loader-nonces']['replace']['_wpnonce'] );
 		$this->assertNotEmpty( $response['wp-refresh-metabox-loader-nonces']['replace']['metabox_loader_nonce'] );
 	}
+
+	/**
+	 * Ensures that giving a sticky post a password through edit_post() unsticks it,
+	 * even when the caller never sends an explicit `visibility` value.
+	 *
+	 * The block editor sends `visibility`, so `edit_post()` reaches the
+	 * `case 'password'` branch and drops the `sticky` flag. Quick Edit never sends
+	 * it, so a post could end up both sticky and password protected, a combination
+	 * the editor itself forbids.
+	 *
+	 * @ticket 64810
+	 *
+	 * @covers ::edit_post
+	 */
+	public function test_edit_post_unsticks_a_post_when_a_password_is_set_without_visibility() {
+		wp_set_current_user( self::$admin_id );
+
+		$post_id = self::factory()->post->create(
+			array(
+				'post_status' => 'publish',
+				'post_author' => self::$admin_id,
+			)
+		);
+
+		stick_post( $post_id );
+		$this->assertTrue( is_sticky( $post_id ), 'The post was not sticky to begin with: check test setup.' );
+
+		// Mirrors Quick Edit, which posts `sticky` but no `visibility`.
+		edit_post(
+			array(
+				'post_ID'       => $post_id,
+				'post_title'    => 'Protected and sticky',
+				'post_status'   => 'publish',
+				'post_password' => 'secret',
+				'sticky'        => 'sticky',
+			)
+		);
+
+		$this->assertSame(
+			'secret',
+			get_post( $post_id )->post_password,
+			'The password was not saved: check test setup.'
+		);
+		$this->assertFalse(
+			is_sticky( $post_id ),
+			'A password protected post was left sticky when no explicit visibility was sent.'
+		);
+	}
 }

It is yours to take, and it belongs in this changeset rather than a separate one, since it only passes with the fix in place. Ticket credit sorts itself out through the props bot either way.

wppoland pushed a commit to wppoland/wordpress-develop that referenced this pull request Sep 13, 2026
Quick Edit posts `sticky` but never posts `visibility`, so `edit_post()`
never reached the `case 'password'` branch that drops the sticky flag. A
post could end up both sticky and password protected, a combination the
block editor itself forbids.

Set `visibility` to `password` whenever a post password is present, so
the existing branch runs whatever the caller sent.

Original patch by Hug0-Drelon in PR WordPress#11180.

See #64810.
wppoland pushed a commit to wppoland/wordpress-develop that referenced this pull request Sep 25, 2026
Quick Edit posts `sticky` but never posts `visibility`, so `edit_post()`
never reached the `case 'password'` branch that drops the sticky flag. A
post could end up both sticky and password protected, a combination the
block editor itself forbids.

Set `visibility` to `password` whenever a post password is present, so
the existing branch runs whatever the caller sent.

Original patch by Hug0-Drelon in PR WordPress#11180.

See #64810.
@wppoland

Copy link
Copy Markdown

I completely agree with @wildworks' assessment in https://core.trac.wordpress.org/ticket/64810#comment:29. Allowing a post to be both password protected and sticky across all code paths is the right architectural direction.

Historically, this restriction seems to have originated merely as an artifact of the Classic Editor's Publish metabox UI (where the sticky checkbox was nested under the "Public" radio button), which was then ported into edit_post() and later codified as a 400 error in WP_REST_Posts_Controller.

From a product and technical standpoint:

  1. Front-end and queries handle this out-of-the-box: A password-protected post keeps its publish status, WP_Query pins it to the top as expected, and post_password_required() protects the content behind the password prompt.
  2. Resolves the Block Editor UX friction: The block editor UI exposes sticky and visibility as independent controls. Users can toggle both, only to be rejected with a 400 rest_invalid_field error upon saving.
  3. Eliminates core inconsistencies: Quick Edit, Bulk Edit, and wp_insert_post() already allow this combination.

I have implemented and tested this unified approach on branch trac/64810-allow-sticky-password:

  • Removed the sticky/password rejection in WP_REST_Posts_Controller.
  • Removed unset( $post_data[sticky] ) from case password in edit_post() and write_post().
  • Relaxed the restriction in wp_xmlrpc_server::_toggle_sticky() so only private posts cannot be sticky.
  • Updated the Classic Editor publish box and JS to allow keeping the sticky state when password visibility is selected.
  • Inverted the existing REST API unit test (test_create_post_with_password_and_sticky_succeeds, plus update coverage) and added test coverage across edit_post() and XML-RPC (test_post_sticky_with_password). All tests pass cleanly.

Ready to open this as an alternative PR or adapt it here if the consensus is to move forward with allowing sticky password-protected posts.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants