diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 75e664ad9b366..cfae702dd6c46 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1432,39 +1432,48 @@ function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = ' $template_hierarchy[] = 'index'; return $template_hierarchy; } + /** * Inject ignoredHookedBlocks metadata attributes into a template or template part. * - * Given a `wp_template` or `wp_template_part` post object, locate all blocks that have + * Given an object that represents a `wp_template` or `wp_template_part` post object + * prepared for inserting or updating the database, locate all blocks that have * hooked blocks, and inject a `metadata.ignoredHookedBlocks` attribute into the anchor * blocks to reflect the latter. * - * @param WP_Post $post A post object with post type set to `wp_template` or `wp_template_part`. - * @return WP_Post The updated post object. + * @since 6.5.0 + * @access private + * + * @param stdClass $post An object representing a template or template part + * prepared for inserting or updating the database. + * @param WP_REST_Request $request Request object. + * @return stdClass The updated object representing a template or template part. */ -function inject_ignored_hooked_blocks_metadata_attributes( $post ) { +function inject_ignored_hooked_blocks_metadata_attributes( $post, $request ) { + $filter_name = current_filter(); + if ( ! str_starts_with( $filter_name, 'rest_pre_insert_' ) ) { + return $post; + } + $post_type = str_replace( 'rest_pre_insert_', '', $filter_name ); + $hooked_blocks = get_hooked_blocks(); if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) { - return; + return $post; } // At this point, the post has already been created. // We need to build the corresponding `WP_Block_Template` object as context argument for the visitor. // To that end, we need to suppress hooked blocks from getting inserted into the template. add_filter( 'hooked_block_types', '__return_empty_array', 99999, 0 ); - $template = _build_block_template_result_from_post( $post ); + $template = $request['id'] ? get_block_template( $request['id'], $post_type ) : null; remove_filter( 'hooked_block_types', '__return_empty_array', 99999 ); $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' ); $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' ); - $blocks = parse_blocks( $template->content ); + $blocks = parse_blocks( $post->post_content ); $content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); - wp_update_post( - array( - 'ID' => $post->ID, - 'post_content' => $content, - ) - ); + $post->post_content = $content; + return $post; } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 3f7e43f8615bc..884357f41350a 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -752,9 +752,8 @@ add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); add_action( 'init', '_wp_register_default_font_collections' ); -// It might be nice to use a filter instead of an action, but the `WP_REST_Templates_Controller` doesn't -// provide one (unlike e.g. `WP_REST_Posts_Controller`, which has `rest_pre_insert_{$this->post_type}`). -add_action( 'rest_after_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes', 10, 3 ); -add_action( 'rest_after_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes', 10, 3 ); +// Add ignoredHookedBlocks metadata attribute to the template and template part post types. +add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes', 10, 2 ); +add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes', 10, 2 ); unset( $filter, $action ); 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 1549fd4295561..cec8a77e04ef9 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 @@ -619,7 +619,8 @@ protected function prepare_item_for_database( $request ) { $changes->post_author = $post_author; } - return $changes; + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ + return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request ); } /** diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 5aa0b65929c47..8d972c004d52e 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -86,6 +86,28 @@ public function set_up() { switch_theme( self::TEST_THEME ); } + /** + * Tear down after each test. + * + * @since 6.5.0 + */ + public function tear_down() { + global $wp_current_filter; + + if ( + 'rest_pre_insert_wp_template' === current_filter() || + 'rest_pre_insert_wp_template_part' === current_filter() + ) { + array_pop( $wp_current_filter ); + } + + if ( WP_Block_Type_Registry::get_instance()->is_registered( 'tests/hooked-block' ) ) { + unregister_block_type( 'tests/hooked-block' ); + } + + parent::tear_down(); + } + /** * @ticket 59338 * @@ -390,4 +412,70 @@ public function test_wp_generate_block_templates_export_file() { } $this->assertTrue( $has_html_files, 'contains at least one html file' ); } + + /** + * @ticket 60671 + * + * @covers inject_ignored_hooked_blocks_metadata_attributes + */ + public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template() { + global $wp_current_filter; + // Mock currently set filter. + $wp_current_filter[] = 'rest_pre_insert_wp_template'; + + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'tests/anchor-block' => 'after', + ), + ) + ); + + $id = self::TEST_THEME . '//' . 'my_template'; + $request = new WP_REST_Request( 'POST', '/wp/v2/templates/' . $id ); + + $changes = new stdClass(); + $changes->post_content = 'Hello'; + + $post = inject_ignored_hooked_blocks_metadata_attributes( $changes, $request ); + $this->assertSame( + 'Hello', + $post->post_content, + 'The hooked block was not injected into the anchor block\'s ignoredHookedBlocks metadata.' + ); + } + + /** + * @ticket 60671 + * + * @covers inject_ignored_hooked_blocks_metadata_attributes + */ + public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template_part() { + global $wp_current_filter; + // Mock currently set filter. + $wp_current_filter[] = 'rest_pre_insert_wp_template_part'; + + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'tests/anchor-block' => 'after', + ), + ) + ); + + $id = self::TEST_THEME . '//' . 'my_template_part'; + $request = new WP_REST_Request( 'POST', '/wp/v2/template-parts/' . $id ); + + $changes = new stdClass(); + $changes->post_content = 'Hello'; + + $post = inject_ignored_hooked_blocks_metadata_attributes( $changes, $request ); + $this->assertSame( + 'Hello', + $post->post_content, + 'The hooked block was not injected into the anchor block\'s ignoredHookedBlocks metadata.' + ); + } } diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index 046f358ba3428..9665939dd6975 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -49,6 +49,22 @@ public static function wpTearDownAfterClass() { wp_delete_post( self::$post->ID ); } + /** + * Tear down after each test. + * + * @since 6.5.0 + */ + public function tear_down() { + if ( has_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes' ) ) { + remove_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes', 10 ); + } + if ( WP_Block_Type_Registry::get_instance()->is_registered( 'tests/block' ) ) { + unregister_block_type( 'tests/hooked-block' ); + } + + parent::tear_down(); + } + /** * @covers WP_REST_Templates_Controller::register_routes * @ticket 54596 @@ -911,4 +927,44 @@ public function test_prepare_item_for_database() { $this->assertEmpty( $prepared->post_content, 'The content was not correct in the prepared template part.' ); } + + /** + * @ticket 60671 + * + * @covers WP_REST_Templates_Controller::prepare_item_for_database + * @covers inject_ignored_hooked_blocks_metadata_attributes + */ + public function test_prepare_item_for_database_injects_hooked_block() { + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'tests/anchor-block' => 'after', + ), + ) + ); + + add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes', 10, 2 ); + + $endpoint = new WP_REST_Templates_Controller( 'wp_template_part' ); + + $prepare_item_for_database = new ReflectionMethod( $endpoint, 'prepare_item_for_database' ); + $prepare_item_for_database->setAccessible( true ); + + $body_params = array( + 'title' => 'Untitled Template Part', + 'slug' => 'untitled-template-part', + 'content' => 'Hello', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/template-parts' ); + $request->set_body_params( $body_params ); + + $prepared = $prepare_item_for_database->invoke( $endpoint, $request ); + $this->assertSame( + 'Hello', + $prepared->post_content, + 'The hooked block was not injected into the anchor block\'s ignoredHookedBlocks metadata.' + ); + } }