Make WordPress Core


Ignore:
Timestamp:
01/29/2024 09:04:18 PM (17 months 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.