wp/wp-includes/blocks/navigation.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
--- 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 );
-}