From 3439f7a80a9e9be37182ec9bbd4cff17fdbf82ab Mon Sep 17 00:00:00 2001 From: liaisontw Date: Tue, 7 Apr 2026 11:56:45 +0800 Subject: [PATCH 1/2] REST API: Restore global post state in WP_REST_Posts_Controller.Ensures that the global $post object is restored to its original state after calling setup_postdata() in prepare_item_for_response(). This fixes a regression where the global context could be 'polluted' during REST API requests.Fixes #43502. --- .../class-wp-rest-posts-controller.php | 7 +++ .../tests/rest-api/rest-posts-controller.php | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 0ab54a3a0d384..5c063629ef019 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1886,12 +1886,16 @@ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post = $item; + $previous_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; $GLOBALS['post'] = $post; setup_postdata( $post ); // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { + $GLOBALS['post'] = $previous_post; + wp_reset_postdata(); + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response( array() ), $post, $request ); } @@ -2207,6 +2211,9 @@ public function prepare_item_for_response( $item, $request ) { * @param WP_Post $post Post object. * @param WP_REST_Request $request Request object. */ + $GLOBALS['post'] = $previous_post; + wp_reset_postdata(); + return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); } diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 212ddde70dd83..64c91e354ea34 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -2805,6 +2805,50 @@ function ( $classes ) { $this->assertTrue( array_is_list( $data['class_list'] ), 'Expected class_list to be a list.' ); } + /** + * @ticket 43502 + * + * @covers WP_REST_Posts_Controller::prepare_item_for_response + */ + public function test_prepare_item_for_response_restores_global_post() { + $post_1 = self::factory()->post->create_and_get(); + $post_2 = self::factory()->post->create_and_get(); + + // Set up a known global $post state. + $GLOBALS['post'] = $post_1; + setup_postdata( $post_1 ); + + $endpoint = new WP_REST_Posts_Controller( 'post' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_2->ID ); + $endpoint->prepare_item_for_response( $post_2, $request ); + + $this->assertSame( + $post_1->ID, + $GLOBALS['post']->ID, + 'Global $post should be restored after prepare_item_for_response().' + ); + } + + /** + * @ticket 43502 + * + * @covers WP_REST_Posts_Controller::prepare_item_for_response + */ + public function test_prepare_item_for_response_restores_null_global_post() { + unset( $GLOBALS['post'] ); + + $post = self::factory()->post->create_and_get(); + + $endpoint = new WP_REST_Posts_Controller( 'post' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post->ID ); + $endpoint->prepare_item_for_response( $post, $request ); + + $this->assertNull( + $GLOBALS['post'], + 'Global $post should be restored to null when it was not set before prepare_item_for_response().' + ); + } + public function test_create_item() { wp_set_current_user( self::$editor_id ); From 09101704ed10e14f4adb3d5aa0f4784c18da4ad0 Mon Sep 17 00:00:00 2001 From: liaisontw Date: Tue, 7 Apr 2026 17:45:22 +0800 Subject: [PATCH 2/2] CI: Rerun due to transient database connection error