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
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,12 @@ public function update_item_permissions_check( $request ) {
return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) );
}

// Allow a client to guard against overwriting changes to a post by sending the If-Unmodified-Since request header.
$if_unmodified_since = $request->get_header( 'If-Unmodified-Since' );
if ( $if_unmodified_since && mysql2date( 'U', $post->post_modified_gmt ) > strtotime( $if_unmodified_since ) ) {
return new WP_Error( 'rest_precondition_failed', __( 'Sorry, the post has been modified on the server since you started editing it. Conflict resolution is required.' ), array( 'status' => 412 ) );
}

return true;
}

Expand Down
66 changes: 66 additions & 0 deletions tests/phpunit/tests/rest-api/rest-posts-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -3244,6 +3244,72 @@ public function test_update_item_with_same_template_that_no_longer_exists() {
$this->assertEquals( 'post-my-invalid-template.php', $data['template'] );
}

/**
* Format MySQL date string as RFC.
*
* @param string $date Date string (YYYY-MM-DD HH:MM:SS).
* @param int $seconds_offset Number of seconds to add/subtract from the date.
* @return string Date formatted as RFC.
*/
protected function format_gmt_date_as_rfc( $date_str, $seconds_offset = 0 ) {
$timestamp = mysql2date( 'U', $date_str );
$timestamp += $seconds_offset;
return str_replace( '+0000', 'GMT', gmdate( 'r', $timestamp ) );
}

/**
* Test If-Unmodified-Since request header when updating posts.
*
* @covers WP_REST_Posts_Controller::update_item_permissions_check()
* @ticket 47676
*/
public function test_update_item_with_if_unmodified_since_precondition() {
wp_set_current_user( self::$editor_id );

// Test updating post with If-Unmodified-Since header matching the post_modified_gmt.
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
$request->add_header( 'content-type', 'application/json' );
$title1 = 'Same as last modified';
$request->set_body( wp_json_encode( $this->set_post_data( array( 'title' => $title1 ) ) ) );
$request->set_header(
'If-Unmodified-Since',
$this->format_gmt_date_as_rfc( get_post( self::$post_id )->post_modified_gmt )
);
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$new_data = $response->get_data();
$this->assertSame( $title1, $new_data['title']['raw'] );
$this->assertSame( $title1, get_post( self::$post_id )->post_title );

// Test updating post with If-Unmodified-Since header being in the future of post_modified_gmt.
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
$request->add_header( 'content-type', 'application/json' );
$title2 = '1 second after last modified';
$request->set_body( wp_json_encode( $this->set_post_data( array( 'title' => $title2 ) ) ) );
$request->set_header(
'If-Unmodified-Since',
$this->format_gmt_date_as_rfc( get_post( self::$post_id )->post_modified_gmt, 1 )
);
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$new_data = $response->get_data();
$this->assertSame( $title2, $new_data['title']['raw'] );
$this->assertSame( $title2, get_post( self::$post_id )->post_title );

// Test updating post with If-Unmodified-Since header being in the past of post_modified_gmt, thus failing the precondition.
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
$request->add_header( 'content-type', 'application/json' );
$title3 = '1 second before last modified';
$request->set_body( wp_json_encode( $this->set_post_data( array( 'title' => $title3 ) ) ) );
$request->set_header(
'If-Unmodified-Since',
$this->format_gmt_date_as_rfc( get_post( self::$post_id )->post_modified_gmt, -1 )
);
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 412, $response->get_status() );
$this->assertSame( $title2, get_post( self::$post_id )->post_title, 'Expected third title to not update due to failed precondition.' );
}

public function verify_post_roundtrip( $input = array(), $expected_output = array() ) {
// Create the post
$request = new WP_REST_Request( 'POST', '/wp/v2/posts' );
Expand Down