diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index c39135316978f..05ab952d48cf7 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -315,6 +315,11 @@ function edit_post( $post_data = null ) { } } + // Infer the password visibility when the caller did not send one of its own. + if ( ! isset( $post_data['visibility'] ) && ! empty( $post_data['post_password'] ) ) { + $post_data['visibility'] = 'password'; + } + if ( isset( $post_data['visibility'] ) ) { switch ( $post_data['visibility'] ) { case 'public': diff --git a/tests/phpunit/tests/admin/includesPost.php b/tests/phpunit/tests/admin/includesPost.php index 92c323041e95e..1db198b0589a0 100644 --- a/tests/phpunit/tests/admin/includesPost.php +++ b/tests/phpunit/tests/admin/includesPost.php @@ -1371,4 +1371,201 @@ 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.' + ); + } + + /** + * Ensures that an explicit `public` visibility clears any existing post password. + * + * @ticket 64810 + * + * @covers ::edit_post + */ + public function test_edit_post_clears_the_password_when_visibility_is_public() { + wp_set_current_user( self::$admin_id ); + + $post_id = self::factory()->post->create( + array( + 'post_status' => 'publish', + 'post_author' => self::$admin_id, + 'post_password' => 'secret', + ) + ); + + edit_post( + array( + 'post_ID' => $post_id, + 'post_title' => 'Now public', + 'post_status' => 'publish', + 'post_password' => '', + 'visibility' => 'public', + ) + ); + + $this->assertSame( '', get_post( $post_id )->post_password ); + } + + /** + * Ensures that an explicit `password` visibility unsticks the post. + * + * The companion test above covers the same branch reached through an + * inferred visibility; this one covers a caller that sends it outright. + * + * @ticket 64810 + * + * @covers ::edit_post + */ + public function test_edit_post_unsticks_the_post_when_visibility_is_password() { + 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 ); + + edit_post( + array( + 'post_ID' => $post_id, + 'post_title' => 'Protected', + 'post_status' => 'publish', + 'post_password' => 'secret', + 'sticky' => 'sticky', + 'visibility' => 'password', + ) + ); + + $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.' ); + } + + /** + * Ensures that an explicit `private` visibility privatises the post, drops any + * password and unsticks it. + * + * The password is sent alongside on purpose. Quick Edit posts + * `visibility => 'private'` whenever "Private" is ticked, so this pair is + * reachable from the UI, and inferring `password` from the password instead + * would leave the post public with its password intact. + * + * @ticket 64810 + * + * @covers ::edit_post + */ + public function test_edit_post_privatises_and_unsticks_the_post_when_visibility_is_private() { + 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 ); + + edit_post( + array( + 'post_ID' => $post_id, + 'post_title' => 'Private', + 'post_status' => 'publish', + 'post_password' => 'secret', + 'sticky' => 'sticky', + 'visibility' => 'private', + ) + ); + + $post = get_post( $post_id ); + + $this->assertSame( 'private', $post->post_status, 'The post was not made private.' ); + $this->assertSame( '', $post->post_password, 'A private post kept a password.' ); + $this->assertFalse( is_sticky( $post_id ), 'A private post was left sticky.' ); + } + + /** + * Ensures that a visibility sent by the caller is never overridden by an inferred one. + * + * The inference only fills a gap. A caller that sends `public` alongside a + * non-empty password still reaches `case 'public'` and still has the password + * dropped, exactly as before this change. + * + * @ticket 64810 + * + * @covers ::edit_post + */ + public function test_edit_post_keeps_an_explicit_visibility_over_an_inferred_one() { + wp_set_current_user( self::$admin_id ); + + $post_id = self::factory()->post->create( + array( + 'post_status' => 'publish', + 'post_author' => self::$admin_id, + ) + ); + + edit_post( + array( + 'post_ID' => $post_id, + 'post_title' => 'Contradictory', + 'post_status' => 'publish', + 'post_password' => 'secret', + 'visibility' => 'public', + ) + ); + + $this->assertSame( + '', + get_post( $post_id )->post_password, + 'An explicit public visibility was overridden by the inferred one.' + ); + } }