[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/blocks/ -> navigation.php (source)

   1  <?php
   2  /**
   3   * Server-side rendering of the `core/navigation` block.
   4   *
   5   * @package WordPress
   6   */
   7  
   8  /**
   9   * Returns the submenu visibility value with backward compatibility
  10   * for the deprecated openSubmenusOnClick attribute.
  11   *
  12   * This function centralizes the migration logic from the boolean
  13   * openSubmenusOnClick to the new submenuVisibility enum.
  14   *
  15   * Backward compatibility: WordPress applies default attribute values, so submenuVisibility
  16   * will always have a value even for legacy blocks. We check the legacy openSubmenusOnClick
  17   * attribute first to preserve original behavior for blocks saved before the migration.
  18   *
  19   * @since 6.9.0
  20   *
  21   * @param array $attributes Block attributes containing submenuVisibility and/or openSubmenusOnClick.
  22   * @return string The visibility mode: 'hover', 'click', or 'always'.
  23   */
  24  function block_core_navigation_get_submenu_visibility( $attributes ) {
  25      $deprecated_open_submenus_on_click = $attributes['openSubmenusOnClick'] ?? null;
  26  
  27      // For backward compatibility, prioritize the legacy attribute if present.
  28      // Legacy blocks have openSubmenusOnClick in the database. Since WordPress applies
  29      // default values, submenuVisibility will also have a value, but we check the legacy
  30      // attribute first to preserve the original behavior. If the block has been updated
  31      // and saved in the editor, then the deprecated attribute will be replaced by submenuVisibility.
  32      if ( null !== $deprecated_open_submenus_on_click ) {
  33          // Convert boolean to string: true -> 'click', false -> 'hover'.
  34          return ! empty( $deprecated_open_submenus_on_click ) ? 'click' : 'hover';
  35      }
  36  
  37      $submenu_visibility = $attributes['submenuVisibility'] ?? null;
  38  
  39      // Use submenuVisibility for migrated/new blocks (where openSubmenusOnClick is null).
  40      return $submenu_visibility ?? 'hover';
  41  }
  42  
  43  /**
  44   * Helper functions used to render the navigation block.
  45   *
  46   * @since 6.5.0
  47   */
  48  class WP_Navigation_Block_Renderer {
  49  
  50      /**
  51       * Used to determine whether or not a navigation has submenus.
  52       *
  53       * @since 6.5.0
  54       */
  55      private static $has_submenus = false;
  56  
  57      /**
  58       * Used to determine which blocks need an <li> wrapper.
  59       *
  60       * @since 6.5.0
  61       *
  62       * @var array
  63       */
  64      private static $needs_list_item_wrapper = array(
  65          'core/site-title',
  66          'core/site-logo',
  67          'core/social-links',
  68      );
  69  
  70      /**
  71       * Keeps track of all the navigation names that have been seen.
  72       *
  73       * @since 6.5.0
  74       *
  75       * @var array
  76       */
  77      private static $seen_menu_names = array();
  78  
  79  
  80      /**
  81       * Returns whether or not this is responsive navigation.
  82       *
  83       * @since 6.5.0
  84       *
  85       * @param array $attributes The block attributes.
  86       * @return bool Returns whether or not this is responsive navigation.
  87       */
  88  	private static function is_responsive( $attributes ) {
  89          /**
  90           * This is for backwards compatibility after the `isResponsive` attribute was been removed.
  91           */
  92  
  93          $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
  94          return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
  95      }
  96  
  97      /**
  98       * Returns whether or not a navigation has a submenu.
  99       *
 100       * @since 6.5.0
 101       *
 102       * @param WP_Block_List $inner_blocks The list of inner blocks.
 103       * @return bool Returns whether or not a navigation has a submenu and also sets the member variable.
 104       */
 105  	private static function has_submenus( $inner_blocks ) {
 106          if ( true === static::$has_submenus ) {
 107              return static::$has_submenus;
 108          }
 109  
 110          foreach ( $inner_blocks as $inner_block ) {
 111              // If this is a page list then work out if any of the pages have children.
 112              if ( 'core/page-list' === $inner_block->name ) {
 113                  $all_pages = get_pages(
 114                      array(
 115                          'sort_column' => 'menu_order,post_title',
 116                          'order'       => 'asc',
 117                      )
 118                  );
 119                  foreach ( (array) $all_pages as $page ) {
 120                      if ( $page->post_parent ) {
 121                          static::$has_submenus = true;
 122                          break;
 123                      }
 124                  }
 125              }
 126              // If this is a navigation submenu then we know we have submenus.
 127              if ( 'core/navigation-submenu' === $inner_block->name ) {
 128                  static::$has_submenus = true;
 129                  break;
 130              }
 131          }
 132  
 133          return static::$has_submenus;
 134      }
 135  
 136      /**
 137       * Determine whether the navigation blocks is interactive.
 138       *
 139       * @since 6.5.0
 140       *
 141       * @param array         $attributes   The block attributes.
 142       * @param WP_Block_List $inner_blocks The list of inner blocks.
 143       * @return bool Returns whether or not to load the view script.
 144       */
 145  	private static function is_interactive( $attributes, $inner_blocks ) {
 146          $has_submenus        = static::has_submenus( $inner_blocks );
 147          $is_responsive_menu  = static::is_responsive( $attributes );
 148          $computed_visibility = block_core_navigation_get_submenu_visibility( $attributes );
 149          $open_on_click       = 'click' === $computed_visibility;
 150          $show_submenu_icon   = ! empty( $attributes['showSubmenuIcon'] );
 151          return ( $has_submenus && ( $open_on_click || $show_submenu_icon ) ) || $is_responsive_menu;
 152      }
 153  
 154      /**
 155       * Returns whether or not a block needs a list item wrapper.
 156       *
 157       * @since 6.5.0
 158       *
 159       * @param WP_Block $block The block.
 160       * @return bool Returns whether or not a block needs a list item wrapper.
 161       */
 162  	private static function does_block_need_a_list_item_wrapper( $block ) {
 163  
 164          /**
 165           * Filter the list of blocks that need a list item wrapper.
 166           *
 167           * Affords the ability to customize which blocks need a list item wrapper when rendered
 168           * within a core/navigation block.
 169           * This is useful for blocks that are not list items but should be wrapped in a list
 170           * item when used as a child of a navigation block.
 171           *
 172           * @since 6.5.0
 173           *
 174           * @param array $needs_list_item_wrapper The list of blocks that need a list item wrapper.
 175           */
 176          $needs_list_item_wrapper = apply_filters( 'block_core_navigation_listable_blocks', static::$needs_list_item_wrapper );
 177  
 178          return in_array( $block->name, $needs_list_item_wrapper, true );
 179      }
 180  
 181      /**
 182       * Returns the markup for a single inner block.
 183       *
 184       * @since 6.5.0
 185       *
 186       * @param WP_Block $inner_block The inner block.
 187       * @return string Returns the markup for a single inner block.
 188       */
 189  	private static function get_markup_for_inner_block( $inner_block ) {
 190          $inner_block_content = $inner_block->render();
 191          if ( ! empty( $inner_block_content ) ) {
 192              if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) {
 193                  return '<li class="wp-block-navigation-item">' . $inner_block_content . '</li>';
 194              }
 195          }
 196  
 197          return $inner_block_content;
 198      }
 199  
 200      /**
 201       * Returns the html for blocks from a template part (without navigation container wrapper).
 202       *
 203       * @since 6.5.0
 204       *
 205       * @param WP_Block_List $blocks The list of blocks to render.
 206       * @return string Returns the html for the template part blocks.
 207       */
 208  	private static function get_template_part_blocks_html( $blocks ) {
 209          $html = '';
 210          foreach ( $blocks as $block ) {
 211              $html .= $block->render();
 212          }
 213          return $html;
 214      }
 215  
 216      /**
 217       * Returns the html for the inner blocks of the navigation block.
 218       *
 219       * @since 6.5.0
 220       *
 221       * @param array         $attributes   The block attributes.
 222       * @param WP_Block_List $inner_blocks The list of inner blocks.
 223       * @return string Returns the html for the inner blocks of the navigation block.
 224       */
 225  	private static function get_inner_blocks_html( $attributes, $inner_blocks ) {
 226          $has_submenus   = static::has_submenus( $inner_blocks );
 227          $is_interactive = static::is_interactive( $attributes, $inner_blocks );
 228  
 229          $style                = static::get_styles( $attributes );
 230          $class                = static::get_classes( $attributes );
 231          $container_attributes = get_block_wrapper_attributes(
 232              array(
 233                  'class' => 'wp-block-navigation__container ' . $class,
 234                  'style' => $style,
 235              )
 236          );
 237  
 238          $inner_blocks_html = '';
 239          $is_list_open      = false;
 240  
 241          foreach ( $inner_blocks as $inner_block ) {
 242              $inner_block_markup = static::get_markup_for_inner_block( $inner_block );
 243              $p                  = new WP_HTML_Tag_Processor( $inner_block_markup );
 244              $is_list_item       = $p->next_tag( 'LI' );
 245  
 246              if ( $is_list_item && ! $is_list_open ) {
 247                  $is_list_open       = true;
 248                  $inner_blocks_html .= sprintf(
 249                      '<ul %1$s>',
 250                      $container_attributes
 251                  );
 252              }
 253  
 254              if ( ! $is_list_item && $is_list_open ) {
 255                  $is_list_open       = false;
 256                  $inner_blocks_html .= '</ul>';
 257              }
 258  
 259              $inner_blocks_html .= $inner_block_markup;
 260          }
 261  
 262          if ( $is_list_open ) {
 263              $inner_blocks_html .= '</ul>';
 264          }
 265  
 266          // Add directives to the submenu if needed.
 267          if ( $has_submenus && $is_interactive ) {
 268              $tags              = new WP_HTML_Tag_Processor( $inner_blocks_html );
 269              $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes );
 270          }
 271  
 272          return $inner_blocks_html;
 273      }
 274  
 275      /**
 276       * Gets the inner blocks for the navigation block from the navigation post.
 277       *
 278       * @since 6.5.0
 279       *
 280       * @param array $attributes The block attributes.
 281       * @return WP_Block_List Returns the inner blocks for the navigation block.
 282       */
 283  	private static function get_inner_blocks_from_navigation_post( $attributes ) {
 284          $navigation_post = get_post( $attributes['ref'] );
 285          if ( ! isset( $navigation_post ) ) {
 286              return new WP_Block_List( array(), $attributes );
 287          }
 288  
 289          // Only published posts are valid. If this is changed then a corresponding change
 290          // must also be implemented in `use-navigation-menu.js`.
 291          if ( 'publish' === $navigation_post->post_status ) {
 292              $parsed_blocks = parse_blocks( $navigation_post->post_content );
 293  
 294              // 'parse_blocks' includes a null block with '\n\n' as the content when
 295              // it encounters whitespace. This code strips it.
 296              $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
 297  
 298              // Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
 299              // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
 300              // before the parse_blocks() call further above, to avoid the extra serialization/parsing.
 301              $markup = serialize_blocks( $blocks );
 302              $markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
 303              $blocks = parse_blocks( $markup );
 304  
 305              // TODO - this uses the full navigation block attributes for the
 306              // context which could be refined.
 307              return new WP_Block_List( $blocks, $attributes );
 308          }
 309      }
 310  
 311      /**
 312       * Gets the inner blocks for the navigation block from the fallback.
 313       *
 314       * @since 6.5.0
 315       *
 316       * @param array $attributes The block attributes.
 317       * @return WP_Block_List Returns the inner blocks for the navigation block.
 318       */
 319  	private static function get_inner_blocks_from_fallback( $attributes ) {
 320          $fallback_blocks = block_core_navigation_get_fallback_blocks();
 321  
 322          // Fallback my have been filtered so do basic test for validity.
 323          if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
 324              return new WP_Block_List( array(), $attributes );
 325          }
 326  
 327          return new WP_Block_List( $fallback_blocks, $attributes );
 328      }
 329  
 330      /**
 331       * Recursively disables overlay menu for navigation blocks within overlay blocks.
 332       * Prevents nested overlays (inception).
 333       *
 334       * @since 6.5.0
 335       *
 336       * @param array $blocks Array of parsed block arrays.
 337       * @return array Modified blocks with overlayMenu set to 'never' for navigation blocks.
 338       */
 339      private static function disable_overlay_menu_for_nested_navigation_blocks( $blocks ) {
 340          if ( empty( $blocks ) || ! is_array( $blocks ) ) {
 341              return $blocks;
 342          }
 343  
 344          foreach ( $blocks as &$block ) {
 345              if ( ! isset( $block['blockName'] ) ) {
 346                  continue;
 347              }
 348  
 349              // If this is a navigation block, disable its overlay menu.
 350              if ( 'core/navigation' === $block['blockName'] ) {
 351                  if ( ! isset( $block['attrs'] ) ) {
 352                      $block['attrs'] = array();
 353                  }
 354                  $block['attrs']['overlayMenu'] = 'never';
 355                  // Mark this as a nested navigation within an overlay template part
 356                  // so we can handle its rendering differently.
 357                  $block['attrs']['_isWithinOverlayTemplatePart'] = true;
 358              }
 359  
 360              // Recursively process inner blocks.
 361              if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
 362                  $block['innerBlocks'] = static::disable_overlay_menu_for_nested_navigation_blocks( $block['innerBlocks'] );
 363              }
 364          }
 365  
 366          return $blocks;
 367      }
 368  
 369      /**
 370       * Gets the inner blocks for the navigation block from an overlay template part.
 371       *
 372       * @since 6.5.0
 373       *
 374       * @param string $overlay_template_part_id The overlay template part ID in format "theme//slug".
 375       * @param array  $attributes                The block attributes.
 376       * @return WP_Block_List Returns the inner blocks for the overlay template part.
 377       */
 378  	private static function get_overlay_blocks_from_template_part( $overlay_template_part_id, $attributes ) {
 379          if ( empty( $overlay_template_part_id ) || ! is_string( $overlay_template_part_id ) ) {
 380              return new WP_Block_List( array(), $attributes );
 381          }
 382  
 383          // Parse the template part ID (format: "theme//slug").
 384          // If it's just a slug, construct the full ID using the current theme.
 385          $parts = explode( '//', $overlay_template_part_id, 2 );
 386          if ( count( $parts ) === 2 ) {
 387              // Already in "theme//slug" format (backward compatibility).
 388              $theme = $parts[0];
 389              $slug  = $parts[1];
 390          } else {
 391              // Just a slug, use current theme.
 392              $theme = get_stylesheet();
 393              $slug  = $overlay_template_part_id;
 394          }
 395  
 396          // Only query for template parts from the active theme.
 397          if ( get_stylesheet() !== $theme ) {
 398              return new WP_Block_List( array(), $attributes );
 399          }
 400  
 401          // Query for the template part post.
 402          $template_part_query = new WP_Query(
 403              array(
 404                  'post_type'           => 'wp_template_part',
 405                  'post_status'         => 'publish',
 406                  'post_name__in'       => array( $slug ),
 407                  'tax_query'           => array(
 408                      array(
 409                          'taxonomy' => 'wp_theme',
 410                          'field'    => 'name',
 411                          'terms'    => $theme,
 412                      ),
 413                  ),
 414                  'posts_per_page'      => 1,
 415                  'no_found_rows'       => true,
 416                  'lazy_load_term_meta' => false, // Do not lazy load term meta, as template parts only have one term.
 417              )
 418          );
 419  
 420          $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null;
 421  
 422          if ( ! $template_part_post ) {
 423              // Try to get from theme file if not in database.
 424              // Construct the full template part ID for get_block_file_template.
 425              $full_template_part_id = $theme . '//' . $slug;
 426              $block_template        = get_block_file_template( $full_template_part_id, 'wp_template_part' );
 427              if ( isset( $block_template->content ) ) {
 428                  // Expand shortcodes before parsing blocks, matching the order in
 429                  // `render_block_core_template_part()`.
 430                  $content       = shortcode_unautop( $block_template->content );
 431                  $content       = do_shortcode( $content );
 432                  $parsed_blocks = parse_blocks( $content );
 433                  $blocks        = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
 434                  // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
 435                  $blocks = static::disable_overlay_menu_for_nested_navigation_blocks( $blocks );
 436                  return new WP_Block_List( $blocks, $attributes );
 437              }
 438              return new WP_Block_List( array(), $attributes );
 439          }
 440  
 441          // Get the template part content.
 442          $block_template = _build_block_template_result_from_post( $template_part_post );
 443          if ( ! isset( $block_template->content ) ) {
 444              return new WP_Block_List( array(), $attributes );
 445          }
 446  
 447          $parsed_blocks = parse_blocks( $block_template->content );
 448  
 449          // 'parse_blocks' includes a null block with '\n\n' as the content when
 450          // it encounters whitespace. This code strips it.
 451          $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
 452  
 453          // Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
 454          $markup = serialize_blocks( $blocks );
 455          $markup = apply_block_hooks_to_content_from_post_object( $markup, $template_part_post );
 456  
 457          // Expand shortcodes before parsing blocks, matching the order in
 458          // `render_block_core_template_part()`.
 459          $markup = shortcode_unautop( $markup );
 460          $markup = do_shortcode( $markup );
 461  
 462          $blocks = parse_blocks( $markup );
 463  
 464          // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
 465          $blocks = static::disable_overlay_menu_for_nested_navigation_blocks( $blocks );
 466  
 467          return new WP_Block_List( $blocks, $attributes );
 468      }
 469  
 470      /**
 471       * Gets the inner blocks for the navigation block.
 472       *
 473       * @since 6.5.0
 474       *
 475       * @param array    $attributes The block attributes.
 476       * @param WP_Block $block The parsed block.
 477       * @return WP_Block_List Returns the inner blocks for the navigation block.
 478       */
 479  	private static function get_inner_blocks( $attributes, $block ) {
 480          $inner_blocks = $block->inner_blocks;
 481  
 482          // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
 483          if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
 484              $attributes['ref'] = $attributes['navigationMenuId'];
 485          }
 486  
 487          // If:
 488          // - the gutenberg plugin is active
 489          // - `__unstableLocation` is defined
 490          // - we have menu items at the defined location
 491          // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
 492          // ...then create inner blocks from the classic menu assigned to that location.
 493          if (
 494              defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
 495              array_key_exists( '__unstableLocation', $attributes ) &&
 496              ! array_key_exists( 'ref', $attributes ) &&
 497              ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
 498          ) {
 499              $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes );
 500          }
 501  
 502          // Load inner blocks from the navigation post.
 503          if ( array_key_exists( 'ref', $attributes ) ) {
 504              $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes );
 505          }
 506  
 507          // If there are no inner blocks then fallback to rendering an appropriate fallback.
 508          if ( empty( $inner_blocks ) ) {
 509              $inner_blocks = static::get_inner_blocks_from_fallback( $attributes );
 510          }
 511  
 512          /**
 513           * Filter navigation block $inner_blocks.
 514           * Allows modification of a navigation block menu items.
 515           *
 516           * @since 6.1.0
 517           *
 518           * @param \WP_Block_List $inner_blocks
 519           */
 520          $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks );
 521  
 522          $post_ids = block_core_navigation_get_post_ids( $inner_blocks );
 523          if ( $post_ids ) {
 524              _prime_post_caches( $post_ids, false, false );
 525          }
 526  
 527          return $inner_blocks;
 528      }
 529  
 530      /**
 531       * Gets the name of the current navigation, if it has one.
 532       *
 533       * @since 6.5.0
 534       *
 535       * @param array $attributes The block attributes.
 536       * @return string Returns the name of the navigation.
 537       */
 538  	private static function get_navigation_name( $attributes ) {
 539  
 540          $navigation_name = $attributes['ariaLabel'] ?? '';
 541  
 542          if ( ! empty( $navigation_name ) ) {
 543              return $navigation_name;
 544          }
 545  
 546          // Load the navigation post.
 547          if ( array_key_exists( 'ref', $attributes ) ) {
 548              $navigation_post = get_post( $attributes['ref'] );
 549              if ( ! isset( $navigation_post ) ) {
 550                  return $navigation_name;
 551              }
 552  
 553              // Only published posts are valid. If this is changed then a corresponding change
 554              // must also be implemented in `use-navigation-menu.js`.
 555              if ( 'publish' === $navigation_post->post_status ) {
 556                  return $navigation_post->post_title;
 557              }
 558          }
 559  
 560          return $navigation_name;
 561      }
 562  
 563      /**
 564       * Returns the layout class for the navigation block.
 565       *
 566       * @since 6.5.0
 567       *
 568       * @param array $attributes The block attributes.
 569       * @return string Returns the layout class for the navigation block.
 570       */
 571  	private static function get_layout_class( $attributes ) {
 572          $layout_justification = array(
 573              'left'          => 'items-justified-left',
 574              'right'         => 'items-justified-right',
 575              'center'        => 'items-justified-center',
 576              'space-between' => 'items-justified-space-between',
 577          );
 578  
 579          $layout_class = '';
 580          if (
 581              isset( $attributes['layout']['justifyContent'] ) &&
 582              isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
 583          ) {
 584              $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
 585          }
 586          if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
 587              $layout_class .= ' is-vertical';
 588          }
 589  
 590          if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
 591              $layout_class .= ' no-wrap';
 592          }
 593          return $layout_class;
 594      }
 595  
 596      /**
 597       * Return classes for the navigation block.
 598       *
 599       * @since 6.5.0
 600       *
 601       * @param array $attributes The block attributes.
 602       * @return string Returns the classes for the navigation block.
 603       */
 604  	private static function get_classes( $attributes ) {
 605          // Restore legacy classnames for submenu positioning.
 606          $layout_class       = static::get_layout_class( $attributes );
 607          $colors             = block_core_navigation_build_css_colors( $attributes );
 608          $font_sizes         = block_core_navigation_build_css_font_sizes( $attributes );
 609          $is_responsive_menu = static::is_responsive( $attributes );
 610  
 611          // Manually add block support text decoration as CSS class.
 612          $text_decoration       = $attributes['style']['typography']['textDecoration'] ?? null;
 613          $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
 614  
 615          $classes = array_merge(
 616              $colors['css_classes'],
 617              $font_sizes['css_classes'],
 618              $is_responsive_menu ? array( 'is-responsive' ) : array(),
 619              $layout_class ? array( $layout_class ) : array(),
 620              $text_decoration ? array( $text_decoration_class ) : array()
 621          );
 622          return implode( ' ', $classes );
 623      }
 624  
 625      /**
 626       * Get styles for the navigation block.
 627       *
 628       * @since 6.5.0
 629       *
 630       * @param array $attributes The block attributes.
 631       * @return string Returns the styles for the navigation block.
 632       */
 633  	private static function get_styles( $attributes ) {
 634          $colors       = block_core_navigation_build_css_colors( $attributes );
 635          $font_sizes   = block_core_navigation_build_css_font_sizes( $attributes );
 636          $block_styles = $attributes['styles'] ?? '';
 637          return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
 638      }
 639  
 640      /**
 641       * Get responsive container classes for the navigation block.
 642       *
 643       * @since 7.0.0
 644       *
 645       * @param bool  $is_hidden_by_default Whether the responsive menu is hidden by default.
 646       * @param bool  $has_custom_overlay Whether a custom overlay is used.
 647       * @param array $colors The colors array.
 648       * @return array Returns the responsive container classes.
 649       */
 650  	private static function get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors ) {
 651          $responsive_container_classes = array( 'wp-block-navigation__responsive-container' );
 652  
 653          if ( $is_hidden_by_default ) {
 654              $responsive_container_classes[] = 'hidden-by-default';
 655          }
 656  
 657          if ( $has_custom_overlay ) {
 658              $responsive_container_classes[] = 'disable-default-overlay';
 659          } else {
 660              // Don't apply overlay color classes if using a custom overlay template part.
 661              // The custom overlay is responsible for its own styling.
 662              $responsive_container_classes[] = implode( ' ', $colors['overlay_css_classes'] );
 663          }
 664  
 665          return $responsive_container_classes;
 666      }
 667  
 668      /**
 669       * Get overlay inline styles for the navigation block.
 670       *
 671       * @since 7.0.0
 672       *
 673       * @param array $colors The colors array.
 674       * @return string Returns the overlay inline styles.
 675       */
 676  	private static function get_overlay_inline_styles( $has_custom_overlay, $colors ) {
 677          $overlay_inline_styles = $has_custom_overlay ? '' : esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) );
 678          return ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '';
 679      }
 680  
 681      /**
 682       * Get the responsive container markup
 683       *
 684       * @since 6.5.0
 685       *
 686       * @param array         $attributes The block attributes.
 687       * @param WP_Block_List $inner_blocks The list of inner blocks.
 688       * @param string        $inner_blocks_html The markup for the inner blocks.
 689       * @return string Returns the container markup.
 690       */
 691  	private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
 692          $is_interactive  = static::is_interactive( $attributes, $inner_blocks );
 693          $colors          = block_core_navigation_build_css_colors( $attributes );
 694          $modal_unique_id = wp_unique_id( 'modal-' );
 695  
 696          $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
 697  
 698          // Set-up variables for custom overlays.
 699          $has_custom_overlay             = false;
 700          $close_button_markup            = '';
 701          $has_custom_overlay_close_block = false;
 702          $overlay_blocks_html            = '';
 703          $custom_overlay_markup          = '';
 704  
 705          // Check if an overlay template part is selected and render it.
 706          // This needs to happen before building classes so we know if overlay blocks actually exist.
 707          if ( ! empty( $attributes['overlay'] ) ) {
 708              // Get blocks from the overlay template part.
 709              $overlay_blocks = static::get_overlay_blocks_from_template_part( $attributes['overlay'], $attributes );
 710              // Render template part blocks directly without navigation container wrapper.
 711              $overlay_blocks_html = static::get_template_part_blocks_html( $overlay_blocks );
 712              // Check if overlay contains a navigation-overlay-close block (detect in rendered HTML so it works with patterns).
 713              $has_custom_overlay_close_block = block_core_navigation_overlay_html_has_close_block( $overlay_blocks_html );
 714              // Add Interactivity API directives to the overlay close block if present.
 715              if ( $has_custom_overlay_close_block && $is_interactive ) {
 716                  $tags                = new WP_HTML_Tag_Processor( $overlay_blocks_html );
 717                  $overlay_blocks_html = block_core_navigation_add_directives_to_overlay_close( $tags );
 718              }
 719              // Images in the overlay are hidden until the menu is opened. Pre-set
 720              // fetchpriority="low" so that when wp_filter_content_tags() processes the
 721              // parent template part, it sees the attribute already present and calls
 722              // wp_get_loading_optimization_attributes() with fetchpriority="low", which both prevents
 723              // fetchpriority="high" from being added and stops the LCP counter from being incremented.
 724              $overlay_blocks_html = block_core_navigation_set_overlay_image_fetch_priority( $overlay_blocks_html );
 725          }
 726  
 727          $has_custom_overlay = ! empty( $overlay_blocks_html );
 728  
 729          $responsive_container_classes = static::get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors );
 730  
 731          $open_button_classes = array(
 732              'wp-block-navigation__responsive-container-open',
 733              $is_hidden_by_default ? 'always-shown' : '',
 734          );
 735  
 736          $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
 737          $toggle_button_icon        = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7.5h16v1.5H4z"></path><path d="M4 15h16v1.5H4z"></path></svg>';
 738          if ( isset( $attributes['icon'] ) ) {
 739              if ( 'menu' === $attributes['icon'] ) {
 740                  $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5z"></path><path d="M5 12.8h14v-1.5H5v1.5z"></path><path d="M5 19h14v-1.5H5V19z"></path></svg>';
 741              }
 742          }
 743          $toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
 744          $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>';
 745          $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
 746          $toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
 747          $toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
 748  
 749          // Add Interactivity API directives to the markup if needed.
 750          $open_button_directives          = '';
 751          $responsive_container_directives = '';
 752          $responsive_dialog_directives    = '';
 753          $close_button_directives         = '';
 754          if ( $is_interactive ) {
 755              $open_button_directives                  = '
 756                  data-wp-on--click="actions.openMenuOnClick"
 757                  data-wp-on--keydown="actions.handleMenuKeydown"
 758              ';
 759              $responsive_container_directives         = '
 760                  data-wp-class--has-modal-open="state.isMenuOpen"
 761                  data-wp-class--is-menu-open="state.isMenuOpen"
 762                  data-wp-watch="callbacks.initMenu"
 763                  data-wp-on--keydown="actions.handleMenuKeydown"
 764                  data-wp-on--focusout="actions.handleMenuFocusout"
 765                  tabindex="-1"
 766              ';
 767              $responsive_dialog_directives            = '
 768                  data-wp-bind--aria-modal="state.ariaModal"
 769                  data-wp-bind--aria-label="state.ariaLabel"
 770                  data-wp-bind--role="state.roleAttribute"
 771              ';
 772              $close_button_directives                 = '
 773                  data-wp-on--click="actions.closeMenuOnClick"
 774              ';
 775              $responsive_container_content_directives = '
 776                  data-wp-watch="callbacks.focusFirstElement"
 777              ';
 778          }
 779  
 780          // Don't apply overlay inline styles if using a custom overlay template part.
 781          // The custom overlay is responsible for its own styling.
 782          $overlay_inline_styles = static::get_overlay_inline_styles( $has_custom_overlay, $colors );
 783  
 784          if ( $has_custom_overlay ) {
 785              $custom_overlay_markup = sprintf(
 786                  '<div class="wp-block-navigation__overlay-container">%s</div>',
 787                  $overlay_blocks_html
 788              );
 789          }
 790  
 791          // Show default close button for all responsive navigation,
 792          // unless custom overlay has its own close block.
 793          if ( ! $has_custom_overlay_close_block ) {
 794              $close_button_markup = sprintf(
 795                  '<button %1$s class="wp-block-navigation__responsive-container-close" %2$s>%3$s</button>',
 796                  $toggle_aria_label_close,
 797                  $close_button_directives,
 798                  $toggle_close_button_content
 799              );
 800          }
 801  
 802          return sprintf(
 803              '<button aria-haspopup="dialog" %3$s class="%6$s" %10$s>%8$s</button>
 804                  <div class="%5$s" %7$s id="%1$s" %11$s>
 805                      <div class="wp-block-navigation__responsive-close" tabindex="-1">
 806                          <div class="wp-block-navigation__responsive-dialog" %12$s>
 807                              %13$s
 808                              <div class="wp-block-navigation__responsive-container-content" %14$s id="%1$s-content">
 809                                  %2$s
 810                                  %15$s
 811                              </div>
 812                          </div>
 813                      </div>
 814                  </div>',
 815              esc_attr( $modal_unique_id ),
 816              $inner_blocks_html,
 817              $toggle_aria_label_open,
 818              $toggle_aria_label_close,
 819              esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ),
 820              esc_attr( trim( implode( ' ', $open_button_classes ) ) ),
 821              $overlay_inline_styles,
 822              $toggle_button_content,
 823              $toggle_close_button_content,
 824              $open_button_directives,
 825              $responsive_container_directives,
 826              $responsive_dialog_directives,
 827              $close_button_markup,
 828              $responsive_container_content_directives,
 829              $has_custom_overlay ? $custom_overlay_markup : ''
 830          );
 831      }
 832  
 833      /**
 834       * Get the wrapper attributes
 835       *
 836       * @since 6.5.0
 837       *
 838       * @param array         $attributes    The block attributes.
 839       * @param WP_Block_List $inner_blocks  A list of inner blocks.
 840       * @return string Returns the navigation block markup.
 841       */
 842  	private static function get_nav_attributes( $attributes, $inner_blocks ) {
 843          $is_interactive     = static::is_interactive( $attributes, $inner_blocks );
 844          $is_responsive_menu = static::is_responsive( $attributes );
 845          $style              = static::get_styles( $attributes );
 846          $class              = static::get_classes( $attributes );
 847          $extra_attributes   = array(
 848              'class' => $class,
 849              'style' => $style,
 850          );
 851          // Only add aria-label for top-level navigation blocks.
 852          // Skip navigation blocks marked as being within overlay template parts.
 853          $is_within_overlay = $attributes['_isWithinOverlayTemplatePart'] ?? false;
 854          if ( $is_within_overlay ) {
 855              $nav_menu_name = static::get_navigation_name( $attributes );
 856          } else {
 857              $nav_menu_name = static::get_unique_navigation_name( $attributes );
 858          }
 859  
 860          if ( ! empty( $nav_menu_name ) ) {
 861              $extra_attributes['aria-label'] = $nav_menu_name;
 862          }
 863          $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes );
 864  
 865          if ( $is_responsive_menu ) {
 866              $nav_element_directives = static::get_nav_element_directives( $is_interactive );
 867              $wrapper_attributes    .= ' ' . $nav_element_directives;
 868          }
 869  
 870          return $wrapper_attributes;
 871      }
 872  
 873      /**
 874       * Gets the nav element directives.
 875       *
 876       * @since 6.5.0
 877       *
 878       * @param bool $is_interactive Whether the block is interactive.
 879       * @return string the directives for the navigation element.
 880       */
 881  	private static function get_nav_element_directives( $is_interactive ) {
 882          if ( ! $is_interactive ) {
 883              return '';
 884          }
 885          // When adding to this array be mindful of security concerns.
 886          $nav_element_context    = wp_interactivity_data_wp_context(
 887              array(
 888                  'overlayOpenedBy' => array(
 889                      'click' => false,
 890                      'hover' => false,
 891                      'focus' => false,
 892                  ),
 893                  'type'            => 'overlay',
 894                  'roleAttribute'   => '',
 895                  'ariaLabel'       => __( 'Menu' ),
 896              )
 897          );
 898          $nav_element_directives = '
 899           data-wp-interactive="core/navigation" '
 900          . $nav_element_context;
 901  
 902          return $nav_element_directives;
 903      }
 904  
 905      /**
 906       * Handle view script module loading.
 907       *
 908       * @since 6.5.0
 909       *
 910       * @param array         $attributes   The block attributes.
 911       * @param WP_Block      $block        The parsed block.
 912       * @param WP_Block_List $inner_blocks The list of inner blocks.
 913       */
 914  	private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
 915          if ( static::is_interactive( $attributes, $inner_blocks ) ) {
 916              wp_enqueue_script_module( '@wordpress/block-library/navigation/view' );
 917          }
 918      }
 919  
 920      /**
 921       * Returns the markup for the navigation block.
 922       *
 923       * @since 6.5.0
 924       *
 925       * @param array         $attributes The block attributes.
 926       * @param WP_Block_List $inner_blocks The list of inner blocks.
 927       * @return string Returns the navigation wrapper markup.
 928       */
 929  	private static function get_inner_block_markup( $attributes, $inner_blocks ) {
 930          $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks );
 931          if ( static::is_responsive( $attributes ) ) {
 932              return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html );
 933          }
 934          return $inner_blocks_html;
 935      }
 936  
 937      /**
 938       * Returns a unique name for the navigation.
 939       *
 940       * @since 6.5.0
 941       *
 942       * @param array $attributes The block attributes.
 943       * @return string Returns a unique name for the navigation.
 944       */
 945  	private static function get_unique_navigation_name( $attributes ) {
 946          $nav_menu_name = static::get_navigation_name( $attributes );
 947  
 948          // This is used to count the number of times a navigation name has been seen,
 949          // so that we can ensure every navigation has a unique id.
 950          if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) ) {
 951              ++static::$seen_menu_names[ $nav_menu_name ];
 952          } else {
 953              static::$seen_menu_names[ $nav_menu_name ] = 1;
 954          }
 955  
 956          // If the menu name has been used previously then append an ID
 957          // to the name to ensure uniqueness across a given post.
 958          if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) {
 959              $count         = static::$seen_menu_names[ $nav_menu_name ];
 960              $nav_menu_name = $nav_menu_name . ' ' . ( $count );
 961          }
 962  
 963          return $nav_menu_name;
 964      }
 965  
 966      /**
 967       * Renders the navigation block.
 968       *
 969       * @since 6.5.0
 970       *
 971       * @param array    $attributes The block attributes.
 972       * @param string   $content    The saved content.
 973       * @param WP_Block $block      The parsed block.
 974       * @return string Returns the navigation block markup.
 975       */
 976  	public static function render( $attributes, $content, $block ) {
 977          /**
 978           * Deprecated:
 979           * The rgbTextColor and rgbBackgroundColor attributes
 980           * have been deprecated in favor of
 981           * customTextColor and customBackgroundColor ones.
 982           * Move the values from old attrs to the new ones.
 983           */
 984          if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
 985              $attributes['customTextColor'] = $attributes['rgbTextColor'];
 986          }
 987  
 988          if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
 989              $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
 990          }
 991  
 992          unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
 993  
 994          $inner_blocks = static::get_inner_blocks( $attributes, $block );
 995          // Prevent navigation blocks referencing themselves from rendering.
 996          if ( block_core_navigation_block_tree_has_block_type(
 997              $inner_blocks,
 998              'core/navigation'
 999          ) ) {
1000              return '';
1001          }
1002  
1003          static::handle_view_script_module_loading( $attributes, $block, $inner_blocks );
1004  
1005          // Use div wrapper if this navigation block is within an overlay template part.
1006          $is_within_overlay = $attributes['_isWithinOverlayTemplatePart'] ?? false;
1007          $tag_name          = $is_within_overlay ? 'div' : 'nav';
1008  
1009          return sprintf(
1010              '<%1$s %2$s>%3$s</%1$s>',
1011              $tag_name,
1012              static::get_nav_attributes( $attributes, $inner_blocks ),
1013              static::get_inner_block_markup( $attributes, $inner_blocks )
1014          );
1015      }
1016  }
1017  
1018  // These functions are used for the __unstableLocation feature and only active
1019  // when the gutenberg plugin is active.
1020  if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
1021      /**
1022       * Returns the menu items for a WordPress menu location.
1023       *
1024       * @since 5.9.0
1025       *
1026       * @param string $location The menu location.
1027       * @return array Menu items for the location.
1028       */
1029      function block_core_navigation_get_menu_items_at_location( $location ) {
1030          if ( empty( $location ) ) {
1031              return;
1032          }
1033  
1034          // Build menu data. The following approximates the code in
1035          // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.
1036  
1037          // Find the location in the list of locations, returning early if the
1038          // location can't be found.
1039          $locations = get_nav_menu_locations();
1040          if ( ! isset( $locations[ $location ] ) ) {
1041              return;
1042          }
1043  
1044          // Get the menu from the location, returning early if there is no
1045          // menu or there was an error.
1046          $menu = wp_get_nav_menu_object( $locations[ $location ] );
1047          if ( ! $menu || is_wp_error( $menu ) ) {
1048              return;
1049          }
1050  
1051          $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
1052          _wp_menu_item_classes_by_context( $menu_items );
1053  
1054          return $menu_items;
1055      }
1056  
1057  
1058      /**
1059       * Sorts a standard array of menu items into a nested structure keyed by the
1060       * id of the parent menu.
1061       *
1062       * @since 5.9.0
1063       *
1064       * @param array $menu_items Menu items to sort.
1065       * @return array An array keyed by the id of the parent menu where each element
1066       *               is an array of menu items that belong to that parent.
1067       */
1068      function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) {
1069          $sorted_menu_items = array();
1070          foreach ( (array) $menu_items as $menu_item ) {
1071              $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
1072          }
1073          unset( $menu_items, $menu_item );
1074  
1075          $menu_items_by_parent_id = array();
1076          foreach ( $sorted_menu_items as $menu_item ) {
1077              $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
1078          }
1079  
1080          return $menu_items_by_parent_id;
1081      }
1082  
1083      /**
1084       * Gets the inner blocks for the navigation block from the unstable location attribute.
1085       *
1086       * @since 6.5.0
1087       *
1088       * @param array $attributes The block attributes.
1089       * @return WP_Block_List Returns the inner blocks for the navigation block.
1090       */
1091      function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) {
1092          $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] );
1093          if ( empty( $menu_items ) ) {
1094              return new WP_Block_List( array(), $attributes );
1095          }
1096  
1097          $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items );
1098          $parsed_blocks           = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
1099          return new WP_Block_List( $parsed_blocks, $attributes );
1100      }
1101  }
1102  
1103  /**
1104   * Checks if the overlay HTML contains a navigation-overlay-close block.
1105   *
1106   * Uses WP_HTML_Tag_Processor to detect the close button in rendered output,
1107   * so it works when the overlay uses patterns (pattern content is rendered at
1108   * output time, not in the block tree).
1109   *
1110   * @since 7.0.0
1111   *
1112   * @param string $html The rendered overlay HTML.
1113   * @return bool True if a close button element is found.
1114   */
1115  function block_core_navigation_overlay_html_has_close_block( $html ) {
1116      $tags = new WP_HTML_Tag_Processor( $html );
1117      return $tags->next_tag(
1118          array(
1119              'tag_name'   => 'BUTTON',
1120              'class_name' => 'wp-block-navigation-overlay-close',
1121          )
1122      );
1123  }
1124  
1125  /**
1126   * Add Interactivity API directives to the navigation-overlay-close block
1127   * markup using the Tag Processor.
1128   *
1129   * @since 6.5.0
1130   *
1131   * @param WP_HTML_Tag_Processor $tags Markup of the navigation block.
1132   * @return string Overlay close markup with the directives injected.
1133   */
1134  function block_core_navigation_add_directives_to_overlay_close( $tags ) {
1135      // Find all navigation-overlay-close buttons.
1136      while ( $tags->next_tag(
1137          array(
1138              'tag_name'   => 'BUTTON',
1139              'class_name' => 'wp-block-navigation-overlay-close',
1140          )
1141      ) ) {
1142          // Add the same close directive as the default close button.
1143          $tags->set_attribute( 'data-wp-on--click', 'actions.closeMenuOnClick' );
1144      }
1145      return $tags->get_updated_html();
1146  }
1147  
1148  /**
1149   * Sets fetchpriority="low" on all IMG tags within the navigation overlay.
1150   *
1151   * Images in the overlay are hidden until the menu is opened, so they should
1152   * not compete with any actual LCP element image on the page.
1153   *
1154   * @since 7.0.0
1155   *
1156   * @param string $overlay_blocks_html The rendered HTML of the overlay blocks.
1157   * @return string Modified HTML with fetchpriority="low" on all IMG tags.
1158   */
1159  function block_core_navigation_set_overlay_image_fetch_priority( string $overlay_blocks_html ): string {
1160      $tags = new WP_HTML_Tag_Processor( $overlay_blocks_html );
1161      while ( $tags->next_tag( 'IMG' ) ) {
1162          $tags->set_attribute( 'fetchpriority', 'low' );
1163      }
1164      return $tags->get_updated_html();
1165  }
1166  
1167  /**
1168   * Add Interactivity API directives to the navigation-submenu and page-list
1169   * blocks markup using the Tag Processor.
1170   *
1171   * @since 6.3.0
1172   *
1173   * @param WP_HTML_Tag_Processor $tags             Markup of the navigation block.
1174   * @param array                 $block_attributes Block attributes.
1175   *
1176   * @return string Submenu markup with the directives injected.
1177   */
1178  function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) {
1179      while ( $tags->next_tag(
1180          array(
1181              'tag_name'   => 'LI',
1182              'class_name' => 'has-child',
1183          )
1184      ) ) {
1185          // Add directives to the parent `<li>`.
1186          $tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
1187          $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu", "modal": null, "previousFocus": null }' );
1188          $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
1189          $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
1190          $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
1191  
1192          // This is a fix for Safari. Without it, Safari doesn't change the active
1193          // element when the user clicks on a button. It can be removed once we add
1194          // an overlay to capture the clicks, instead of relying on the focusout
1195          // event.
1196          $tags->set_attribute( 'tabindex', '-1' );
1197  
1198          $computed_visibility = block_core_navigation_get_submenu_visibility( $block_attributes );
1199          $open_on_hover       = 'hover' === $computed_visibility;
1200  
1201          if ( $open_on_hover ) {
1202              $tags->set_attribute( 'data-wp-on--pointerenter', 'actions.openMenuOnHover' );
1203              $tags->set_attribute( 'data-wp-on--pointerleave', 'actions.closeMenuOnHover' );
1204          }
1205  
1206          // Add directives to the toggle submenu button.
1207          if ( $tags->next_tag(
1208              array(
1209                  'tag_name'   => 'BUTTON',
1210                  'class_name' => 'wp-block-navigation-submenu__toggle',
1211              )
1212          ) ) {
1213              $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
1214              $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
1215              // The `aria-expanded` attribute for SSR is already added in the submenu block.
1216          }
1217          // Add directives to the submenu.
1218          if ( $tags->next_tag(
1219              array(
1220                  'tag_name'   => 'UL',
1221                  'class_name' => 'wp-block-navigation__submenu-container',
1222              )
1223          ) ) {
1224              $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' );
1225          }
1226  
1227          // Iterate through subitems if exist.
1228          block_core_navigation_add_directives_to_submenu( $tags, $block_attributes );
1229      }
1230      return $tags->get_updated_html();
1231  }
1232  
1233  /**
1234   * Build an array with CSS classes and inline styles defining the colors
1235   * which will be applied to the navigation markup in the front-end.
1236   *
1237   * @since 5.9.0
1238   *
1239   * @param array $attributes Navigation block attributes.
1240   *
1241   * @return array Colors CSS classes and inline styles.
1242   */
1243  function block_core_navigation_build_css_colors( $attributes ) {
1244      $colors = array(
1245          'css_classes'           => array(),
1246          'inline_styles'         => '',
1247          'overlay_css_classes'   => array(),
1248          'overlay_inline_styles' => '',
1249      );
1250  
1251      // Text color.
1252      $has_named_text_color  = array_key_exists( 'textColor', $attributes );
1253      $has_custom_text_color = array_key_exists( 'customTextColor', $attributes );
1254  
1255      // If has text color.
1256      if ( $has_custom_text_color || $has_named_text_color ) {
1257          // Add has-text-color class.
1258          $colors['css_classes'][] = 'has-text-color';
1259      }
1260  
1261      if ( $has_named_text_color ) {
1262          // Add the color class.
1263          $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] );
1264      } elseif ( $has_custom_text_color ) {
1265          // Add the custom color inline style.
1266          $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] );
1267      }
1268  
1269      // Background color.
1270      $has_named_background_color  = array_key_exists( 'backgroundColor', $attributes );
1271      $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes );
1272  
1273      // If has background color.
1274      if ( $has_custom_background_color || $has_named_background_color ) {
1275          // Add has-background class.
1276          $colors['css_classes'][] = 'has-background';
1277      }
1278  
1279      if ( $has_named_background_color ) {
1280          // Add the background-color class.
1281          $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
1282      } elseif ( $has_custom_background_color ) {
1283          // Add the custom background-color inline style.
1284          $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] );
1285      }
1286  
1287      // Overlay text color.
1288      $has_named_overlay_text_color  = array_key_exists( 'overlayTextColor', $attributes );
1289      $has_custom_overlay_text_color = array_key_exists( 'customOverlayTextColor', $attributes );
1290  
1291      // If has overlay text color.
1292      if ( $has_custom_overlay_text_color || $has_named_overlay_text_color ) {
1293          // Add has-text-color class.
1294          $colors['overlay_css_classes'][] = 'has-text-color';
1295      }
1296  
1297      if ( $has_named_overlay_text_color ) {
1298          // Add the overlay color class.
1299          $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', $attributes['overlayTextColor'] );
1300      } elseif ( $has_custom_overlay_text_color ) {
1301          // Add the custom overlay color inline style.
1302          $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $attributes['customOverlayTextColor'] );
1303      }
1304  
1305      // Overlay background color.
1306      $has_named_overlay_background_color  = array_key_exists( 'overlayBackgroundColor', $attributes );
1307      $has_custom_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $attributes );
1308  
1309      // If has overlay background color.
1310      if ( $has_custom_overlay_background_color || $has_named_overlay_background_color ) {
1311          // Add has-background class.
1312          $colors['overlay_css_classes'][] = 'has-background';
1313      }
1314  
1315      if ( $has_named_overlay_background_color ) {
1316          // Add the overlay background-color class.
1317          $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', $attributes['overlayBackgroundColor'] );
1318      } elseif ( $has_custom_overlay_background_color ) {
1319          // Add the custom overlay background-color inline style.
1320          $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customOverlayBackgroundColor'] );
1321      }
1322  
1323      return $colors;
1324  }
1325  
1326  /**
1327   * Build an array with CSS classes and inline styles defining the font sizes
1328   * which will be applied to the navigation markup in the front-end.
1329   *
1330   * @since 5.9.0
1331   *
1332   * @param array $attributes Navigation block attributes.
1333   *
1334   * @return array Font size CSS classes and inline styles.
1335   */
1336  function block_core_navigation_build_css_font_sizes( $attributes ) {
1337      // CSS classes.
1338      $font_sizes = array(
1339          'css_classes'   => array(),
1340          'inline_styles' => '',
1341      );
1342  
1343      $has_named_font_size  = array_key_exists( 'fontSize', $attributes );
1344      $has_custom_font_size = array_key_exists( 'customFontSize', $attributes );
1345  
1346      if ( $has_named_font_size ) {
1347          // Add the font size class.
1348          $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] );
1349      } elseif ( $has_custom_font_size ) {
1350          // Add the custom font size inline style.
1351          $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] );
1352      }
1353  
1354      return $font_sizes;
1355  }
1356  
1357  /**
1358   * Returns the top-level submenu SVG chevron icon.
1359   *
1360   * @since 5.9.0
1361   *
1362   * @return string
1363   */
1364  function block_core_navigation_render_submenu_icon() {
1365      return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"></path></svg>';
1366  }
1367  
1368  /**
1369   * Filter out empty "null" blocks from the block list.
1370   * 'parse_blocks' includes a null block with '\n\n' as the content when
1371   * it encounters whitespace. This is not a bug but rather how the parser
1372   * is designed.
1373   *
1374   * @since 5.9.0
1375   *
1376   * @param array $parsed_blocks the parsed blocks to be normalized.
1377   * @return array the normalized parsed blocks.
1378   */
1379  function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) {
1380      $filtered = array_filter(
1381          $parsed_blocks,
1382          static function ( $block ) {
1383              return isset( $block['blockName'] );
1384          }
1385      );
1386  
1387      // Reset keys.
1388      return array_values( $filtered );
1389  }
1390  
1391  /**
1392   * Recursively checks if blocks contain a specific block type.
1393   *
1394   * @since 7.0.0
1395   *
1396   * @param WP_Block_List $blocks           The list of blocks to check.
1397   * @param string        $block_type       The block type to search for (e.g., 'core/navigation').
1398   * @param array         $skip_block_types Optional. Block types to skip when recursing. Default empty array.
1399   * @return bool Returns true if the specified block type is found.
1400   */
1401  function block_core_navigation_block_tree_has_block_type( $blocks, $block_type, $skip_block_types = array() ) {
1402      if ( empty( $blocks ) ) {
1403          return false;
1404      }
1405  
1406      foreach ( $blocks as $block ) {
1407          if ( $block_type === $block->name ) {
1408              return true;
1409          }
1410  
1411          // Recursively check inner blocks, skipping specified block types.
1412          if ( ! in_array( $block->name, $skip_block_types, true ) && ! empty( $block->inner_blocks ) ) {
1413              if ( block_core_navigation_block_tree_has_block_type( $block->inner_blocks, $block_type, $skip_block_types ) ) {
1414                  return true;
1415              }
1416          }
1417      }
1418  
1419      return false;
1420  }
1421  
1422  /**
1423   * Returns true if the navigation block contains a nested navigation block.
1424   *
1425   * @since 6.2.0
1426   * @deprecated 7.0.0 Use block_core_navigation_block_tree_has_block_type() instead.
1427   *
1428   * @param WP_Block_List $inner_blocks Inner block instance to be normalized.
1429   * @return bool true if the navigation block contains a nested navigation block.
1430   */
1431  function block_core_navigation_block_contains_core_navigation( $inner_blocks ) {
1432      _deprecated_function( __FUNCTION__, '7.0.0', 'block_core_navigation_block_tree_has_block_type()' );
1433  
1434      return block_core_navigation_block_tree_has_block_type(
1435          $inner_blocks,
1436          'core/navigation'
1437      );
1438  }
1439  
1440  /**
1441   * Retrieves the appropriate fallback to be used on the front of the
1442   * site when there is no menu assigned to the Nav block.
1443   *
1444   * This aims to mirror how the fallback mechanic for wp_nav_menu works.
1445   * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information.
1446   *
1447   * @since 5.9.0
1448   *
1449   * @return array the array of blocks to be used as a fallback.
1450   */
1451  function block_core_navigation_get_fallback_blocks() {
1452      $page_list_fallback = array(
1453          array(
1454              'blockName'    => 'core/page-list',
1455              'innerContent' => array(),
1456              'attrs'        => array(),
1457          ),
1458      );
1459  
1460      $registry = WP_Block_Type_Registry::get_instance();
1461  
1462      // If `core/page-list` is not registered then return empty blocks.
1463      $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array();
1464      $navigation_post = WP_Navigation_Fallback::get_fallback();
1465  
1466      // Use the first non-empty Navigation as fallback if available.
1467      if ( $navigation_post ) {
1468          $parsed_blocks  = parse_blocks( $navigation_post->post_content );
1469          $maybe_fallback = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
1470  
1471          // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
1472          // In this case default to the (Page List) fallback.
1473          $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
1474  
1475          // Run Block Hooks algorithm to inject hooked blocks.
1476          // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
1477          // TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
1478          // before the parse_blocks() call further above, to avoid the extra serialization/parsing.
1479          $markup          = serialize_blocks( $fallback_blocks );
1480          $markup          = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
1481          $fallback_blocks = parse_blocks( $markup );
1482      }
1483  
1484      /**
1485       * Filters the fallback experience for the Navigation block.
1486       *
1487       * Returning a falsey value will opt out of the fallback and cause the block not to render.
1488       * To customise the blocks provided return an array of blocks - these should be valid
1489       * children of the `core/navigation` block.
1490       *
1491       * @since 5.9.0
1492       *
1493       * @param array[] $fallback_blocks default fallback blocks provided by the default block mechanic.
1494       */
1495      return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks );
1496  }
1497  
1498  /**
1499   * Iterate through all inner blocks recursively and get navigation link block's post IDs.
1500   *
1501   * @since 6.0.0
1502   *
1503   * @param WP_Block_List $inner_blocks Block list class instance.
1504   *
1505   * @return array Array of post IDs.
1506   */
1507  function block_core_navigation_get_post_ids( $inner_blocks ) {
1508      $post_ids = array_map( 'block_core_navigation_from_block_get_post_ids', iterator_to_array( $inner_blocks ) );
1509      return array_unique( array_merge( ...$post_ids ) );
1510  }
1511  
1512  /**
1513   * Get post IDs from a navigation link block instance.
1514   *
1515   * @since 6.0.0
1516   *
1517   * @param WP_Block $block Instance of a block.
1518   *
1519   * @return array Array of post IDs.
1520   */
1521  function block_core_navigation_from_block_get_post_ids( $block ) {
1522      $post_ids = array();
1523  
1524      if ( $block->inner_blocks ) {
1525          $post_ids = block_core_navigation_get_post_ids( $block->inner_blocks );
1526      }
1527  
1528      if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) {
1529          if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] && isset( $block->attributes['id'] ) ) {
1530              $post_ids[] = $block->attributes['id'];
1531          }
1532      }
1533  
1534      return $post_ids;
1535  }
1536  
1537  /**
1538   * Renders the `core/navigation` block on server.
1539   *
1540   * @since 5.9.0
1541   *
1542   * @param array    $attributes The block attributes.
1543   * @param string   $content    The saved content.
1544   * @param WP_Block $block      The parsed block.
1545   *
1546   * @return string Returns the navigation block markup.
1547   */
1548  function render_block_core_navigation( $attributes, $content, $block ) {
1549      return WP_Navigation_Block_Renderer::render( $attributes, $content, $block );
1550  }
1551  
1552  /**
1553   * Register the navigation block.
1554   *
1555   * @since 5.9.0
1556   *
1557   * @uses render_block_core_navigation()
1558   * @throws WP_Error An WP_Error exception parsing the block definition.
1559   */
1560  function register_block_core_navigation() {
1561      register_block_type_from_metadata(
1562          __DIR__ . '/navigation',
1563          array(
1564              'render_callback' => 'render_block_core_navigation',
1565          )
1566      );
1567  }
1568  
1569  add_action( 'init', 'register_block_core_navigation' );
1570  
1571  /**
1572   * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
1573   *
1574   * @since 5.9.0
1575   *
1576   * @param array $parsed_block The block being rendered.
1577   *
1578   * @return array The block being rendered without typographic presets.
1579   */
1580  function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) {
1581      if ( 'core/navigation' === $parsed_block['blockName'] ) {
1582          $attribute_to_prefix_map = array(
1583              'fontStyle'      => 'var:preset|font-style|',
1584              'fontWeight'     => 'var:preset|font-weight|',
1585              'textDecoration' => 'var:preset|text-decoration|',
1586              'textTransform'  => 'var:preset|text-transform|',
1587          );
1588          foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) {
1589              if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) {
1590                  $prefix_len      = strlen( $prefix );
1591                  $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ];
1592                  if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) {
1593                      $attribute_value = substr( $attribute_value, $prefix_len );
1594                  }
1595                  if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) {
1596                      $attribute_value = 'line-through';
1597                  }
1598              }
1599          }
1600      }
1601  
1602      return $parsed_block;
1603  }
1604  
1605  add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' );
1606  
1607  /**
1608   * Turns menu item data into a nested array of parsed blocks
1609   *
1610   * @since 5.9.0
1611   *
1612   * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
1613   *
1614   * @param array $menu_items               An array of menu items that represent
1615   *                                        an individual level of a menu.
1616   * @param array $menu_items_by_parent_id  An array keyed by the id of the
1617   *                                        parent menu where each element is an
1618   *                                        array of menu items that belong to
1619   *                                        that parent.
1620   * @return array An array of parsed block data.
1621   */
1622  function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
1623  
1624      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::parse_blocks_from_menu_items' );
1625  
1626      if ( empty( $menu_items ) ) {
1627          return array();
1628      }
1629  
1630      $blocks = array();
1631  
1632      foreach ( $menu_items as $menu_item ) {
1633          $class_name       = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
1634          $id               = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
1635          $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
1636          $rel              = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
1637          $kind             = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
1638  
1639          $block = array(
1640              'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link',
1641              'attrs'     => array(
1642                  'className'     => $class_name,
1643                  'description'   => $menu_item->description,
1644                  'id'            => $id,
1645                  'kind'          => $kind,
1646                  'label'         => $menu_item->title,
1647                  'opensInNewTab' => $opens_in_new_tab,
1648                  'rel'           => $rel,
1649                  'title'         => $menu_item->attr_title,
1650                  'type'          => $menu_item->object,
1651                  'url'           => $menu_item->url,
1652              ),
1653          );
1654  
1655          $block['innerBlocks']  = isset( $menu_items_by_parent_id[ $menu_item->ID ] )
1656              ? block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id )
1657              : array();
1658          $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] );
1659  
1660          $blocks[] = $block;
1661      }
1662  
1663      return $blocks;
1664  }
1665  
1666  /**
1667   * Get the classic navigation menu to use as a fallback.
1668   *
1669   * @since 6.2.0
1670   *
1671   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback() instead.
1672   *
1673   * @return object WP_Term The classic navigation.
1674   */
1675  function block_core_navigation_get_classic_menu_fallback() {
1676  
1677      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback' );
1678  
1679      $classic_nav_menus = wp_get_nav_menus();
1680  
1681      // If menus exist.
1682      if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) ) {
1683          // Handles simple use case where user has a classic menu and switches to a block theme.
1684  
1685          // Returns the menu assigned to location `primary`.
1686          $locations = get_nav_menu_locations();
1687          if ( isset( $locations['primary'] ) ) {
1688              $primary_menu = wp_get_nav_menu_object( $locations['primary'] );
1689              if ( $primary_menu ) {
1690                  return $primary_menu;
1691              }
1692          }
1693  
1694          // Returns a menu if `primary` is its slug.
1695          foreach ( $classic_nav_menus as $classic_nav_menu ) {
1696              if ( 'primary' === $classic_nav_menu->slug ) {
1697                  return $classic_nav_menu;
1698              }
1699          }
1700  
1701          // Otherwise return the most recently created classic menu.
1702          usort(
1703              $classic_nav_menus,
1704              static function ( $a, $b ) {
1705                  return $b->term_id - $a->term_id;
1706              }
1707          );
1708          return $classic_nav_menus[0];
1709      }
1710  }
1711  
1712  /**
1713   * Converts a classic navigation to blocks.
1714   *
1715   * @since 6.2.0
1716   *
1717   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
1718   *
1719   * @param  object $classic_nav_menu WP_Term The classic navigation object to convert.
1720   * @return array the normalized parsed blocks.
1721   */
1722  function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
1723  
1724      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks' );
1725  
1726      // BEGIN: Code that already exists in wp_nav_menu().
1727      $menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) );
1728  
1729      // Set up the $menu_item variables.
1730      _wp_menu_item_classes_by_context( $menu_items );
1731  
1732      $sorted_menu_items = array();
1733      foreach ( (array) $menu_items as $menu_item ) {
1734          $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
1735      }
1736  
1737      unset( $menu_items, $menu_item );
1738  
1739      // END: Code that already exists in wp_nav_menu().
1740  
1741      $menu_items_by_parent_id = array();
1742      foreach ( $sorted_menu_items as $menu_item ) {
1743          $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
1744      }
1745  
1746      $inner_blocks = block_core_navigation_parse_blocks_from_menu_items(
1747          $menu_items_by_parent_id[0] ?? array(),
1748          $menu_items_by_parent_id
1749      );
1750  
1751      return serialize_blocks( $inner_blocks );
1752  }
1753  
1754  /**
1755   * If there's a classic menu then use it as a fallback.
1756   *
1757   * @since 6.2.0
1758   *
1759   * @deprecated 6.3.0 Use WP_Navigation_Fallback::create_classic_menu_fallback() instead.
1760   *
1761   * @return array the normalized parsed blocks.
1762   */
1763  function block_core_navigation_maybe_use_classic_menu_fallback() {
1764  
1765      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::create_classic_menu_fallback' );
1766  
1767      // See if we have a classic menu.
1768      $classic_nav_menu = block_core_navigation_get_classic_menu_fallback();
1769  
1770      if ( ! $classic_nav_menu ) {
1771          return;
1772      }
1773  
1774      // If we have a classic menu then convert it to blocks.
1775      $classic_nav_menu_blocks = block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu );
1776  
1777      if ( empty( $classic_nav_menu_blocks ) ) {
1778          return;
1779      }
1780  
1781      // Create a new navigation menu from the classic menu.
1782      $wp_insert_post_result = wp_insert_post(
1783          array(
1784              'post_content' => $classic_nav_menu_blocks,
1785              'post_title'   => $classic_nav_menu->name,
1786              'post_name'    => $classic_nav_menu->slug,
1787              'post_status'  => 'publish',
1788              'post_type'    => 'wp_navigation',
1789          ),
1790          true // So that we can check whether the result is an error.
1791      );
1792  
1793      if ( is_wp_error( $wp_insert_post_result ) ) {
1794          return;
1795      }
1796  
1797      // Fetch the most recently published navigation which will be the classic one created above.
1798      return block_core_navigation_get_most_recently_published_navigation();
1799  }
1800  
1801  /**
1802   * Finds the most recently published `wp_navigation` Post.
1803   *
1804   * @since 6.1.0
1805   *
1806   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_most_recently_published_navigation() instead.
1807   *
1808   * @return WP_Post|null the first non-empty Navigation or null.
1809   */
1810  function block_core_navigation_get_most_recently_published_navigation() {
1811  
1812      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_most_recently_published_navigation' );
1813  
1814      // Default to the most recently created menu.
1815      $parsed_args = array(
1816          'post_type'              => 'wp_navigation',
1817          'no_found_rows'          => true,
1818          'update_post_meta_cache' => false,
1819          'update_post_term_cache' => false,
1820          'order'                  => 'DESC',
1821          'orderby'                => 'date',
1822          'post_status'            => 'publish',
1823          'posts_per_page'         => 1, // get only the most recent.
1824      );
1825  
1826      $navigation_post = new WP_Query( $parsed_args );
1827      if ( count( $navigation_post->posts ) > 0 ) {
1828          return $navigation_post->posts[0];
1829      }
1830  
1831      return null;
1832  }


Generated : Wed Jun 24 08:20:11 2026 Cross-referenced by PHPXref