From 9e57a608985409788a6dabe5a8a11585f8524bd0 Mon Sep 17 00:00:00 2001 From: Dhrupo Date: Tue, 10 Nov 2026 22:54:32 +0600 Subject: [PATCH] REST API: Skip rendering `title` and `content` when the schema omits their `rendered` properties. WP_REST_Blocks_Controller removes `title.rendered` and `content.rendered` from the synced pattern schema, because a block only renders in the context of a post or a page. WP_REST_Posts_Controller::prepare_item_for_response() rendered both anyway, running `the_title` and `the_content` on every pattern REST response, only for filter_response_by_context() to discard the results. With hooks on `the_content`, that is wasted work on a hot editor path. Only run the rendering passes when the schema actually contains the `rendered` property, so schemas that keep the property are unaffected. Fixes #66644. --- .../class-wp-rest-posts-controller.php | 13 ++++- .../tests/rest-api/rest-blocks-controller.php | 49 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) 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 0c24925ff9245..3dded105df7bf 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 @@ -1896,6 +1896,15 @@ public function prepare_item_for_response( $item, $request ) { } $fields = $this->get_fields_for_response( $request ); + $schema = $this->get_item_schema(); + + /* + * Some controllers, such as the synced patterns controller, remove the + * `rendered` properties from the schema, since a block only renders in the + * context of a post or page. Skip the rendering passes for those fields. + */ + $can_render_title = isset( $schema['properties']['title']['properties']['rendered'] ); + $can_render_content = isset( $schema['properties']['content']['properties']['rendered'] ); // Base fields for every post. $data = array(); @@ -1975,7 +1984,7 @@ public function prepare_item_for_response( $item, $request ) { if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $post->post_title; } - if ( rest_is_field_included( 'title.rendered', $fields ) ) { + if ( $can_render_title && rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); @@ -2001,7 +2010,7 @@ public function prepare_item_for_response( $item, $request ) { if ( rest_is_field_included( 'content.raw', $fields ) ) { $data['content']['raw'] = $post->post_content; } - if ( rest_is_field_included( 'content.rendered', $fields ) ) { + if ( $can_render_content && rest_is_field_included( 'content.rendered', $fields ) ) { /** This filter is documented in wp-includes/post-template.php */ $data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ); } diff --git a/tests/phpunit/tests/rest-api/rest-blocks-controller.php b/tests/phpunit/tests/rest-api/rest-blocks-controller.php index 30b8b0d88c68e..ba10c4c65fe84 100644 --- a/tests/phpunit/tests/rest-api/rest-blocks-controller.php +++ b/tests/phpunit/tests/rest-api/rest-blocks-controller.php @@ -221,6 +221,55 @@ public function test_content() { ); } + /** + * Check that the rendering callbacks for the stripped `rendered` fields do not run. + * + * `title.rendered` and `content.rendered` are removed from the pattern schema + * and response, so the `the_title` and `the_content` passes should not run at all. + * + * @ticket 66644 + */ + public function test_rendered_fields_are_not_rendered() { + wp_set_current_user( self::$user_ids['author'] ); + + $post_id = wp_insert_post( + array( + 'post_type' => 'wp_block', + 'post_status' => 'publish', + 'post_title' => 'Pattern with excerpt', + 'post_content' => '

Hello!

', + 'post_excerpt' => 'Pattern excerpt', + ) + ); + + $the_content_calls = 0; + $the_title_calls = 0; + + $count_the_content = static function ( $content ) use ( &$the_content_calls ) { + $the_content_calls++; + return $content; + }; + $count_the_title = static function ( $title ) use ( &$the_title_calls ) { + $the_title_calls++; + return $title; + }; + + add_filter( 'the_content', $count_the_content ); + add_filter( 'the_title', $count_the_title ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . $post_id ); + $response = rest_get_server()->dispatch( $request ); + + remove_filter( 'the_content', $count_the_content ); + remove_filter( 'the_title', $count_the_title ); + + wp_delete_post( $post_id ); + + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( 0, $the_content_calls ); + $this->assertSame( 0, $the_title_calls ); + } + /** * Check that the `wp_pattern_sync_status` postmeta is moved from meta array to top * level of response.