--- a/wp/wp-includes/blocks/navigation.php Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/blocks/navigation.php Fri Sep 05 18:52:52 2025 +0200
@@ -241,13 +241,12 @@
// it encounters whitespace. This code strips it.
$blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
- 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];
-
- $blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks;
- }
+ // Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
+ // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
+ // before the parse_blocks() call further above, to avoid the extra serialization/parsing.
+ $markup = serialize_blocks( $blocks );
+ $markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
+ $blocks = parse_blocks( $markup );
// TODO - this uses the full navigation block attributes for the
// context which could be refined.
@@ -346,6 +345,10 @@
$navigation_name = $attributes['ariaLabel'] ?? '';
+ if ( ! empty( $navigation_name ) ) {
+ return $navigation_name;
+ }
+
// Load the navigation post.
if ( array_key_exists( 'ref', $attributes ) ) {
$navigation_post = get_post( $attributes['ref'] );
@@ -483,7 +486,7 @@
}
}
$toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
- $toggle_close_button_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg>';
+ $toggle_close_button_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"></path></svg>';
$toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
$toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
$toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
@@ -537,8 +540,8 @@
$inner_blocks_html,
$toggle_aria_label_open,
$toggle_aria_label_close,
- esc_attr( implode( ' ', $responsive_container_classes ) ),
- esc_attr( implode( ' ', $open_button_classes ) ),
+ esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ),
+ esc_attr( trim( implode( ' ', $open_button_classes ) ) ),
( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '',
$toggle_button_content,
$toggle_close_button_content,
@@ -565,13 +568,14 @@
$is_responsive_menu = static::is_responsive( $attributes );
$style = static::get_styles( $attributes );
$class = static::get_classes( $attributes );
- $wrapper_attributes = get_block_wrapper_attributes(
- array(
- 'class' => $class,
- 'style' => $style,
- 'aria-label' => $nav_menu_name,
- )
+ $extra_attributes = array(
+ 'class' => $class,
+ 'style' => $style,
);
+ if ( ! empty( $nav_menu_name ) ) {
+ $extra_attributes['aria-label'] = $nav_menu_name;
+ }
+ $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes );
if ( $is_responsive_menu ) {
$nav_element_directives = static::get_nav_element_directives( $is_interactive );
@@ -624,18 +628,7 @@
*/
private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
if ( static::is_interactive( $attributes, $inner_blocks ) ) {
- $suffix = wp_scripts_get_suffix();
- if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
- $module_url = gutenberg_url( '/build/interactivity/navigation.min.js' );
- }
-
- wp_register_script_module(
- '@wordpress/block-library/navigation',
- isset( $module_url ) ? $module_url : includes_url( "blocks/navigation/view{$suffix}.js" ),
- array( '@wordpress/interactivity' ),
- defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
- );
- wp_enqueue_script_module( '@wordpress/block-library/navigation' );
+ wp_enqueue_script_module( '@wordpress/block-library/navigation/view' );
}
}
@@ -826,7 +819,7 @@
) ) {
// Add directives to the parent `<li>`.
$tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
- $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' );
+ $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu", "modal": null }' );
$tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
$tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
$tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
@@ -1083,16 +1076,13 @@
// In this case default to the (Page List) fallback.
$fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
- 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 );
- $blocks = parse_blocks( $markup );
-
- if ( isset( $blocks[0]['innerBlocks'] ) ) {
- $fallback_blocks = $blocks[0]['innerBlocks'];
- }
- }
+ // 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.
+ // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
+ // before the parse_blocks() call further above, to avoid the extra serialization/parsing.
+ $markup = serialize_blocks( $fallback_blocks );
+ $markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
+ $fallback_blocks = parse_blocks( $markup );
}
/**
@@ -1446,245 +1436,3 @@
return null;
}
-
-/**
- * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
- *
- * @since 6.5.0
- *
- * @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.
- *
- * @since 6.5.0
- *
- * @param array $inner_blocks Parsed inner blocks of a Navigation block.
- * @param WP_Post $post `wp_navigation` post object corresponding to the block.
- *
- * @return array the normalized parsed blocks.
- */
-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 );
- if ( ! empty( $ignored_hooked_blocks ) ) {
- $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true );
- $attributes['metadata'] = array(
- 'ignoredHookedBlocks' => $ignored_hooked_blocks,
- );
- }
- }
-
- $mock_anchor_parent_block = array(
- 'blockName' => 'core/navigation',
- 'attrs' => $attributes,
- 'innerBlocks' => $inner_blocks,
- 'innerContent' => array_fill( 0, count( $inner_blocks ), 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.
- *
- * @since 6.5.0
- *
- * @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.
- *
- * @since 6.5.0
- *
- * @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.
- *
- * @access private
- * @since 6.5.0
- *
- * @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 ) ) {
- return $post;
- }
-
- /**
- * Skip meta generation when consumers intentionally update specific Navigation fields
- * and omit the content update.
- */
- if ( ! isset( $post->post_content ) ) {
- return $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 );
-
- /*
- * 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']
- : array();
-
- 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 );
- return $post;
-}
-
-/*
- * 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.
- */
-if (
- ! 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.
- *
- * @since 6.5.0
- *
- * @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'] ) ) {
- return $response;
- }
- $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 );
-
- return $response;
-}
-
-/*
- * 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.
- */
-if (
- ! 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 );
-}