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
Show file tree
Hide file tree
Changes from 9 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
183 changes: 183 additions & 0 deletions lib/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,186 @@ 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;
}
global $wpdb;

// 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, let's pretend the stylesheet is as it used to be.
$pretend_old_theme = function() use ( $old_theme ) {
adamziel marked this conversation as resolved.
Show resolved Hide resolved
return $old_theme->get_stylesheet();
};
add_filter( 'option_stylesheet', $pretend_old_theme );

$locations = get_nav_menu_locations();
$mapping = get_option( 'fse_navigation_areas', array() );
adamziel marked this conversation as resolved.
Show resolved Hide resolved

foreach ( $locations as $location_name => $menu_id ) {
// Get the menu from the location, returning early if there is no
adamziel marked this conversation as resolved.
Show resolved Hide resolved
// menu or there was an error.
$menu = wp_get_nav_menu_object( $menu_id );
if ( ! $menu || is_wp_error( $menu ) ) {
continue;
}

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

$menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items );
$parsed_blocks = gutenberg_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' => $menu->name,
'post_content' => serialize_blocks( $parsed_blocks ),
'post_status' => 'publish',
);
// Get or create to avoid creating too many wp_navigation posts.
$matching_posts = $wpdb->get_results(
adamziel marked this conversation as resolved.
Show resolved Hide resolved
$wpdb->prepare(
'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s',
adamziel marked this conversation as resolved.
Show resolved Hide resolved
$post_data['post_type'],
md5( $post_data['post_content'] ),
$post_data['post_status']
)
);

if ( count( $matching_posts ) ) {
$navigation_post_id = $matching_posts[0]->id;
} else {
$navigation_post_id = wp_insert_post( $post_data );
}

$mapping[ $location_name ] = $navigation_post_id;
}
remove_filter( 'option_stylesheet', $pretend_old_theme );

update_option( 'fse_navigation_areas', $mapping );
}

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


/**
* Returns the menu items for a WordPress menu location.
*
* @param string $location The menu location.
* @return array Menu items for the location.
*/
function gutenberg_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_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_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_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;
}
111 changes: 0 additions & 111 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,117 +85,6 @@ function block_core_navigation_build_css_font_sizes( $attributes ) {
return $font_sizes;
}

/**
* Returns the menu items for a WordPress menu location.
*
* @param string $location The menu location.
* @return array Menu items for the location.
*/
function gutenberg_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_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_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' => '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'] = gutenberg_parse_blocks_from_menu_items(
isset( $menu_items_by_parent_id[ $menu_item->ID ] )
? $menu_items_by_parent_id[ $menu_item->ID ]
: array(),
$menu_items_by_parent_id
);

$blocks[] = $block;
}

return $blocks;
}

/**
* Returns the top-level submenu SVG chevron icon.
*
Expand Down