Skip to content
5 changes: 5 additions & 0 deletions src/wp-admin/includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
197 changes: 197 additions & 0 deletions tests/phpunit/tests/admin/includesPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -1373,4 +1373,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.'
);
}
}
Loading