Block Hooks: Respect "multiple": false in hooked blocks.

If a prospective hooked block has its `multiple` block-supports field set to `false` (thus allowing only one instance of the block to be present), ensure that:

1. Only one instance of the block will be inserted if it's not yet present in the current context.
2. The block will not be inserted at all if an instance of it is already present in the current context.

As always in Block Hooks parlance, "context" denotes the containing template, template part, pattern, or navigation post that a hooked block is supposed to be inserted into.

The markup of a webpage that uses a Block Theme typically comprises a number of such contexts -- one template and any number of template parts, patterns, and navigation posts. Note that the limitation imposed by this changeset only applies on a per-context basis, so it's still possible that the resulting page contains more than one instance of a hooked block with `"multiple": false` set, as each context could contribute up to one such instance.

Props bernhard-reiter, jonsurrell, gziolo.
Fixes #61902.
Built from https://develop.svn.wordpress.org/trunk@59124


git-svn-id: http://core.svn.wordpress.org/trunk@58520 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Bernhard Reiter 2024-09-30 12:23:17 +00:00
parent 6cf0577be8
commit 1b6e00c306
2 changed files with 71 additions and 3 deletions

View File

@ -1056,9 +1056,77 @@ function apply_block_hooks_to_content( $content, $context, $callback = 'insert_h
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback );
}
$blocks = parse_blocks( $content );
$block_allows_multiple_instances = array();
/*
* Remove hooked blocks from `$hooked_block_types` if they have `multiple` set to false and
* are already present in `$content`.
*/
foreach ( $hooked_blocks as $anchor_block_type => $relative_positions ) {
foreach ( $relative_positions as $relative_position => $hooked_block_types ) {
foreach ( $hooked_block_types as $index => $hooked_block_type ) {
$hooked_block_type_definition =
WP_Block_Type_Registry::get_instance()->get_registered( $hooked_block_type );
return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
$block_allows_multiple_instances[ $hooked_block_type ] =
block_has_support( $hooked_block_type_definition, 'multiple', true );
if (
! $block_allows_multiple_instances[ $hooked_block_type ] &&
has_block( $hooked_block_type, $content )
) {
unset( $hooked_blocks[ $anchor_block_type ][ $relative_position ][ $index ] );
}
}
if ( empty( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) ) {
unset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] );
}
}
if ( empty( $hooked_blocks[ $anchor_block_type ] ) ) {
unset( $hooked_blocks[ $anchor_block_type ] );
}
}
/*
* We also need to cover the case where the hooked block is not present in
* `$content` at first and we're allowed to insert it once -- but not again.
*/
$suppress_single_instance_blocks = static function ( $hooked_block_types ) use ( &$block_allows_multiple_instances, $content ) {
static $single_instance_blocks_present_in_content = array();
foreach ( $hooked_block_types as $index => $hooked_block_type ) {
if ( ! isset( $block_allows_multiple_instances[ $hooked_block_type ] ) ) {
$hooked_block_type_definition =
WP_Block_Type_Registry::get_instance()->get_registered( $hooked_block_type );
$block_allows_multiple_instances[ $hooked_block_type ] =
block_has_support( $hooked_block_type_definition, 'multiple', true );
}
if ( $block_allows_multiple_instances[ $hooked_block_type ] ) {
continue;
}
// The block doesn't allow multiple instances, so we need to check if it's already present.
if (
in_array( $hooked_block_type, $single_instance_blocks_present_in_content, true ) ||
has_block( $hooked_block_type, $content )
) {
unset( $hooked_block_types[ $index ] );
} else {
// We can insert the block once, but need to remember not to insert it again.
$single_instance_blocks_present_in_content[] = $hooked_block_type;
}
}
return $hooked_block_types;
};
add_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX );
$content = traverse_and_serialize_blocks(
parse_blocks( $content ),
$before_block_visitor,
$after_block_visitor
);
remove_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX );
return $content;
}
/**

View File

@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '6.7-alpha-59123';
$wp_version = '6.7-alpha-59124';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.