diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 98c151048a4a1..0ce64a659d6a6 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -549,10 +549,15 @@ function _build_block_template_result_from_file( $template_file, $template_type $template->area = $template_file['area']; } - $blocks = parse_blocks( $template_content ); - $before_block_visitor = make_before_block_visitor( $template ); - $after_block_visitor = make_after_block_visitor( $template ); - $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + $before_block_visitor = '_inject_theme_attribute_in_template_part_block'; + $after_block_visitor = null; + $hooked_blocks = get_hooked_blocks(); + if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { + $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template ); + $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template ); + } + $blocks = parse_blocks( $template_content ); + $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); return $template; } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 45acfd90d89d4..00d9fdae3e26e 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -723,14 +723,13 @@ function get_dynamic_block_names() { } /** - * Retrieves block types hooked into the given block, grouped by their relative position. + * Retrieves block types hooked into the given block, grouped by anchor block type and the relative position. * * @since 6.4.0 * - * @param string $name Block type name including namespace. - * @return array[] Array of block types grouped by their relative position. + * @return array[] Array of block types grouped by anchor block type and the relative position. */ -function get_hooked_blocks( $name ) { +function get_hooked_blocks() { $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); $hooked_blocks = array(); foreach ( $block_types as $block_type ) { @@ -738,15 +737,16 @@ function get_hooked_blocks( $name ) { continue; } foreach ( $block_type->block_hooks as $anchor_block_type => $relative_position ) { - if ( $anchor_block_type !== $name ) { - continue; + if ( ! isset( $hooked_blocks[ $anchor_block_type ] ) ) { + $hooked_blocks[ $anchor_block_type ] = array(); } - if ( ! isset( $hooked_blocks[ $relative_position ] ) ) { - $hooked_blocks[ $relative_position ] = array(); + if ( ! isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) ) { + $hooked_blocks[ $anchor_block_type ][ $relative_position ] = array(); } - $hooked_blocks[ $relative_position ][] = $block_type->name; + $hooked_blocks[ $anchor_block_type ][ $relative_position ][] = $block_type->name; } } + return $hooked_blocks; } @@ -760,11 +760,12 @@ function get_hooked_blocks( $name ) { * @since 6.4.0 * @access private * - * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. + * @param array $hooked_blocks An array of blocks hooked to another given block. + * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. * @return callable A function that returns the serialized markup for the given block, * including the markup for any hooked blocks before it. */ -function make_before_block_visitor( $context ) { +function make_before_block_visitor( $hooked_blocks, $context ) { /** * Injects hooked blocks before the given block, injects the `theme` attribute into Template Part blocks, and returns the serialized markup. * @@ -777,7 +778,7 @@ function make_before_block_visitor( $context ) { * @param array $prev The previous sibling block of the given block. * @return string The serialized markup for the given block, with the markup for any hooked blocks prepended to it. */ - return function ( &$block, $parent_block = null, $prev = null ) use ( $context ) { + return function ( &$block, $parent_block = null, $prev = null ) use ( $hooked_blocks, $context ) { _inject_theme_attribute_in_template_part_block( $block ); $markup = ''; @@ -786,9 +787,8 @@ function make_before_block_visitor( $context ) { // Candidate for first-child insertion. $relative_position = 'first_child'; $anchor_block_type = $parent_block['blockName']; - $hooked_block_types = get_hooked_blocks( $anchor_block_type ); - $hooked_block_types = isset( $hooked_block_types[ $relative_position ] ) - ? $hooked_block_types[ $relative_position ] + $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) + ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] : array(); /** @@ -810,9 +810,8 @@ function make_before_block_visitor( $context ) { $relative_position = 'before'; $anchor_block_type = $block['blockName']; - $hooked_block_types = get_hooked_blocks( $anchor_block_type ); - $hooked_block_types = isset( $hooked_block_types[ $relative_position ] ) - ? $hooked_block_types[ $relative_position ] + $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) + ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] : array(); /** This filter is documented in wp-includes/blocks.php */ @@ -835,11 +834,12 @@ function make_before_block_visitor( $context ) { * @since 6.4.0 * @access private * - * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. + * @param array $hooked_blocks An array of blocks hooked to another block. + * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. * @return callable A function that returns the serialized markup for the given block, * including the markup for any hooked blocks after it. */ -function make_after_block_visitor( $context ) { +function make_after_block_visitor( $hooked_blocks, $context ) { /** * Injects hooked blocks after the given block, and returns the serialized markup. * @@ -851,15 +851,14 @@ function make_after_block_visitor( $context ) { * @param array $next The next sibling block of the given block. * @return string The serialized markup for the given block, with the markup for any hooked blocks appended to it. */ - return function ( &$block, $parent_block = null, $next = null ) use ( $context ) { + return function ( &$block, $parent_block = null, $next = null ) use ( $hooked_blocks, $context ) { $markup = ''; $relative_position = 'after'; $anchor_block_type = $block['blockName']; - $hooked_block_types = get_hooked_blocks( $anchor_block_type ); - $hooked_block_types = isset( $hooked_block_types[ $relative_position ] ) - ? $hooked_block_types[ $relative_position ] - : array(); + $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) + ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] + : array(); /** This filter is documented in wp-includes/blocks.php */ $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); @@ -871,9 +870,8 @@ function make_after_block_visitor( $context ) { // Candidate for last-child insertion. $relative_position = 'last_child'; $anchor_block_type = $parent_block['blockName']; - $hooked_block_types = get_hooked_blocks( $anchor_block_type ); - $hooked_block_types = isset( $hooked_block_types[ $relative_position ] ) - ? $hooked_block_types[ $relative_position ] + $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) + ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] : array(); /** This filter is documented in wp-includes/blocks.php */ diff --git a/src/wp-includes/class-wp-block-patterns-registry.php b/src/wp-includes/class-wp-block-patterns-registry.php index 56bcd8fd7785c..d1ef2ad422a59 100644 --- a/src/wp-includes/class-wp-block-patterns-registry.php +++ b/src/wp-includes/class-wp-block-patterns-registry.php @@ -152,6 +152,28 @@ public function unregister( $pattern_name ) { return true; } + /** + * Prepares the content of a block pattern. If hooked blocks are registered, they get injected into the pattern, + * when they met the defined criteria. + * + * @since 6.4.0 + * + * @param array $pattern Registered pattern properties. + * @param array $hooked_blocks The list of hooked blocks. + * @return string The content of the block pattern. + */ + private function prepare_content( $pattern, $hooked_blocks ) { + $content = $pattern['content']; + if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { + $blocks = parse_blocks( $content ); + $before_block_visitor = make_before_block_visitor( $hooked_blocks, $pattern ); + $after_block_visitor = make_after_block_visitor( $hooked_blocks, $pattern ); + $content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + } + + return $content; + } + /** * Retrieves an array containing the properties of a registered block pattern. * @@ -165,11 +187,8 @@ public function get_registered( $pattern_name ) { return null; } - $pattern = $this->registered_patterns[ $pattern_name ]; - $blocks = parse_blocks( $pattern['content'] ); - $before_block_visitor = make_before_block_visitor( $pattern ); - $after_block_visitor = make_after_block_visitor( $pattern ); - $pattern['content'] = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + $pattern = $this->registered_patterns[ $pattern_name ]; + $pattern['content'] = $this->prepare_content( $pattern, get_hooked_blocks() ); return $pattern; } @@ -184,17 +203,14 @@ public function get_registered( $pattern_name ) { * and per style. */ public function get_all_registered( $outside_init_only = false ) { - $patterns = array_values( + $patterns = array_values( $outside_init_only ? $this->registered_patterns_outside_init : $this->registered_patterns ); - + $hooked_blocks = get_hooked_blocks(); foreach ( $patterns as $index => $pattern ) { - $blocks = parse_blocks( $pattern['content'] ); - $before_block_visitor = make_before_block_visitor( $pattern ); - $after_block_visitor = make_after_block_visitor( $pattern ); - $patterns[ $index ]['content'] = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + $patterns[ $index ]['content'] = $this->prepare_content( $pattern, $hooked_blocks ); } return $patterns; } diff --git a/tests/phpunit/tests/blocks/getHookedBlocks.php b/tests/phpunit/tests/blocks/getHookedBlocks.php index e20bc6d34c451..4052e2a36fe36 100644 --- a/tests/phpunit/tests/blocks/getHookedBlocks.php +++ b/tests/phpunit/tests/blocks/getHookedBlocks.php @@ -62,7 +62,7 @@ private function switch_to_block_theme_hooked_blocks() { * @covers ::get_hooked_blocks */ public function test_get_hooked_blocks_no_match_found() { - $result = get_hooked_blocks( 'tests/no-hooked-blocks' ); + $result = get_hooked_blocks(); $this->assertSame( array(), $result ); } @@ -98,53 +98,38 @@ public function test_get_hooked_blocks_matches_found() { $this->assertSame( array( - 'before' => array( - 'tests/injected-one', - 'tests/injected-two', + 'tests/hooked-at-before' => array( + 'before' => array( + 'tests/injected-one', + 'tests/injected-two', + ), ), - ), - get_hooked_blocks( 'tests/hooked-at-before' ), - 'block hooked at the before position' - ); - $this->assertSame( - array( - 'after' => array( - 'tests/injected-one', - 'tests/injected-two', + 'tests/hooked-at-after' => array( + 'after' => array( + 'tests/injected-one', + 'tests/injected-two', + ), ), - ), - get_hooked_blocks( 'tests/hooked-at-after' ), - 'block hooked at the after position' - ); - $this->assertSame( - array( - 'first_child' => array( - 'tests/injected-two', + 'tests/hooked-at-before-and-after' => array( + 'before' => array( + 'tests/injected-one', + ), + 'after' => array( + 'tests/injected-two', + ), ), - ), - get_hooked_blocks( 'tests/hooked-at-first-child' ), - 'block hooked at the first child position' - ); - $this->assertSame( - array( - 'last_child' => array( - 'tests/injected-two', - ), - ), - get_hooked_blocks( 'tests/hooked-at-last-child' ), - 'block hooked at the last child position' - ); - $this->assertSame( - array( - 'before' => array( - 'tests/injected-one', + 'tests/hooked-at-first-child' => array( + 'first_child' => array( + 'tests/injected-two', + ), ), - 'after' => array( - 'tests/injected-two', + 'tests/hooked-at-last-child' => array( + 'last_child' => array( + 'tests/injected-two', + ), ), ), - get_hooked_blocks( 'tests/hooked-at-before-and-after' ), - 'block hooked before one block and after another' + get_hooked_blocks() ); }