diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php index 5501c190c13ee..415333661f935 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php @@ -387,6 +387,7 @@ public function get_item_permissions_check( $request ) { * Retrieves one revision from the collection. * * @since 4.7.0 + * @since 6.5.0 Added a condition to check that parent id matches revision parent id. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. @@ -402,6 +403,15 @@ public function get_item( $request ) { return $revision; } + if ( (int) $parent->ID !== (int) $revision->post_parent ) { + return new WP_Error( + 'rest_revision_parent_id_mismatch', + /* translators: %d: A post id. */ + sprintf( __( 'The revision does not belong to the specified parent with id of "%d"' ), $parent->ID ), + array( 'status' => 404 ) + ); + } + $response = $this->prepare_item_for_response( $revision, $request ); return rest_ensure_response( $response ); } diff --git a/tests/phpunit/tests/rest-api/rest-revisions-controller.php b/tests/phpunit/tests/rest-api/rest-revisions-controller.php index 74cd040d85f68..08138055062f3 100644 --- a/tests/phpunit/tests/rest-api/rest-revisions-controller.php +++ b/tests/phpunit/tests/rest-api/rest-revisions-controller.php @@ -9,6 +9,7 @@ */ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase { protected static $post_id; + protected static $post_id_2; protected static $page_id; protected static $editor_id; @@ -22,10 +23,12 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase private $revision_id2; private $revision_3; private $revision_id3; + private $revision_2_1_id; public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { - self::$post_id = $factory->post->create(); - self::$page_id = $factory->post->create( array( 'post_type' => 'page' ) ); + self::$post_id = $factory->post->create(); + self::$post_id_2 = $factory->post->create(); + self::$page_id = $factory->post->create( array( 'post_type' => 'page' ) ); self::$editor_id = $factory->user->create( array( @@ -57,12 +60,25 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'ID' => self::$post_id, ) ); + wp_update_post( + array( + 'post_content' => 'A second post.', + 'ID' => self::$post_id_2, + ) + ); + wp_update_post( + array( + 'post_content' => 'A second post. How prolific.', + 'ID' => self::$post_id_2, + ) + ); wp_set_current_user( 0 ); } public static function wpTearDownAfterClass() { // Also deletes revisions. wp_delete_post( self::$post_id, true ); + wp_delete_post( self::$post_id_2, true ); wp_delete_post( self::$page_id, true ); self::delete_user( self::$editor_id ); @@ -72,6 +88,7 @@ public static function wpTearDownAfterClass() { public function set_up() { parent::set_up(); + // Set first post revision vars. $revisions = wp_get_post_revisions( self::$post_id ); $this->total_revisions = count( $revisions ); $this->revisions = $revisions; @@ -81,6 +98,11 @@ public function set_up() { $this->revision_id2 = $this->revision_2->ID; $this->revision_3 = array_pop( $revisions ); $this->revision_id3 = $this->revision_3->ID; + + // Set second post revision vars. + $revisions = wp_get_post_revisions( self::$post_id_2 ); + $post_2_revision = array_pop( $revisions ); + $this->revision_2_1_id = $post_2_revision->ID; } public function _filter_map_meta_cap_remove_no_allow_revisions( $caps, $cap, $user_id, $args ) { @@ -234,6 +256,31 @@ public function test_get_item_invalid_parent_post_type() { $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); } + /** + * @ticket 59875 + */ + public function test_get_item_valid_parent_id() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( self::$post_id, $data['parent'], "The returned revision's id should match the parent id." ); + $this->check_get_revision_response( $response, $this->revision_1 ); + } + + /** + * @ticket 59875 + */ + public function test_get_item_invalid_parent_id() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_2_1_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_revision_parent_id_mismatch', $response, 404 ); + + $expected_message = 'The revision does not belong to the specified parent with id of "' . self::$post_id . '"'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + public function test_delete_item() { wp_set_current_user( self::$editor_id ); $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 ); diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php index 91370b21c726b..62d36abc4a110 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -19,6 +19,11 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll */ const TEMPLATE_NAME = 'my_template'; + /** + * @var string + */ + const TEMPLATE_NAME_2 = 'my_template_2'; + /** * @var string */ @@ -51,6 +56,15 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll */ private static $template_post; + /** + * Template post. + * + * @since 6.5.0 + * + * @var WP_Post + */ + private static $template_post_2; + /** * @var array */ @@ -123,6 +137,26 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'post_content' => 'Content revision #5', ) ); + + // Create a new template post to test the get_item method. + self::$template_post_2 = $factory->post->create_and_get( + array( + 'post_type' => self::PARENT_POST_TYPE, + 'post_name' => self::TEMPLATE_NAME_2, + 'post_title' => 'My Template 2', + 'post_content' => 'Content 2', + 'post_excerpt' => 'Description of my template 2', + 'tax_input' => array( + 'wp_theme' => array( + self::TEST_THEME, + ), + ), + ) + ); + wp_set_post_terms( self::$template_post_2->ID, self::TEST_THEME, 'wp_theme' ); + + var_dump( self::$template_post->ID ); + var_dump( self::$template_post_2->ID ); } /** @@ -336,6 +370,23 @@ public function test_get_item_not_found() { $this->assertErrorResponse( 'rest_post_invalid_parent', $response, WP_Http::NOT_FOUND ); } + /** + * @ticket 59875 + */ + public function test_get_item_invalid_parent_id() { + wp_set_current_user( self::$admin_id ); + $revisions = wp_get_post_revisions( self::$template_post, array( 'fields' => 'ids' ) ); + $revision_id = array_shift( $revisions ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME_2 . '/revisions/' . $revision_id ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_revision_parent_id_mismatch', $response, 404 ); + + $expected_message = 'The revision does not belong to the specified parent with id of "' . self::$template_post_2->ID . '"'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + /** * @covers WP_REST_Template_Revisions_Controller::prepare_item_for_response * @ticket 56922