Skip to content
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

Migrate classic menus to block-based menus on theme switch #36255

Merged
merged 21 commits into from
Nov 5, 2021
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d84b8cd
Migrate classic menus to block-based menus on theme switch
adamziel Nov 5, 2021
dc6d69f
Add more comments
adamziel Nov 5, 2021
f66987e
Short circuit if switching to a theme that does not support FSE
adamziel Nov 5, 2021
7cacf6e
Preserve the menu name on migration
adamziel Nov 5, 2021
9dc0f18
Replace WP_Query with wpdb->get_results
adamziel Nov 5, 2021
0625e9a
Adjust gutenberg_parse_blocks_from_menu_items to make use of innerCon…
adamziel Nov 5, 2021
b9a69ad
Lint
adamziel Nov 5, 2021
edf52e5
Replace return with continue
adamziel Nov 5, 2021
7faeb4f
Code style: Assign $mapping after $locations
adamziel Nov 5, 2021
65e8f08
Update lib/navigation.php
adamziel Nov 5, 2021
d96e8c3
Update lib/navigation.php
adamziel Nov 5, 2021
2c82824
Rename $mapping to $area_mapping
adamziel Nov 5, 2021
f00ca3f
Merge branch 'try/migrate-classic-menus-on-theme-switch' of github.co…
adamziel Nov 5, 2021
f4ec981
Rename $pretend_old_theme to $get_old_theme_stylesheet
adamziel Nov 5, 2021
60ef8e1
Explain why custom SQL is used instead of WP_Query
adamziel Nov 5, 2021
9d9e0be
Use post_name instead of MD5 matching
adamziel Nov 5, 2021
7f64dd9
Update the comment, remove global $wpdb
adamziel Nov 5, 2021
9a9db7b
Only convert classic menus to blocks when the matching post wasn't found
adamziel Nov 5, 2021
09905a7
Don't remove PHP functions from the navigation block, instead introdu…
adamziel Nov 5, 2021
5aee63f
Add a comment to explain how some functions are pasted
adamziel Nov 5, 2021
fd8dd34
Use "Classic menu" as prefix for the migrated post
adamziel Nov 5, 2021
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
194 changes: 194 additions & 0 deletions lib/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,197 @@ function gutenberg_get_navigation_areas_paths_to_preload() {
}
return $paths;
}

/**
* Migrates classic menus to block-based menus on theme switch.
*
* @param string $new_name Name of the new theme.
* @param WP_Theme $new_theme New theme.
* @param WP_Theme $old_theme Old theme.
* @see switch_theme WordPress action.
*/
function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_theme ) {
// Do nothing when switching to a theme that does not support site editor.
if ( ! gutenberg_experimental_is_site_editor_available() ) {
return;
}

// get_nav_menu_locations() calls get_theme_mod() which depends on the stylesheet option.
// At the same time, switch_theme runs only after the stylesheet option was updated to $new_theme.
// To retrieve theme mods of the old theme, the getter is hooked to get_option( 'stylesheet' ) so that we
// get the old theme, which causes the get_nav_menu_locations to get the locations of the old theme.
$get_old_theme_stylesheet = function() use ( $old_theme ) {
return $old_theme->get_stylesheet();
};
add_filter( 'option_stylesheet', $get_old_theme_stylesheet );

$locations = get_nav_menu_locations();
$area_mapping = get_option( 'fse_navigation_areas', array() );

foreach ( $locations as $location_name => $menu_id ) {
// Get the menu from the location, skipping if there is no
// menu or there was an error.
$menu = wp_get_nav_menu_object( $menu_id );
if ( ! $menu || is_wp_error( $menu ) ) {
continue;
}

$menu_items = gutenberg_global_get_menu_items_at_location( $location_name );
if ( empty( $menu_items ) ) {
continue;
}

$post_name = 'classic_menu_' . $menu_id;
$post_status = 'publish';

// Get or create to avoid creating too many wp_navigation posts.
$query = new WP_Query;
$matching_posts = $query->query(
array(
'name' => $post_name,
'post_status' => $post_status,
'post_type' => 'wp_navigation',
'posts_per_page' => 1,
)
);

if ( count( $matching_posts ) ) {
$navigation_post_id = $matching_posts[0]->ID;
} else {
$menu_items_by_parent_id = gutenberg_global_sort_menu_items_by_parent_id( $menu_items );
$parsed_blocks = gutenberg_global_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
$post_data = array(
'post_type' => 'wp_navigation',
'post_title' => sprintf(
/* translators: %s: the name of the menu, e.g. "Main Menu". */
__( 'Classic menu: %s', 'gutenberg' ),
$menu->name
),
'post_name' => $post_name,
'post_content' => serialize_blocks( $parsed_blocks ),
'post_status' => $post_status,
);
$navigation_post_id = wp_insert_post( $post_data );
}

$area_mapping[ $location_name ] = $navigation_post_id;
}
remove_filter( 'option_stylesheet', $get_old_theme_stylesheet );

update_option( 'fse_navigation_areas', $area_mapping );
}

add_action( 'switch_theme', 'gutenberg_migrate_nav_on_theme_switch', 200, 3 );

// The functions below are copied over from packages/block-library/src/navigation/index.php
// Let's figure out a better way of managing these global PHP dependencies.

/**
* Returns the menu items for a WordPress menu location.
*
* @param string $location The menu location.
* @return array Menu items for the location.
*/
function gutenberg_global_get_menu_items_at_location( $location ) {
if ( empty( $location ) ) {
return;
}

// Build menu data. The following approximates the code in
// `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.

// Find the location in the list of locations, returning early if the
// location can't be found.
$locations = get_nav_menu_locations();
if ( ! isset( $locations[ $location ] ) ) {
return;
}

// Get the menu from the location, returning early if there is no
// menu or there was an error.
$menu = wp_get_nav_menu_object( $locations[ $location ] );
if ( ! $menu || is_wp_error( $menu ) ) {
return;
}

$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
_wp_menu_item_classes_by_context( $menu_items );

return $menu_items;
}


/**
* Sorts a standard array of menu items into a nested structure keyed by the
* id of the parent menu.
*
* @param array $menu_items Menu items to sort.
* @return array An array keyed by the id of the parent menu where each element
* is an array of menu items that belong to that parent.
*/
function gutenberg_global_sort_menu_items_by_parent_id( $menu_items ) {
$sorted_menu_items = array();
foreach ( (array) $menu_items as $menu_item ) {
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
}
unset( $menu_items, $menu_item );

$menu_items_by_parent_id = array();
foreach ( $sorted_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
*
* @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.
*/
function gutenberg_global_parse_blocks_from_menu_items( $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 ] )
? gutenberg_global_parse_blocks_from_menu_items( $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;
}