Skip to content

Commit

Permalink
Block Hooks: Fix in Navigation block (#59021)
Browse files Browse the repository at this point in the history
Update the Block Hooks mechanism as used in the Navigation block upon writing the REST API response received from a client to the DB.

The Block Hooks mechanism recently underwent some overhaul (in WordPress/wordpress-develop#6087), which decoupled hooked block injection from setting the `ignoredHookedBlocks` metadata. The latter is no longer done by `insert_hooked_blocks` but by a separate function called `set_ignored_hooked_blocks_metadata` (that can be passed as a callback to the `make_{before|after}_block` visitor factories).

Since the implementation of the Navigation block relied on the previous functionality, it needs to be updated to work properly: We need to run block tree traversal with `set_ignored_hooked_blocks_metadata` rather than `insert_hooked_blocks` on the template content received from the endpoint for the Navigation post type, upon persisting to the DB.

Co-authored-by: ockham <[email protected]>
Co-authored-by: tjcafferkey <[email protected]>
Co-authored-by: gziolo <[email protected]>
  • Loading branch information
4 people authored Feb 19, 2024
1 parent 80f0554 commit 467aade
Showing 1 changed file with 88 additions and 32 deletions.
120 changes: 88 additions & 32 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) {
// it encounters whitespace. This code strips it.
$blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );

if ( function_exists( 'get_hooked_block_markup' ) ) {
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
// Run Block Hooks algorithm to inject hooked blocks.
$markup = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post );
$root_nav_block = parse_blocks( $markup )[0];
Expand Down Expand Up @@ -1024,7 +1024,7 @@ function block_core_navigation_get_fallback_blocks() {
// In this case default to the (Page List) fallback.
$fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;

if ( function_exists( 'get_hooked_block_markup' ) ) {
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
// Run Block Hooks algorithm to inject hooked blocks.
// We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
$markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post );
Expand Down Expand Up @@ -1369,25 +1369,28 @@ function block_core_navigation_get_most_recently_published_navigation() {
}

/**
* Insert hooked blocks into a Navigation block.
*
* Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
* this function inserts hooked blocks into it, and returns the serialized inner blocks in a
* mock Navigation block wrapper.
* Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
*
* If there are any hooked blocks that need to be inserted as the Navigation block's first or last
* children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
* of those hooked blocks should be exempted from insertion.
* @param string $serialized_block The serialized markup of a block and its inner blocks.
* @return string
*/
function block_core_navigation_remove_serialized_parent_block( $serialized_block ) {
$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
$end = strrpos( $serialized_block, '<!--' );
return substr( $serialized_block, $start, $end - $start );
}

/**
* Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object.
* The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute.
*
* @param array $inner_blocks Parsed inner blocks of a Navigation block.
* @param WP_Post $post `wp_navigation` post object corresponding to the block.
* @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
*
* @return array the normalized parsed blocks.
*/
function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
$before_block_visitor = null;
$after_block_visitor = null;
$hooked_blocks = get_hooked_blocks();
$attributes = array();
function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) {
$attributes = array();

if ( isset( $post->ID ) ) {
$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
Expand All @@ -1405,15 +1408,62 @@ function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
'innerBlocks' => $inner_blocks,
'innerContent' => array_fill( 0, count( $inner_blocks ), null ),
);
$before_block_visitor = null;
$after_block_visitor = null;

return $mock_anchor_parent_block;
}

/**
* Insert hooked blocks into a Navigation block.
*
* Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
* this function inserts hooked blocks into it, and returns the serialized inner blocks in a
* mock Navigation block wrapper.
*
* If there are any hooked blocks that need to be inserted as the Navigation block's first or last
* children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
* of those hooked blocks should be exempted from insertion.
*
* @param array $inner_blocks Parsed inner blocks of a Navigation block.
* @param WP_Post $post `wp_navigation` post object corresponding to the block.
* @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
*/
function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
$mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
$hooked_blocks = get_hooked_blocks();
$before_block_visitor = null;
$after_block_visitor = null;

if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
$before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
}

return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
}

/**
* Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks.
*
* Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
* this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a
* mock Navigation block wrapper.
*
* @param array $inner_blocks Parsed inner blocks of a Navigation block.
* @param WP_Post $post `wp_navigation` post object corresponding to the block.
* @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
*/
function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) {
$mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
$hooked_blocks = get_hooked_blocks();
$before_block_visitor = null;
$after_block_visitor = null;

if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
$before_block_visitor = make_before_block_visitor( $hooked_blocks, $post );
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $post );
$before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
}

return traverse_and_serialize_block( $mock_anchor_parent_block, $before_block_visitor, $after_block_visitor );
return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
}

/**
Expand All @@ -1422,12 +1472,11 @@ function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
* @param WP_Post $post Post object.
*/
function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
// We run the Block Hooks mechanism so it will return the list of ignored hooked blocks
// in the mock root Navigation block's metadata attribute.
// We ignore the rest of the returned `$markup`; `$post->post_content` already has the hooked
// blocks inserted, whereas `$markup` will have them inserted twice.
$blocks = parse_blocks( $post->post_content );
$markup = block_core_navigation_insert_hooked_blocks( $blocks, $post );
// We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
// all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
$blocks = parse_blocks( $post->post_content );
$markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, $post );

$root_nav_block = parse_blocks( $markup )[0];
$ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks']
Expand All @@ -1441,6 +1490,15 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
}
update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
}

$serialized_inner_blocks = block_core_navigation_remove_serialized_parent_block( $markup );

wp_update_post(
array(
'ID' => $post->ID,
'post_content' => $serialized_inner_blocks,
)
);
}

// Before adding our filter, we verify if it's already added in Core.
Expand All @@ -1450,7 +1508,7 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {

// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
// that are not present in Gutenberg's WP 6.5 compatibility layer.
if ( function_exists( 'get_hooked_block_markup' ) && ! has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
add_action( 'rest_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10, 3 );
}

Expand All @@ -1470,9 +1528,7 @@ function block_core_navigation_insert_hooked_blocks_into_rest_response( $respons
$content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post );

// Remove mock Navigation block wrapper.
$start = strpos( $content, '-->' ) + strlen( '-->' );
$end = strrpos( $content, '<!--' );
$content = substr( $content, $start, $end - $start );
$content = block_core_navigation_remove_serialized_parent_block( $content );

$response->data['content']['raw'] = $content;
$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
Expand All @@ -1487,6 +1543,6 @@ function block_core_navigation_insert_hooked_blocks_into_rest_response( $respons

// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
// that are not present in Gutenberg's WP 6.5 compatibility layer.
if ( function_exists( 'get_hooked_block_markup' ) && ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) ) {
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) ) {
add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 );
}

0 comments on commit 467aade

Please sign in to comment.