diff --git a/wp-includes/block-template-utils.php b/wp-includes/block-template-utils.php index 9e0eafcc99..87ad50634c 100644 --- a/wp-includes/block-template-utils.php +++ b/wp-includes/block-template-utils.php @@ -601,8 +601,10 @@ function _build_block_template_result_from_file( $template_file, $template_type $template->area = $template_file['area']; } - $blocks = parse_blocks( $template_content ); - $template->content = traverse_and_serialize_blocks( $blocks, '_inject_theme_attribute_in_template_part_block' ); + $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 ); return $template; } diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index b7f36cff38..4b39ff2400 100644 --- a/wp-includes/blocks.php +++ b/wp-includes/blocks.php @@ -751,6 +751,9 @@ function get_hooked_blocks( $name ) { $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); $hooked_blocks = array(); foreach ( $block_types as $block_type ) { + if ( ! property_exists( $block_type, 'block_hooks' ) || ! is_array( $block_type->block_hooks ) ) { + continue; + } foreach ( $block_type->block_hooks as $anchor_block_type => $relative_position ) { if ( $anchor_block_type === $name ) { $hooked_blocks[ $block_type->name ] = $relative_position; @@ -760,6 +763,128 @@ function get_hooked_blocks( $name ) { return $hooked_blocks; } +/** + * Returns a function that injects the theme attribute into, and hooked blocks before, a given block. + * + * The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`, + * where it will inject the `theme` attribute into all Template Part blocks, and prepend the markup for + * any blocks hooked `before` the given block and as its parent's `first_child`, respectively. + * + * @since 6.4.0 + * @access private + * + * @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 ) { + /** + * Injects hooked blocks before the given block, injects the `theme` attribute into Template Part blocks, and returns the serialized markup. + * + * If the current block is a Template Part block, inject the `theme` attribute. + * Furthermore, prepend the markup for any blocks hooked `before` the given block and as its parent's + * `first_child`, respectively, to the serialized markup for the given block. + * + * @param array $block The block to inject the theme attribute into, and hooked blocks before. + * @param array $parent The parent block of the given block. + * @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 = null, $prev = null ) use ( $context ) { + _inject_theme_attribute_in_template_part_block( $block ); + + $markup = ''; + + if ( $parent && ! $prev ) { + // Candidate for first-child insertion. + $hooked_blocks_for_parent = get_hooked_blocks( $parent['blockName'] ); + foreach ( $hooked_blocks_for_parent as $hooked_block_type => $relative_position ) { + if ( 'first_child' === $relative_position ) { + $hooked_block_markup = get_comment_delimited_block_content( $hooked_block_type, array(), '' ); + /** This filter is documented in wp-includes/blocks.php */ + $markup .= apply_filters( 'inject_hooked_block_markup', $hooked_block_markup, $hooked_block_type, $relative_position, $parent, $context ); + } + } + } + + $hooked_blocks = get_hooked_blocks( $block['blockName'] ); + foreach ( $hooked_blocks as $hooked_block_type => $relative_position ) { + if ( 'before' === $relative_position ) { + $hooked_block_markup = get_comment_delimited_block_content( $hooked_block_type, array(), '' ); + /** + * Filters the serialized markup of a hooked block. + * + * @since 6.4.0 + * + * @param string $hooked_block_markup The serialized markup of the hooked block. + * @param string $hooked_block_type The type of the hooked block. + * @param string $relative_position The relative position of the hooked block. + * Can be one of 'before', 'after', 'first_child', or 'last_child'. + * @param array $block The anchor block. + * @param WP_Block_Template|array $context The block template, template part, or pattern that the anchor block belongs to. + */ + $markup .= apply_filters( 'inject_hooked_block_markup', $hooked_block_markup, $hooked_block_type, $relative_position, $block, $context ); + } + } + + return $markup; + }; +} + +/** + * Returns a function that injects the hooked blocks after a given block. + * + * The returned function can be used as `$post_callback` argument to `traverse_and_serialize_block(s)`, + * where it will append the markup for any blocks hooked `after` the given block and as its parent's + * `last_child`, respectively. + * + * @since 6.4.0 + * @access private + * + * @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 ) { + /** + * Injects hooked blocks after the given block, and returns the serialized markup. + * + * Append the markup for any blocks hooked `after` the given block and as its parent's + * `last_child`, respectively, to the serialized markup for the given block. + * + * @param array $block The block to inject the hooked blocks after. + * @param array $parent The parent block of the given block. + * @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 = null, $next = null ) use ( $context ) { + $markup = ''; + + $hooked_blocks = get_hooked_blocks( $block['blockName'] ); + foreach ( $hooked_blocks as $hooked_block_type => $relative_position ) { + if ( 'after' === $relative_position ) { + $hooked_block_markup = get_comment_delimited_block_content( $hooked_block_type, array(), '' ); + /** This filter is documented in wp-includes/blocks.php */ + $markup .= apply_filters( 'inject_hooked_block_markup', $hooked_block_markup, $hooked_block_type, $relative_position, $block, $context ); + } + } + + if ( $parent && ! $next ) { + // Candidate for last-child insertion. + $hooked_blocks_for_parent = get_hooked_blocks( $parent['blockName'] ); + foreach ( $hooked_blocks_for_parent as $hooked_block_type => $relative_position ) { + if ( 'last_child' === $relative_position ) { + $hooked_block_markup = get_comment_delimited_block_content( $hooked_block_type, array(), '' ); + /** This filter is documented in wp-includes/blocks.php */ + $markup .= apply_filters( 'inject_hooked_block_markup', $hooked_block_markup, $hooked_block_type, $relative_position, $parent, $context ); + } + } + } + + return $markup; + }; +} + /** * Given an array of attributes, returns a string in the serialized attributes * format prepared for post content. diff --git a/wp-includes/class-wp-block-patterns-registry.php b/wp-includes/class-wp-block-patterns-registry.php index a83b35185b..56bcd8fd77 100644 --- a/wp-includes/class-wp-block-patterns-registry.php +++ b/wp-includes/class-wp-block-patterns-registry.php @@ -165,7 +165,13 @@ final class WP_Block_Patterns_Registry { return null; } - return $this->registered_patterns[ $pattern_name ]; + $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 ); + + return $pattern; } /** @@ -178,11 +184,19 @@ final class WP_Block_Patterns_Registry { * and per style. */ public function get_all_registered( $outside_init_only = false ) { - return array_values( + $patterns = array_values( $outside_init_only ? $this->registered_patterns_outside_init : $this->registered_patterns ); + + 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 ); + } + return $patterns; } /** diff --git a/wp-includes/version.php b/wp-includes/version.php index ea8385eba9..1b7ea47ecb 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.4-alpha-56648'; +$wp_version = '6.4-alpha-56649'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.