Make WordPress Core

Changeset 52145


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

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.