Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocks: Insert hooked blocks #5261

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
125 changes: 125 additions & 0 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'] );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_hooked_blocks is going to be called too many times. I will work on some optimizations tomorrow.

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.
Expand Down
18 changes: 16 additions & 2 deletions src/wp-includes/class-wp-block-patterns-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,13 @@ public function get_registered( $pattern_name ) {
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;
}

/**
Expand All @@ -178,11 +184,19 @@ public function get_registered( $pattern_name ) {
* 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;
}

/**
Expand Down
140 changes: 139 additions & 1 deletion tests/phpunit/tests/blocks/serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function test_serialized_block_name() {
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks() {
public function test_traverse_and_serialize_blocks_pre_callback_modifies_current_block() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

Expand All @@ -80,6 +80,144 @@ public static function add_attribute_to_inner_block( &$block ) {
}
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_pre_callback_prepends_to_inner_block() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, array( __CLASS__, 'insert_next_to_inner_block_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:tests/inserted-block /--><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->",
$actual
);
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_post_callback_appends_to_inner_block() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, null, array( __CLASS__, 'insert_next_to_inner_block_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner --><!-- wp:tests/inserted-block /-->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->",
$actual
);
}

public static function insert_next_to_inner_block_callback( $block ) {
if ( 'core/inner' !== $block['blockName'] ) {
return '';
}

return get_comment_delimited_block_content( 'tests/inserted-block', array(), '' );
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_pre_callback_prepends_to_child_blocks() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, array( __CLASS__, 'insert_next_to_child_blocks_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:tests/inserted-block {\"parent\":\"core/outer\"} /--><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:tests/inserted-block {\"parent\":\"core/outer\"} /--><!-- wp:void /--><!-- /wp:outer -->",
$actual
);
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_post_callback_appends_to_child_blocks() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, null, array( __CLASS__, 'insert_next_to_child_blocks_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner --><!-- wp:tests/inserted-block {\"parent\":\"core/outer\"} /-->\n\nExample.\n\n<!-- wp:void /--><!-- wp:tests/inserted-block {\"parent\":\"core/outer\"} /--><!-- /wp:outer -->",
$actual
);
}

public static function insert_next_to_child_blocks_callback( $block, $parent_block ) {
if ( ! isset( $parent_block ) ) {
return '';
}

return get_comment_delimited_block_content(
'tests/inserted-block',
array(
'parent' => $parent_block['blockName'],
),
''
);
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_pre_callback_prepends_if_prev_block() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, array( __CLASS__, 'insert_next_to_if_prev_or_next_block_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:tests/inserted-block {\"prev_or_next\":\"core/inner\"} /--><!-- wp:void /--><!-- /wp:outer -->",
$actual
);
}

/**
* @ticket 59313
*
* @covers ::traverse_and_serialize_blocks
*/
public function test_traverse_and_serialize_blocks_post_callback_appends_if_prev_block() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = traverse_and_serialize_blocks( $blocks, null, array( __CLASS__, 'insert_next_to_if_prev_or_next_block_callback' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner --><!-- wp:tests/inserted-block {\"prev_or_next\":\"core/void\"} /-->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->",
$actual
);
}

public static function insert_next_to_if_prev_or_next_block_callback( $block, $parent_block, $prev_or_next ) {
if ( ! isset( $prev_or_next ) ) {
return '';
}

return get_comment_delimited_block_content(
'tests/inserted-block',
array(
'prev_or_next' => $prev_or_next['blockName'],
),
''
);
}

/**
* @ticket 59327
* @ticket 59412
Expand Down
2 changes: 2 additions & 0 deletions tests/phpunit/tests/rest-api/rest-block-type-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public static function wpTearDownAfterClass() {
self::delete_user( self::$admin_id );
self::delete_user( self::$subscriber_id );
unregister_block_type( 'fake/test' );
unregister_block_type( 'fake/invalid' );
unregister_block_type( 'fake/false' );
}

/**
Expand Down