[ 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   * Helper functions used to render the navigation block.
  10   */
  11  class WP_Navigation_Block_Renderer {
  12  
  13      /**
  14       * Used to determine whether or not a navigation has submenus.
  15       */
  16      private static $has_submenus = false;
  17  
  18      /**
  19       * Used to determine which blocks need an <li> wrapper.
  20       *
  21       * @var array
  22       */
  23      private static $needs_list_item_wrapper = array(
  24          'core/site-title',
  25          'core/site-logo',
  26      );
  27  
  28      /**
  29       * Keeps track of all the navigation names that have been seen.
  30       *
  31       * @var array
  32       */
  33      private static $seen_menu_names = array();
  34  
  35      /**
  36       * Returns whether or not this is responsive navigation.
  37       *
  38       * @param array $attributes The block attributes.
  39       * @return bool Returns whether or not this is responsive navigation.
  40       */
  41  	private static function is_responsive( $attributes ) {
  42          /**
  43           * This is for backwards compatibility after the `isResponsive` attribute was been removed.
  44           */
  45  
  46          $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
  47          return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
  48      }
  49  
  50      /**
  51       * Returns whether or not a navigation has a submenu.
  52       *
  53       * @param WP_Block_List $inner_blocks The list of inner blocks.
  54       * @return bool Returns whether or not a navigation has a submenu and also sets the member variable.
  55       */
  56  	private static function has_submenus( $inner_blocks ) {
  57          if ( true === static::$has_submenus ) {
  58              return static::$has_submenus;
  59          }
  60  
  61          foreach ( $inner_blocks as $inner_block ) {
  62              // If this is a page list then work out if any of the pages have children.
  63              if ( 'core/page-list' === $inner_block->name ) {
  64                  $all_pages = get_pages(
  65                      array(
  66                          'sort_column' => 'menu_order,post_title',
  67                          'order'       => 'asc',
  68                      )
  69                  );
  70                  foreach ( (array) $all_pages as $page ) {
  71                      if ( $page->post_parent ) {
  72                          static::$has_submenus = true;
  73                          break;
  74                      }
  75                  }
  76              }
  77              // If this is a navigation submenu then we know we have submenus.
  78              if ( 'core/navigation-submenu' === $inner_block->name ) {
  79                  static::$has_submenus = true;
  80                  break;
  81              }
  82          }
  83  
  84          return static::$has_submenus;
  85      }
  86  
  87      /**
  88       * Determine whether the navigation blocks is interactive.
  89       *
  90       * @param array         $attributes   The block attributes.
  91       * @param WP_Block_List $inner_blocks The list of inner blocks.
  92       * @return bool Returns whether or not to load the view script.
  93       */
  94  	private static function is_interactive( $attributes, $inner_blocks ) {
  95          $has_submenus       = static::has_submenus( $inner_blocks );
  96          $is_responsive_menu = static::is_responsive( $attributes );
  97          return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu;
  98      }
  99  
 100      /**
 101       * Returns whether or not a block needs a list item wrapper.
 102       *
 103       * @param WP_Block $block The block.
 104       * @return bool Returns whether or not a block needs a list item wrapper.
 105       */
 106  	private static function does_block_need_a_list_item_wrapper( $block ) {
 107  
 108          /**
 109           * Filter the list of blocks that need a list item wrapper.
 110           *
 111           * Affords the ability to customize which blocks need a list item wrapper when rendered
 112           * within a core/navigation block.
 113           * This is useful for blocks that are not list items but should be wrapped in a list
 114           * item when used as a child of a navigation block.
 115           *
 116           * @since 6.5.0
 117           *
 118           * @param array $needs_list_item_wrapper The list of blocks that need a list item wrapper.
 119           * @return array The list of blocks that need a list item wrapper.
 120           */
 121          $needs_list_item_wrapper = apply_filters( 'block_core_navigation_listable_blocks', static::$needs_list_item_wrapper );
 122  
 123          return in_array( $block->name, $needs_list_item_wrapper, true );
 124      }
 125  
 126      /**
 127       * Returns the markup for a single inner block.
 128       *
 129       * @param WP_Block $inner_block The inner block.
 130       * @return string Returns the markup for a single inner block.
 131       */
 132  	private static function get_markup_for_inner_block( $inner_block ) {
 133          $inner_block_content = $inner_block->render();
 134          if ( ! empty( $inner_block_content ) ) {
 135              if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) {
 136                  return '<li class="wp-block-navigation-item">' . $inner_block_content . '</li>';
 137              }
 138          }
 139  
 140          return $inner_block_content;
 141      }
 142  
 143      /**
 144       * Returns the html for the inner blocks of the navigation block.
 145       *
 146       * @param array         $attributes   The block attributes.
 147       * @param WP_Block_List $inner_blocks The list of inner blocks.
 148       * @return string Returns the html for the inner blocks of the navigation block.
 149       */
 150  	private static function get_inner_blocks_html( $attributes, $inner_blocks ) {
 151          $has_submenus   = static::has_submenus( $inner_blocks );
 152          $is_interactive = static::is_interactive( $attributes, $inner_blocks );
 153  
 154          $style                = static::get_styles( $attributes );
 155          $class                = static::get_classes( $attributes );
 156          $container_attributes = get_block_wrapper_attributes(
 157              array(
 158                  'class' => 'wp-block-navigation__container ' . $class,
 159                  'style' => $style,
 160              )
 161          );
 162  
 163          $inner_blocks_html = '';
 164          $is_list_open      = false;
 165  
 166          foreach ( $inner_blocks as $inner_block ) {
 167              $inner_block_markup = static::get_markup_for_inner_block( $inner_block );
 168              $p                  = new WP_HTML_Tag_Processor( $inner_block_markup );
 169              $is_list_item       = $p->next_tag( 'LI' );
 170  
 171              if ( $is_list_item && ! $is_list_open ) {
 172                  $is_list_open       = true;
 173                  $inner_blocks_html .= sprintf(
 174                      '<ul %1$s>',
 175                      $container_attributes
 176                  );
 177              }
 178  
 179              if ( ! $is_list_item && $is_list_open ) {
 180                  $is_list_open       = false;
 181                  $inner_blocks_html .= '</ul>';
 182              }
 183  
 184              $inner_blocks_html .= $inner_block_markup;
 185          }
 186  
 187          if ( $is_list_open ) {
 188              $inner_blocks_html .= '</ul>';
 189          }
 190  
 191          // Add directives to the submenu if needed.
 192          if ( $has_submenus && $is_interactive ) {
 193              $tags              = new WP_HTML_Tag_Processor( $inner_blocks_html );
 194              $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes );
 195          }
 196  
 197          return $inner_blocks_html;
 198      }
 199  
 200      /**
 201       * Gets the inner blocks for the navigation block from the navigation post.
 202       *
 203       * @param array $attributes The block attributes.
 204       * @return WP_Block_List Returns the inner blocks for the navigation block.
 205       */
 206  	private static function get_inner_blocks_from_navigation_post( $attributes ) {
 207          $navigation_post = get_post( $attributes['ref'] );
 208          if ( ! isset( $navigation_post ) ) {
 209              return new WP_Block_List( array(), $attributes );
 210          }
 211  
 212          // Only published posts are valid. If this is changed then a corresponding change
 213          // must also be implemented in `use-navigation-menu.js`.
 214          if ( 'publish' === $navigation_post->post_status ) {
 215              $parsed_blocks = parse_blocks( $navigation_post->post_content );
 216  
 217              // 'parse_blocks' includes a null block with '\n\n' as the content when
 218              // it encounters whitespace. This code strips it.
 219              $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
 220  
 221              if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
 222                  // Run Block Hooks algorithm to inject hooked blocks.
 223                  $markup         = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post );
 224                  $root_nav_block = parse_blocks( $markup )[0];
 225  
 226                  $blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks;
 227              }
 228  
 229              // TODO - this uses the full navigation block attributes for the
 230              // context which could be refined.
 231              return new WP_Block_List( $blocks, $attributes );
 232          }
 233      }
 234  
 235      /**
 236       * Gets the inner blocks for the navigation block from the fallback.
 237       *
 238       * @param array $attributes The block attributes.
 239       * @return WP_Block_List Returns the inner blocks for the navigation block.
 240       */
 241  	private static function get_inner_blocks_from_fallback( $attributes ) {
 242          $fallback_blocks = block_core_navigation_get_fallback_blocks();
 243  
 244          // Fallback my have been filtered so do basic test for validity.
 245          if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
 246              return new WP_Block_List( array(), $attributes );
 247          }
 248  
 249          return new WP_Block_List( $fallback_blocks, $attributes );
 250      }
 251  
 252      /**
 253       * Gets the inner blocks for the navigation block.
 254       *
 255       * @param array    $attributes The block attributes.
 256       * @param WP_Block $block The parsed block.
 257       * @return WP_Block_List Returns the inner blocks for the navigation block.
 258       */
 259  	private static function get_inner_blocks( $attributes, $block ) {
 260          $inner_blocks = $block->inner_blocks;
 261  
 262          // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
 263          if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
 264              $attributes['ref'] = $attributes['navigationMenuId'];
 265          }
 266  
 267          // If:
 268          // - the gutenberg plugin is active
 269          // - `__unstableLocation` is defined
 270          // - we have menu items at the defined location
 271          // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
 272          // ...then create inner blocks from the classic menu assigned to that location.
 273          if (
 274              defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
 275              array_key_exists( '__unstableLocation', $attributes ) &&
 276              ! array_key_exists( 'ref', $attributes ) &&
 277              ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
 278          ) {
 279              $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes );
 280          }
 281  
 282          // Load inner blocks from the navigation post.
 283          if ( array_key_exists( 'ref', $attributes ) ) {
 284              $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes );
 285          }
 286  
 287          // If there are no inner blocks then fallback to rendering an appropriate fallback.
 288          if ( empty( $inner_blocks ) ) {
 289              $inner_blocks = static::get_inner_blocks_from_fallback( $attributes );
 290          }
 291  
 292          /**
 293           * Filter navigation block $inner_blocks.
 294           * Allows modification of a navigation block menu items.
 295           *
 296           * @since 6.1.0
 297           *
 298           * @param \WP_Block_List $inner_blocks
 299           */
 300          $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks );
 301  
 302          $post_ids = block_core_navigation_get_post_ids( $inner_blocks );
 303          if ( $post_ids ) {
 304              _prime_post_caches( $post_ids, false, false );
 305          }
 306  
 307          return $inner_blocks;
 308      }
 309  
 310      /**
 311       * Gets the name of the current navigation, if it has one.
 312       *
 313       * @param array $attributes The block attributes.
 314       * @return string Returns the name of the navigation.
 315       */
 316  	private static function get_navigation_name( $attributes ) {
 317  
 318          $navigation_name = $attributes['ariaLabel'] ?? '';
 319  
 320          // Load the navigation post.
 321          if ( array_key_exists( 'ref', $attributes ) ) {
 322              $navigation_post = get_post( $attributes['ref'] );
 323              if ( ! isset( $navigation_post ) ) {
 324                  return $navigation_name;
 325              }
 326  
 327              // Only published posts are valid. If this is changed then a corresponding change
 328              // must also be implemented in `use-navigation-menu.js`.
 329              if ( 'publish' === $navigation_post->post_status ) {
 330                  $navigation_name = $navigation_post->post_title;
 331  
 332                  // This is used to count the number of times a navigation name has been seen,
 333                  // so that we can ensure every navigation has a unique id.
 334                  if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) {
 335                      ++static::$seen_menu_names[ $navigation_name ];
 336                  } else {
 337                      static::$seen_menu_names[ $navigation_name ] = 1;
 338                  }
 339              }
 340          }
 341  
 342          return $navigation_name;
 343      }
 344  
 345      /**
 346       * Returns the layout class for the navigation block.
 347       *
 348       * @param array $attributes The block attributes.
 349       * @return string Returns the layout class for the navigation block.
 350       */
 351  	private static function get_layout_class( $attributes ) {
 352          $layout_justification = array(
 353              'left'          => 'items-justified-left',
 354              'right'         => 'items-justified-right',
 355              'center'        => 'items-justified-center',
 356              'space-between' => 'items-justified-space-between',
 357          );
 358  
 359          $layout_class = '';
 360          if (
 361              isset( $attributes['layout']['justifyContent'] ) &&
 362              isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
 363          ) {
 364              $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
 365          }
 366          if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
 367              $layout_class .= ' is-vertical';
 368          }
 369  
 370          if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
 371              $layout_class .= ' no-wrap';
 372          }
 373          return $layout_class;
 374      }
 375  
 376      /**
 377       * Return classes for the navigation block.
 378       *
 379       * @param array $attributes The block attributes.
 380       * @return string Returns the classes for the navigation block.
 381       */
 382  	private static function get_classes( $attributes ) {
 383          // Restore legacy classnames for submenu positioning.
 384          $layout_class       = static::get_layout_class( $attributes );
 385          $colors             = block_core_navigation_build_css_colors( $attributes );
 386          $font_sizes         = block_core_navigation_build_css_font_sizes( $attributes );
 387          $is_responsive_menu = static::is_responsive( $attributes );
 388  
 389          // Manually add block support text decoration as CSS class.
 390          $text_decoration       = $attributes['style']['typography']['textDecoration'] ?? null;
 391          $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
 392  
 393          $classes = array_merge(
 394              $colors['css_classes'],
 395              $font_sizes['css_classes'],
 396              $is_responsive_menu ? array( 'is-responsive' ) : array(),
 397              $layout_class ? array( $layout_class ) : array(),
 398              $text_decoration ? array( $text_decoration_class ) : array()
 399          );
 400          return implode( ' ', $classes );
 401      }
 402  
 403      /**
 404       * Get styles for the navigation block.
 405       *
 406       * @param array $attributes The block attributes.
 407       * @return string Returns the styles for the navigation block.
 408       */
 409  	private static function get_styles( $attributes ) {
 410          $colors       = block_core_navigation_build_css_colors( $attributes );
 411          $font_sizes   = block_core_navigation_build_css_font_sizes( $attributes );
 412          $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
 413          return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
 414      }
 415  
 416      /**
 417       * Get the responsive container markup
 418       *
 419       * @param array         $attributes The block attributes.
 420       * @param WP_Block_List $inner_blocks The list of inner blocks.
 421       * @param string        $inner_blocks_html The markup for the inner blocks.
 422       * @return string Returns the container markup.
 423       */
 424  	private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
 425          $is_interactive  = static::is_interactive( $attributes, $inner_blocks );
 426          $colors          = block_core_navigation_build_css_colors( $attributes );
 427          $modal_unique_id = wp_unique_id( 'modal-' );
 428  
 429          $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
 430  
 431          $responsive_container_classes = array(
 432              'wp-block-navigation__responsive-container',
 433              $is_hidden_by_default ? 'hidden-by-default' : '',
 434              implode( ' ', $colors['overlay_css_classes'] ),
 435          );
 436          $open_button_classes          = array(
 437              'wp-block-navigation__responsive-container-open',
 438              $is_hidden_by_default ? 'always-shown' : '',
 439          );
 440  
 441          $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
 442          $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"><rect x="4" y="7.5" width="16" height="1.5" /><rect x="4" y="15" width="16" height="1.5" /></svg>';
 443          if ( isset( $attributes['icon'] ) ) {
 444              if ( 'menu' === $attributes['icon'] ) {
 445                  $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>';
 446              }
 447          }
 448          $toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
 449          $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>';
 450          $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
 451          $toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
 452          $toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
 453  
 454          // Add Interactivity API directives to the markup if needed.
 455          $open_button_directives          = '';
 456          $responsive_container_directives = '';
 457          $responsive_dialog_directives    = '';
 458          $close_button_directives         = '';
 459          if ( $is_interactive ) {
 460              $open_button_directives                  = '
 461                  data-wp-on--click="actions.openMenuOnClick"
 462                  data-wp-on--keydown="actions.handleMenuKeydown"
 463              ';
 464              $responsive_container_directives         = '
 465                  data-wp-class--has-modal-open="state.isMenuOpen"
 466                  data-wp-class--is-menu-open="state.isMenuOpen"
 467                  data-wp-watch="callbacks.initMenu"
 468                  data-wp-on--keydown="actions.handleMenuKeydown"
 469                  data-wp-on--focusout="actions.handleMenuFocusout"
 470                  tabindex="-1"
 471              ';
 472              $responsive_dialog_directives            = '
 473                  data-wp-bind--aria-modal="state.ariaModal"
 474                  data-wp-bind--aria-label="state.ariaLabel"
 475                  data-wp-bind--role="state.roleAttribute"
 476              ';
 477              $close_button_directives                 = '
 478                  data-wp-on--click="actions.closeMenuOnClick"
 479              ';
 480              $responsive_container_content_directives = '
 481                  data-wp-watch="callbacks.focusFirstElement"
 482              ';
 483          }
 484  
 485          return sprintf(
 486              '<button aria-haspopup="dialog" %3$s class="%6$s" %10$s>%8$s</button>
 487                  <div class="%5$s" style="%7$s" id="%1$s" %11$s>
 488                      <div class="wp-block-navigation__responsive-close" tabindex="-1">
 489                          <div class="wp-block-navigation__responsive-dialog" %12$s>
 490                              <button %4$s class="wp-block-navigation__responsive-container-close" %13$s>%9$s</button>
 491                              <div class="wp-block-navigation__responsive-container-content" %14$s id="%1$s-content">
 492                                  %2$s
 493                              </div>
 494                          </div>
 495                      </div>
 496                  </div>',
 497              esc_attr( $modal_unique_id ),
 498              $inner_blocks_html,
 499              $toggle_aria_label_open,
 500              $toggle_aria_label_close,
 501              esc_attr( implode( ' ', $responsive_container_classes ) ),
 502              esc_attr( implode( ' ', $open_button_classes ) ),
 503              esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ),
 504              $toggle_button_content,
 505              $toggle_close_button_content,
 506              $open_button_directives,
 507              $responsive_container_directives,
 508              $responsive_dialog_directives,
 509              $close_button_directives,
 510              $responsive_container_content_directives
 511          );
 512      }
 513  
 514      /**
 515       * Get the wrapper attributes
 516       *
 517       * @param array         $attributes    The block attributes.
 518       * @param WP_Block_List $inner_blocks  A list of inner blocks.
 519       * @return string Returns the navigation block markup.
 520       */
 521  	private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) {
 522          $nav_menu_name      = static::get_unique_navigation_name( $attributes );
 523          $is_interactive     = static::is_interactive( $attributes, $inner_blocks );
 524          $is_responsive_menu = static::is_responsive( $attributes );
 525          $style              = static::get_styles( $attributes );
 526          $class              = static::get_classes( $attributes );
 527          $wrapper_attributes = get_block_wrapper_attributes(
 528              array(
 529                  'class'      => $class,
 530                  'style'      => $style,
 531                  'aria-label' => $nav_menu_name,
 532              )
 533          );
 534  
 535          if ( $is_responsive_menu ) {
 536              $nav_element_directives = static::get_nav_element_directives( $is_interactive );
 537              $wrapper_attributes    .= ' ' . $nav_element_directives;
 538          }
 539  
 540          return $wrapper_attributes;
 541      }
 542  
 543      /**
 544       * Gets the nav element directives.
 545       *
 546       * @param bool $is_interactive Whether the block is interactive.
 547       * @return string the directives for the navigation element.
 548       */
 549  	private static function get_nav_element_directives( $is_interactive ) {
 550          if ( ! $is_interactive ) {
 551              return '';
 552          }
 553          // When adding to this array be mindful of security concerns.
 554          $nav_element_context    = wp_interactivity_data_wp_context(
 555              array(
 556                  'overlayOpenedBy' => array(
 557                      'click' => false,
 558                      'hover' => false,
 559                      'focus' => false,
 560                  ),
 561                  'type'            => 'overlay',
 562                  'roleAttribute'   => '',
 563                  'ariaLabel'       => __( 'Menu' ),
 564              )
 565          );
 566          $nav_element_directives = '
 567           data-wp-interactive="core/navigation" '
 568          . $nav_element_context;
 569  
 570          return $nav_element_directives;
 571      }
 572  
 573      /**
 574       * Handle view script module loading.
 575       *
 576       * @param array         $attributes   The block attributes.
 577       * @param WP_Block      $block        The parsed block.
 578       * @param WP_Block_List $inner_blocks The list of inner blocks.
 579       */
 580  	private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
 581          if ( static::is_interactive( $attributes, $inner_blocks ) ) {
 582              $suffix = wp_scripts_get_suffix();
 583              if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
 584                  $module_url = gutenberg_url( '/build/interactivity/navigation.min.js' );
 585              }
 586  
 587              wp_register_script_module(
 588                  '@wordpress/block-library/navigation',
 589                  isset( $module_url ) ? $module_url : includes_url( "blocks/navigation/view{$suffix}.js" ),
 590                  array( '@wordpress/interactivity' ),
 591                  defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
 592              );
 593              wp_enqueue_script_module( '@wordpress/block-library/navigation' );
 594          }
 595      }
 596  
 597      /**
 598       * Returns the markup for the navigation block.
 599       *
 600       * @param array         $attributes The block attributes.
 601       * @param WP_Block_List $inner_blocks The list of inner blocks.
 602       * @return string Returns the navigation wrapper markup.
 603       */
 604  	private static function get_wrapper_markup( $attributes, $inner_blocks ) {
 605          $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks );
 606          if ( static::is_responsive( $attributes ) ) {
 607              return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html );
 608          }
 609          return $inner_blocks_html;
 610      }
 611  
 612      /**
 613       * Returns a unique name for the navigation.
 614       *
 615       * @param array $attributes The block attributes.
 616       * @return string Returns a unique name for the navigation.
 617       */
 618  	private static function get_unique_navigation_name( $attributes ) {
 619          $nav_menu_name = static::get_navigation_name( $attributes );
 620  
 621          // If the menu name has been used previously then append an ID
 622          // to the name to ensure uniqueness across a given post.
 623          if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) {
 624              $count         = static::$seen_menu_names[ $nav_menu_name ];
 625              $nav_menu_name = $nav_menu_name . ' ' . ( $count );
 626          }
 627  
 628          return $nav_menu_name;
 629      }
 630  
 631      /**
 632       * Renders the navigation block.
 633       *
 634       * @param array    $attributes The block attributes.
 635       * @param string   $content    The saved content.
 636       * @param WP_Block $block      The parsed block.
 637       * @return string Returns the navigation block markup.
 638       */
 639  	public static function render( $attributes, $content, $block ) {
 640          /**
 641           * Deprecated:
 642           * The rgbTextColor and rgbBackgroundColor attributes
 643           * have been deprecated in favor of
 644           * customTextColor and customBackgroundColor ones.
 645           * Move the values from old attrs to the new ones.
 646           */
 647          if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
 648              $attributes['customTextColor'] = $attributes['rgbTextColor'];
 649          }
 650  
 651          if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
 652              $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
 653          }
 654  
 655          unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
 656  
 657          $inner_blocks = static::get_inner_blocks( $attributes, $block );
 658          // Prevent navigation blocks referencing themselves from rendering.
 659          if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) {
 660              return '';
 661          }
 662  
 663          static::handle_view_script_module_loading( $attributes, $block, $inner_blocks );
 664  
 665          return sprintf(
 666              '<nav %1$s>%2$s</nav>',
 667              static::get_nav_wrapper_attributes( $attributes, $inner_blocks ),
 668              static::get_wrapper_markup( $attributes, $inner_blocks )
 669          );
 670      }
 671  }
 672  
 673  // These functions are used for the __unstableLocation feature and only active
 674  // when the gutenberg plugin is active.
 675  if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
 676      /**
 677       * Returns the menu items for a WordPress menu location.
 678       *
 679       * @param string $location The menu location.
 680       * @return array Menu items for the location.
 681       */
 682      function block_core_navigation_get_menu_items_at_location( $location ) {
 683          if ( empty( $location ) ) {
 684              return;
 685          }
 686  
 687          // Build menu data. The following approximates the code in
 688          // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.
 689  
 690          // Find the location in the list of locations, returning early if the
 691          // location can't be found.
 692          $locations = get_nav_menu_locations();
 693          if ( ! isset( $locations[ $location ] ) ) {
 694              return;
 695          }
 696  
 697          // Get the menu from the location, returning early if there is no
 698          // menu or there was an error.
 699          $menu = wp_get_nav_menu_object( $locations[ $location ] );
 700          if ( ! $menu || is_wp_error( $menu ) ) {
 701              return;
 702          }
 703  
 704          $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
 705          _wp_menu_item_classes_by_context( $menu_items );
 706  
 707          return $menu_items;
 708      }
 709  
 710  
 711      /**
 712       * Sorts a standard array of menu items into a nested structure keyed by the
 713       * id of the parent menu.
 714       *
 715       * @param array $menu_items Menu items to sort.
 716       * @return array An array keyed by the id of the parent menu where each element
 717       *               is an array of menu items that belong to that parent.
 718       */
 719      function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) {
 720          $sorted_menu_items = array();
 721          foreach ( (array) $menu_items as $menu_item ) {
 722              $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
 723          }
 724          unset( $menu_items, $menu_item );
 725  
 726          $menu_items_by_parent_id = array();
 727          foreach ( $sorted_menu_items as $menu_item ) {
 728              $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
 729          }
 730  
 731          return $menu_items_by_parent_id;
 732      }
 733  
 734      /**
 735       * Gets the inner blocks for the navigation block from the unstable location attribute.
 736       *
 737       * @param array $attributes The block attributes.
 738       * @return WP_Block_List Returns the inner blocks for the navigation block.
 739       */
 740      function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) {
 741          $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] );
 742          if ( empty( $menu_items ) ) {
 743              return new WP_Block_List( array(), $attributes );
 744          }
 745  
 746          $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items );
 747          $parsed_blocks           = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
 748          return new WP_Block_List( $parsed_blocks, $attributes );
 749      }
 750  }
 751  
 752  /**
 753   * Add Interactivity API directives to the navigation-submenu and page-list
 754   * blocks markup using the Tag Processor.
 755   *
 756   * @param WP_HTML_Tag_Processor $tags             Markup of the navigation block.
 757   * @param array                 $block_attributes Block attributes.
 758   *
 759   * @return string Submenu markup with the directives injected.
 760   */
 761  function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) {
 762      while ( $tags->next_tag(
 763          array(
 764              'tag_name'   => 'LI',
 765              'class_name' => 'has-child',
 766          )
 767      ) ) {
 768          // Add directives to the parent `<li>`.
 769          $tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
 770          $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' );
 771          $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
 772          $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
 773          $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
 774  
 775          // This is a fix for Safari. Without it, Safari doesn't change the active
 776          // element when the user clicks on a button. It can be removed once we add
 777          // an overlay to capture the clicks, instead of relying on the focusout
 778          // event.
 779          $tags->set_attribute( 'tabindex', '-1' );
 780  
 781          if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) {
 782              $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' );
 783              $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' );
 784          }
 785  
 786          // Add directives to the toggle submenu button.
 787          if ( $tags->next_tag(
 788              array(
 789                  'tag_name'   => 'BUTTON',
 790                  'class_name' => 'wp-block-navigation-submenu__toggle',
 791              )
 792          ) ) {
 793              $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
 794              $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
 795              // The `aria-expanded` attribute for SSR is already added in the submenu block.
 796          }
 797          // Add directives to the submenu.
 798          if ( $tags->next_tag(
 799              array(
 800                  'tag_name'   => 'UL',
 801                  'class_name' => 'wp-block-navigation__submenu-container',
 802              )
 803          ) ) {
 804              $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' );
 805          }
 806  
 807          // Iterate through subitems if exist.
 808          block_core_navigation_add_directives_to_submenu( $tags, $block_attributes );
 809      }
 810      return $tags->get_updated_html();
 811  }
 812  
 813  /**
 814   * Build an array with CSS classes and inline styles defining the colors
 815   * which will be applied to the navigation markup in the front-end.
 816   *
 817   * @param array $attributes Navigation block attributes.
 818   *
 819   * @return array Colors CSS classes and inline styles.
 820   */
 821  function block_core_navigation_build_css_colors( $attributes ) {
 822      $colors = array(
 823          'css_classes'           => array(),
 824          'inline_styles'         => '',
 825          'overlay_css_classes'   => array(),
 826          'overlay_inline_styles' => '',
 827      );
 828  
 829      // Text color.
 830      $has_named_text_color  = array_key_exists( 'textColor', $attributes );
 831      $has_custom_text_color = array_key_exists( 'customTextColor', $attributes );
 832  
 833      // If has text color.
 834      if ( $has_custom_text_color || $has_named_text_color ) {
 835          // Add has-text-color class.
 836          $colors['css_classes'][] = 'has-text-color';
 837      }
 838  
 839      if ( $has_named_text_color ) {
 840          // Add the color class.
 841          $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] );
 842      } elseif ( $has_custom_text_color ) {
 843          // Add the custom color inline style.
 844          $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] );
 845      }
 846  
 847      // Background color.
 848      $has_named_background_color  = array_key_exists( 'backgroundColor', $attributes );
 849      $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes );
 850  
 851      // If has background color.
 852      if ( $has_custom_background_color || $has_named_background_color ) {
 853          // Add has-background class.
 854          $colors['css_classes'][] = 'has-background';
 855      }
 856  
 857      if ( $has_named_background_color ) {
 858          // Add the background-color class.
 859          $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
 860      } elseif ( $has_custom_background_color ) {
 861          // Add the custom background-color inline style.
 862          $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] );
 863      }
 864  
 865      // Overlay text color.
 866      $has_named_overlay_text_color  = array_key_exists( 'overlayTextColor', $attributes );
 867      $has_custom_overlay_text_color = array_key_exists( 'customOverlayTextColor', $attributes );
 868  
 869      // If has overlay text color.
 870      if ( $has_custom_overlay_text_color || $has_named_overlay_text_color ) {
 871          // Add has-text-color class.
 872          $colors['overlay_css_classes'][] = 'has-text-color';
 873      }
 874  
 875      if ( $has_named_overlay_text_color ) {
 876          // Add the overlay color class.
 877          $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', $attributes['overlayTextColor'] );
 878      } elseif ( $has_custom_overlay_text_color ) {
 879          // Add the custom overlay color inline style.
 880          $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $attributes['customOverlayTextColor'] );
 881      }
 882  
 883      // Overlay background color.
 884      $has_named_overlay_background_color  = array_key_exists( 'overlayBackgroundColor', $attributes );
 885      $has_custom_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $attributes );
 886  
 887      // If has overlay background color.
 888      if ( $has_custom_overlay_background_color || $has_named_overlay_background_color ) {
 889          // Add has-background class.
 890          $colors['overlay_css_classes'][] = 'has-background';
 891      }
 892  
 893      if ( $has_named_overlay_background_color ) {
 894          // Add the overlay background-color class.
 895          $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', $attributes['overlayBackgroundColor'] );
 896      } elseif ( $has_custom_overlay_background_color ) {
 897          // Add the custom overlay background-color inline style.
 898          $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customOverlayBackgroundColor'] );
 899      }
 900  
 901      return $colors;
 902  }
 903  
 904  /**
 905   * Build an array with CSS classes and inline styles defining the font sizes
 906   * which will be applied to the navigation markup in the front-end.
 907   *
 908   * @param array $attributes Navigation block attributes.
 909   *
 910   * @return array Font size CSS classes and inline styles.
 911   */
 912  function block_core_navigation_build_css_font_sizes( $attributes ) {
 913      // CSS classes.
 914      $font_sizes = array(
 915          'css_classes'   => array(),
 916          'inline_styles' => '',
 917      );
 918  
 919      $has_named_font_size  = array_key_exists( 'fontSize', $attributes );
 920      $has_custom_font_size = array_key_exists( 'customFontSize', $attributes );
 921  
 922      if ( $has_named_font_size ) {
 923          // Add the font size class.
 924          $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] );
 925      } elseif ( $has_custom_font_size ) {
 926          // Add the custom font size inline style.
 927          $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] );
 928      }
 929  
 930      return $font_sizes;
 931  }
 932  
 933  /**
 934   * Returns the top-level submenu SVG chevron icon.
 935   *
 936   * @return string
 937   */
 938  function block_core_navigation_render_submenu_icon() {
 939      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>';
 940  }
 941  
 942  /**
 943   * Filter out empty "null" blocks from the block list.
 944   * 'parse_blocks' includes a null block with '\n\n' as the content when
 945   * it encounters whitespace. This is not a bug but rather how the parser
 946   * is designed.
 947   *
 948   * @param array $parsed_blocks the parsed blocks to be normalized.
 949   * @return array the normalized parsed blocks.
 950   */
 951  function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) {
 952      $filtered = array_filter(
 953          $parsed_blocks,
 954          static function ( $block ) {
 955              return isset( $block['blockName'] );
 956          }
 957      );
 958  
 959      // Reset keys.
 960      return array_values( $filtered );
 961  }
 962  
 963  /**
 964   * Returns true if the navigation block contains a nested navigation block.
 965   *
 966   * @param WP_Block_List $inner_blocks Inner block instance to be normalized.
 967   * @return bool true if the navigation block contains a nested navigation block.
 968   */
 969  function block_core_navigation_block_contains_core_navigation( $inner_blocks ) {
 970      foreach ( $inner_blocks as $block ) {
 971          if ( 'core/navigation' === $block->name ) {
 972              return true;
 973          }
 974          if ( $block->inner_blocks && block_core_navigation_block_contains_core_navigation( $block->inner_blocks ) ) {
 975              return true;
 976          }
 977      }
 978  
 979      return false;
 980  }
 981  
 982  /**
 983   * Retrieves the appropriate fallback to be used on the front of the
 984   * site when there is no menu assigned to the Nav block.
 985   *
 986   * This aims to mirror how the fallback mechanic for wp_nav_menu works.
 987   * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information.
 988   *
 989   * @return array the array of blocks to be used as a fallback.
 990   */
 991  function block_core_navigation_get_fallback_blocks() {
 992      $page_list_fallback = array(
 993          array(
 994              'blockName'    => 'core/page-list',
 995              'innerContent' => array(),
 996              'attrs'        => array(),
 997          ),
 998      );
 999  
1000      $registry = WP_Block_Type_Registry::get_instance();
1001  
1002      // If `core/page-list` is not registered then return empty blocks.
1003      $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array();
1004      $navigation_post = WP_Navigation_Fallback::get_fallback();
1005  
1006      // Use the first non-empty Navigation as fallback if available.
1007      if ( $navigation_post ) {
1008          $parsed_blocks  = parse_blocks( $navigation_post->post_content );
1009          $maybe_fallback = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
1010  
1011          // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
1012          // In this case default to the (Page List) fallback.
1013          $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
1014  
1015          if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
1016              // Run Block Hooks algorithm to inject hooked blocks.
1017              // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
1018              $markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post );
1019              $blocks = parse_blocks( $markup );
1020  
1021              if ( isset( $blocks[0]['innerBlocks'] ) ) {
1022                  $fallback_blocks = $blocks[0]['innerBlocks'];
1023              }
1024          }
1025      }
1026  
1027      /**
1028       * Filters the fallback experience for the Navigation block.
1029       *
1030       * Returning a falsey value will opt out of the fallback and cause the block not to render.
1031       * To customise the blocks provided return an array of blocks - these should be valid
1032       * children of the `core/navigation` block.
1033       *
1034       * @since 5.9.0
1035       *
1036       * @param array[] $fallback_blocks default fallback blocks provided by the default block mechanic.
1037       */
1038      return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks );
1039  }
1040  
1041  /**
1042   * Iterate through all inner blocks recursively and get navigation link block's post IDs.
1043   *
1044   * @param WP_Block_List $inner_blocks Block list class instance.
1045   *
1046   * @return array Array of post IDs.
1047   */
1048  function block_core_navigation_get_post_ids( $inner_blocks ) {
1049      $post_ids = array_map( 'block_core_navigation_from_block_get_post_ids', iterator_to_array( $inner_blocks ) );
1050      return array_unique( array_merge( ...$post_ids ) );
1051  }
1052  
1053  /**
1054   * Get post IDs from a navigation link block instance.
1055   *
1056   * @param WP_Block $block Instance of a block.
1057   *
1058   * @return array Array of post IDs.
1059   */
1060  function block_core_navigation_from_block_get_post_ids( $block ) {
1061      $post_ids = array();
1062  
1063      if ( $block->inner_blocks ) {
1064          $post_ids = block_core_navigation_get_post_ids( $block->inner_blocks );
1065      }
1066  
1067      if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) {
1068          if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] && isset( $block->attributes['id'] ) ) {
1069              $post_ids[] = $block->attributes['id'];
1070          }
1071      }
1072  
1073      return $post_ids;
1074  }
1075  
1076  /**
1077   * Renders the `core/navigation` block on server.
1078   *
1079   * @param array    $attributes The block attributes.
1080   * @param string   $content    The saved content.
1081   * @param WP_Block $block      The parsed block.
1082   *
1083   * @return string Returns the navigation block markup.
1084   */
1085  function render_block_core_navigation( $attributes, $content, $block ) {
1086      return WP_Navigation_Block_Renderer::render( $attributes, $content, $block );
1087  }
1088  
1089  /**
1090   * Register the navigation block.
1091   *
1092   * @uses render_block_core_navigation()
1093   * @throws WP_Error An WP_Error exception parsing the block definition.
1094   */
1095  function register_block_core_navigation() {
1096      register_block_type_from_metadata(
1097          __DIR__ . '/navigation',
1098          array(
1099              'render_callback' => 'render_block_core_navigation',
1100          )
1101      );
1102  }
1103  
1104  add_action( 'init', 'register_block_core_navigation' );
1105  
1106  /**
1107   * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
1108   *
1109   * @param array $parsed_block The block being rendered.
1110   *
1111   * @return array The block being rendered without typographic presets.
1112   */
1113  function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) {
1114      if ( 'core/navigation' === $parsed_block['blockName'] ) {
1115          $attribute_to_prefix_map = array(
1116              'fontStyle'      => 'var:preset|font-style|',
1117              'fontWeight'     => 'var:preset|font-weight|',
1118              'textDecoration' => 'var:preset|text-decoration|',
1119              'textTransform'  => 'var:preset|text-transform|',
1120          );
1121          foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) {
1122              if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) {
1123                  $prefix_len      = strlen( $prefix );
1124                  $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ];
1125                  if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) {
1126                      $attribute_value = substr( $attribute_value, $prefix_len );
1127                  }
1128                  if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) {
1129                      $attribute_value = 'line-through';
1130                  }
1131              }
1132          }
1133      }
1134  
1135      return $parsed_block;
1136  }
1137  
1138  add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' );
1139  
1140  /**
1141   * Turns menu item data into a nested array of parsed blocks
1142   *
1143   * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
1144   *
1145   * @param array $menu_items               An array of menu items that represent
1146   *                                        an individual level of a menu.
1147   * @param array $menu_items_by_parent_id  An array keyed by the id of the
1148   *                                        parent menu where each element is an
1149   *                                        array of menu items that belong to
1150   *                                        that parent.
1151   * @return array An array of parsed block data.
1152   */
1153  function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
1154  
1155      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::parse_blocks_from_menu_items' );
1156  
1157      if ( empty( $menu_items ) ) {
1158          return array();
1159      }
1160  
1161      $blocks = array();
1162  
1163      foreach ( $menu_items as $menu_item ) {
1164          $class_name       = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
1165          $id               = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
1166          $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
1167          $rel              = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
1168          $kind             = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
1169  
1170          $block = array(
1171              'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link',
1172              'attrs'     => array(
1173                  'className'     => $class_name,
1174                  'description'   => $menu_item->description,
1175                  'id'            => $id,
1176                  'kind'          => $kind,
1177                  'label'         => $menu_item->title,
1178                  'opensInNewTab' => $opens_in_new_tab,
1179                  'rel'           => $rel,
1180                  'title'         => $menu_item->attr_title,
1181                  'type'          => $menu_item->object,
1182                  'url'           => $menu_item->url,
1183              ),
1184          );
1185  
1186          $block['innerBlocks']  = isset( $menu_items_by_parent_id[ $menu_item->ID ] )
1187              ? block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id )
1188              : array();
1189          $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] );
1190  
1191          $blocks[] = $block;
1192      }
1193  
1194      return $blocks;
1195  }
1196  
1197  /**
1198   * Get the classic navigation menu to use as a fallback.
1199   *
1200   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback() instead.
1201   *
1202   * @return object WP_Term The classic navigation.
1203   */
1204  function block_core_navigation_get_classic_menu_fallback() {
1205  
1206      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback' );
1207  
1208      $classic_nav_menus = wp_get_nav_menus();
1209  
1210      // If menus exist.
1211      if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) ) {
1212          // Handles simple use case where user has a classic menu and switches to a block theme.
1213  
1214          // Returns the menu assigned to location `primary`.
1215          $locations = get_nav_menu_locations();
1216          if ( isset( $locations['primary'] ) ) {
1217              $primary_menu = wp_get_nav_menu_object( $locations['primary'] );
1218              if ( $primary_menu ) {
1219                  return $primary_menu;
1220              }
1221          }
1222  
1223          // Returns a menu if `primary` is its slug.
1224          foreach ( $classic_nav_menus as $classic_nav_menu ) {
1225              if ( 'primary' === $classic_nav_menu->slug ) {
1226                  return $classic_nav_menu;
1227              }
1228          }
1229  
1230          // Otherwise return the most recently created classic menu.
1231          usort(
1232              $classic_nav_menus,
1233              static function ( $a, $b ) {
1234                  return $b->term_id - $a->term_id;
1235              }
1236          );
1237          return $classic_nav_menus[0];
1238      }
1239  }
1240  
1241  /**
1242   * Converts a classic navigation to blocks.
1243   *
1244   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
1245   *
1246   * @param  object $classic_nav_menu WP_Term The classic navigation object to convert.
1247   * @return array the normalized parsed blocks.
1248   */
1249  function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
1250  
1251      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks' );
1252  
1253      // BEGIN: Code that already exists in wp_nav_menu().
1254      $menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) );
1255  
1256      // Set up the $menu_item variables.
1257      _wp_menu_item_classes_by_context( $menu_items );
1258  
1259      $sorted_menu_items = array();
1260      foreach ( (array) $menu_items as $menu_item ) {
1261          $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
1262      }
1263  
1264      unset( $menu_items, $menu_item );
1265  
1266      // END: Code that already exists in wp_nav_menu().
1267  
1268      $menu_items_by_parent_id = array();
1269      foreach ( $sorted_menu_items as $menu_item ) {
1270          $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
1271      }
1272  
1273      $inner_blocks = block_core_navigation_parse_blocks_from_menu_items(
1274          isset( $menu_items_by_parent_id[0] )
1275              ? $menu_items_by_parent_id[0]
1276              : array(),
1277          $menu_items_by_parent_id
1278      );
1279  
1280      return serialize_blocks( $inner_blocks );
1281  }
1282  
1283  /**
1284   * If there's a classic menu then use it as a fallback.
1285   *
1286   * @deprecated 6.3.0 Use WP_Navigation_Fallback::create_classic_menu_fallback() instead.
1287   *
1288   * @return array the normalized parsed blocks.
1289   */
1290  function block_core_navigation_maybe_use_classic_menu_fallback() {
1291  
1292      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::create_classic_menu_fallback' );
1293  
1294      // See if we have a classic menu.
1295      $classic_nav_menu = block_core_navigation_get_classic_menu_fallback();
1296  
1297      if ( ! $classic_nav_menu ) {
1298          return;
1299      }
1300  
1301      // If we have a classic menu then convert it to blocks.
1302      $classic_nav_menu_blocks = block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu );
1303  
1304      if ( empty( $classic_nav_menu_blocks ) ) {
1305          return;
1306      }
1307  
1308      // Create a new navigation menu from the classic menu.
1309      $wp_insert_post_result = wp_insert_post(
1310          array(
1311              'post_content' => $classic_nav_menu_blocks,
1312              'post_title'   => $classic_nav_menu->name,
1313              'post_name'    => $classic_nav_menu->slug,
1314              'post_status'  => 'publish',
1315              'post_type'    => 'wp_navigation',
1316          ),
1317          true // So that we can check whether the result is an error.
1318      );
1319  
1320      if ( is_wp_error( $wp_insert_post_result ) ) {
1321          return;
1322      }
1323  
1324      // Fetch the most recently published navigation which will be the classic one created above.
1325      return block_core_navigation_get_most_recently_published_navigation();
1326  }
1327  
1328  /**
1329   * Finds the most recently published `wp_navigation` Post.
1330   *
1331   * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_most_recently_published_navigation() instead.
1332   *
1333   * @return WP_Post|null the first non-empty Navigation or null.
1334   */
1335  function block_core_navigation_get_most_recently_published_navigation() {
1336  
1337      _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_most_recently_published_navigation' );
1338  
1339      // Default to the most recently created menu.
1340      $parsed_args = array(
1341          'post_type'              => 'wp_navigation',
1342          'no_found_rows'          => true,
1343          'update_post_meta_cache' => false,
1344          'update_post_term_cache' => false,
1345          'order'                  => 'DESC',
1346          'orderby'                => 'date',
1347          'post_status'            => 'publish',
1348          'posts_per_page'         => 1, // get only the most recent.
1349      );
1350  
1351      $navigation_post = new WP_Query( $parsed_args );
1352      if ( count( $navigation_post->posts ) > 0 ) {
1353          return $navigation_post->posts[0];
1354      }
1355  
1356      return null;
1357  }
1358  
1359  /**
1360   * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
1361   *
1362   * @param string $serialized_block The serialized markup of a block and its inner blocks.
1363   * @return string
1364   */
1365  function block_core_navigation_remove_serialized_parent_block( $serialized_block ) {
1366      $start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
1367      $end   = strrpos( $serialized_block, '<!--' );
1368      return substr( $serialized_block, $start, $end - $start );
1369  }
1370  
1371  /**
1372   * Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object.
1373   * The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute.
1374   *
1375   * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
1376   * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
1377   *
1378   * @return array the normalized parsed blocks.
1379   */
1380  function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) {
1381      $attributes = array();
1382  
1383      if ( isset( $post->ID ) ) {
1384          $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
1385          if ( ! empty( $ignored_hooked_blocks ) ) {
1386              $ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
1387              $attributes['metadata'] = array(
1388                  'ignoredHookedBlocks' => $ignored_hooked_blocks,
1389              );
1390          }
1391      }
1392  
1393      $mock_anchor_parent_block = array(
1394          'blockName'    => 'core/navigation',
1395          'attrs'        => $attributes,
1396          'innerBlocks'  => $inner_blocks,
1397          'innerContent' => array_fill( 0, count( $inner_blocks ), null ),
1398      );
1399  
1400      return $mock_anchor_parent_block;
1401  }
1402  
1403  /**
1404   * Insert hooked blocks into a Navigation block.
1405   *
1406   * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
1407   * this function inserts hooked blocks into it, and returns the serialized inner blocks in a
1408   * mock Navigation block wrapper.
1409   *
1410   * If there are any hooked blocks that need to be inserted as the Navigation block's first or last
1411   * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
1412   * of those hooked blocks should be exempted from insertion.
1413   *
1414   * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
1415   * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
1416   * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
1417   */
1418  function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
1419      $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
1420      $hooked_blocks         = get_hooked_blocks();
1421      $before_block_visitor  = null;
1422      $after_block_visitor   = null;
1423  
1424      if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
1425          $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
1426          $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' );
1427      }
1428  
1429      return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
1430  }
1431  
1432  /**
1433   * Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks.
1434   *
1435   * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
1436   * this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a
1437   * mock Navigation block wrapper.
1438   *
1439   * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
1440   * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
1441   * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
1442   */
1443  function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) {
1444      $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );
1445      $hooked_blocks         = get_hooked_blocks();
1446      $before_block_visitor  = null;
1447      $after_block_visitor   = null;
1448  
1449      if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
1450          $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
1451          $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' );
1452      }
1453  
1454      return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor );
1455  }
1456  
1457  /**
1458   * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
1459   *
1460   * @access private
1461   * @since 6.5.0
1462   *
1463   * @param stdClass $post Post object.
1464   * @return stdClass The updated post object.
1465   */
1466  function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
1467      /*
1468       * In this scenario the user has likely tried to create a navigation via the REST API.
1469       * In which case we won't have a post ID to work with and store meta against.
1470       */
1471      if ( empty( $post->ID ) ) {
1472          return $post;
1473      }
1474  
1475      /**
1476       * Skip meta generation when consumers intentionally update specific Navigation fields
1477       * and omit the content update.
1478       */
1479      if ( ! isset( $post->post_content ) ) {
1480          return $post;
1481      }
1482  
1483      /*
1484       * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
1485       * all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
1486       */
1487      $blocks = parse_blocks( $post->post_content );
1488  
1489      /*
1490       * Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that
1491       * we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be
1492       * used as context for hooked blocks insertion).
1493       * We thus have to look it up from the DB,based on `$post->ID`.
1494       */
1495      $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) );
1496  
1497      $root_nav_block        = parse_blocks( $markup )[0];
1498      $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
1499          ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks']
1500          : array();
1501  
1502      if ( ! empty( $ignored_hooked_blocks ) ) {
1503          $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
1504          if ( ! empty( $existing_ignored_hooked_blocks ) ) {
1505              $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
1506              $ignored_hooked_blocks          = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
1507          }
1508          update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
1509      }
1510  
1511      $post->post_content = block_core_navigation_remove_serialized_parent_block( $markup );
1512      return $post;
1513  }
1514  
1515  /*
1516   * Before adding our filter, we verify if it's already added in Core.
1517   * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
1518   * Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
1519   */
1520  $rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1521  
1522  /*
1523   * Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
1524   * that are not present in Gutenberg's WP 6.5 compatibility layer.
1525   */
1526  if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
1527      add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' );
1528  }
1529  
1530  /*
1531   * Previous versions of Gutenberg were attaching the block_core_navigation_update_ignore_hooked_blocks_meta
1532   * function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_).
1533   * To avoid collisions, we need to remove the filter from that action if it's present.
1534   */
1535  if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
1536      remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback );
1537  }
1538  
1539  /**
1540   * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
1541   *
1542   * @param WP_REST_Response $response The response object.
1543   * @param WP_Post          $post     Post object.
1544   * @return WP_REST_Response The response object.
1545   */
1546  function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) {
1547      if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
1548          return $response;
1549      }
1550      $parsed_blocks = parse_blocks( $response->data['content']['raw'] );
1551      $content       = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post );
1552  
1553      // Remove mock Navigation block wrapper.
1554      $content = block_core_navigation_remove_serialized_parent_block( $content );
1555  
1556      $response->data['content']['raw']      = $content;
1557      $response->data['content']['rendered'] = apply_filters( 'the_content', $content );
1558  
1559      return $response;
1560  }
1561  
1562  /*
1563   *  Before adding our filter, we verify if it's already added in Core.
1564   * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_".
1565   * Therefore, we concatenate the Core's function name to circumvent this prefix for our check.
1566   */
1567  $rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response';
1568  
1569  /*
1570   * Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
1571   * that are not present in Gutenberg's WP 6.5 compatibility layer.
1572   */
1573  if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) ) {
1574      add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 );
1575  }


Generated : Tue Apr 23 08:20:01 2024 Cross-referenced by PHPXref