-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Backport to WP 6.3 - Navigation fallbacks from 4648 #4713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
ramonjd
wants to merge
16
commits into
WordPress:trunk
from
ramonjd:backport/navigation-fallbacks-from-4648
Closed
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
2cb6d2a
REST endpoint
getdave ad7b55f
Add fallback class
getdave 2d78fac
Use correct fallback class
getdave 6863daf
Use default textdomain
getdave 14bf427
Add classic menu converter class
getdave 404c6a8
Add “since”
getdave 0297bee
Include all files from central settings file
getdave 4b23e2b
Port controller tests
getdave ba0df30
Initially port tests
getdave b7648e9
Include test file contents
getdave 43016c6
Initialise Navigation Fallback Controller
getdave 7f556d8
Add new lines after @since
getdave 5c63046
PHPCS spaces
getdave 20d8a6b
Add schema modification filter
getdave 079d539
Add @ticket annotations
getdave aa05b2c
updated fixtures
ramonjd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
130 changes: 130 additions & 0 deletions
130
src/wp-includes/class-wp-classic-to-block-menu-converter.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<?php | ||
/** | ||
* WP_Classic_To_Block_Menu_Converter class | ||
* | ||
* @package WordPress | ||
* @since 6.3.0 | ||
*/ | ||
|
||
/** | ||
* Converts a Classic Menu to Block Menu blocks. | ||
* | ||
* @since 6.3.0. | ||
* @access public | ||
*/ | ||
class WP_Classic_To_Block_Menu_Converter { | ||
|
||
/** | ||
* Converts a Classic Menu to blocks. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @param WP_Term $menu The Menu term object of the menu to convert. | ||
* @return string the serialized and normalized parsed blocks. | ||
*/ | ||
public static function convert( $menu ) { | ||
|
||
if ( ! is_nav_menu( $menu ) ) { | ||
return new WP_Error( | ||
'invalid_menu', | ||
__( 'The menu provided is not a valid menu.' ) | ||
); | ||
} | ||
|
||
$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) ); | ||
|
||
if ( empty( $menu_items ) ) { | ||
return array(); | ||
} | ||
|
||
// Set up the $menu_item variables. | ||
// Adds the class property classes for the current context, if applicable. | ||
_wp_menu_item_classes_by_context( $menu_items ); | ||
|
||
$menu_items_by_parent_id = static::group_by_parent_id( $menu_items ); | ||
|
||
$first_menu_item = isset( $menu_items_by_parent_id[0] ) | ||
? $menu_items_by_parent_id[0] | ||
: array(); | ||
|
||
$inner_blocks = static::to_blocks( | ||
$first_menu_item, | ||
$menu_items_by_parent_id | ||
); | ||
|
||
return serialize_blocks( $inner_blocks ); | ||
} | ||
|
||
/** | ||
* Returns an array of menu items grouped by the id of the parent menu item. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @param array $menu_items An array of menu items. | ||
* @return array | ||
*/ | ||
private static function group_by_parent_id( $menu_items ) { | ||
$menu_items_by_parent_id = array(); | ||
|
||
foreach ( $menu_items as $menu_item ) { | ||
$menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; | ||
} | ||
|
||
return $menu_items_by_parent_id; | ||
} | ||
|
||
/** | ||
* Turns menu item data into a nested array of parsed blocks | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @param array $menu_items An array of menu items that represent | ||
* an individual level of a menu. | ||
* @param array $menu_items_by_parent_id An array keyed by the id of the | ||
* parent menu where each element is an | ||
* array of menu items that belong to | ||
* that parent. | ||
* @return array An array of parsed block data. | ||
*/ | ||
private static function to_blocks( $menu_items, $menu_items_by_parent_id ) { | ||
|
||
if ( empty( $menu_items ) ) { | ||
return array(); | ||
} | ||
|
||
$blocks = array(); | ||
|
||
foreach ( $menu_items as $menu_item ) { | ||
$class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; | ||
$id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; | ||
$opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; | ||
$rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; | ||
$kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; | ||
|
||
$block = array( | ||
'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', | ||
'attrs' => array( | ||
'className' => $class_name, | ||
'description' => $menu_item->description, | ||
'id' => $id, | ||
'kind' => $kind, | ||
'label' => $menu_item->title, | ||
'opensInNewTab' => $opens_in_new_tab, | ||
'rel' => $rel, | ||
'title' => $menu_item->attr_title, | ||
'type' => $menu_item->object, | ||
'url' => $menu_item->url, | ||
), | ||
); | ||
|
||
$block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) | ||
? static::to_blocks( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) | ||
: array(); | ||
$block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); | ||
|
||
$blocks[] = $block; | ||
} | ||
|
||
return $blocks; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
<?php | ||
/** | ||
* WP_Navigation_Fallback class | ||
* | ||
* Manages fallback behavior for Navigation menus. | ||
* | ||
* @package WordPress | ||
* @subpackage Navigation | ||
* @since 6.3.0 | ||
*/ | ||
|
||
/** | ||
* Manages fallback behavior for Navigation menus. | ||
* | ||
* @access public | ||
* @since 6.3.0. | ||
*/ | ||
class WP_Navigation_Fallback { | ||
|
||
/** | ||
* Gets (and/or creates) an appropriate fallback Navigation Menu. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return WP_Post|null the fallback Navigation Post or null. | ||
*/ | ||
public static function get_fallback() { | ||
|
||
$fallback = static::get_most_recently_published_navigation(); | ||
|
||
if ( $fallback ) { | ||
return $fallback; | ||
} | ||
|
||
$fallback = static::create_classic_menu_fallback(); | ||
|
||
if ( $fallback && ! is_wp_error( $fallback ) ) { | ||
// Return the newly created fallback post object which will now be the most recently created navigation menu. | ||
return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); | ||
} | ||
|
||
$fallback = static::create_default_fallback(); | ||
|
||
if ( $fallback && ! is_wp_error( $fallback ) ) { | ||
// Return the newly created fallback post object which will now be the most recently created navigation menu. | ||
return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Finds the most recently published `wp_navigation` post type. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return WP_Post|null the first non-empty Navigation or null. | ||
*/ | ||
private static function get_most_recently_published_navigation() { | ||
|
||
$parsed_args = array( | ||
'post_type' => 'wp_navigation', | ||
'no_found_rows' => true, | ||
'update_post_meta_cache' => false, | ||
'update_post_term_cache' => false, | ||
'order' => 'DESC', | ||
'orderby' => 'date', | ||
'post_status' => 'publish', | ||
'posts_per_page' => 1, | ||
); | ||
|
||
$navigation_post = new WP_Query( $parsed_args ); | ||
|
||
if ( count( $navigation_post->posts ) > 0 ) { | ||
return $navigation_post->posts[0]; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Creates a Navigation Menu post from a Classic Menu. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. | ||
*/ | ||
private static function create_classic_menu_fallback() { | ||
// See if we have a classic menu. | ||
$classic_nav_menu = static::get_fallback_classic_menu(); | ||
|
||
if ( ! $classic_nav_menu ) { | ||
return new WP_Error( 'no_classic_menus', __( 'No Classic Menus found.' ) ); | ||
} | ||
|
||
// If there is a classic menu then convert it to blocks. | ||
$classic_nav_menu_blocks = WP_Classic_To_Block_Menu_Converter::convert( $classic_nav_menu ); | ||
|
||
if ( empty( $classic_nav_menu_blocks ) ) { | ||
return new WP_Error( 'cannot_convert_classic_menu', __( 'Unable to convert Classic Menu to blocks.' ) ); | ||
} | ||
|
||
// Create a new navigation menu from the classic menu. | ||
$classic_menu_fallback = wp_insert_post( | ||
array( | ||
'post_content' => $classic_nav_menu_blocks, | ||
'post_title' => $classic_nav_menu->name, | ||
'post_name' => $classic_nav_menu->slug, | ||
'post_status' => 'publish', | ||
'post_type' => 'wp_navigation', | ||
), | ||
true // So that we can check whether the result is an error. | ||
); | ||
|
||
return $classic_menu_fallback; | ||
} | ||
|
||
/** | ||
* Determine the most appropriate classic navigation menu to use as a fallback. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return WP_Term|null The most appropriate classic navigation menu to use as a fallback. | ||
*/ | ||
private static function get_fallback_classic_menu() { | ||
$classic_nav_menus = wp_get_nav_menus(); | ||
|
||
if ( ! $classic_nav_menus || is_wp_error( $classic_nav_menus ) ) { | ||
return null; | ||
} | ||
|
||
$nav_menu = static::get_nav_menu_at_primary_location(); | ||
|
||
if ( $nav_menu ) { | ||
return $nav_menu; | ||
} | ||
|
||
$nav_menu = static::get_nav_menu_with_primary_slug( $classic_nav_menus ); | ||
|
||
if ( $nav_menu ) { | ||
return $nav_menu; | ||
} | ||
|
||
return static::get_most_recently_created_nav_menu( $classic_nav_menus ); | ||
} | ||
|
||
|
||
/** | ||
* Sorts the classic menus and returns the most recently created one. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. | ||
* @return WP_Term The most recently created classic nav menu. | ||
*/ | ||
private static function get_most_recently_created_nav_menu( $classic_nav_menus ) { | ||
usort( | ||
$classic_nav_menus, | ||
static function( $a, $b ) { | ||
return $b->term_id - $a->term_id; | ||
} | ||
); | ||
|
||
return $classic_nav_menus[0]; | ||
} | ||
|
||
/** | ||
* Returns the classic menu with the slug `primary` if it exists. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. | ||
* @return WP_Term|null The classic nav menu with the slug `primary` or null. | ||
*/ | ||
private static function get_nav_menu_with_primary_slug( $classic_nav_menus ) { | ||
foreach ( $classic_nav_menus as $classic_nav_menu ) { | ||
if ( 'primary' === $classic_nav_menu->slug ) { | ||
return $classic_nav_menu; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
|
||
/** | ||
* Gets the classic menu assigned to the `primary` navigation menu location | ||
* if it exists. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return WP_Term|null The classic nav menu assigned to the `primary` location or null. | ||
*/ | ||
private static function get_nav_menu_at_primary_location() { | ||
$locations = get_nav_menu_locations(); | ||
|
||
if ( isset( $locations['primary'] ) ) { | ||
$primary_menu = wp_get_nav_menu_object( $locations['primary'] ); | ||
|
||
if ( $primary_menu ) { | ||
return $primary_menu; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Creates a default Navigation Block Menu fallback. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. | ||
*/ | ||
private static function create_default_fallback() { | ||
|
||
$default_blocks = static::get_default_fallback_blocks(); | ||
|
||
// Create a new navigation menu from the fallback blocks. | ||
$default_fallback = wp_insert_post( | ||
array( | ||
'post_content' => $default_blocks, | ||
'post_title' => _x( 'Navigation', 'Title of a Navigation menu' ), | ||
'post_name' => 'navigation', | ||
'post_status' => 'publish', | ||
'post_type' => 'wp_navigation', | ||
), | ||
true // So that we can check whether the result is an error. | ||
); | ||
|
||
return $default_fallback; | ||
} | ||
|
||
/** | ||
* Gets the rendered markup for the default fallback blocks. | ||
* | ||
* @since 6.3.0. | ||
* | ||
* @return string default blocks markup to use a the fallback. | ||
*/ | ||
private static function get_default_fallback_blocks() { | ||
$registry = WP_Block_Type_Registry::get_instance(); | ||
|
||
// If `core/page-list` is not registered then use empty blocks. | ||
return $registry->is_registered( 'core/page-list' ) ? '<!-- wp:page-list /-->' : ''; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of count use
found_posts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mukeshpanchal27
Makes sense to me.
Looks like this has been committed. I'll do a follow up after Beta 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Core class: #4733
Updating Gutenberg: WordPress/gutenberg#51999
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might have to follow up another day actually
$navigation_post->found_posts
returns0
and I haven't got time to dig around. Sorry!