Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/js/_enqueues/wp/autosave.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ window.autosave = function() {
data.auto_draft = '1';
}

if ( $( '#active_post_lock' ).length ) {
data.active_post_lock = $( '#active_post_lock' ).val() || '';
}

return data;
}

Expand Down
3 changes: 1 addition & 2 deletions src/wp-admin/includes/misc.php
Original file line number Diff line number Diff line change
Expand Up @@ -1201,9 +1201,8 @@ function wp_refresh_post_lock( $response, $data, $screen_id ) {
return $response;
}

$user_id = wp_check_post_lock( $post_id );
$user_id = wp_check_post_lock( $post_id, $received['lock'] ?? '' );
$user = get_userdata( $user_id );

if ( $user ) {
$error = array(
'name' => $user->display_name,
Expand Down
35 changes: 24 additions & 11 deletions src/wp-admin/includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -1708,11 +1708,14 @@ function _wp_post_thumbnail_html( $thumbnail_id = null, $post = null ) {
*
* @since 2.5.0
*
* @param int|WP_Post $post ID or object of the post to check for editing.
* @param int|WP_Post $post ID or object of the post to check for editing.
* @param string $active_lock Optional. The active lock held by the current editing session.
* When provided, the current user's stale lock is treated as locked.
* @return int|false ID of the user with lock. False if the post does not exist, post is not locked,
* the user with lock does not exist, or the post is locked by current user.
* the user with lock does not exist, or the post is locked by the current session.
*/
function wp_check_post_lock( $post ) {
function wp_check_post_lock( $post, $active_lock = '' ) {

$post = get_post( $post );

if ( ! $post ) {
Expand All @@ -1725,9 +1728,10 @@ function wp_check_post_lock( $post ) {
return false;
}

$lock = explode( ':', $lock );
$time = $lock[0];
$user = isset( $lock[1] ) ? (int) $lock[1] : (int) get_post_meta( $post->ID, '_edit_last', true );
$lock_value = $lock;
$lock = explode( ':', $lock );
$time = $lock[0];
$user = isset( $lock[1] ) ? (int) $lock[1] : (int) get_post_meta( $post->ID, '_edit_last', true );

if ( ! get_userdata( $user ) ) {
return false;
Expand All @@ -1736,13 +1740,18 @@ function wp_check_post_lock( $post ) {
/** This filter is documented in wp-admin/includes/ajax-actions.php */
$time_window = apply_filters( 'wp_check_post_lock_window', 150 );

if ( $time && $time > time() - $time_window && get_current_user_id() !== $user ) {
return $user;
if ( $time && $time > time() - $time_window ) {
if ( get_current_user_id() !== $user ) {
return $user;
}

if ( $active_lock && $active_lock !== $lock_value ) {
return $user;
}
}

return false;
}

/**
* Marks the post as currently being edited by the current user.
*
Expand Down Expand Up @@ -2093,7 +2102,9 @@ function post_preview() {

$is_autosave = false;

if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() === (int) $post->post_author
$active_post_lock = $post_data['active_post_lock'] ?? '';

if ( ! wp_check_post_lock( $post->ID, $active_post_lock ) && get_current_user_id() === (int) $post->post_author
&& ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status )
) {
$saved_post_id = edit_post();
Expand Down Expand Up @@ -2168,7 +2179,9 @@ function wp_autosave( $post_data ) {
$post_data['post_category'] = explode( ',', $post_data['catslist'] );
}

if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() === (int) $post->post_author
$active_post_lock = $post_data['active_post_lock'] ?? '';

if ( ! wp_check_post_lock( $post->ID, $active_post_lock ) && get_current_user_id() === (int) $post->post_author
&& ( 'auto-draft' === $post->post_status || 'draft' === $post->post_status )
) {
// Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked.
Expand Down
26 changes: 26 additions & 0 deletions tests/phpunit/tests/admin/includesPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public function test__wp_translate_postdata_cap_checks_editor() {
* @ticket 25272
*/
public function test_edit_post_auto_draft() {

wp_set_current_user( self::$editor_id );
$post = self::factory()->post->create_and_get( array( 'post_status' => 'auto-draft' ) );
$this->assertSame( 'auto-draft', $post->post_status );
Expand All @@ -145,6 +146,31 @@ public function test_edit_post_auto_draft() {
$this->assertSame( 'draft', get_post( $post->ID )->post_status );
}

/**
* @ticket 28288
*/
public function test_wp_check_post_lock_allows_the_current_session() {
wp_set_current_user( self::$editor_id );

$lock = implode( ':', wp_set_post_lock( self::$post_id ) );

$this->assertFalse( wp_check_post_lock( self::$post_id, $lock ) );
}

/**
* @ticket 28288
*/
public function test_wp_check_post_lock_detects_a_stale_same_user_lock() {
wp_set_current_user( self::$editor_id );

$stale_lock = ( time() - 1 ) . ':' . self::$editor_id;
$fresh_lock = time() . ':' . self::$editor_id;

update_post_meta( self::$post_id, '_edit_lock', $fresh_lock );

$this->assertNotSame( $stale_lock, $fresh_lock );
$this->assertSame( self::$editor_id, wp_check_post_lock( self::$post_id, $stale_lock ) );
}
/**
* @ticket 30615
*/
Expand Down
48 changes: 48 additions & 0 deletions tests/phpunit/tests/ajax/wpAjaxHeartbeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public function test_autosave_post() {
* Tests autosaving a locked post.
*/
public function test_autosave_locked_post() {

// Lock the post to another user.
wp_set_current_user( self::$editor_id );
wp_set_post_lock( self::$post_id );
Expand Down Expand Up @@ -133,6 +134,53 @@ public function test_autosave_locked_post() {
$this->assertStringContainsString( $md5, $autosave->post_content );
}

/**
* Tests autosaving a post from a stale lock held by the same user.
*
* @ticket 28288
*/
public function test_autosave_stale_same_user_lock_creates_an_autosave() {
wp_set_current_user( self::$admin_id );

$stale_lock = ( time() - 1 ) . ':' . self::$admin_id;
$fresh_lock = time() . ':' . self::$admin_id;
$md5 = md5( uniqid() );
$_POST = array(
'action' => 'heartbeat',
'_nonce' => wp_create_nonce( 'heartbeat-nonce' ),
'data' => array(
'wp_autosave' => array(
'post_id' => self::$post_id,
'_wpnonce' => wp_create_nonce( 'update-post_' . self::$post_id ),
'post_content' => self::$post->post_content . PHP_EOL . $md5,
'post_type' => 'post',
'active_post_lock' => $stale_lock,
),
),
);

update_post_meta( self::$post_id, '_edit_lock', $fresh_lock );

$this->assertNotSame( $stale_lock, $fresh_lock );

try {
$this->_handleAjax( 'heartbeat' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}

$response = json_decode( $this->_last_response, true );

$this->assertNotEmpty( $response['wp_autosave'] );
$this->assertTrue( $response['wp_autosave']['success'] );

$post = get_post( self::$post_id );
$this->assertStringNotContainsString( $md5, $post->post_content );

$autosave = wp_get_post_autosave( self::$post_id, get_current_user_id() );
$this->assertNotEmpty( $autosave );
$this->assertStringContainsString( $md5, $autosave->post_content );
}
/**
* Tests with an invalid nonce.
*/
Expand Down
Loading