: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* 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, '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_navigation_block, $before_block_visitor, $after_block_visitor );
* Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
* @param stdClass $post Post object.
* @return stdClass The updated post object.
function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
* In this scenario the user has likely tried to create a navigation via the REST API.
* In which case we won't have a post ID to work with and store meta against.
if ( empty( $post->ID ) ) {
* Skip meta generation when consumers intentionally update specific Navigation fields
* and omit the content update.
if ( ! isset( $post->post_content ) ) {
* 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 );
* Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that
* we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be
* used as context for hooked blocks insertion).
* We thus have to look it up from the DB,based on `$post->ID`.
$markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) );
$root_nav_block = parse_blocks( $markup )[0];
$ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks']
if ( ! empty( $ignored_hooked_blocks ) ) {
$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
if ( ! empty( $existing_ignored_hooked_blocks ) ) {
$existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
$ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
$post->post_content = block_core_navigation_remove_serialized_parent_block( $markup );
* Before adding our filter, we verify if it's already added in Core.
* However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
* Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
$rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
* Do not add the `block_core_navigation_update_ignore_hooked_blocks_meta` filter in the following cases:
* - If Core has added the `update_ignored_hooked_blocks_postmeta` filter already (WP >= 6.6);
* - or if the `set_ignored_hooked_blocks_metadata` function is unavailable (which is required for the filter to work. It was introduced by WP 6.5 but is not present in Gutenberg's WP 6.5 compatibility layer);
* - or if the `$rest_insert_wp_navigation_core_callback` filter has already been added.
! has_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' ) &&
function_exists( 'set_ignored_hooked_blocks_metadata' ) &&
! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback )
add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' );
* Previous versions of Gutenberg were attaching the block_core_navigation_update_ignore_hooked_blocks_meta
* function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_).
* To avoid collisions, we need to remove the filter from that action if it's present.
if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback );
* Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
* @param WP_REST_Response $response The response object.
* @param WP_Post $post Post object.
* @return WP_REST_Response The response object.
function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) {
if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
$parsed_blocks = parse_blocks( $response->data['content']['raw'] );
$content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post );
// Remove mock Navigation block wrapper.
$content = block_core_navigation_remove_serialized_parent_block( $content );
$response->data['content']['raw'] = $content;
/** This filter is documented in wp-includes/post-template.php */
$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
* Before adding our filter, we verify if it's already added in Core.
* However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
* Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
$rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response';
* Do not add the `block_core_navigation_insert_hooked_blocks_into_rest_response` filter in the following cases:
* - If Core has added the `insert_hooked_blocks_into_rest_response` filter already (WP >= 6.6);
* - or if the `set_ignored_hooked_blocks_metadata` function is unavailable (which is required for the filter to work. It was introduced by WP 6.5 but is not present in Gutenberg's WP 6.5 compatibility layer);
* - or if the `$rest_prepare_wp_navigation_core_callback` filter has already been added.
! has_filter( 'rest_prepare_wp_navigation', 'insert_hooked_blocks_into_rest_response' ) &&
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 );