wp/wp-includes/blocks/navigation.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   239 
   239 
   240 			// 'parse_blocks' includes a null block with '\n\n' as the content when
   240 			// 'parse_blocks' includes a null block with '\n\n' as the content when
   241 			// it encounters whitespace. This code strips it.
   241 			// it encounters whitespace. This code strips it.
   242 			$blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
   242 			$blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
   243 
   243 
   244 			if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
   244 			// Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
   245 				// Run Block Hooks algorithm to inject hooked blocks.
   245 			// TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
   246 				$markup         = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post );
   246 			// before the parse_blocks() call further above, to avoid the extra serialization/parsing.
   247 				$root_nav_block = parse_blocks( $markup )[0];
   247 			$markup = serialize_blocks( $blocks );
   248 
   248 			$markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
   249 				$blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks;
   249 			$blocks = parse_blocks( $markup );
   250 			}
       
   251 
   250 
   252 			// TODO - this uses the full navigation block attributes for the
   251 			// TODO - this uses the full navigation block attributes for the
   253 			// context which could be refined.
   252 			// context which could be refined.
   254 			return new WP_Block_List( $blocks, $attributes );
   253 			return new WP_Block_List( $blocks, $attributes );
   255 		}
   254 		}
   344 	 */
   343 	 */
   345 	private static function get_navigation_name( $attributes ) {
   344 	private static function get_navigation_name( $attributes ) {
   346 
   345 
   347 		$navigation_name = $attributes['ariaLabel'] ?? '';
   346 		$navigation_name = $attributes['ariaLabel'] ?? '';
   348 
   347 
       
   348 		if ( ! empty( $navigation_name ) ) {
       
   349 			return $navigation_name;
       
   350 		}
       
   351 
   349 		// Load the navigation post.
   352 		// Load the navigation post.
   350 		if ( array_key_exists( 'ref', $attributes ) ) {
   353 		if ( array_key_exists( 'ref', $attributes ) ) {
   351 			$navigation_post = get_post( $attributes['ref'] );
   354 			$navigation_post = get_post( $attributes['ref'] );
   352 			if ( ! isset( $navigation_post ) ) {
   355 			if ( ! isset( $navigation_post ) ) {
   353 				return $navigation_name;
   356 				return $navigation_name;
   481 			if ( 'menu' === $attributes['icon'] ) {
   484 			if ( 'menu' === $attributes['icon'] ) {
   482 				$toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5zm0 7.8h14v-1.5H5v1.5zM5 19h14v-1.5H5V19z" /></svg>';
   485 				$toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5zm0 7.8h14v-1.5H5v1.5zM5 19h14v-1.5H5V19z" /></svg>';
   483 			}
   486 			}
   484 		}
   487 		}
   485 		$toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
   488 		$toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
   486 		$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>';
   489 		$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>';
   487 		$toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
   490 		$toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
   488 		$toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
   491 		$toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
   489 		$toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
   492 		$toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
   490 
   493 
   491 		// Add Interactivity API directives to the markup if needed.
   494 		// Add Interactivity API directives to the markup if needed.
   535 				</div>',
   538 				</div>',
   536 			esc_attr( $modal_unique_id ),
   539 			esc_attr( $modal_unique_id ),
   537 			$inner_blocks_html,
   540 			$inner_blocks_html,
   538 			$toggle_aria_label_open,
   541 			$toggle_aria_label_open,
   539 			$toggle_aria_label_close,
   542 			$toggle_aria_label_close,
   540 			esc_attr( implode( ' ', $responsive_container_classes ) ),
   543 			esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ),
   541 			esc_attr( implode( ' ', $open_button_classes ) ),
   544 			esc_attr( trim( implode( ' ', $open_button_classes ) ) ),
   542 			( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '',
   545 			( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '',
   543 			$toggle_button_content,
   546 			$toggle_button_content,
   544 			$toggle_close_button_content,
   547 			$toggle_close_button_content,
   545 			$open_button_directives,
   548 			$open_button_directives,
   546 			$responsive_container_directives,
   549 			$responsive_container_directives,
   563 		$nav_menu_name      = static::get_unique_navigation_name( $attributes );
   566 		$nav_menu_name      = static::get_unique_navigation_name( $attributes );
   564 		$is_interactive     = static::is_interactive( $attributes, $inner_blocks );
   567 		$is_interactive     = static::is_interactive( $attributes, $inner_blocks );
   565 		$is_responsive_menu = static::is_responsive( $attributes );
   568 		$is_responsive_menu = static::is_responsive( $attributes );
   566 		$style              = static::get_styles( $attributes );
   569 		$style              = static::get_styles( $attributes );
   567 		$class              = static::get_classes( $attributes );
   570 		$class              = static::get_classes( $attributes );
   568 		$wrapper_attributes = get_block_wrapper_attributes(
   571 		$extra_attributes   = array(
   569 			array(
   572 			'class' => $class,
   570 				'class'      => $class,
   573 			'style' => $style,
   571 				'style'      => $style,
       
   572 				'aria-label' => $nav_menu_name,
       
   573 			)
       
   574 		);
   574 		);
       
   575 		if ( ! empty( $nav_menu_name ) ) {
       
   576 			$extra_attributes['aria-label'] = $nav_menu_name;
       
   577 		}
       
   578 		$wrapper_attributes = get_block_wrapper_attributes( $extra_attributes );
   575 
   579 
   576 		if ( $is_responsive_menu ) {
   580 		if ( $is_responsive_menu ) {
   577 			$nav_element_directives = static::get_nav_element_directives( $is_interactive );
   581 			$nav_element_directives = static::get_nav_element_directives( $is_interactive );
   578 			$wrapper_attributes    .= ' ' . $nav_element_directives;
   582 			$wrapper_attributes    .= ' ' . $nav_element_directives;
   579 		}
   583 		}
   622 	 * @param WP_Block      $block        The parsed block.
   626 	 * @param WP_Block      $block        The parsed block.
   623 	 * @param WP_Block_List $inner_blocks The list of inner blocks.
   627 	 * @param WP_Block_List $inner_blocks The list of inner blocks.
   624 	 */
   628 	 */
   625 	private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
   629 	private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
   626 		if ( static::is_interactive( $attributes, $inner_blocks ) ) {
   630 		if ( static::is_interactive( $attributes, $inner_blocks ) ) {
   627 			$suffix = wp_scripts_get_suffix();
   631 			wp_enqueue_script_module( '@wordpress/block-library/navigation/view' );
   628 			if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
       
   629 				$module_url = gutenberg_url( '/build/interactivity/navigation.min.js' );
       
   630 			}
       
   631 
       
   632 			wp_register_script_module(
       
   633 				'@wordpress/block-library/navigation',
       
   634 				isset( $module_url ) ? $module_url : includes_url( "blocks/navigation/view{$suffix}.js" ),
       
   635 				array( '@wordpress/interactivity' ),
       
   636 				defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
       
   637 			);
       
   638 			wp_enqueue_script_module( '@wordpress/block-library/navigation' );
       
   639 		}
   632 		}
   640 	}
   633 	}
   641 
   634 
   642 	/**
   635 	/**
   643 	 * Returns the markup for the navigation block.
   636 	 * Returns the markup for the navigation block.
   824 			'class_name' => 'has-child',
   817 			'class_name' => 'has-child',
   825 		)
   818 		)
   826 	) ) {
   819 	) ) {
   827 		// Add directives to the parent `<li>`.
   820 		// Add directives to the parent `<li>`.
   828 		$tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
   821 		$tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
   829 		$tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' );
   822 		$tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu", "modal": null }' );
   830 		$tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
   823 		$tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
   831 		$tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
   824 		$tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
   832 		$tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
   825 		$tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
   833 
   826 
   834 		// This is a fix for Safari. Without it, Safari doesn't change the active
   827 		// This is a fix for Safari. Without it, Safari doesn't change the active
  1081 
  1074 
  1082 		// Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
  1075 		// Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
  1083 		// In this case default to the (Page List) fallback.
  1076 		// In this case default to the (Page List) fallback.
  1084 		$fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
  1077 		$fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
  1085 
  1078 
  1086 		if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
  1079 		// Run Block Hooks algorithm to inject hooked blocks.
  1087 			// Run Block Hooks algorithm to inject hooked blocks.
  1080 		// We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
  1088 			// We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
  1081 		// TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
  1089 			$markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post );
  1082 		// before the parse_blocks() call further above, to avoid the extra serialization/parsing.
  1090 			$blocks = parse_blocks( $markup );
  1083 		$markup          = serialize_blocks( $fallback_blocks );
  1091 
  1084 		$markup          = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
  1092 			if ( isset( $blocks[0]['innerBlocks'] ) ) {
  1085 		$fallback_blocks = parse_blocks( $markup );
  1093 				$fallback_blocks = $blocks[0]['innerBlocks'];
       
  1094 			}
       
  1095 		}
       
  1096 	}
  1086 	}
  1097 
  1087 
  1098 	/**
  1088 	/**
  1099 	 * Filters the fallback experience for the Navigation block.
  1089 	 * Filters the fallback experience for the Navigation block.
  1100 	 *
  1090 	 *
  1444 		return $navigation_post->posts[0];
  1434 		return $navigation_post->posts[0];
  1445 	}
  1435 	}
  1446 
  1436 
  1447 	return null;
  1437 	return null;
  1448 }
  1438 }
  1449 
       
  1450 /**
       
  1451  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
       
  1452  *
       
  1453  * @since 6.5.0
       
  1454  *
       
  1455  * @param string $serialized_block The serialized markup of a block and its inner blocks.
       
  1456  * @return string
       
  1457  */
       
  1458 function block_core_navigation_remove_serialized_parent_block( $serialized_block ) {
       
  1459 	$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
       
  1460 	$end   = strrpos( $serialized_block, '<!--' );
       
  1461 	return substr( $serialized_block, $start, $end - $start );
       
  1462 }
       
  1463 
       
  1464 /**
       
  1465  * Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object.
       
  1466  * The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute.
       
  1467  *
       
  1468  * @since 6.5.0
       
  1469  *
       
  1470  * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
       
  1471  * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
       
  1472  *
       
  1473  * @return array the normalized parsed blocks.
       
  1474  */
       
  1475 function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) {
       
  1476 	$attributes = array();
       
  1477 
       
  1478 	if ( isset( $post->ID ) ) {
       
  1479 		$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1480 		if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1481 			$ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
       
  1482 			$attributes['metadata'] = array(
       
  1483 				'ignoredHookedBlocks' => $ignored_hooked_blocks,
       
  1484 			);
       
  1485 		}
       
  1486 	}
       
  1487 
       
  1488 	$mock_anchor_parent_block = array(
       
  1489 		'blockName'    => 'core/navigation',
       
  1490 		'attrs'        => $attributes,
       
  1491 		'innerBlocks'  => $inner_blocks,
       
  1492 		'innerContent' => array_fill( 0, count( $inner_blocks ), null ),
       
  1493 	);
       
  1494 
       
  1495 	return $mock_anchor_parent_block;
       
  1496 }
       
  1497 
       
  1498 /**
       
  1499  * Insert hooked blocks into a Navigation block.
       
  1500  *
       
  1501  * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
       
  1502  * this function inserts hooked blocks into it, and returns the serialized inner blocks in a
       
  1503  * mock Navigation block wrapper.
       
  1504  *
       
  1505  * If there are any hooked blocks that need to be inserted as the Navigation block's first or last
       
  1506  * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
       
  1507  * of those hooked blocks should be exempted from insertion.
       
  1508  *
       
  1509  * @since 6.5.0
       
  1510  *
       
  1511  * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
       
  1512  * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
       
  1513  * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
       
  1514  */
       
  1515 function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
       
  1516 	$mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
       
  1517 	$hooked_blocks         = get_hooked_blocks();
       
  1518 	$before_block_visitor  = null;
       
  1519 	$after_block_visitor   = null;
       
  1520 
       
  1521 	if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
       
  1522 		$before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
       
  1523 		$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
       
  1524 	}
       
  1525 
       
  1526 	return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
       
  1527 }
       
  1528 
       
  1529 /**
       
  1530  * Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks.
       
  1531  *
       
  1532  * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
       
  1533  * this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a
       
  1534  * mock Navigation block wrapper.
       
  1535  *
       
  1536  * @since 6.5.0
       
  1537  *
       
  1538  * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
       
  1539  * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
       
  1540  * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
       
  1541  */
       
  1542 function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) {
       
  1543 	$mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
       
  1544 	$hooked_blocks         = get_hooked_blocks();
       
  1545 	$before_block_visitor  = null;
       
  1546 	$after_block_visitor   = null;
       
  1547 
       
  1548 	if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
       
  1549 		$before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
       
  1550 		$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
       
  1551 	}
       
  1552 
       
  1553 	return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
       
  1554 }
       
  1555 
       
  1556 /**
       
  1557  * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
       
  1558  *
       
  1559  * @access private
       
  1560  * @since 6.5.0
       
  1561  *
       
  1562  * @param stdClass $post Post object.
       
  1563  * @return stdClass The updated post object.
       
  1564  */
       
  1565 function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
       
  1566 	/*
       
  1567 	 * In this scenario the user has likely tried to create a navigation via the REST API.
       
  1568 	 * In which case we won't have a post ID to work with and store meta against.
       
  1569 	 */
       
  1570 	if ( empty( $post->ID ) ) {
       
  1571 		return $post;
       
  1572 	}
       
  1573 
       
  1574 	/**
       
  1575 	 * Skip meta generation when consumers intentionally update specific Navigation fields
       
  1576 	 * and omit the content update.
       
  1577 	 */
       
  1578 	if ( ! isset( $post->post_content ) ) {
       
  1579 		return $post;
       
  1580 	}
       
  1581 
       
  1582 	/*
       
  1583 	 * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
       
  1584 	 * all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
       
  1585 	 */
       
  1586 	$blocks = parse_blocks( $post->post_content );
       
  1587 
       
  1588 	/*
       
  1589 	 * Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that
       
  1590 	 * we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be
       
  1591 	 * used as context for hooked blocks insertion).
       
  1592 	 * We thus have to look it up from the DB,based on `$post->ID`.
       
  1593 	 */
       
  1594 	$markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) );
       
  1595 
       
  1596 	$root_nav_block        = parse_blocks( $markup )[0];
       
  1597 	$ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
       
  1598 		? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks']
       
  1599 		: array();
       
  1600 
       
  1601 	if ( ! empty( $ignored_hooked_blocks ) ) {
       
  1602 		$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
       
  1603 		if ( ! empty( $existing_ignored_hooked_blocks ) ) {
       
  1604 			$existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
       
  1605 			$ignored_hooked_blocks          = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
       
  1606 		}
       
  1607 		update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
       
  1608 	}
       
  1609 
       
  1610 	$post->post_content = block_core_navigation_remove_serialized_parent_block( $markup );
       
  1611 	return $post;
       
  1612 }
       
  1613 
       
  1614 /*
       
  1615  * Before adding our filter, we verify if it's already added in Core.
       
  1616  * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
       
  1617  * Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
       
  1618  */
       
  1619 $rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
       
  1620 
       
  1621 /*
       
  1622  * Do not add the `block_core_navigation_update_ignore_hooked_blocks_meta` filter in the following cases:
       
  1623  * - If Core has added the `update_ignored_hooked_blocks_postmeta` filter already (WP >= 6.6);
       
  1624  * - 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);
       
  1625  * - or if the `$rest_insert_wp_navigation_core_callback` filter has already been added.
       
  1626  */
       
  1627 if (
       
  1628 	! has_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' ) &&
       
  1629 	function_exists( 'set_ignored_hooked_blocks_metadata' ) &&
       
  1630 	! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback )
       
  1631 ) {
       
  1632 	add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' );
       
  1633 }
       
  1634 
       
  1635 /*
       
  1636  * Previous versions of Gutenberg were attaching the block_core_navigation_update_ignore_hooked_blocks_meta
       
  1637  * function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_).
       
  1638  * To avoid collisions, we need to remove the filter from that action if it's present.
       
  1639  */
       
  1640 if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
       
  1641 	remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback );
       
  1642 }
       
  1643 
       
  1644 /**
       
  1645  * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
       
  1646  *
       
  1647  * @since 6.5.0
       
  1648  *
       
  1649  * @param WP_REST_Response $response The response object.
       
  1650  * @param WP_Post          $post     Post object.
       
  1651  * @return WP_REST_Response The response object.
       
  1652  */
       
  1653 function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) {
       
  1654 	if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
       
  1655 		return $response;
       
  1656 	}
       
  1657 	$parsed_blocks = parse_blocks( $response->data['content']['raw'] );
       
  1658 	$content       = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post );
       
  1659 
       
  1660 	// Remove mock Navigation block wrapper.
       
  1661 	$content = block_core_navigation_remove_serialized_parent_block( $content );
       
  1662 
       
  1663 	$response->data['content']['raw'] = $content;
       
  1664 
       
  1665 	/** This filter is documented in wp-includes/post-template.php */
       
  1666 	$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
       
  1667 
       
  1668 	return $response;
       
  1669 }
       
  1670 
       
  1671 /*
       
  1672  *  Before adding our filter, we verify if it's already added in Core.
       
  1673  * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
       
  1674  * Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
       
  1675  */
       
  1676 $rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response';
       
  1677 
       
  1678 /*
       
  1679  * Do not add the `block_core_navigation_insert_hooked_blocks_into_rest_response` filter in the following cases:
       
  1680  * - If Core has added the `insert_hooked_blocks_into_rest_response` filter already (WP >= 6.6);
       
  1681  * - 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);
       
  1682  * - or if the `$rest_prepare_wp_navigation_core_callback` filter has already been added.
       
  1683  */
       
  1684 if (
       
  1685 	! has_filter( 'rest_prepare_wp_navigation', 'insert_hooked_blocks_into_rest_response' ) &&
       
  1686 	function_exists( 'set_ignored_hooked_blocks_metadata' ) &&
       
  1687 	! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback )
       
  1688 ) {
       
  1689 	add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 );
       
  1690 }