diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index b2429f1f482cb..d01bcfc69bbec 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -24,6 +24,13 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { */ protected $post_type; + /** + * The template being prepared for the response, so prepare_links() can reuse it. + * + * @since 7.2.0 + */ + private ?WP_Block_Template $prepared_template = null; + /** * Constructor. * @@ -832,7 +839,9 @@ public function prepare_item_for_response( $item, $request ) { $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { - $links = $this->prepare_links( $template->id ); + $this->prepared_template = $template; + $links = $this->prepare_links( $template->id ); + $this->prepared_template = null; $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions(); @@ -984,7 +993,13 @@ protected function prepare_links( $id ) { ); if ( post_type_supports( $this->post_type, 'revisions' ) ) { - $template = get_block_template( $id, $this->post_type ); + // Reuse the template being prepared instead of querying it again for each item. + if ( $this->prepared_template instanceof WP_Block_Template && $this->prepared_template->id === $id ) { + $template = $this->prepared_template; + } else { + // Only reached when a subclass calls prepare_links() outside of prepare_item_for_response(). + $template = get_block_template( $id, $this->post_type ); + } if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) { $revisions = wp_get_latest_revision_id_and_total_count( $template->wp_id ); $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index 2403e1f3115f3..d098bc9c0e321 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -1247,4 +1247,54 @@ public function test_prepare_item_for_database_injects_hooked_block() { 'The hooked block was not injected into the anchor block\'s ignoredHookedBlocks metadata.' ); } + + /** + * Tests that preparing a collection does not fetch each template again to build its links. + * + * @ticket 66381 + * + * @covers WP_REST_Templates_Controller::prepare_links + */ + public function test_get_items_should_not_refetch_each_template_for_links() { + wp_set_current_user( self::$admin_id ); + + $mock = new MockAction(); + add_filter( 'pre_get_block_template', array( $mock, 'filter' ) ); + $response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', '/wp/v2/templates' ) ); + + $this->assertSame( 200, $response->get_status(), 'The request should succeed.' ); + $this->assertSame( 0, $mock->get_call_count(), 'Templates should not be fetched again to prepare their links.' ); + } + + /** + * Tests that the version history link of a customized template is kept when preparing a collection. + * + * @ticket 66381 + * + * @covers WP_REST_Templates_Controller::prepare_links + */ + public function test_get_items_should_include_version_history_link_for_customized_template() { + wp_set_current_user( self::$admin_id ); + + $this->assertIsInt( + wp_update_post( + array( + 'ID' => self::$template_post->ID, + 'post_content' => 'Content for a revision.', + ), + true + ) + ); + $revisions = wp_get_latest_revision_id_and_total_count( self::$template_post->ID ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates' ); + $data = rest_get_server()->response_to_data( rest_get_server()->dispatch( $request ), false ); + $item = $this->find_and_normalize_template_by_id( $data, 'default//my_template' ); + $found = wp_list_filter( $data, array( 'id' => 'default//my_template' ) ); + $links = array_first( $found )['_links']; + + $this->assertNotEmpty( $item, 'The customized template should be in the collection.' ); + $this->assertSame( $revisions['count'], $links['version-history'][0]['count'], 'The version history count should match the revisions of the template.' ); + $this->assertSame( $revisions['latest_id'], $links['predecessor-version'][0]['id'], 'The predecessor version should be the latest revision.' ); + } }