Make WordPress Core


Ignore:
Timestamp:
11/12/2021 03:53:18 AM (3 years ago)
Author:
noisysocks
Message:

Editor: Add Navigation Area infrastructure

Copies Navigation Area infrastrucutre from lib/navigation.php in Gutenberg. This
allows a Navigation block to be associated with a particular area which persists
when switching theme.

Props antonvlasenko, mamaduka, spacedmonkey.
See #54337.

File:
1 edited

Legend:

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

    r52133 r52145  
    3030 * @access private
    3131 */
    32 function _register_default_navigation_areas() {
     32function _wp_register_default_navigation_areas() {
    3333    register_navigation_areas(
    3434        array(
     
    5151    return $navigation_areas;
    5252}
     53
     54/**
     55 * Migrates classic menus to a block-based navigation post on theme switch.
     56 * Assigns the created navigation post to the corresponding navigation area.
     57 *
     58 * @since 5.9.0
     59 * @access private
     60 *
     61 * @param string   $new_name  Name of the new theme.
     62 * @param WP_Theme $new_theme New theme.
     63 * @param WP_Theme $old_theme Old theme.
     64 */
     65function _wp_migrate_menu_to_navigation_post( $new_name, WP_Theme $new_theme, WP_Theme $old_theme ) {
     66    // Do nothing when switching to a theme that does not support site editor.
     67    if ( ! wp_is_block_template_theme() ) {
     68        return;
     69    }
     70
     71    // get_nav_menu_locations() calls get_theme_mod() which depends on the stylesheet option.
     72    // At the same time, switch_theme runs only after the stylesheet option was updated to $new_theme.
     73    // To retrieve theme mods of the old theme, the getter is hooked to get_option( 'stylesheet' ) so that we
     74    // get the old theme, which causes the get_nav_menu_locations to get the locations of the old theme.
     75    $get_old_theme_stylesheet = static function() use ( $old_theme ) {
     76        return $old_theme->get_stylesheet();
     77    };
     78    add_filter( 'option_stylesheet', $get_old_theme_stylesheet );
     79
     80    $locations    = get_nav_menu_locations();
     81    $area_mapping = get_option( 'fse_navigation_areas', array() );
     82
     83    foreach ( $locations as $location_name => $menu_id ) {
     84        // Get the menu from the location, skipping if there is no
     85        // menu or there was an error.
     86        $menu = wp_get_nav_menu_object( $menu_id );
     87        if ( ! $menu || is_wp_error( $menu ) ) {
     88            continue;
     89        }
     90
     91        $menu_items = _wp_get_menu_items_at_location( $location_name );
     92        if ( empty( $menu_items ) ) {
     93            continue;
     94        }
     95
     96        $post_name = 'classic_menu_' . $menu_id;
     97
     98        // Get or create to avoid creating too many wp_navigation posts.
     99        $query          = new WP_Query;
     100        $matching_posts = $query->query(
     101            array(
     102                'name'           => $post_name,
     103                'post_status'    => 'publish',
     104                'post_type'      => 'wp_navigation',
     105                'posts_per_page' => 1,
     106                'fields'         => 'ids',
     107            )
     108        );
     109
     110        if ( ! empty( $matching_posts ) ) {
     111            $navigation_post_id = $matching_posts[0]->ID;
     112        } else {
     113            $menu_items_by_parent_id = _wp_sort_menu_items_by_parent_id( $menu_items );
     114            $parsed_blocks           = _wp_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
     115            $post_data               = array(
     116                'post_type'    => 'wp_navigation',
     117                'post_title'   => sprintf(
     118                    /* translators: %s: the name of the menu, e.g. "Main Menu". */
     119                    __( 'Classic menu: %s' ),
     120                    $menu->name
     121                ),
     122                'post_name'    => $post_name,
     123                'post_content' => serialize_blocks( $parsed_blocks ),
     124                'post_status'  => 'publish',
     125            );
     126            $navigation_post_id      = wp_insert_post( $post_data );
     127        }
     128
     129        $area_mapping[ $location_name ] = $navigation_post_id;
     130    }
     131    remove_filter( 'option_stylesheet', $get_old_theme_stylesheet );
     132
     133    update_option( 'fse_navigation_areas', $area_mapping );
     134}
     135
     136/**
     137 * Returns the menu items for a WordPress menu location.
     138 *
     139 * @since 5.9.0
     140 * @access private
     141 *
     142 * @param string $location The menu location.
     143 * @return array Menu items for the location.
     144 */
     145function _wp_get_menu_items_at_location( $location ) {
     146    if ( empty( $location ) ) {
     147        return;
     148    }
     149
     150    // Build menu data. The following approximates the code in `wp_nav_menu()`.
     151
     152    // Find the location in the list of locations, returning early if the
     153    // location can't be found.
     154    $locations = get_nav_menu_locations();
     155    if ( ! isset( $locations[ $location ] ) ) {
     156        return;
     157    }
     158
     159    // Get the menu from the location, returning early if there is no
     160    // menu or there was an error.
     161    $menu = wp_get_nav_menu_object( $locations[ $location ] );
     162    if ( ! $menu || is_wp_error( $menu ) ) {
     163        return;
     164    }
     165
     166    $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
     167    _wp_menu_item_classes_by_context( $menu_items );
     168
     169    return $menu_items;
     170}
     171
     172/**
     173 * Sorts a standard array of menu items into a nested structure keyed by the
     174 * id of the parent menu.
     175 *
     176 * @since 5.9.0
     177 * @access private
     178 *
     179 * @param array $menu_items Menu items to sort.
     180 * @return array An array keyed by the id of the parent menu where each element
     181 *               is an array of menu items that belong to that parent.
     182 */
     183function _wp_sort_menu_items_by_parent_id( $menu_items ) {
     184    $sorted_menu_items = array();
     185    foreach ( $menu_items as $menu_item ) {
     186        $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
     187    }
     188    unset( $menu_items, $menu_item );
     189
     190    $menu_items_by_parent_id = array();
     191    foreach ( $sorted_menu_items as $menu_item ) {
     192        $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
     193    }
     194
     195    return $menu_items_by_parent_id;
     196}
     197
     198/**
     199 * Turns menu item data into a nested array of parsed blocks
     200 *
     201 * @since 5.9.0
     202 * @access private
     203 *
     204 * @param array $menu_items               An array of menu items that represent
     205 *                                        an individual level of a menu.
     206 * @param array $menu_items_by_parent_id  An array keyed by the id of the
     207 *                                        parent menu where each element is an
     208 *                                        array of menu items that belong to
     209 *                                        that parent.
     210 * @return array An array of parsed block data.
     211 */
     212function _wp_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
     213    if ( empty( $menu_items ) ) {
     214        return array();
     215    }
     216
     217    $blocks = array();
     218
     219    foreach ( $menu_items as $menu_item ) {
     220        $class_name       = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
     221        $id               = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
     222        $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
     223        $rel              = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
     224        $kind             = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
     225
     226        $block = array(
     227            'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link',
     228            'attrs'     => array(
     229                'className'     => $class_name,
     230                'description'   => $menu_item->description,
     231                'id'            => $id,
     232                'kind'          => $kind,
     233                'label'         => $menu_item->title,
     234                'opensInNewTab' => $opens_in_new_tab,
     235                'rel'           => $rel,
     236                'title'         => $menu_item->attr_title,
     237                'type'          => $menu_item->object,
     238                'url'           => $menu_item->url,
     239            ),
     240        );
     241
     242        if ( isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ) {
     243            $block['innerBlocks'] = _wp_parse_blocks_from_menu_items(
     244                $menu_items_by_parent_id[ $menu_item->ID ],
     245                $menu_items_by_parent_id
     246            );
     247        } else {
     248            $block['innerBlocks'] = array();
     249        }
     250
     251        $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] );
     252
     253        $blocks[] = $block;
     254    }
     255
     256    return $blocks;
     257}
Note: See TracChangeset for help on using the changeset viewer.