From d84b8cd85d3fee5289595579ab7eae40332f9773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 13:40:53 +0100 Subject: [PATCH 01/20] Migrate classic menus to block-based menus on theme switch --- lib/navigation.php | 162 ++++++++++++++++++ .../block-library/src/navigation/index.php | 111 ------------ 2 files changed, 162 insertions(+), 111 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 99756781ee8e6..cf392908badc1 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -571,3 +571,165 @@ function gutenberg_get_navigation_areas_paths_to_preload() { } return $paths; } + +/** + * Description todo + * + * @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 ) { + $pretend_old_theme = function() use ( $old_theme ) { + return $old_theme->get_stylesheet(); + }; + add_filter( 'option_stylesheet', $pretend_old_theme ); + + $mapping = get_option( 'fse_navigation_areas', array() ); + $locations = array_keys( get_nav_menu_locations() ); + + foreach ( $locations as $location_name ) { + $menu_items = gutenberg_get_menu_items_at_location( $location_name ); + if ( empty( $menu_items ) ) { + return ''; + } + + $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' => $location_name, + 'post_content' => serialize_blocks( $parsed_blocks ), + 'post_status' => 'publish', + ); + + $query = new WP_Query; + $matching_posts = $query->query( $post_data ); + 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' => '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; +} diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index f51b994920406..c0018a9b46f17 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -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. * From dc6d69f6571370bd106fe3d2738f5fd2c28ed3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 13:53:45 +0100 Subject: [PATCH 02/20] Add more comments --- lib/navigation.php | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index cf392908badc1..31f17cd2e092f 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -573,7 +573,7 @@ function gutenberg_get_navigation_areas_paths_to_preload() { } /** - * Description todo + * 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. @@ -581,6 +581,9 @@ function gutenberg_get_navigation_areas_paths_to_preload() { * @see switch_theme WordPress action. */ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_theme ) { + // 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 ) { return $old_theme->get_stylesheet(); }; @@ -706,26 +709,26 @@ function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_par $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, - ), + '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 + 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; From f66987eeab0392db338a13c432e4fb9836f03012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 13:56:12 +0100 Subject: [PATCH 03/20] Short circuit if switching to a theme that does not support FSE --- lib/navigation.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/navigation.php b/lib/navigation.php index 31f17cd2e092f..9b1a806c7fc8a 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -581,6 +581,11 @@ function gutenberg_get_navigation_areas_paths_to_preload() { * @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, let's pretend the stylesheet is as it used to be. From 7cacf6ef12705552de7ddd46b8346fba620b658f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 13:59:49 +0100 Subject: [PATCH 04/20] Preserve the menu name on migration --- lib/navigation.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 9b1a806c7fc8a..cabed8ee71d82 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -595,9 +595,16 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them add_filter( 'option_stylesheet', $pretend_old_theme ); $mapping = get_option( 'fse_navigation_areas', array() ); - $locations = array_keys( get_nav_menu_locations() ); + $locations = get_nav_menu_locations(); + + foreach ( $locations as $location_name => $menu_id ) { + // Get the menu from the location, returning early 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; + } - foreach ( $locations as $location_name ) { $menu_items = gutenberg_get_menu_items_at_location( $location_name ); if ( empty( $menu_items ) ) { return ''; @@ -607,7 +614,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $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' => $location_name, + 'post_title' => $menu->name, 'post_content' => serialize_blocks( $parsed_blocks ), 'post_status' => 'publish', ); From 9dc0f189c67218b59617ec88c09a78045164da56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 14:12:15 +0100 Subject: [PATCH 05/20] Replace WP_Query with wpdb->get_results --- lib/navigation.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index cabed8ee71d82..388bd243f0425 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -585,6 +585,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 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. @@ -619,10 +620,18 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 'post_status' => 'publish', ); - $query = new WP_Query; - $matching_posts = $query->query( $post_data ); + // Get or create to avoid creating too many wp_navigation posts. + $matching_posts = $wpdb->get_results( + $wpdb->prepare( + 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s', + $post_data['post_type'], + md5( $post_data['post_content'] ), + $post_data['post_status'] + ) + ); + if ( count( $matching_posts ) ) { - $navigation_post_id = $matching_posts[0]->ID; + $navigation_post_id = $matching_posts[0]->id; } else { $navigation_post_id = wp_insert_post( $post_data ); } From 0625e9a343b9bb08975746e8e8f6f139c2361ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 14:31:50 +0100 Subject: [PATCH 06/20] Adjust gutenberg_parse_blocks_from_menu_items to make use of innerContent --- lib/navigation.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 388bd243f0425..dc54ac10715f4 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -619,7 +619,6 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them '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( $wpdb->prepare( @@ -730,7 +729,7 @@ function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_par $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; $block = array( - 'blockName' => 'core/navigation-link', + '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, @@ -745,12 +744,10 @@ function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_par ), ); - $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 - ); + $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; } From b9a69ad3ab442a2442610c82eb99ebb3f80672b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 14:33:12 +0100 Subject: [PATCH 07/20] Lint --- lib/navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index dc54ac10715f4..2084bb0721fc2 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -622,7 +622,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them // Get or create to avoid creating too many wp_navigation posts. $matching_posts = $wpdb->get_results( $wpdb->prepare( - 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s', + 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s', $post_data['post_type'], md5( $post_data['post_content'] ), $post_data['post_status'] From edf52e5f96db2ef0ebcbbc14d1d5be4958dc47e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 14:36:20 +0100 Subject: [PATCH 08/20] Replace return with continue --- lib/navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index 2084bb0721fc2..658d51292aa71 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -608,7 +608,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $menu_items = gutenberg_get_menu_items_at_location( $location_name ); if ( empty( $menu_items ) ) { - return ''; + continue; } $menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items ); From 7faeb4f6ca0b815b7b8a95c99c3be15427622abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 14:37:01 +0100 Subject: [PATCH 09/20] Code style: Assign $mapping after $locations --- lib/navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index 658d51292aa71..5662e2c67cca7 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -595,8 +595,8 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them }; add_filter( 'option_stylesheet', $pretend_old_theme ); - $mapping = get_option( 'fse_navigation_areas', array() ); $locations = get_nav_menu_locations(); + $mapping = get_option( 'fse_navigation_areas', array() ); foreach ( $locations as $location_name => $menu_id ) { // Get the menu from the location, returning early if there is no From 65e8f0885c93f7f77750f96b58585f37bdbce94c Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 5 Nov 2021 15:28:14 +0100 Subject: [PATCH 10/20] Update lib/navigation.php --- lib/navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index 5662e2c67cca7..6cf8248108638 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -599,7 +599,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $mapping = get_option( 'fse_navigation_areas', array() ); foreach ( $locations as $location_name => $menu_id ) { - // Get the menu from the location, returning early if there is no + // 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 ) ) { From d96e8c35b67978f7190e38e76e2dddac35eae1df Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 5 Nov 2021 15:28:56 +0100 Subject: [PATCH 11/20] Update lib/navigation.php --- lib/navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index 6cf8248108638..d9cf9fbb226b9 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -622,7 +622,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them // Get or create to avoid creating too many wp_navigation posts. $matching_posts = $wpdb->get_results( $wpdb->prepare( - 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s', + 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s LIMIT 1', $post_data['post_type'], md5( $post_data['post_content'] ), $post_data['post_status'] From 2c828243fd8334888aa4fb6cd22824b8ac7d8c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:31:43 +0100 Subject: [PATCH 12/20] Rename $mapping to $area_mapping --- lib/navigation.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 5662e2c67cca7..58ba9e5f3aac4 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -595,8 +595,8 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them }; add_filter( 'option_stylesheet', $pretend_old_theme ); - $locations = get_nav_menu_locations(); - $mapping = get_option( 'fse_navigation_areas', array() ); + $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, returning early if there is no @@ -635,11 +635,11 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $navigation_post_id = wp_insert_post( $post_data ); } - $mapping[ $location_name ] = $navigation_post_id; + $area_mapping[ $location_name ] = $navigation_post_id; } remove_filter( 'option_stylesheet', $pretend_old_theme ); - update_option( 'fse_navigation_areas', $mapping ); + update_option( 'fse_navigation_areas', $area_mapping ); } add_action( 'switch_theme', 'gutenberg_migrate_nav_on_theme_switch', 200, 3 ); From f4ec98134c0a6014971ccf481400c5e2b7c844f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:33:41 +0100 Subject: [PATCH 13/20] Rename $pretend_old_theme to $get_old_theme_stylesheet --- lib/navigation.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 21c410700a2a1..bda92b03cda0a 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -590,10 +590,10 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them // 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 ) { + $get_old_theme_stylesheet = function() use ( $old_theme ) { return $old_theme->get_stylesheet(); }; - add_filter( 'option_stylesheet', $pretend_old_theme ); + add_filter( 'option_stylesheet', $get_old_theme_stylesheet ); $locations = get_nav_menu_locations(); $area_mapping = get_option( 'fse_navigation_areas', array() ); @@ -637,7 +637,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $area_mapping[ $location_name ] = $navigation_post_id; } - remove_filter( 'option_stylesheet', $pretend_old_theme ); + remove_filter( 'option_stylesheet', $get_old_theme_stylesheet ); update_option( 'fse_navigation_areas', $area_mapping ); } From 60ef8e1a1ad376fe1118e3bca6e0e35b2e4127f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:35:15 +0100 Subject: [PATCH 14/20] Explain why custom SQL is used instead of WP_Query --- lib/navigation.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index bda92b03cda0a..718d8fac1abbd 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -619,7 +619,8 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 'post_content' => serialize_blocks( $parsed_blocks ), 'post_status' => 'publish', ); - // Get or create to avoid creating too many wp_navigation posts. + // Get or create to avoid creating too many wp_navigation posts. Using custom SQL because WP_Query + // can't filter by post_content. $matching_posts = $wpdb->get_results( $wpdb->prepare( 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s LIMIT 1', From 9d9e0bea441def585083c6d5e53a84e465e55544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:55:24 +0100 Subject: [PATCH 15/20] Use post_name instead of MD5 matching --- lib/navigation.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 718d8fac1abbd..c4991e59db09a 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -616,22 +616,24 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $post_data = array( 'post_type' => 'wp_navigation', 'post_title' => $menu->name, + 'post_name' => 'classic_menu_' . $menu_id, 'post_content' => serialize_blocks( $parsed_blocks ), 'post_status' => 'publish', ); - // Get or create to avoid creating too many wp_navigation posts. Using custom SQL because WP_Query - // can't filter by post_content. - $matching_posts = $wpdb->get_results( - $wpdb->prepare( - 'SELECT id FROM wp_posts WHERE post_type = %s AND MD5( post_content ) = %s AND post_status = %s LIMIT 1', - $post_data['post_type'], - md5( $post_data['post_content'] ), - $post_data['post_status'] + + // Get or create to avoid creating too many wp_navigation posts. + $query = new WP_Query; + $matching_posts = $query->query( + array( + 'name' => $post_data['post_name'], + 'post_status' => $post_data['post_status'], + 'post_type' => 'wp_navigation', + 'posts_per_page' => 1, ) ); if ( count( $matching_posts ) ) { - $navigation_post_id = $matching_posts[0]->id; + $navigation_post_id = $matching_posts[0]->ID; } else { $navigation_post_id = wp_insert_post( $post_data ); } From 7f64dd9bdf14464208b70511bd3c8e36c8b8874d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:57:12 +0100 Subject: [PATCH 16/20] Update the comment, remove global $wpdb --- lib/navigation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index c4991e59db09a..11d6423077f2a 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -585,11 +585,11 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 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. + // 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(); }; From 9a9db7b0f9110b44317afdb35697bd690a14845b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 15:59:35 +0100 Subject: [PATCH 17/20] Only convert classic menus to blocks when the matching post wasn't found --- lib/navigation.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 11d6423077f2a..2dd8f80d2bee8 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -611,22 +611,15 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 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_name' => 'classic_menu_' . $menu_id, - 'post_content' => serialize_blocks( $parsed_blocks ), - 'post_status' => 'publish', - ); + $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_data['post_name'], - 'post_status' => $post_data['post_status'], + 'name' => $post_name, + 'post_status' => $post_status, 'post_type' => 'wp_navigation', 'posts_per_page' => 1, ) @@ -635,7 +628,16 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them if ( count( $matching_posts ) ) { $navigation_post_id = $matching_posts[0]->ID; } else { - $navigation_post_id = wp_insert_post( $post_data ); + $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_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; From 09905a7438db1201cae35b618a89a11cebdd0815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 16:03:24 +0100 Subject: [PATCH 18/20] Don't remove PHP functions from the navigation block, instead introduce new ones --- lib/navigation.php | 14 +-- .../block-library/src/navigation/index.php | 111 ++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/lib/navigation.php b/lib/navigation.php index 2dd8f80d2bee8..17e8a90efb0e9 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -606,7 +606,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them continue; } - $menu_items = gutenberg_get_menu_items_at_location( $location_name ); + $menu_items = gutenberg_global_get_menu_items_at_location( $location_name ); if ( empty( $menu_items ) ) { continue; } @@ -628,8 +628,8 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them if ( count( $matching_posts ) ) { $navigation_post_id = $matching_posts[0]->ID; } else { - $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 ); + $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' => $menu->name, @@ -656,7 +656,7 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them * @param string $location The menu location. * @return array Menu items for the location. */ -function gutenberg_get_menu_items_at_location( $location ) { +function gutenberg_global_get_menu_items_at_location( $location ) { if ( empty( $location ) ) { return; } @@ -693,7 +693,7 @@ function gutenberg_get_menu_items_at_location( $location ) { * @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 ) { +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; @@ -719,7 +719,7 @@ function gutenberg_sort_menu_items_by_parent_id( $menu_items ) { * that parent. * @return array An array of parsed block data. */ -function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { +function gutenberg_global_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { if ( empty( $menu_items ) ) { return array(); } @@ -750,7 +750,7 @@ function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_par ); $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 ) + ? 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'] ); diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index c0018a9b46f17..f51b994920406 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -85,6 +85,117 @@ 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. * From 5aee63f3df8a3e25b7e20a8ed0daedecef30d8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 16:04:46 +0100 Subject: [PATCH 19/20] Add a comment to explain how some functions are pasted --- lib/navigation.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/navigation.php b/lib/navigation.php index 17e8a90efb0e9..52e3031499ac8 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -649,6 +649,8 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them 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. From fd8dd3402c9c7a816d28eee236224de387c3cfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 5 Nov 2021 16:11:44 +0100 Subject: [PATCH 20/20] Use "Classic menu" as prefix for the migrated post --- lib/navigation.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/navigation.php b/lib/navigation.php index 52e3031499ac8..796dc57948761 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -632,7 +632,11 @@ function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_them $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' => $menu->name, + '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,