Skip to content

Commit

Permalink
Block Hooks: Respect "multiple": false
Browse files Browse the repository at this point in the history
  • Loading branch information
ockham committed Sep 27, 2024
1 parent 2952e56 commit dcbdba9
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 1 deletion.
53 changes: 52 additions & 1 deletion src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -1056,9 +1056,60 @@ function apply_block_hooks_to_content( $content, $context, $callback = 'insert_h
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback );
}

$block_allows_multiple_instances = array();
// Remove hooked blocks from the list if they have `multiple` set to false and are already
// present in `$content`.
foreach ( $hooked_blocks as &$relative_positions ) {
foreach ( $relative_positions as &$hooked_block_types ) {
foreach ( $hooked_block_types as &$hooked_block_type ) {
$hooked_block_type_definition =
WP_Block_Type_Registry::get_instance()->get_registered( $hooked_block_type );
if ( block_has_support( $hooked_block_type_definition, 'multiple', true ) ) {
$block_allows_multiple_instances[ $hooked_block_type ] = true;
} elseif ( has_block( $hooked_block_type, $content ) ) {
unset( $hooked_block_type );
if ( empty( $hooked_block_types ) ) {
unset( $hooked_block_types );
}
if ( empty( $relative_positions ) ) {
unset( $relative_positions );
}
}
}
}
}

// We also need to cover the case where there 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 = function ( $hooked_block_types, $relative_position, $anchor_block_name ) use ( &$block_allows_multiple_instances, $content ) {
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 ] && has_block( $hooked_block_type, $content ) ) {
unset( $hooked_block_types[ $index ] );
}
} elseif ( ! $block_allows_multiple_instances[ $hooked_block_type ] ) {
// An instance of the hooked block was previously encountered, and its `multiple` support is false.
unset( $hooked_block_types[ $index ] );
}
}

return $hooked_block_types;
};
add_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX, 3 );

$blocks = parse_blocks( $content );

return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
$updated_content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );

remove_filter( 'hooked_block_types', $suppress_single_instance_blocks, PHP_INT_MAX );

return $updated_content;
}

/**
Expand Down
101 changes: 101 additions & 0 deletions tests/phpunit/tests/blocks/applyBlockHooksToContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ public static function wpSetUpBeforeClass() {
),
)
);

register_block_type(
'tests/hooked-block-with-multiple-false',
array(
'block_hooks' => array(
'tests/other-anchor-block' => 'after',
),
'supports' => array(
'multiple' => false,
),
)
);

register_block_type(
'tests/dynamically-hooked-block-with-multiple-false',
array(
'supports' => array(
'multiple' => false,
),
)
);
}

/**
Expand All @@ -38,6 +59,8 @@ public static function wpTearDownAfterClass() {
$registry = WP_Block_Type_Registry::get_instance();

$registry->unregister( 'tests/hooked-block' );
$registry->unregister( 'tests/hooked-block-with-multiple-false' );
$registry->unregister( 'tests/dynamically-hooked-block-with-multiple-false' );
}

/**
Expand Down Expand Up @@ -67,4 +90,82 @@ public function test_apply_block_hooks_to_content_inserts_hooked_block() {
$actual
);
}

/**
* @ticket 61902
*/
public function test_apply_block_hooks_to_content_respect_multiple_false() {
$context = new WP_Block_Template();
$context->content = '<!-- wp:tests/hooked-block-with-multiple-false /--><!-- wp:tests/other-anchor-block /-->';

$actual = apply_block_hooks_to_content( $context->content, $context, 'insert_hooked_blocks' );
$this->assertSame(
'<!-- wp:tests/hooked-block-with-multiple-false /--><!-- wp:tests/other-anchor-block /-->',
$actual
);
}

/**
* @ticket 61902
*/
public function test_apply_block_hooks_to_content_respect_multiple_false_after_inserting_once() {
$context = new WP_Block_Template();
$context->content = '<!-- wp:tests/other-anchor-block /--><!-- wp:tests/other-block /--><!-- wp:tests/other-anchor-block /-->';

$actual = apply_block_hooks_to_content( $context->content, $context, 'insert_hooked_blocks' );
$this->assertSame(
'<!-- wp:tests/other-anchor-block /--><!-- wp:tests/hooked-block-with-multiple-false /--><!-- wp:tests/other-block /--><!-- wp:tests/other-anchor-block /-->',
$actual
);
}

/**
* @ticket 61902
*/
public function test_apply_block_hooks_to_content_respect_multiple_false_with_filter() {
$filter = function ( $hooked_block_types, $relative_position, $anchor_block_type ) {
if ( 'tests/yet-another-anchor-block' === $anchor_block_type && 'after' === $relative_position ) {
$hooked_block_types[] = 'tests/dynamically-hooked-block-with-multiple-false';
}

return $hooked_block_types;
};

$context = new WP_Block_Template();
$context->content = '<!-- wp:tests/dynamically-hooked-block-with-multiple-false /--><!-- wp:tests/yet-another-anchor-block /-->';

add_filter( 'hooked_block_types', $filter, 10, 3 );
$actual = apply_block_hooks_to_content( $context->content, $context, 'insert_hooked_blocks' );
remove_filter( 'hooked_block_types', $filter, 10 );

$this->assertSame(
'<!-- wp:tests/dynamically-hooked-block-with-multiple-false /--><!-- wp:tests/yet-another-anchor-block /-->',
$actual
);
}

/**
* @ticket 61902
*/
public function test_apply_block_hooks_to_content_respect_multiple_false_after_inserting_once_with_filter() {
$filter = function ( $hooked_block_types, $relative_position, $anchor_block_type ) {
if ( 'tests/yet-another-anchor-block' === $anchor_block_type && 'after' === $relative_position ) {
$hooked_block_types[] = 'tests/dynamically-hooked-block-with-multiple-false';
}

return $hooked_block_types;
};

$context = new WP_Block_Template();
$context->content = '<!-- wp:tests/yet-another-anchor-block /--><!-- wp:tests/other-block /--><!-- wp:tests/yet-another-anchor-block /-->';

add_filter( 'hooked_block_types', $filter, 10, 3 );
$actual = apply_block_hooks_to_content( $context->content, $context, 'insert_hooked_blocks' );
remove_filter( 'hooked_block_types', $filter, 10 );

$this->assertSame(
'<!-- wp:tests/yet-another-anchor-block /--><!-- wp:tests/dynamically-hooked-block-with-multiple-false /--><!-- wp:tests/other-block /--><!-- wp:tests/yet-another-anchor-block /-->',
$actual
);
}
}

0 comments on commit dcbdba9

Please sign in to comment.