Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions src/wp-includes/class-wp-classic-to-block-menu-converter.php
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;
}
}
247 changes: 247 additions & 0 deletions src/wp-includes/class-wp-navigation-fallback.php
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 ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( count( $navigation_post->posts ) > 0 ) {
if ( $navigation_post->found_posts > 0 ) {

Instead of count use found_posts

Copy link
Member Author

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.

Copy link
Member Author

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

Copy link
Member Author

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 returns 0 and I haven't got time to dig around. Sorry!

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 /-->' : '';
}
}
Loading