From b39f58ec3eab0f2b66dccebbf0fc97baaaaa9a15 Mon Sep 17 00:00:00 2001 From: bernhard-reiter Date: Mon, 3 Jun 2024 12:03:53 +0000 Subject: [PATCH] Block Hooks: Move ignoredHookedBlocks metadata injection logic. As of [57790], the Templates endpoint uses the `rest_pre_insert_*` filter to inject the `ignoredHookedBlocks` metadata attribute into anchor blocks, prior to persisting a template or template part to the database. The same principle was implemented for the Navigation endpoint (where additionally, first and last child blocks added at the top level are store in the `wp_navigation` post object's post meta). The required logic was added to the Navigation block's code, i.e. inside the Gutenberg code repository, and then synchronized to Core. In order to harmonize the code between the two endpoints, this changeset introduces a new `update_ignored_hooked_blocks_postmeta` function, which is based on the Navigation block's `block_core_navigation_update_ignore_hooked_blocks_meta`, alongside a few helper functions, and hooks it to the `rest_pre_insert_wp_navigation` filter hook. (The Navigation block has been prepared in [58275] to add an additional conditional to check for the new `update_ignored_hooked_blocks_postmeta` filter so there won't be any collisions.) Eventually, this will allow to deprecate `block_core_navigation_update_ignore_hooked_blocks_meta` (and some related functions), and remove the relevant code from the Navigation block. It also paves the way for some other future changes, such as inserting a hooked block as a Template Part block's first or last child (#60854). Props tomjcafferkey, bernhard-reiter. Fixes #60759. git-svn-id: https://develop.svn.wordpress.org/trunk@58291 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/block-template-utils.php | 6 +- src/wp-includes/blocks.php | 118 ++++++++++- src/wp-includes/default-filters.php | 3 + .../updateIgnoredHookedBlocksPostMeta.php | 196 ++++++++++++++++++ 4 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 0a404da8c6..58ee129d2a 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1599,11 +1599,7 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated return $template; } - $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( $changes->post_content ); - $changes->post_content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' ); return $changes; } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index e33912b617..e68eb36b4b 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1003,14 +1003,124 @@ function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_po } /** + * Runs the hooked blocks algorithm on the given content. + * + * @since 6.6.0 + * @access private + * + * @param string $content Serialized content. + * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, + * or pattern that the blocks belong to. + * @param callable $callback A function that will be called for each block to generate + * the markup for a given list of blocks that are hooked to it. + * Default: 'insert_hooked_blocks'. + * @return string The serialized markup. + */ +function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) { + $hooked_blocks = get_hooked_blocks(); + if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) { + return $content; + } + + $blocks = parse_blocks( $content ); + + $before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback ); + $after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback ); + + return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); +} + +/** + * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks. + * + * @since 6.6.0 + * @access private + * + * @param string $serialized_block The serialized markup of a block and its inner blocks. + * @return string The serialized markup of the inner blocks. + */ +function remove_serialized_parent_block( $serialized_block ) { + $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); + $end = strrpos( $serialized_block, ''; + $post = new stdClass(); + $post->ID = self::$navigation_post->ID; + $post->post_content = $original_markup; + $post->post_type = 'wp_navigation'; + + $post = update_ignored_hooked_blocks_postmeta( $post ); + + // We expect the '&' character to be replaced with its unicode representation. + $expected_markup = str_replace( '&', '\u0026', $original_markup ); + + $this->assertSame( + $expected_markup, + $post->post_content, + 'Post content did not match expected markup with entities escaped.' + ); + $this->assertSame( + array( 'tests/my-block' ), + json_decode( get_post_meta( self::$navigation_post->ID, '_wp_ignored_hooked_blocks', true ), true ), + 'Block was not added to ignored hooked blocks metadata.' + ); + } + + /** + * @ticket 60759 + */ + public function test_update_ignored_hooked_blocks_postmeta_dont_modify_no_post_id() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/navigation' => 'last_child', + ), + ) + ); + + $original_markup = ''; + $post = new stdClass(); + $post->post_content = $original_markup; + $post->post_type = 'wp_navigation'; + + $post = update_ignored_hooked_blocks_postmeta( $post ); + + $this->assertSame( + $original_markup, + $post->post_content, + 'Post content did not match the original markup.' + ); + } + + /** + * @ticket 60759 + */ + public function test_update_ignored_hooked_blocks_postmeta_retains_content_if_not_set() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/navigation' => 'last_child', + ), + ) + ); + + $post = new stdClass(); + $post->ID = self::$navigation_post->ID; + $post->post_title = 'Navigation Menu with changes'; + $post->post_type = 'wp_navigation'; + + $post = update_ignored_hooked_blocks_postmeta( $post ); + + $this->assertSame( + 'Navigation Menu with changes', + $post->post_title, + 'Post title was changed.' + ); + + $this->assertFalse( + isset( $post->post_content ), + 'Post content should not be set.' + ); + } + + /** + * @ticket 60759 + */ + public function test_update_ignored_hooked_blocks_postmeta_dont_modify_if_not_navigation() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/navigation' => 'last_child', + ), + ) + ); + + $original_markup = ''; + $post = new stdClass(); + $post->ID = self::$navigation_post->ID; + $post->post_content = $original_markup; + $post->post_type = 'post'; + + $post = update_ignored_hooked_blocks_postmeta( $post ); + + $this->assertSame( + $original_markup, + $post->post_content, + 'Post content did not match the original markup.' + ); + } + + /** + * @ticket 60759 + */ + public function test_update_ignored_hooked_blocks_postmeta_dont_modify_if_no_post_type() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/navigation' => 'last_child', + ), + ) + ); + + $original_markup = ''; + $post = new stdClass(); + $post->ID = self::$navigation_post->ID; + $post->post_content = $original_markup; + + $post = update_ignored_hooked_blocks_postmeta( $post ); + + $this->assertSame( + $original_markup, + $post->post_content, + 'Post content did not match the original markup.' + ); + } +}