Make WordPress Core


Ignore:
Timestamp:
01/29/2024 09:04:18 PM (3 years ago)
Author:
youknowriad
Message:

Editor: Update the WordPress packages to the Gutenberg 16.7 RC2 version.

This patch, somewhat small brings a lot to WordPress.
This includes features like:

  • DataViews.
  • Customization tools like box shadow, background size and repeat.
  • UI improvements in the site editor.
  • Preferences sharing between the post and site editors.
  • Unified panels and editors between post and site editors.
  • Improved template mode in the post editor.
  • Iterations to multiple interactive blocks.
  • Preparing the blocks and UI for pattern overrides.
  • and a lot more.

Props luisherranz, gziolo, isabel_brison, costdev, jonsurrell, peterwilsoncc, get_dave, antonvlasenko, desrosj.
See #60315.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks/navigation.php

    r56987 r57377  
    55 * @package WordPress
    66 */
     7
     8/**
     9 * Helper functions used to render the navigation block.
     10 */
     11class WP_Navigation_Block_Renderer {
     12        /**
     13         * Used to determine which blocks are wrapped in an <li>.
     14         *
     15         * @var array
     16         */
     17        private static $nav_blocks_wrapped_in_list_item = array(
     18                'core/navigation-link',
     19                'core/home-link',
     20                'core/site-title',
     21                'core/site-logo',
     22                'core/navigation-submenu',
     23        );
     24
     25        /**
     26         * Used to determine which blocks need an <li> wrapper.
     27         *
     28         * @var array
     29         */
     30        private static $needs_list_item_wrapper = array(
     31                'core/site-title',
     32                'core/site-logo',
     33        );
     34
     35        /**
     36         * Keeps track of all the navigation names that have been seen.
     37         *
     38         * @var array
     39         */
     40        private static $seen_menu_names = array();
     41
     42        /**
     43         * Returns whether or not this is responsive navigation.
     44         *
     45         * @param array $attributes The block attributes.
     46         * @return bool Returns whether or not this is responsive navigation.
     47         */
     48        private static function is_responsive( $attributes ) {
     49                /**
     50                 * This is for backwards compatibility after the `isResponsive` attribute was been removed.
     51                 */
     52
     53                $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
     54                return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
     55        }
     56
     57        /**
     58         * Returns whether or not a navigation has a submenu.
     59         *
     60         * @param WP_Block_List $inner_blocks The list of inner blocks.
     61         * @return bool Returns whether or not a navigation has a submenu.
     62         */
     63        private static function has_submenus( $inner_blocks ) {
     64                foreach ( $inner_blocks as $inner_block ) {
     65                        $inner_block_content = $inner_block->render();
     66                        $p                   = new WP_HTML_Tag_Processor( $inner_block_content );
     67                        if ( $p->next_tag(
     68                                array(
     69                                        'name'       => 'LI',
     70                                        'class_name' => 'has-child',
     71                                )
     72                        ) ) {
     73                                return true;
     74                        }
     75                }
     76                return false;
     77        }
     78
     79        /**
     80         * Determine whether the navigation blocks is interactive.
     81         *
     82         * @param array         $attributes   The block attributes.
     83         * @param WP_Block_List $inner_blocks The list of inner blocks.
     84         * @return bool Returns whether or not to load the view script.
     85         */
     86        private static function is_interactive( $attributes, $inner_blocks ) {
     87                $has_submenus       = static::has_submenus( $inner_blocks );
     88                $is_responsive_menu = static::is_responsive( $attributes );
     89                return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu;
     90        }
     91
     92        /**
     93         * Returns whether or not a block needs a list item wrapper.
     94         *
     95         * @param WP_Block $block The block.
     96         * @return bool Returns whether or not a block needs a list item wrapper.
     97         */
     98        private static function does_block_need_a_list_item_wrapper( $block ) {
     99                return in_array( $block->name, static::$needs_list_item_wrapper, true );
     100        }
     101
     102        /**
     103         * Returns the markup for a single inner block.
     104         *
     105         * @param WP_Block $inner_block The inner block.
     106         * @return string Returns the markup for a single inner block.
     107         */
     108        private static function get_markup_for_inner_block( $inner_block ) {
     109                $inner_block_content = $inner_block->render();
     110                if ( ! empty( $inner_block_content ) ) {
     111                        if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) {
     112                                return '<li class="wp-block-navigation-item">' . $inner_block_content . '</li>';
     113                        }
     114
     115                        return $inner_block_content;
     116                }
     117        }
     118
     119        /**
     120         * Returns the html for the inner blocks of the navigation block.
     121         *
     122         * @param array         $attributes   The block attributes.
     123         * @param WP_Block_List $inner_blocks The list of inner blocks.
     124         * @return string Returns the html for the inner blocks of the navigation block.
     125         */
     126        private static function get_inner_blocks_html( $attributes, $inner_blocks ) {
     127                $has_submenus   = static::has_submenus( $inner_blocks );
     128                $is_interactive = static::is_interactive( $attributes, $inner_blocks );
     129
     130                $style                = static::get_styles( $attributes );
     131                $class                = static::get_classes( $attributes );
     132                $container_attributes = get_block_wrapper_attributes(
     133                        array(
     134                                'class' => 'wp-block-navigation__container ' . $class,
     135                                'style' => $style,
     136                        )
     137                );
     138
     139                $inner_blocks_html = '';
     140                $is_list_open      = false;
     141
     142                foreach ( $inner_blocks as $inner_block ) {
     143                        $is_list_item = in_array( $inner_block->name, static::$nav_blocks_wrapped_in_list_item, true );
     144
     145                        if ( $is_list_item && ! $is_list_open ) {
     146                                $is_list_open       = true;
     147                                $inner_blocks_html .= sprintf(
     148                                        '<ul %1$s>',
     149                                        $container_attributes
     150                                );
     151                        }
     152
     153                        if ( ! $is_list_item && $is_list_open ) {
     154                                $is_list_open       = false;
     155                                $inner_blocks_html .= '</ul>';
     156                        }
     157
     158                        $inner_blocks_html .= static::get_markup_for_inner_block( $inner_block );
     159                }
     160
     161                if ( $is_list_open ) {
     162                        $inner_blocks_html .= '</ul>';
     163                }
     164
     165                // Add directives to the submenu if needed.
     166                if ( $has_submenus && $is_interactive ) {
     167                        $tags              = new WP_HTML_Tag_Processor( $inner_blocks_html );
     168                        $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes );
     169                }
     170
     171                return $inner_blocks_html;
     172        }
     173
     174        /**
     175         * Gets the inner blocks for the navigation block from the navigation post.
     176         *
     177         * @param array $attributes The block attributes.
     178         * @return WP_Block_List Returns the inner blocks for the navigation block.
     179         */
     180        private static function get_inner_blocks_from_navigation_post( $attributes ) {
     181                $navigation_post = get_post( $attributes['ref'] );
     182                if ( ! isset( $navigation_post ) ) {
     183                        return new WP_Block_List( array(), $attributes );
     184                }
     185
     186                // Only published posts are valid. If this is changed then a corresponding change
     187                // must also be implemented in `use-navigation-menu.js`.
     188                if ( 'publish' === $navigation_post->post_status ) {
     189                        $parsed_blocks = parse_blocks( $navigation_post->post_content );
     190
     191                        // 'parse_blocks' includes a null block with '\n\n' as the content when
     192                        // it encounters whitespace. This code strips it.
     193                        $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
     194
     195                        if ( function_exists( 'get_hooked_blocks' ) ) {
     196                                // Run Block Hooks algorithm to inject hooked blocks.
     197                                $markup         = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post );
     198                                $root_nav_block = parse_blocks( $markup )[0];
     199
     200                                $blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks;
     201                        }
     202
     203                        // TODO - this uses the full navigation block attributes for the
     204                        // context which could be refined.
     205                        return new WP_Block_List( $blocks, $attributes );
     206                }
     207        }
     208
     209        /**
     210         * Gets the inner blocks for the navigation block from the fallback.
     211         *
     212         * @param array $attributes The block attributes.
     213         * @return WP_Block_List Returns the inner blocks for the navigation block.
     214         */
     215        private static function get_inner_blocks_from_fallback( $attributes ) {
     216                $fallback_blocks = block_core_navigation_get_fallback_blocks();
     217
     218                // Fallback my have been filtered so do basic test for validity.
     219                if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
     220                        return new WP_Block_List( array(), $attributes );
     221                }
     222
     223                return new WP_Block_List( $fallback_blocks, $attributes );
     224        }
     225
     226        /**
     227         * Gets the inner blocks for the navigation block.
     228         *
     229         * @param array    $attributes The block attributes.
     230         * @param WP_Block $block The parsed block.
     231         * @return WP_Block_List Returns the inner blocks for the navigation block.
     232         */
     233        private static function get_inner_blocks( $attributes, $block ) {
     234                $inner_blocks = $block->inner_blocks;
     235
     236                // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
     237                if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
     238                        $attributes['ref'] = $attributes['navigationMenuId'];
     239                }
     240
     241                // If:
     242                // - the gutenberg plugin is active
     243                // - `__unstableLocation` is defined
     244                // - we have menu items at the defined location
     245                // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
     246                // ...then create inner blocks from the classic menu assigned to that location.
     247                if (
     248                        defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
     249                        array_key_exists( '__unstableLocation', $attributes ) &&
     250                        ! array_key_exists( 'ref', $attributes ) &&
     251                        ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
     252                ) {
     253                        $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes );
     254                }
     255
     256                // Load inner blocks from the navigation post.
     257                if ( array_key_exists( 'ref', $attributes ) ) {
     258                        $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes );
     259                }
     260
     261                // If there are no inner blocks then fallback to rendering an appropriate fallback.
     262                if ( empty( $inner_blocks ) ) {
     263                        $inner_blocks = static::get_inner_blocks_from_fallback( $attributes );
     264                }
     265
     266                /**
     267                 * Filter navigation block $inner_blocks.
     268                 * Allows modification of a navigation block menu items.
     269                 *
     270                 * @since 6.1.0
     271                 *
     272                 * @param \WP_Block_List $inner_blocks
     273                 */
     274                $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks );
     275
     276                $post_ids = block_core_navigation_get_post_ids( $inner_blocks );
     277                if ( $post_ids ) {
     278                        _prime_post_caches( $post_ids, false, false );
     279                }
     280
     281                return $inner_blocks;
     282        }
     283
     284        /**
     285         * Gets the name of the current navigation, if it has one.
     286         *
     287         * @param array $attributes The block attributes.
     288         * @return string Returns the name of the navigation.
     289         */
     290        private static function get_navigation_name( $attributes ) {
     291
     292                $navigation_name = $attributes['ariaLabel'] ?? '';
     293
     294                // Load the navigation post.
     295                if ( array_key_exists( 'ref', $attributes ) ) {
     296                        $navigation_post = get_post( $attributes['ref'] );
     297                        if ( ! isset( $navigation_post ) ) {
     298                                return $navigation_name;
     299                        }
     300
     301                        // Only published posts are valid. If this is changed then a corresponding change
     302                        // must also be implemented in `use-navigation-menu.js`.
     303                        if ( 'publish' === $navigation_post->post_status ) {
     304                                $navigation_name = $navigation_post->post_title;
     305
     306                                // This is used to count the number of times a navigation name has been seen,
     307                                // so that we can ensure every navigation has a unique id.
     308                                if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) {
     309                                        ++static::$seen_menu_names[ $navigation_name ];
     310                                } else {
     311                                        static::$seen_menu_names[ $navigation_name ] = 1;
     312                                }
     313                        }
     314                }
     315
     316                return $navigation_name;
     317        }
     318
     319        /**
     320         * Returns the layout class for the navigation block.
     321         *
     322         * @param array $attributes The block attributes.
     323         * @return string Returns the layout class for the navigation block.
     324         */
     325        private static function get_layout_class( $attributes ) {
     326                $layout_justification = array(
     327                        'left'          => 'items-justified-left',
     328                        'right'         => 'items-justified-right',
     329                        'center'        => 'items-justified-center',
     330                        'space-between' => 'items-justified-space-between',
     331                );
     332
     333                $layout_class = '';
     334                if (
     335                        isset( $attributes['layout']['justifyContent'] ) &&
     336                        isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
     337                ) {
     338                        $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
     339                }
     340                if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
     341                        $layout_class .= ' is-vertical';
     342                }
     343
     344                if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
     345                        $layout_class .= ' no-wrap';
     346                }
     347                return $layout_class;
     348        }
     349
     350        /**
     351         * Return classes for the navigation block.
     352         *
     353         * @param array $attributes The block attributes.
     354         * @return string Returns the classes for the navigation block.
     355         */
     356        private static function get_classes( $attributes ) {
     357                // Restore legacy classnames for submenu positioning.
     358                $layout_class       = static::get_layout_class( $attributes );
     359                $colors             = block_core_navigation_build_css_colors( $attributes );
     360                $font_sizes         = block_core_navigation_build_css_font_sizes( $attributes );
     361                $is_responsive_menu = static::is_responsive( $attributes );
     362
     363                // Manually add block support text decoration as CSS class.
     364                $text_decoration       = $attributes['style']['typography']['textDecoration'] ?? null;
     365                $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
     366
     367                // Sets the is-collapsed class when the navigation is set to always use the overlay.
     368                // This saves us from needing to do this check in the view.js file (see the collapseNav function).
     369                $is_collapsed_class = static::is_always_overlay( $attributes ) ? array( 'is-collapsed' ) : array();
     370
     371                $classes = array_merge(
     372                        $colors['css_classes'],
     373                        $font_sizes['css_classes'],
     374                        $is_responsive_menu ? array( 'is-responsive' ) : array(),
     375                        $layout_class ? array( $layout_class ) : array(),
     376                        $text_decoration ? array( $text_decoration_class ) : array(),
     377                        $is_collapsed_class
     378                );
     379                return implode( ' ', $classes );
     380        }
     381
     382        private static function is_always_overlay( $attributes ) {
     383                return isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
     384        }
     385
     386        /**
     387         * Get styles for the navigation block.
     388         *
     389         * @param array $attributes The block attributes.
     390         * @return string Returns the styles for the navigation block.
     391         */
     392        private static function get_styles( $attributes ) {
     393                $colors       = block_core_navigation_build_css_colors( $attributes );
     394                $font_sizes   = block_core_navigation_build_css_font_sizes( $attributes );
     395                $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
     396                return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
     397        }
     398
     399        /**
     400         * Get the responsive container markup
     401         *
     402         * @param array         $attributes The block attributes.
     403         * @param WP_Block_List $inner_blocks The list of inner blocks.
     404         * @param string        $inner_blocks_html The markup for the inner blocks.
     405         * @return string Returns the container markup.
     406         */
     407        private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
     408                $is_interactive  = static::is_interactive( $attributes, $inner_blocks );
     409                $colors          = block_core_navigation_build_css_colors( $attributes );
     410                $modal_unique_id = wp_unique_id( 'modal-' );
     411
     412                $responsive_container_classes = array(
     413                        'wp-block-navigation__responsive-container',
     414                        implode( ' ', $colors['overlay_css_classes'] ),
     415                );
     416                $open_button_classes          = array(
     417                        'wp-block-navigation__responsive-container-open',
     418                );
     419
     420                $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
     421                $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>';
     422                if ( isset( $attributes['icon'] ) ) {
     423                        if ( 'menu' === $attributes['icon'] ) {
     424                                $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>';
     425                        }
     426                }
     427                $toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
     428                $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>';
     429                $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
     430                $toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
     431                $toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
     432
     433                // Add Interactivity API directives to the markup if needed.
     434                $open_button_directives          = '';
     435                $responsive_container_directives = '';
     436                $responsive_dialog_directives    = '';
     437                $close_button_directives         = '';
     438                if ( $is_interactive ) {
     439                        $open_button_directives                  = '
     440                                data-wp-on--click="actions.openMenuOnClick"
     441                                data-wp-on--keydown="actions.handleMenuKeydown"
     442                        ';
     443                        $responsive_container_directives         = '
     444                                data-wp-class--has-modal-open="state.isMenuOpen"
     445                                data-wp-class--is-menu-open="state.isMenuOpen"
     446                                data-wp-watch="callbacks.initMenu"
     447                                data-wp-on--keydown="actions.handleMenuKeydown"
     448                                data-wp-on--focusout="actions.handleMenuFocusout"
     449                                tabindex="-1"
     450                        ';
     451                        $responsive_dialog_directives            = '
     452                                data-wp-bind--aria-modal="state.ariaModal"
     453                                data-wp-bind--aria-label="state.ariaLabel"
     454                                data-wp-bind--role="state.roleAttribute"
     455                        ';
     456                        $close_button_directives                 = '
     457                                data-wp-on--click="actions.closeMenuOnClick"
     458                        ';
     459                        $responsive_container_content_directives = '
     460                                data-wp-watch="callbacks.focusFirstElement"
     461                        ';
     462                }
     463
     464                return sprintf(
     465                        '<button aria-haspopup="dialog" %3$s class="%6$s" %10$s>%8$s</button>
     466                                <div class="%5$s" style="%7$s" id="%1$s" %11$s>
     467                                        <div class="wp-block-navigation__responsive-close" tabindex="-1">
     468                                                <div class="wp-block-navigation__responsive-dialog" %12$s>
     469                                                        <button %4$s class="wp-block-navigation__responsive-container-close" %13$s>%9$s</button>
     470                                                        <div class="wp-block-navigation__responsive-container-content" %14$s id="%1$s-content">
     471                                                                %2$s
     472                                                        </div>
     473                                                </div>
     474                                        </div>
     475                                </div>',
     476                        esc_attr( $modal_unique_id ),
     477                        $inner_blocks_html,
     478                        $toggle_aria_label_open,
     479                        $toggle_aria_label_close,
     480                        esc_attr( implode( ' ', $responsive_container_classes ) ),
     481                        esc_attr( implode( ' ', $open_button_classes ) ),
     482                        esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ),
     483                        $toggle_button_content,
     484                        $toggle_close_button_content,
     485                        $open_button_directives,
     486                        $responsive_container_directives,
     487                        $responsive_dialog_directives,
     488                        $close_button_directives,
     489                        $responsive_container_content_directives
     490                );
     491        }
     492
     493        /**
     494         * Get the wrapper attributes
     495         *
     496         * @param array         $attributes    The block attributes.
     497         * @param WP_Block_List $inner_blocks  A list of inner blocks.
     498         * @return string Returns the navigation block markup.
     499         */
     500        private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) {
     501                $nav_menu_name      = static::get_unique_navigation_name( $attributes );
     502                $is_interactive     = static::is_interactive( $attributes, $inner_blocks );
     503                $is_responsive_menu = static::is_responsive( $attributes );
     504                $style              = static::get_styles( $attributes );
     505                $class              = static::get_classes( $attributes );
     506                $wrapper_attributes = get_block_wrapper_attributes(
     507                        array(
     508                                'class'      => $class,
     509                                'style'      => $style,
     510                                'aria-label' => $nav_menu_name,
     511                        )
     512                );
     513
     514                if ( $is_responsive_menu ) {
     515                        $nav_element_directives = static::get_nav_element_directives( $is_interactive, $attributes );
     516                        $wrapper_attributes    .= ' ' . $nav_element_directives;
     517                }
     518
     519                return $wrapper_attributes;
     520        }
     521
     522        /**
     523         * Gets the nav element directives.
     524         *
     525         * @param bool  $is_interactive Whether the block is interactive.
     526         * @param array $attributes     The block attributes.
     527         * @return string the directives for the navigation element.
     528         */
     529        private static function get_nav_element_directives( $is_interactive, $attributes ) {
     530                if ( ! $is_interactive ) {
     531                        return '';
     532                }
     533                // When adding to this array be mindful of security concerns.
     534                $nav_element_context    = wp_json_encode(
     535                        array(
     536                                'overlayOpenedBy' => array(),
     537                                'type'            => 'overlay',
     538                                'roleAttribute'   => '',
     539                                'ariaLabel'       => __( 'Menu' ),
     540                        ),
     541                        JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP
     542                );
     543                $nav_element_directives = '
     544                        data-wp-interactive=\'{"namespace":"core/navigation"}\'
     545                        data-wp-context=\'' . $nav_element_context . '\'
     546                ';
     547
     548                /*
     549                * When the navigation's 'overlayMenu' attribute is set to 'always', JavaScript
     550                * is not needed for collapsing the menu because the class is set manually.
     551                */
     552                if ( ! static::is_always_overlay( $attributes ) ) {
     553                        $nav_element_directives .= 'data-wp-init="callbacks.initNav"';
     554                        $nav_element_directives .= ' '; // space separator
     555                        $nav_element_directives .= 'data-wp-class--is-collapsed="context.isCollapsed"';
     556                }
     557
     558                return $nav_element_directives;
     559        }
     560
     561        /**
     562         * Handle view script module loading.
     563         *
     564         * @param array         $attributes   The block attributes.
     565         * @param WP_Block      $block        The parsed block.
     566         * @param WP_Block_List $inner_blocks The list of inner blocks.
     567         */
     568        private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) {
     569                if ( static::is_interactive( $attributes, $inner_blocks ) ) {
     570                        wp_enqueue_script_module( '@wordpress/block-library/navigation' );
     571                }
     572        }
     573
     574        /**
     575         * Returns the markup for the navigation block.
     576         *
     577         * @param array         $attributes The block attributes.
     578         * @param WP_Block_List $inner_blocks The list of inner blocks.
     579         * @return string Returns the navigation wrapper markup.
     580         */
     581        private static function get_wrapper_markup( $attributes, $inner_blocks ) {
     582                $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks );
     583                if ( static::is_responsive( $attributes ) ) {
     584                        return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html );
     585                }
     586                return $inner_blocks_html;
     587        }
     588
     589        /**
     590         * Returns a unique name for the navigation.
     591         *
     592         * @param array $attributes The block attributes.
     593         * @return string Returns a unique name for the navigation.
     594         */
     595        private static function get_unique_navigation_name( $attributes ) {
     596                $nav_menu_name = static::get_navigation_name( $attributes );
     597
     598                // If the menu name has been used previously then append an ID
     599                // to the name to ensure uniqueness across a given post.
     600                if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) {
     601                        $count         = static::$seen_menu_names[ $nav_menu_name ];
     602                        $nav_menu_name = $nav_menu_name . ' ' . ( $count );
     603                }
     604
     605                return $nav_menu_name;
     606        }
     607
     608        /**
     609         * Renders the navigation block.
     610         *
     611         * @param array    $attributes The block attributes.
     612         * @param string   $content    The saved content.
     613         * @param WP_Block $block      The parsed block.
     614         * @return string Returns the navigation block markup.
     615         */
     616        public static function render( $attributes, $content, $block ) {
     617                /**
     618                 * Deprecated:
     619                 * The rgbTextColor and rgbBackgroundColor attributes
     620                 * have been deprecated in favor of
     621                 * customTextColor and customBackgroundColor ones.
     622                 * Move the values from old attrs to the new ones.
     623                 */
     624                if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
     625                        $attributes['customTextColor'] = $attributes['rgbTextColor'];
     626                }
     627
     628                if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
     629                        $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
     630                }
     631
     632                unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
     633
     634                $inner_blocks = static::get_inner_blocks( $attributes, $block );
     635                // Prevent navigation blocks referencing themselves from rendering.
     636                if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) {
     637                        return '';
     638                }
     639
     640                static::handle_view_script_module_loading( $attributes, $block, $inner_blocks );
     641
     642                return sprintf(
     643                        '<nav %1$s>%2$s</nav>',
     644                        static::get_nav_wrapper_attributes( $attributes, $inner_blocks ),
     645                        static::get_wrapper_markup( $attributes, $inner_blocks )
     646                );
     647        }
     648}
    7649
    8650// These functions are used for the __unstableLocation feature and only active
    … …  
    66708                return $menu_items_by_parent_id;
    67709        }
    68 }
    69 
     710
     711        /**
     712         * Gets the inner blocks for the navigation block from the unstable location attribute.
     713         *
     714         * @param array $attributes The block attributes.
     715         * @return WP_Block_List Returns the inner blocks for the navigation block.
     716         */
     717        function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) {
     718                $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] );
     719                if ( empty( $menu_items ) ) {
     720                        return new WP_Block_List( array(), $attributes );
     721                }
     722
     723                $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items );
     724                $parsed_blocks           = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
     725                return new WP_Block_List( $parsed_blocks, $attributes );
     726        }
     727}
    70728
    71729/**
    … …  
    73731 * blocks markup using the Tag Processor.
    74732 *
    75  * @param string $w Markup of the navigation block.
    76  * @param array  $block_attributes Block attributes.
     733 * @param WP_HTML_Tag_Processor $tags            Markup of the navigation block.
     734 * @param array                 $block_attributes Block attributes.
    77735 *
    78736 * @return string Submenu markup with the directives injected.
    79737 */
    80 function block_core_navigation_add_directives_to_submenu( $w, $block_attributes ) {
    81         while ( $w->next_tag(
     738function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) {
     739        while ( $tags->next_tag(
    82740                array(
    83741                        'tag_name'   => 'LI',
    … …  
    86744        ) ) {
    87745                // Add directives to the parent `<li>`.
    88                 $w->set_attribute( 'data-wp-interactive', true );
    89                 $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' );
    90                 $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' );
    91                 $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' );
    92                 $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' );
     746                $tags->set_attribute( 'data-wp-interactive', '{ "namespace": "core/navigation" }' );
     747                $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": {}, "type": "submenu" }' );
     748                $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
     749                $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
     750                $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
    93751
    94752                // This is a fix for Safari. Without it, Safari doesn't change the active
    … …  
    96754                // an overlay to capture the clicks, instead of relying on the focusout
    97755                // event.
    98                 $w->set_attribute( 'tabindex', '-1' );
     756                $tags->set_attribute( 'tabindex', '-1' );
    99757
    100758                if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) {
    101                         $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' );
    102                         $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' );
     759                        $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' );
     760                        $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' );
    103761                }
    104762
    105763                // Add directives to the toggle submenu button.
    106                 if ( $w->next_tag(
     764                if ( $tags->next_tag(
    107765                        array(
    108766                                'tag_name'   => 'BUTTON',
    … …  
    110768                        )
    111769                ) ) {
    112                         $w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' );
    113                         $w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' );
     770                        $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
     771                        $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
    114772                        // The `aria-expanded` attribute for SSR is already added in the submenu block.
    115773                }
    116774                // Add directives to the submenu.
    117                 if ( $w->next_tag(
     775                if ( $tags->next_tag(
    118776                        array(
    119777                                'tag_name'   => 'UL',
    … …  
    121779                        )
    122780                ) ) {
    123                         $w->set_attribute( 'data-wp-on--focus', 'actions.core.navigation.openMenuOnFocus' );
     781                        $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' );
    124782                }
    125783
    126784                // Iterate through subitems if exist.
    127                 block_core_navigation_add_directives_to_submenu( $w, $block_attributes );
    128         }
    129         return $w->get_updated_html();
     785                block_core_navigation_add_directives_to_submenu( $tags, $block_attributes );
     786        }
     787        return $tags->get_updated_html();
    130788}
    131789
    … …  
    334992                // In this case default to the (Page List) fallback.
    335993                $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
     994
     995                if ( function_exists( 'get_hooked_blocks' ) ) {
     996                        // Run Block Hooks algorithm to inject hooked blocks.
     997                        // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
     998                        $markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post );
     999                        $blocks = parse_blocks( $markup );
     1000
     1001                        if ( isset( $blocks[0]['innerBlocks'] ) ) {
     1002                                $fallback_blocks = $blocks[0]['innerBlocks'];
     1003                        }
     1004                }
    3361005        }
    3371006
    … …  
    3451014         * @since 5.9.0
    3461015         *
    347          * @param array[] default fallback blocks provided by the default block mechanic.
     1016         * @param array[] $fallback_blocks default fallback blocks provided by the default block mechanic.
    3481017         */
    3491018        return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks );
    … …  
    3921061 * @param WP_Block $block      The parsed block.
    3931062 *
    394  * @return string Returns the post content with the legacy widget added.
     1063 * @return string Returns the navigation block markup.
    3951064 */
    3961065function render_block_core_navigation( $attributes, $content, $block ) {
    397         static $seen_menu_names = array();
    398 
    399         // Flag used to indicate whether the rendered output is considered to be
    400         // a fallback (i.e. the block has no menu associated with it).
    401         $is_fallback = false;
    402 
    403         $nav_menu_name = $attributes['ariaLabel'] ?? '';
    404 
    405         /**
    406          * Deprecated:
    407          * The rgbTextColor and rgbBackgroundColor attributes
    408          * have been deprecated in favor of
    409          * customTextColor and customBackgroundColor ones.
    410          * Move the values from old attrs to the new ones.
    411          */
    412         if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
    413                 $attributes['customTextColor'] = $attributes['rgbTextColor'];
    414         }
    415 
    416         if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
    417                 $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
    418         }
    419 
    420         unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
    421 
    422         /**
    423          * This is for backwards compatibility after `isResponsive` attribute has been removed.
    424          */
    425         $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
    426         $is_responsive_menu           = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
    427 
    428         $inner_blocks = $block->inner_blocks;
    429 
    430         // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
    431         if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
    432                 $attributes['ref'] = $attributes['navigationMenuId'];
    433         }
    434 
    435         // If:
    436         // - the gutenberg plugin is active
    437         // - `__unstableLocation` is defined
    438         // - we have menu items at the defined location
    439         // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
    440         // ...then create inner blocks from the classic menu assigned to that location.
    441         if (
    442                 defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
    443                 array_key_exists( '__unstableLocation', $attributes ) &&
    444                 ! array_key_exists( 'ref', $attributes ) &&
    445                 ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
    446         ) {
    447                 $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] );
    448                 if ( empty( $menu_items ) ) {
    449                         return '';
    450                 }
    451 
    452                 $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items );
    453                 $parsed_blocks           = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
    454                 $inner_blocks            = new WP_Block_List( $parsed_blocks, $attributes );
    455         }
    456 
    457         // Load inner blocks from the navigation post.
    458         if ( array_key_exists( 'ref', $attributes ) ) {
    459                 $navigation_post = get_post( $attributes['ref'] );
    460                 if ( ! isset( $navigation_post ) ) {
    461                         return '';
    462                 }
    463 
    464                 // Only published posts are valid. If this is changed then a corresponding change
    465                 // must also be implemented in `use-navigation-menu.js`.
    466                 if ( 'publish' === $navigation_post->post_status ) {
    467                         $nav_menu_name = $navigation_post->post_title;
    468 
    469                         if ( isset( $seen_menu_names[ $nav_menu_name ] ) ) {
    470                                 ++$seen_menu_names[ $nav_menu_name ];
    471                         } else {
    472                                 $seen_menu_names[ $nav_menu_name ] = 1;
    473                         }
    474 
    475                         $parsed_blocks = parse_blocks( $navigation_post->post_content );
    476 
    477                         // 'parse_blocks' includes a null block with '\n\n' as the content when
    478                         // it encounters whitespace. This code strips it.
    479                         $compacted_blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
    480 
    481                         // TODO - this uses the full navigation block attributes for the
    482                         // context which could be refined.
    483                         $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes );
    484                 }
    485         }
    486 
    487         // If there are no inner blocks then fallback to rendering an appropriate fallback.
    488         if ( empty( $inner_blocks ) ) {
    489                 $is_fallback = true; // indicate we are rendering the fallback.
    490 
    491                 $fallback_blocks = block_core_navigation_get_fallback_blocks();
    492 
    493                 // Fallback my have been filtered so do basic test for validity.
    494                 if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
    495                         return '';
    496                 }
    497 
    498                 $inner_blocks = new WP_Block_List( $fallback_blocks, $attributes );
    499         }
    500 
    501         if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) {
    502                 return '';
    503         }
    504 
    505         /**
    506          * Filter navigation block $inner_blocks.
    507          * Allows modification of a navigation block menu items.
    508          *
    509          * @since 6.1.0
    510          *
    511          * @param \WP_Block_List $inner_blocks
    512          */
    513         $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks );
    514 
    515         $layout_justification = array(
    516                 'left'          => 'items-justified-left',
    517                 'right'         => 'items-justified-right',
    518                 'center'        => 'items-justified-center',
    519                 'space-between' => 'items-justified-space-between',
    520         );
    521 
    522         // Restore legacy classnames for submenu positioning.
    523         $layout_class = '';
    524         if (
    525                 isset( $attributes['layout']['justifyContent'] ) &&
    526                 isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
    527         ) {
    528                 $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
    529         }
    530         if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
    531                 $layout_class .= ' is-vertical';
    532         }
    533 
    534         if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
    535                 $layout_class .= ' no-wrap';
    536         }
    537 
    538         // Manually add block support text decoration as CSS class.
    539         $text_decoration       = $attributes['style']['typography']['textDecoration'] ?? null;
    540         $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
    541 
    542         $colors     = block_core_navigation_build_css_colors( $attributes );
    543         $font_sizes = block_core_navigation_build_css_font_sizes( $attributes );
    544         $classes    = array_merge(
    545                 $colors['css_classes'],
    546                 $font_sizes['css_classes'],
    547                 $is_responsive_menu ? array( 'is-responsive' ) : array(),
    548                 $layout_class ? array( $layout_class ) : array(),
    549                 $is_fallback ? array( 'is-fallback' ) : array(),
    550                 $text_decoration ? array( $text_decoration_class ) : array()
    551         );
    552 
    553         $post_ids = block_core_navigation_get_post_ids( $inner_blocks );
    554         if ( $post_ids ) {
    555                 _prime_post_caches( $post_ids, false, false );
    556         }
    557 
    558         $list_item_nav_blocks = array(
    559                 'core/navigation-link',
    560                 'core/home-link',
    561                 'core/site-title',
    562                 'core/site-logo',
    563                 'core/navigation-submenu',
    564         );
    565 
    566         $needs_list_item_wrapper = array(
    567                 'core/site-title',
    568                 'core/site-logo',
    569         );
    570 
    571         $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
    572         $style        = $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
    573         $class        = implode( ' ', $classes );
    574 
    575         // If the menu name has been used previously then append an ID
    576         // to the name to ensure uniqueness across a given post.
    577         if ( isset( $seen_menu_names[ $nav_menu_name ] ) && $seen_menu_names[ $nav_menu_name ] > 1 ) {
    578                 $count         = $seen_menu_names[ $nav_menu_name ];
    579                 $nav_menu_name = $nav_menu_name . ' ' . ( $count );
    580         }
    581 
    582         $wrapper_attributes = get_block_wrapper_attributes(
    583                 array(
    584                         'class'      => $class,
    585                         'style'      => $style,
    586                         'aria-label' => $nav_menu_name,
    587                 )
    588         );
    589 
    590         $container_attributes = get_block_wrapper_attributes(
    591                 array(
    592                         'class' => 'wp-block-navigation__container ' . $class,
    593                         'style' => $style,
    594                 )
    595         );
    596 
    597         $inner_blocks_html = '';
    598         $is_list_open      = false;
    599         $has_submenus      = false;
    600         foreach ( $inner_blocks as $inner_block ) {
    601                 $is_list_item = in_array( $inner_block->name, $list_item_nav_blocks, true );
    602 
    603                 if ( $is_list_item && ! $is_list_open ) {
    604                         $is_list_open       = true;
    605                         $inner_blocks_html .= sprintf(
    606                                 '<ul %1$s>',
    607                                 $container_attributes
    608                         );
    609                 }
    610 
    611                 if ( ! $is_list_item && $is_list_open ) {
    612                         $is_list_open       = false;
    613                         $inner_blocks_html .= '</ul>';
    614                 }
    615 
    616                 $inner_block_content = $inner_block->render();
    617                 $p                   = new WP_HTML_Tag_Processor( $inner_block_content );
    618                 if ( $p->next_tag(
    619                         array(
    620                                 'name'       => 'LI',
    621                                 'class_name' => 'has-child',
    622                         )
    623                 ) ) {
    624                         $has_submenus = true;
    625                 }
    626                 if ( ! empty( $inner_block_content ) ) {
    627                         if ( in_array( $inner_block->name, $needs_list_item_wrapper, true ) ) {
    628                                 $inner_blocks_html .= '<li class="wp-block-navigation-item">' . $inner_block_content . '</li>';
    629                         } else {
    630                                 $inner_blocks_html .= $inner_block_content;
    631                         }
    632                 }
    633         }
    634 
    635         if ( $is_list_open ) {
    636                 $inner_blocks_html .= '</ul>';
    637         }
    638 
    639         $should_load_view_script = ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu;
    640         $view_js_file            = 'wp-block-navigation-view';
    641 
    642         // If the script already exists, there is no point in removing it from viewScript.
    643         if ( ! wp_script_is( $view_js_file ) ) {
    644                 $script_handles = $block->block_type->view_script_handles;
    645 
    646                 // If the script is not needed, and it is still in the `view_script_handles`, remove it.
    647                 if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) {
    648                         $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) );
    649                 }
    650                 // If the script is needed, but it was previously removed, add it again.
    651                 if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) {
    652                         $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) );
    653                 }
    654         }
    655 
    656         // Add directives to the submenu if needed.
    657         if ( $has_submenus && $should_load_view_script ) {
    658                 $w                 = new WP_HTML_Tag_Processor( $inner_blocks_html );
    659                 $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $w, $attributes );
    660         }
    661 
    662         $modal_unique_id = wp_unique_id( 'modal-' );
    663 
    664         // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive,
    665         // return early if they don't.
    666         if ( ! $is_responsive_menu ) {
    667                 return sprintf(
    668                         '<nav %1$s>%2$s</nav>',
    669                         $wrapper_attributes,
    670                         $inner_blocks_html
    671                 );
    672         }
    673 
    674         $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
    675 
    676         $responsive_container_classes = array(
    677                 'wp-block-navigation__responsive-container',
    678                 $is_hidden_by_default ? 'hidden-by-default' : '',
    679                 implode( ' ', $colors['overlay_css_classes'] ),
    680         );
    681         $open_button_classes          = array(
    682                 'wp-block-navigation__responsive-container-open',
    683                 $is_hidden_by_default ? 'always-shown' : '',
    684         );
    685 
    686         $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
    687         $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>';
    688         if ( isset( $attributes['icon'] ) ) {
    689                 if ( 'menu' === $attributes['icon'] ) {
    690                         $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>';
    691                 }
    692         }
    693         $toggle_button_content       = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
    694         $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>';
    695         $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
    696         $toggle_aria_label_open      = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
    697         $toggle_aria_label_close     = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
    698 
    699         // Add Interactivity API directives to the markup if needed.
    700         $nav_element_directives          = '';
    701         $open_button_directives          = '';
    702         $responsive_container_directives = '';
    703         $responsive_dialog_directives    = '';
    704         $close_button_directives         = '';
    705         if ( $should_load_view_script ) {
    706                 $nav_element_context             = wp_json_encode(
    707                         array(
    708                                 'core' => array(
    709                                         'navigation' => array(
    710                                                 'overlayOpenedBy' => array(),
    711                                                 'type'            => 'overlay',
    712                                                 'roleAttribute'   => '',
    713                                                 'ariaLabel'       => __( 'Menu' ),
    714                                         ),
    715                                 ),
    716                         ),
    717                         JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP
    718                 );
    719                 $nav_element_directives          = '
    720                         data-wp-interactive
    721                         data-wp-context=\'' . $nav_element_context . '\'
    722                 ';
    723                 $open_button_directives          = '
    724                         data-wp-on--click="actions.core.navigation.openMenuOnClick"
    725                         data-wp-on--keydown="actions.core.navigation.handleMenuKeydown"
    726                 ';
    727                 $responsive_container_directives = '
    728                         data-wp-class--has-modal-open="selectors.core.navigation.isMenuOpen"
    729                         data-wp-class--is-menu-open="selectors.core.navigation.isMenuOpen"
    730                         data-wp-effect="effects.core.navigation.initMenu"
    731                         data-wp-on--keydown="actions.core.navigation.handleMenuKeydown"
    732                         data-wp-on--focusout="actions.core.navigation.handleMenuFocusout"
    733                         tabindex="-1"
    734                 ';
    735                 $responsive_dialog_directives    = '
    736                         data-wp-bind--aria-modal="selectors.core.navigation.ariaModal"
    737                         data-wp-bind--aria-label="selectors.core.navigation.ariaLabel"
    738                         data-wp-bind--role="selectors.core.navigation.roleAttribute"
    739                         data-wp-effect="effects.core.navigation.focusFirstElement"
    740                 ';
    741                 $close_button_directives         = '
    742                         data-wp-on--click="actions.core.navigation.closeMenuOnClick"
    743                 ';
    744         }
    745 
    746         $responsive_container_markup = sprintf(
    747                 '<button aria-haspopup="true" %3$s class="%6$s" %10$s>%8$s</button>
    748                         <div class="%5$s" style="%7$s" id="%1$s" %11$s>
    749                                 <div class="wp-block-navigation__responsive-close" tabindex="-1">
    750                                         <div class="wp-block-navigation__responsive-dialog" %12$s>
    751                                                         <button %4$s class="wp-block-navigation__responsive-container-close" %13$s>%9$s</button>
    752                                                 <div class="wp-block-navigation__responsive-container-content" id="%1$s-content">
    753                                                         %2$s
    754                                                 </div>
    755                                         </div>
    756                                 </div>
    757                         </div>',
    758                 esc_attr( $modal_unique_id ),
    759                 $inner_blocks_html,
    760                 $toggle_aria_label_open,
    761                 $toggle_aria_label_close,
    762                 esc_attr( implode( ' ', $responsive_container_classes ) ),
    763                 esc_attr( implode( ' ', $open_button_classes ) ),
    764                 esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ),
    765                 $toggle_button_content,
    766                 $toggle_close_button_content,
    767                 $open_button_directives,
    768                 $responsive_container_directives,
    769                 $responsive_dialog_directives,
    770                 $close_button_directives
    771         );
    772 
    773         return sprintf(
    774                 '<nav %1$s %3$s>%2$s</nav>',
    775                 $wrapper_attributes,
    776                 $responsive_container_markup,
    777                 $nav_element_directives
    778         );
     1066        return WP_Navigation_Block_Renderer::render( $attributes, $content, $block );
    7791067}
    7801068
    … …  
    7911079                        'render_callback' => 'render_block_core_navigation',
    7921080                )
     1081        );
     1082
     1083        wp_register_script_module(
     1084                '@wordpress/block-library/navigation',
     1085                defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/navigation.min.js' ) : includes_url( 'blocks/navigation/view.min.js' ),
     1086                array( '@wordpress/interactivity' ),
     1087                defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
    7931088        );
    7941089}
    … …  
    8311126
    8321127/**
    833  * Ensure that the view script has the `wp-interactivity` dependency.
    834  *
    835  * @since 6.4.0
    836  *
    837  * @global WP_Scripts $wp_scripts
    838  */
    839 function block_core_navigation_ensure_interactivity_dependency() {
    840         global $wp_scripts;
    841         if (
    842                 isset( $wp_scripts->registered['wp-block-navigation-view'] ) &&
    843                 ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-navigation-view']->deps, true )
    844         ) {
    845                 $wp_scripts->registered['wp-block-navigation-view']->deps[] = 'wp-interactivity';
    846         }
    847 }
    848 
    849 add_action( 'wp_print_scripts', 'block_core_navigation_ensure_interactivity_dependency' );
    850 
    851 /**
    8521128 * Turns menu item data into a nested array of parsed blocks
    8531129 *
    … …  
    10671343        return null;
    10681344}
     1345
     1346/**
     1347 * Insert hooked blocks into a Navigation block.
     1348 *
     1349 * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
     1350 * this function inserts hooked blocks into it, and returns the serialized inner blocks in a
     1351 * mock Navigation block wrapper.
     1352 *
     1353 * If there are any hooked blocks that need to be inserted as the Navigation block's first or last
     1354 * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
     1355 * of those hooked blocks should be exempted from insertion.
     1356 *
     1357 * @param array   $inner_blocks Parsed inner blocks of a Navigation block.
     1358 * @param WP_Post $post         `wp_navigation` post object corresponding to the block.
     1359 * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
     1360 */
     1361function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post = null ) {
     1362        $before_block_visitor = null;
     1363        $after_block_visitor  = null;
     1364        $hooked_blocks        = get_hooked_blocks();
     1365        $attributes           = array();
     1366
     1367        if ( isset( $post->ID ) ) {
     1368                $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
     1369                if ( ! empty( $ignored_hooked_blocks ) ) {
     1370                        $ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
     1371                        $attributes['metadata'] = array(
     1372                                'ignoredHookedBlocks' => $ignored_hooked_blocks,
     1373                        );
     1374                }
     1375        }
     1376
     1377        $mock_anchor_parent_block = array(
     1378                'blockName'    => 'core/navigation',
     1379                'attrs'        => $attributes,
     1380                'innerBlocks'  => $inner_blocks,
     1381                'innerContent' => array_fill( 0, count( $inner_blocks ), null ),
     1382        );
     1383        $before_block_visitor     = null;
     1384        $after_block_visitor      = null;
     1385
     1386        if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
     1387                $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post );
     1388                $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $post );
     1389        }
     1390
     1391        return traverse_and_serialize_block( $mock_anchor_parent_block, $before_block_visitor, $after_block_visitor );
     1392}
     1393
     1394/**
     1395 * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
     1396 *
     1397 * @param WP_Post $post Post object.
     1398 */
     1399function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
     1400        if ( ! isset( $post->ID ) ) {
     1401                return;
     1402        }
     1403
     1404        // We run the Block Hooks mechanism so it will return the list of ignored hooked blocks
     1405        // in the mock root Navigation block's metadata attribute.
     1406        // We ignore the rest of the returned `$markup`; `$post->post_content` already has the hooked
     1407        // blocks inserted, whereas `$markup` will have them inserted twice.
     1408        $blocks                = parse_blocks( $post->post_content );
     1409        $markup                = block_core_navigation_insert_hooked_blocks( $blocks, $post );
     1410        $root_nav_block        = parse_blocks( $markup )[0];
     1411        $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
     1412                ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks']
     1413                : array();
     1414
     1415        if ( ! empty( $ignored_hooked_blocks ) ) {
     1416                $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
     1417                if ( ! empty( $existing_ignored_hooked_blocks ) ) {
     1418                        $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
     1419                        $ignored_hooked_blocks          = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
     1420                }
     1421                update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
     1422        }
     1423}
     1424
     1425// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.4
     1426// that are not present in Gutenberg's WP 6.4 compatibility layer.
     1427if ( function_exists( 'get_hooked_blocks' ) ) {
     1428        add_action( 'rest_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10, 3 );
     1429}
     1430
     1431/**
     1432 * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
     1433 *
     1434 * @param WP_REST_Response $response The response object.
     1435 * @param WP_Post          $post     Post object.
     1436 * @param WP_REST_Request  $request  Request object.
     1437 * @return WP_REST_Response The response object.
     1438 */
     1439function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) {
     1440        if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
     1441                return $response;
     1442        }
     1443        $parsed_blocks = parse_blocks( $response->data['content']['raw'] );
     1444        $content       = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post );
     1445
     1446        // Remove mock Navigation block wrapper.
     1447        $start   = strpos( $content, '-->' ) + strlen( '-->' );
     1448        $end     = strrpos( $content, '<!--' );
     1449        $content = substr( $content, $start, $end - $start );
     1450
     1451        $response->data['content']['raw']      = $content;
     1452        $response->data['content']['rendered'] = apply_filters( 'the_content', $content );
     1453
     1454        return $response;
     1455}
     1456
     1457// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.4
     1458// that are not present in Gutenberg's WP 6.4 compatibility layer.
     1459if ( function_exists( 'get_hooked_blocks' ) ) {
     1460        add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 );
     1461}
Note: See TracChangeset for help on using the changeset viewer.