| @@ -36,10 +36,11 @@ | ||
| 36 | 36 | 'labels' => $labels, |
| 37 | 37 | 'description' => __( 'Navigation menus.', 'gutenberg' ), |
| 38 | 38 | 'public' => false, |
| 39 | 39 | 'has_archive' => false, |
| 40 | - 'show_ui' => true, | |
| 41 | - 'show_in_menu' => false, | |
| 40 | + // We should disable UI for non-FSE themes. | |
| 41 | + 'show_ui' => gutenberg_is_fse_theme(), | |
| 42 | + 'show_in_menu' => 'themes.php', | |
| 42 | 43 | 'show_in_admin_bar' => false, |
| 43 | 44 | 'show_in_rest' => true, |
| 44 | 45 | 'map_meta_cap' => true, |
| 45 | 46 | 'rest_base' => 'navigation', |
| @@ -48,20 +49,8 @@ | ||
| 48 | 49 | 'title', |
| 49 | 50 | 'editor', |
| 50 | 51 | 'revisions', |
| 51 | 52 | ), |
| 52 | - 'capabilities' => array( | |
| 53 | - 'edit_others_posts' => 'edit_theme_options', | |
| 54 | - 'delete_posts' => 'edit_theme_options', | |
| 55 | - 'publish_posts' => 'edit_theme_options', | |
| 56 | - 'create_posts' => 'edit_theme_options', | |
| 57 | - 'read_private_posts' => 'edit_theme_options', | |
| 58 | - 'delete_private_posts' => 'edit_theme_options', | |
| 59 | - 'delete_published_posts' => 'edit_theme_options', | |
| 60 | - 'delete_others_posts' => 'edit_theme_options', | |
| 61 | - 'edit_private_posts' => 'edit_theme_options', | |
| 62 | - 'edit_published_posts' => 'edit_theme_options', | |
| 63 | - ), | |
| 64 | 53 | ); |
| 65 | 54 | |
| 66 | 55 | register_post_type( 'wp_navigation', $args ); |
| 67 | 56 | } |
| @@ -192,8 +181,114 @@ | ||
| 192 | 181 | return $gutenberg_navigation_areas; |
| 193 | 182 | } |
| 194 | 183 | |
| 195 | 184 | /** |
| 185 | + * Returns the API paths to preload to make the navigation area block load fast. | |
| 186 | + * | |
| 187 | + * @return array A list of paths. | |
| 188 | + */ | |
| 189 | +function gutenberg_get_navigation_areas_paths_to_preload() { | |
| 190 | + $areas = gutenberg_get_navigation_areas_menus(); | |
| 191 | + $active_areas = array_intersect_key( $areas, gutenberg_get_navigation_areas() ); | |
| 192 | + $paths = array( | |
| 193 | + '/wp/v2/block-navigation-areas?context=edit', | |
| 194 | + ); | |
| 195 | + foreach ( $active_areas as $post_id ) { | |
| 196 | + if ( 0 !== $post_id ) { | |
| 197 | + $paths[] = "/wp/v2/navigation/$post_id?context=edit"; | |
| 198 | + } | |
| 199 | + } | |
| 200 | + return $paths; | |
| 201 | +} | |
| 202 | + | |
| 203 | +/** | |
| 204 | + * Migrates classic menus to a block-based navigation post on theme switch. | |
| 205 | + * Assigns the created navigation post to the corresponding navigation area. | |
| 206 | + * | |
| 207 | + * @param string $new_name Name of the new theme. | |
| 208 | + * @param WP_Theme $new_theme New theme. | |
| 209 | + * @param WP_Theme $old_theme Old theme. | |
| 210 | + * @see switch_theme WordPress action. | |
| 211 | + */ | |
| 212 | +function gutenberg_migrate_menu_to_navigation_post( $new_name, $new_theme, $old_theme ) { | |
| 213 | + // Do nothing when switching to a theme that does not support site editor. | |
| 214 | + if ( ! gutenberg_experimental_is_site_editor_available() ) { | |
| 215 | + return; | |
| 216 | + } | |
| 217 | + | |
| 218 | + // get_nav_menu_locations() calls get_theme_mod() which depends on the stylesheet option. | |
| 219 | + // At the same time, switch_theme runs only after the stylesheet option was updated to $new_theme. | |
| 220 | + // To retrieve theme mods of the old theme, the getter is hooked to get_option( 'stylesheet' ) so that we | |
| 221 | + // get the old theme, which causes the get_nav_menu_locations to get the locations of the old theme. | |
| 222 | + $get_old_theme_stylesheet = function() use ( $old_theme ) { | |
| 223 | + return $old_theme->get_stylesheet(); | |
| 224 | + }; | |
| 225 | + add_filter( 'option_stylesheet', $get_old_theme_stylesheet ); | |
| 226 | + | |
| 227 | + $locations = get_nav_menu_locations(); | |
| 228 | + $area_mapping = gutenberg_get_navigation_areas_menus(); | |
| 229 | + | |
| 230 | + foreach ( $locations as $location_name => $menu_id ) { | |
| 231 | + // Get the menu from the location, skipping if there is no | |
| 232 | + // menu or there was an error. | |
| 233 | + $menu = wp_get_nav_menu_object( $menu_id ); | |
| 234 | + if ( ! $menu || is_wp_error( $menu ) ) { | |
| 235 | + continue; | |
| 236 | + } | |
| 237 | + | |
| 238 | + $menu_items = gutenberg_get_menu_items_at_location( $location_name ); | |
| 239 | + if ( empty( $menu_items ) ) { | |
| 240 | + continue; | |
| 241 | + } | |
| 242 | + | |
| 243 | + $post_name = 'classic_menu_' . $menu_id; | |
| 244 | + $post_status = 'publish'; | |
| 245 | + | |
| 246 | + // Get or create to avoid creating too many wp_navigation posts. | |
| 247 | + $query = new WP_Query; | |
| 248 | + $matching_posts = $query->query( | |
| 249 | + array( | |
| 250 | + 'name' => $post_name, | |
| 251 | + 'post_status' => $post_status, | |
| 252 | + 'post_type' => 'wp_navigation', | |
| 253 | + 'posts_per_page' => 1, | |
| 254 | + ) | |
| 255 | + ); | |
| 256 | + | |
| 257 | + if ( count( $matching_posts ) ) { | |
| 258 | + $navigation_post_id = $matching_posts[0]->ID; | |
| 259 | + } else { | |
| 260 | + $menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items ); | |
| 261 | + $parsed_blocks = gutenberg_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); | |
| 262 | + $post_data = array( | |
| 263 | + 'post_type' => 'wp_navigation', | |
| 264 | + 'post_title' => sprintf( | |
| 265 | + /* translators: %s: the name of the menu, e.g. "Main Menu". */ | |
| 266 | + __( 'Classic menu: %s', 'gutenberg' ), | |
| 267 | + $menu->name | |
| 268 | + ), | |
| 269 | + 'post_name' => $post_name, | |
| 270 | + 'post_content' => serialize_blocks( $parsed_blocks ), | |
| 271 | + 'post_status' => $post_status, | |
| 272 | + ); | |
| 273 | + $navigation_post_id = wp_insert_post( $post_data, true ); | |
| 274 | + // If wp_insert_post fails *at any time*, then bale out of the entire | |
| 275 | + // migration attempt returning the WP_Error object. | |
| 276 | + if ( is_wp_error( $navigation_post_id ) ) { | |
| 277 | + return $navigation_post_id; | |
| 278 | + } | |
| 279 | + } | |
| 280 | + | |
| 281 | + $area_mapping[ $location_name ] = $navigation_post_id; | |
| 282 | + } | |
| 283 | + remove_filter( 'option_stylesheet', $get_old_theme_stylesheet ); | |
| 284 | + | |
| 285 | + update_option( 'wp_navigation_areas', $area_mapping ); | |
| 286 | +} | |
| 287 | + | |
| 288 | +add_action( 'switch_theme', 'gutenberg_migrate_menu_to_navigation_post', 99, 3 ); | |
| 289 | + | |
| 290 | +/** | |
| 196 | 291 | * Retrieves navigation areas. |
| 197 | 292 | * |
| 198 | 293 | * @return array Navigation areas. |
| 199 | 294 | */ |
| @@ -206,8 +301,121 @@ | ||
| 206 | 301 | $legacy_option_key = 'fse_navigation_areas'; |
| 207 | 302 | $areas = get_option( $legacy_option_key, array() ); |
| 208 | 303 | } |
| 209 | 304 | return $areas; |
| 305 | +} | |
| 306 | + | |
| 307 | +// The functions below are copied over from packages/block-library/src/navigation/index.php | |
| 308 | +// Let's figure out a better way of managing these global PHP dependencies. | |
| 309 | + | |
| 310 | +/** | |
| 311 | + * Returns the menu items for a WordPress menu location. | |
| 312 | + * | |
| 313 | + * @param string $location The menu location. | |
| 314 | + * @return array Menu items for the location. | |
| 315 | + */ | |
| 316 | +function gutenberg_get_menu_items_at_location( $location ) { | |
| 317 | + if ( empty( $location ) ) { | |
| 318 | + return; | |
| 319 | + } | |
| 320 | + | |
| 321 | + // Build menu data. The following approximates the code in | |
| 322 | + // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`. | |
| 323 | + | |
| 324 | + // Find the location in the list of locations, returning early if the | |
| 325 | + // location can't be found. | |
| 326 | + $locations = get_nav_menu_locations(); | |
| 327 | + if ( ! isset( $locations[ $location ] ) ) { | |
| 328 | + return; | |
| 329 | + } | |
| 330 | + | |
| 331 | + // Get the menu from the location, returning early if there is no | |
| 332 | + // menu or there was an error. | |
| 333 | + $menu = wp_get_nav_menu_object( $locations[ $location ] ); | |
| 334 | + if ( ! $menu || is_wp_error( $menu ) ) { | |
| 335 | + return; | |
| 336 | + } | |
| 337 | + | |
| 338 | + $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) ); | |
| 339 | + _wp_menu_item_classes_by_context( $menu_items ); | |
| 340 | + | |
| 341 | + return $menu_items; | |
| 342 | +} | |
| 343 | + | |
| 344 | + | |
| 345 | +/** | |
| 346 | + * Sorts a standard array of menu items into a nested structure keyed by the | |
| 347 | + * id of the parent menu. | |
| 348 | + * | |
| 349 | + * @param array $menu_items Menu items to sort. | |
| 350 | + * @return array An array keyed by the id of the parent menu where each element | |
| 351 | + * is an array of menu items that belong to that parent. | |
| 352 | + */ | |
| 353 | +function gutenberg_sort_menu_items_by_parent_id( $menu_items ) { | |
| 354 | + $sorted_menu_items = array(); | |
| 355 | + foreach ( (array) $menu_items as $menu_item ) { | |
| 356 | + $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; | |
| 357 | + } | |
| 358 | + unset( $menu_items, $menu_item ); | |
| 359 | + | |
| 360 | + $menu_items_by_parent_id = array(); | |
| 361 | + foreach ( $sorted_menu_items as $menu_item ) { | |
| 362 | + $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; | |
| 363 | + } | |
| 364 | + | |
| 365 | + return $menu_items_by_parent_id; | |
| 366 | +} | |
| 367 | + | |
| 368 | +/** | |
| 369 | + * Turns menu item data into a nested array of parsed blocks | |
| 370 | + * | |
| 371 | + * @param array $menu_items An array of menu items that represent | |
| 372 | + * an individual level of a menu. | |
| 373 | + * @param array $menu_items_by_parent_id An array keyed by the id of the | |
| 374 | + * parent menu where each element is an | |
| 375 | + * array of menu items that belong to | |
| 376 | + * that parent. | |
| 377 | + * @return array An array of parsed block data. | |
| 378 | + */ | |
| 379 | +function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { | |
| 380 | + if ( empty( $menu_items ) ) { | |
| 381 | + return array(); | |
| 382 | + } | |
| 383 | + | |
| 384 | + $blocks = array(); | |
| 385 | + | |
| 386 | + foreach ( $menu_items as $menu_item ) { | |
| 387 | + $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; | |
| 388 | + $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; | |
| 389 | + $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; | |
| 390 | + $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; | |
| 391 | + $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; | |
| 392 | + | |
| 393 | + $block = array( | |
| 394 | + 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', | |
| 395 | + 'attrs' => array( | |
| 396 | + 'className' => $class_name, | |
| 397 | + 'description' => $menu_item->description, | |
| 398 | + 'id' => $id, | |
| 399 | + 'kind' => $kind, | |
| 400 | + 'label' => $menu_item->title, | |
| 401 | + 'opensInNewTab' => $opens_in_new_tab, | |
| 402 | + 'rel' => $rel, | |
| 403 | + 'title' => $menu_item->attr_title, | |
| 404 | + 'type' => $menu_item->object, | |
| 405 | + 'url' => $menu_item->url, | |
| 406 | + ), | |
| 407 | + ); | |
| 408 | + | |
| 409 | + $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) | |
| 410 | + ? gutenberg_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) | |
| 411 | + : array(); | |
| 412 | + $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); | |
| 413 | + | |
| 414 | + $blocks[] = $block; | |
| 415 | + } | |
| 416 | + | |
| 417 | + return $blocks; | |
| 210 | 418 | } |
| 211 | 419 | |
| 212 | 420 | /** |
| 213 | 421 | * Shim that hides ability to edit visibility and status for wp_navigation type posts. |