Make WordPress Core

Changeset 52145


Ignore:
Timestamp:
11/12/2021 03:53:18 AM (2 months 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.

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/post.php

    r52141 r52145  
    24482448    do_action( 'block_editor_meta_box_hidden_fields', $post );
    24492449}
     2450
     2451/**
     2452 * Disable block editor for wp_navigation type posts so they can be managed via the UI.
     2453 *
     2454 * @since 5.9.0
     2455 * @access private
     2456 *
     2457 * @param bool   $value Whether the CPT supports block editor or not.
     2458 * @param string $post_type Post type.
     2459 *
     2460 * @return bool
     2461 */
     2462function _disable_block_editor_for_navigation_post_type( $value, $post_type ) {
     2463    if ( 'wp_navigation' === $post_type ) {
     2464        return false;
     2465    }
     2466
     2467    return $value;
     2468}
     2469
     2470/**
     2471 * This callback disables the content editor for wp_navigation type posts.
     2472 * Content editor cannot handle wp_navigation type posts correctly.
     2473 * We cannot disable the "editor" feature in the wp_navigation's CPT definition
     2474 * because it disables the ability to save navigation blocks via REST API.
     2475 *
     2476 * @since 5.9.0
     2477 * @access private
     2478 *
     2479 * @param WP_Post $post An instance of WP_Post class.
     2480 */
     2481function _disable_content_editor_for_navigation_post_type( $post ) {
     2482    $post_type = get_post_type( $post );
     2483    if ( 'wp_navigation' !== $post_type ) {
     2484        return;
     2485    }
     2486
     2487    remove_post_type_support( $post_type, 'editor' );
     2488}
     2489
     2490/**
     2491 * This callback enables content editor for wp_navigation type posts.
     2492 * We need to enable it back because we disable it to hide
     2493 * the content editor for wp_navigation type posts.
     2494 *
     2495 * @since 5.9.0
     2496 * @access private
     2497 *
     2498 * @see _disable_content_editor_for_navigation_post_type
     2499 *
     2500 * @param WP_Post $post An instance of WP_Post class.
     2501 */
     2502function _enable_content_editor_for_navigation_post_type( $post ) {
     2503    $post_type = get_post_type( $post );
     2504    if ( 'wp_navigation' !== $post_type ) {
     2505        return;
     2506    }
     2507
     2508    add_post_type_support( $post_type, 'editor' );
     2509}
  • trunk/src/wp-admin/site-editor.php

    r52071 r52145  
    6060    '/wp/v2/global-styles/' . $active_global_styles_id,
    6161    '/wp/v2/themes/' . $active_theme . '/global-styles',
     62    '/wp/v2/block-navigation-areas?context=edit',
    6263);
     64
     65$areas        = get_option( 'fse_navigation_areas', array() );
     66$active_areas = array_intersect_key( $areas, get_navigation_areas() );
     67foreach ( $active_areas as $post_id ) {
     68    if ( $post_id ) {
     69        $preload_paths[] = add_query_args( 'context', 'edit', rest_get_route_for_post( $post_id ) );
     70    }
     71}
     72
    6373block_editor_rest_api_preload( $preload_paths, $block_editor_context );
    6474
  • trunk/src/wp-includes/default-filters.php

    r52133 r52145  
    584584add_action( 'admin_footer-widgets.php', 'wp_add_iframed_editor_assets_html' );
    585585
     586add_action( 'use_block_editor_for_post_type', '_disable_block_editor_for_navigation_post_type', 10, 2 );
     587add_action( 'edit_form_after_title', '_disable_content_editor_for_navigation_post_type' );
     588add_action( 'edit_form_after_editor', '_enable_content_editor_for_navigation_post_type' );
     589
     590/*
     591 * Disable "Post Attributes" for wp_navigation post type. The attributes are
     592 * also conditionally enabled when a site has custom templates. Block Theme
     593 * templates can be available for every post type.
     594 */
     595add_filter( 'theme_wp_navigation_templates', '__return_empty_array' );
     596
    586597// Taxonomy.
    587598add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority.
     
    671682
    672683// Navigation areas.
    673 add_action( 'setup_theme', '_register_default_navigation_areas' );
     684add_action( 'setup_theme', '_wp_register_default_navigation_areas' );
     685add_action( 'switch_theme', '_wp_migrate_menu_to_navigation_post', 99, 3 );
    674686
    675687unset( $filter, $action );
  • 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}
  • trunk/src/wp-includes/post.php

    r52128 r52145  
    481481                'name'                  => __( 'Navigation Menus' ),
    482482                'singular_name'         => __( 'Navigation Menu' ),
    483                 'menu_name'             => _x( 'Navigation Menus', 'Admin Menu text' ),
    484483                'add_new'               => _x( 'Add New', 'Navigation Menu' ),
    485484                'add_new_item'          => __( 'Add New Navigation Menu' ),
     
    487486                'edit_item'             => __( 'Edit Navigation Menu' ),
    488487                'view_item'             => __( 'View Navigation Menu' ),
    489                 'all_items'             => __( 'All Navigation Menus' ),
     488                'all_items'             => __( 'Navigation Menus' ),
    490489                'search_items'          => __( 'Search Navigation Menus' ),
    491490                'parent_item_colon'     => __( 'Parent Navigation Menu:' ),
     
    495494                'insert_into_item'      => __( 'Insert into Navigation Menu' ),
    496495                'uploaded_to_this_item' => __( 'Uploaded to this Navigation Menu' ),
    497                 // Some of these are a bit weird, what are they for?
    498496                'filter_items_list'     => __( 'Filter Navigation Menu list' ),
    499497                'items_list_navigation' => __( 'Navigation Menus list navigation' ),
    500498                'items_list'            => __( 'Navigation Menus list' ),
    501499            ),
     500            'description'           => __( 'Navigation menus that can be inserted into your site.' ),
    502501            'public'                => false,
    503502            '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
    504503            'has_archive'           => false,
    505             'show_ui'               => false,
     504            'show_ui'               => wp_is_block_template_theme(),
    506505            'show_in_menu'          => 'themes.php',
    507506            'show_in_admin_bar'     => false,
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r52133 r52145  
    1144611446    },
    1144711447    "wp_navigation": {
    11448         "description": "",
     11448        "description": "Navigation menus that can be inserted into your site.",
    1144911449        "hierarchical": false,
    1145011450        "name": "Navigation Menus",
Note: See TracChangeset for help on using the changeset viewer.