PluginProbe
Gutenberg / 11.9.1
Gutenberg v11.9.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / navigation.php

navigation.php in Gutenberg 11.9.1, at lib/navigation.php

777 lines 26.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions used in making nav menus interopable with block editors.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Shim that hooks into `wp_update_nav_menu_item` and makes it so that nav menu
10 * items support a 'content' field. This field contains HTML and is used by nav
11 * menu items with `type` set to `'block'`.
12 *
13 * Specifically, this shim makes it so that:
14 *
15 * 1) The `wp_update_nav_menu_item()` function supports setting
16 * `'menu-item-content'` on a menu item. When merged to Core, this functionality
17 * should exist in `wp_update_nav_menu_item()`.
18 *
19 * 2) Updating a menu via nav-menus.php supports setting `'menu-item-content'`
20 * on a menu item. When merged to Core, this functionality should exist in
21 * `wp_nav_menu_update_menu_items()`.
22 *
23 * 3) The `customize_save` ajax action supports setting `'content'` on a nav
24 * menu item. When merged to Core, this functionality should exist in
25 * `WP_Customize_Manager::save()`.
26 *
27 * This shim can be removed when the Gutenberg plugin requires a WordPress
28 * version that has the ticket below.
29 *
30 * @see https://core.trac.wordpress.org/ticket/50544
31 *
32 * @param int $menu_id ID of the updated menu.
33 * @param int $menu_item_db_id ID of the new menu item.
34 * @param array $args An array of arguments used to update/add the menu item.
35 */
36 function gutenberg_update_nav_menu_item_content( $menu_id, $menu_item_db_id, $args ) {
37 global $wp_customize;
38
39 // Support setting content in nav-menus.php by grabbing the value from
40 // $_POST. This belongs in `wp_nav_menu_update_menu_items()`.
41 if ( isset( $_POST['menu-item-content'][ $menu_item_db_id ] ) ) {
42 $args['menu-item-content'] = wp_unslash( $_POST['menu-item-content'][ $menu_item_db_id ] );
43 }
44
45 // Support setting content in customize_save admin-ajax.php requests by
46 // grabbing the unsanitized $_POST values. This belongs in
47 // `WP_Customize_Manager::save()`.
48 if ( isset( $wp_customize ) ) {
49 $values = $wp_customize->unsanitized_post_values();
50 if ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
51 if ( is_string( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
52 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content'];
53 } elseif ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'] ) ) {
54 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'];
55 }
56 }
57 }
58
59 // Everything else belongs in `wp_update_nav_menu_item()`.
60
61 $defaults = array(
62 'menu-item-content' => '',
63 );
64
65 $args = wp_parse_args( $args, $defaults );
66
67 update_post_meta( $menu_item_db_id, '_menu_item_content', wp_slash( $args['menu-item-content'] ) );
68 }
69 add_action( 'wp_update_nav_menu_item', 'gutenberg_update_nav_menu_item_content', 10, 3 );
70
71 /**
72 * Shim that hooks into `wp_setup_nav_menu_items` and makes it so that nav menu
73 * items have a 'content' field. This field contains HTML and is used by nav
74 * menu items with `type` set to `'block'`.
75 *
76 * Specifically, this shim makes it so that the `wp_setup_nav_menu_item()`
77 * function sets `content` on the returned menu item. When merged to Core, this
78 * functionality should exist in `wp_setup_nav_menu_item()`.
79 *
80 * This shim can be removed when the Gutenberg plugin requires a WordPress
81 * version that has the ticket below.
82 *
83 * @see https://core.trac.wordpress.org/ticket/50544
84 *
85 * @param object $menu_item The menu item object.
86 *
87 * @return object Updated menu item object.
88 */
89 function gutenberg_setup_block_nav_menu_item( $menu_item ) {
90 if ( 'block' === $menu_item->type ) {
91 $menu_item->type_label = __( 'Block', 'gutenberg' );
92 $menu_item->content = ! isset( $menu_item->content ) ? get_post_meta( $menu_item->db_id, '_menu_item_content', true ) : $menu_item->content;
93
94 // Set to make the menu item display nicely in nav-menus.php.
95 $menu_item->object = 'block';
96 $menu_item->title = __( 'Block', 'gutenberg' );
97 }
98
99 return $menu_item;
100 }
101 add_filter( 'wp_setup_nav_menu_item', 'gutenberg_setup_block_nav_menu_item' );
102
103 /**
104 * Shim that hooks into `walker_nav_menu_start_el` and makes it so that the
105 * default walker which renders a menu will correctly render the HTML associated
106 * with any navigation menu item that has `type` set to `'block`'.
107 *
108 * Specifically, this shim makes it so that `Walker_Nav_Menu::start_el()`
109 * renders the `content` of a nav menu item when its `type` is `'block'`. When
110 * merged to Core, this functionality should exist in
111 * `Walker_Nav_Menu::start_el()`.
112 *
113 * This shim can be removed when the Gutenberg plugin requires a WordPress
114 * version that has the ticket below.
115 *
116 * @see https://core.trac.wordpress.org/ticket/50544
117 *
118 * @param string $item_output The menu item's starting HTML output.
119 * @param WP_Post $item Menu item data object.
120 * @param int $depth Depth of menu item. Used for padding.
121 * @param stdClass $args An object of wp_nav_menu() arguments.
122 *
123 * @return string The menu item's updated HTML output.
124 */
125 function gutenberg_output_block_nav_menu_item( $item_output, $item, $depth, $args ) {
126 if ( 'block' === $item->type ) {
127 $item_output = $args->before;
128 /** This filter is documented in wp-includes/post-template.php */
129 $item_output .= apply_filters( 'the_content', $item->content );
130 $item_output .= $args->after;
131 }
132
133 return $item_output;
134 }
135 add_filter( 'walker_nav_menu_start_el', 'gutenberg_output_block_nav_menu_item', 10, 4 );
136
137 /**
138 * Shim that prevents menu items with type `'block'` from being rendered in the
139 * frontend when the theme does not support block menus.
140 *
141 * Specifically, this shim makes it so that `wp_nav_menu()` will remove any menu
142 * items that have a `type` of `'block'` from `$sorted_menu_items`. When merged
143 * to Core, this functionality should exist in `wp_nav_menu()`.
144 *
145 * This shim can be removed when the Gutenberg plugin requires a WordPress
146 * version that has the ticket below.
147 *
148 * @see https://core.trac.wordpress.org/ticket/50544
149 *
150 * @param array $menu_items The menu items, sorted by each menu item's menu order.
151 *
152 * @return array Updated menu items, sorted by each menu item's menu order.
153 */
154 function gutenberg_remove_block_nav_menu_items( $menu_items ) {
155 // We should uncomment the line below when the block-nav-menus feature becomes stable.
156 // @see https://github.com/WordPress/gutenberg/issues/34265.
157 /*if ( current_theme_supports( 'block-nav-menus' ) ) {*/
158 if ( false ) {
159 return $menu_items;
160 }
161
162 return array_filter(
163 $menu_items,
164 function( $menu_item ) {
165 return 'block' !== $menu_item->type;
166 }
167 );
168 }
169 add_filter( 'wp_nav_menu_objects', 'gutenberg_remove_block_nav_menu_items', 10 );
170
171 /**
172 * Recursively converts a list of menu items into a list of blocks. This is a
173 * helper function used by `gutenberg_output_block_nav_menu()`.
174 *
175 * Transformation depends on the menu item type. Link menu items are turned into
176 * a `core/navigation-link` block. Block menu items are simply parsed.
177 *
178 * @param array $menu_items The menu items to convert, sorted by each menu item's menu order.
179 * @param array $menu_items_by_parent_id All menu items, indexed by their parent's ID.
180
181 * @return array Updated menu items, sorted by each menu item's menu order.
182 */
183 function gutenberg_convert_menu_items_to_blocks(
184 $menu_items,
185 &$menu_items_by_parent_id
186 ) {
187 if ( empty( $menu_items ) ) {
188 return array();
189 }
190
191 $blocks = array();
192
193 foreach ( $menu_items as $menu_item ) {
194 if ( 'block' === $menu_item->type ) {
195 $parsed_blocks = parse_blocks( $menu_item->content );
196
197 if ( count( $parsed_blocks ) ) {
198 $block = $parsed_blocks[0];
199 } else {
200 $block = array(
201 'blockName' => 'core/freeform',
202 'attrs' => array(
203 'originalContent' => $menu_item->content,
204 ),
205 );
206 }
207 } else {
208 $block = array(
209 'blockName' => 'core/navigation-link',
210 'attrs' => array(
211 'label' => $menu_item->title,
212 'url' => $menu_item->url,
213 ),
214 );
215 }
216
217 $block['innerBlocks'] = gutenberg_convert_menu_items_to_blocks(
218 isset( $menu_items_by_parent_id[ $menu_item->ID ] )
219 ? $menu_items_by_parent_id[ $menu_item->ID ]
220 : array(),
221 $menu_items_by_parent_id
222 );
223
224 $blocks[] = $block;
225 }
226
227 return $blocks;
228 }
229
230 /**
231 * Shim that causes `wp_nav_menu()` to output a Navigation block instead of a
232 * nav menu when the theme supports block menus. The Navigation block is
233 * constructed by transforming the stored tree of menu items into a tree of
234 * blocks.
235 *
236 * Specifically, this shim makes it so that `wp_nav_menu()` returns early when
237 * the theme supports block menus. When merged to Core, this functionality
238 * should exist in `wp_nav_menu()` after `$sorted_menu_items` is set. The
239 * duplicated code (marked using BEGIN and END) can be deleted.
240 *
241 * This shim can be removed when the Gutenberg plugin requires a WordPress
242 * version that has the ticket below.
243 *
244 * @see https://core.trac.wordpress.org/ticket/50544
245 *
246 * @param string|null $output Nav menu output to short-circuit with. Default null.
247 * @param stdClass $args An object containing wp_nav_menu() arguments.
248 *
249 * @return string|null Nav menu output to short-circuit with.
250 */
251 function gutenberg_output_block_nav_menu( $output, $args ) {
252 // We should uncomment the line below when the block-nav-menus feature becomes stable.
253 // @see https://github.com/WordPress/gutenberg/issues/34265.
254 /*if ( ! current_theme_supports( 'block-nav-menus' ) ) {*/
255 if ( true ) {
256 return null;
257 }
258
259 // BEGIN: Code that already exists in wp_nav_menu().
260
261 // Get the nav menu based on the requested menu.
262 $menu = wp_get_nav_menu_object( $args->menu );
263
264 // Get the nav menu based on the theme_location.
265 $locations = get_nav_menu_locations();
266 if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
267 $menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
268 }
269
270 // Get the first menu that has items if we still can't find a menu.
271 if ( ! $menu && ! $args->theme_location ) {
272 $menus = wp_get_nav_menus();
273 foreach ( $menus as $menu_maybe ) {
274 $menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
275 if ( $menu_items ) {
276 $menu = $menu_maybe;
277 break;
278 }
279 }
280 }
281
282 if ( empty( $args->menu ) ) {
283 $args->menu = $menu;
284 }
285
286 // If the menu exists, get its items.
287 if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
288 $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
289 }
290
291 // Set up the $menu_item variables.
292 _wp_menu_item_classes_by_context( $menu_items );
293
294 $sorted_menu_items = array();
295 foreach ( (array) $menu_items as $menu_item ) {
296 $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
297 }
298
299 unset( $menu_items, $menu_item );
300
301 // END: Code that already exists in wp_nav_menu().
302
303 $menu_items_by_parent_id = array();
304 foreach ( $sorted_menu_items as $menu_item ) {
305 $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
306 }
307
308 $block_attributes = array();
309 if ( isset( $args->block_attributes ) ) {
310 $block_attributes = $args->block_attributes;
311 }
312
313 $navigation_block = array(
314 'blockName' => 'core/navigation',
315 'attrs' => $block_attributes,
316 'innerBlocks' => gutenberg_convert_menu_items_to_blocks(
317 isset( $menu_items_by_parent_id[0] )
318 ? $menu_items_by_parent_id[0]
319 : array(),
320 $menu_items_by_parent_id
321 ),
322 );
323
324 return render_block( $navigation_block );
325 }
326 add_filter( 'pre_wp_nav_menu', 'gutenberg_output_block_nav_menu', 10, 2 );
327
328 /**
329 * Shim that makes nav-menus.php nicely display a menu item with its `type` set to
330 * `'block'`.
331 *
332 * Specifically, this shim makes it so that `Walker_Nav_Menu_Edit::start_el()`
333 * outputs extra form fields. When merged to Core, this markup should exist in
334 * `Walker_Nav_Menu_Edit::start_el()`.
335 *
336 * This shim can be removed when the Gutenberg plugin requires a WordPress
337 * version that has the ticket below.
338 *
339 * @see https://core.trac.wordpress.org/ticket/50544
340 *
341 * @param int $item_id Menu item ID.
342 * @param WP_Post $item Menu item data object.
343 */
344 function gutenberg_output_block_menu_item_custom_fields( $item_id, $item ) {
345 if ( 'block' === $item->type ) {
346 ?>
347 <p class="field-content description description-wide">
348 <label for="edit-menu-item-content-<?php echo $item_id; ?>">
349 <?php _e( 'Content', 'gutenberg' ); ?><br />
350 <textarea id="edit-menu-item-content-<?php echo $item_id; ?>" class="widefat" rows="3" cols="20" name="menu-item-content[<?php echo $item_id; ?>]" readonly><?php echo esc_textarea( trim( $item->content ) ); ?></textarea>
351 </label>
352 </p>
353 <?php
354 }
355 }
356 add_action( 'wp_nav_menu_item_custom_fields', 'gutenberg_output_block_menu_item_custom_fields', 10, 2 );
357
358 /**
359 * Shim that adds extra styling to nav-menus.php. This lets us style menu items
360 * that have a `type` set to `'block'`. When merged to Core, this CSS should be
361 * moved to nav-menus.css.
362 *
363 * This shim can be removed when the Gutenberg plugin requires a WordPress
364 * version that has the ticket below.
365 *
366 * @see https://core.trac.wordpress.org/ticket/50544
367 *
368 * @param string $hook The current admin page.
369 */
370 function gutenberg_add_block_menu_item_styles_to_nav_menus( $hook ) {
371 if ( 'nav-menus.php' === $hook ) {
372 $css = <<<CSS
373 /**
374 * HACK: We're hiding the description field using CSS because this
375 * cannot be done using a filter. When merged to Core, we should
376 * actually remove the field from
377 * `Walker_Nav_Menu_Edit::start_el()`.
378 */
379 .menu-item-block .description:not(.field-content) {
380 display: none;
381 }
382 CSS;
383 wp_add_inline_style( 'nav-menus', $css );
384 }
385 }
386 add_action( 'admin_enqueue_scripts', 'gutenberg_add_block_menu_item_styles_to_nav_menus' );
387
388
389 /**
390 * Registers block editor 'wp_navigation' post type.
391 */
392 function gutenberg_register_navigation_post_type() {
393 $labels = array(
394 'name' => __( 'Navigation Menus', 'gutenberg' ),
395 'singular_name' => __( 'Navigation Menu', 'gutenberg' ),
396 'menu_name' => _x( 'Navigation Menus', 'Admin Menu text', 'gutenberg' ),
397 'add_new' => _x( 'Add New', 'Navigation Menu', 'gutenberg' ),
398 'add_new_item' => __( 'Add New Navigation Menu', 'gutenberg' ),
399 'new_item' => __( 'New Navigation Menu', 'gutenberg' ),
400 'edit_item' => __( 'Edit Navigation Menu', 'gutenberg' ),
401 'view_item' => __( 'View Navigation Menu', 'gutenberg' ),
402 'all_items' => __( 'All Navigation Menus', 'gutenberg' ),
403 'search_items' => __( 'Search Navigation Menus', 'gutenberg' ),
404 'parent_item_colon' => __( 'Parent Navigation Menu:', 'gutenberg' ),
405 'not_found' => __( 'No Navigation Menu found.', 'gutenberg' ),
406 'not_found_in_trash' => __( 'No Navigation Menu found in Trash.', 'gutenberg' ),
407 'archives' => __( 'Navigation Menu archives', 'gutenberg' ),
408 'insert_into_item' => __( 'Insert into Navigation Menu', 'gutenberg' ),
409 'uploaded_to_this_item' => __( 'Uploaded to this Navigation Menu', 'gutenberg' ),
410 // Some of these are a bit weird, what are they for?
411 'filter_items_list' => __( 'Filter Navigation Menu list', 'gutenberg' ),
412 'items_list_navigation' => __( 'Navigation Menus list navigation', 'gutenberg' ),
413 'items_list' => __( 'Navigation Menus list', 'gutenberg' ),
414 );
415
416 $args = array(
417 'labels' => $labels,
418 'description' => __( 'Navigation menus.', 'gutenberg' ),
419 'public' => false,
420 'has_archive' => false,
421 // We should disable UI for non-FSE themes.
422 'show_ui' => gutenberg_is_fse_theme(),
423 'show_in_menu' => 'themes.php',
424 'show_in_admin_bar' => false,
425 'show_in_rest' => true,
426 'map_meta_cap' => true,
427 'rest_base' => 'navigation',
428 'rest_controller_class' => WP_REST_Posts_Controller::class,
429 'supports' => array(
430 'title',
431 'editor',
432 'revisions',
433 ),
434 );
435
436 register_post_type( 'wp_navigation', $args );
437 }
438 add_action( 'init', 'gutenberg_register_navigation_post_type' );
439
440 /**
441 * Disable "Post Attributes" for wp_navigation post type.
442 *
443 * The attributes are also conditionally enabled when a site has custom templates.
444 * Block Theme templates can be available for every post type.
445 */
446 add_filter( 'theme_wp_navigation_templates', '__return_empty_array' );
447
448 /**
449 * Disable block editor for wp_navigation type posts so they can be managed via the UI.
450 *
451 * @param bool $value Whether the CPT supports block editor or not.
452 * @param string $post_type Post type.
453 *
454 * @return bool
455 */
456 function gutenberg_disable_block_editor_for_navigation_post_type( $value, $post_type ) {
457 if ( 'wp_navigation' === $post_type ) {
458 return false;
459 }
460
461 return $value;
462 }
463
464 add_filter( 'use_block_editor_for_post_type', 'gutenberg_disable_block_editor_for_navigation_post_type', 10, 2 );
465
466 /**
467 * This callback disables the content editor for wp_navigation type posts.
468 * Content editor cannot handle wp_navigation type posts correctly.
469 * We cannot disable the "editor" feature in the wp_navigation's CPT definition
470 * because it disables the ability to save navigation blocks via REST API.
471 *
472 * @param WP_Post $post An instance of WP_Post class.
473 */
474 function gutenberg_disable_content_editor_for_navigation_post_type( $post ) {
475 $post_type = get_post_type( $post );
476 if ( 'wp_navigation' !== $post_type ) {
477 return;
478 }
479
480 remove_post_type_support( $post_type, 'editor' );
481 }
482
483 add_action( 'edit_form_after_title', 'gutenberg_disable_content_editor_for_navigation_post_type', 10, 1 );
484
485 /**
486 * This callback enables content editor for wp_navigation type posts.
487 * We need to enable it back because we disable it to hide
488 * the content editor for wp_navigation type posts.
489 *
490 * @see gutenberg_disable_content_editor_for_navigation_post_type
491 *
492 * @param WP_Post $post An instance of WP_Post class.
493 */
494 function gutenberg_enable_content_editor_for_navigation_post_type( $post ) {
495 $post_type = get_post_type( $post );
496 if ( 'wp_navigation' !== $post_type ) {
497 return;
498 }
499
500 add_post_type_support( $post_type, 'editor' );
501 }
502
503 add_action( 'edit_form_after_editor', 'gutenberg_enable_content_editor_for_navigation_post_type', 10, 1 );
504
505 /**
506 * Rename the menu title from "All Navigation Menus" to "Navigation Menus".
507 */
508 function gutenberg_rename_navigation_menus_admin_menu_entry() {
509 global $submenu;
510 if ( ! isset( $submenu['themes.php'] ) ) {
511 return;
512 }
513
514 $post_type = get_post_type_object( 'wp_navigation' );
515 if ( ! $post_type ) {
516 return;
517 }
518
519 $menu_title_index = 0;
520 foreach ( $submenu['themes.php'] as $key => $menu_item ) {
521 if ( $post_type->labels->all_items === $menu_item[ $menu_title_index ] ) {
522 $submenu['themes.php'][ $key ][ $menu_title_index ] = $post_type->labels->menu_name; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
523 return;
524 }
525 }
526 }
527
528 add_action( 'admin_menu', 'gutenberg_rename_navigation_menus_admin_menu_entry' );
529
530 /**
531 * Registers the navigation areas supported by the current theme. The expected
532 * shape of the argument is:
533 * array(
534 * 'primary' => 'Primary',
535 * 'secondary' => 'Secondary',
536 * 'tertiary' => 'Tertiary',
537 * )
538 *
539 * @param array $new_areas Supported navigation areas.
540 */
541 function gutenberg_register_navigation_areas( $new_areas ) {
542 global $gutenberg_navigation_areas;
543 $gutenberg_navigation_areas = $new_areas;
544 }
545
546 // Register the default navigation areas.
547 gutenberg_register_navigation_areas(
548 array(
549 'primary' => 'Primary',
550 'secondary' => 'Secondary',
551 'tertiary' => 'Tertiary',
552 )
553 );
554
555 /**
556 * Returns the available navigation areas.
557 *
558 * @return array Registered navigation areas.
559 */
560 function gutenberg_get_navigation_areas() {
561 global $gutenberg_navigation_areas;
562 return $gutenberg_navigation_areas;
563 }
564
565 /**
566 * Returns the API paths to preload to make the navigation area block load fast.
567 *
568 * @return array A list of paths.
569 */
570 function gutenberg_get_navigation_areas_paths_to_preload() {
571 $areas = get_option( 'fse_navigation_areas', array() );
572 $active_areas = array_intersect_key( $areas, gutenberg_get_navigation_areas() );
573 $paths = array(
574 '/__experimental/block-navigation-areas?context=edit',
575 );
576 foreach ( $active_areas as $post_id ) {
577 if ( 0 !== $post_id ) {
578 $paths[] = "/wp/v2/navigation/$post_id?context=edit";
579 }
580 }
581 return $paths;
582 }
583
584 /**
585 * Migrates classic menus to block-based menus on theme switch.
586 *
587 * @param string $new_name Name of the new theme.
588 * @param WP_Theme $new_theme New theme.
589 * @param WP_Theme $old_theme Old theme.
590 * @see switch_theme WordPress action.
591 */
592 function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_theme ) {
593 // Do nothing when switching to a theme that does not support site editor.
594 if ( ! gutenberg_experimental_is_site_editor_available() ) {
595 return;
596 }
597
598 // get_nav_menu_locations() calls get_theme_mod() which depends on the stylesheet option.
599 // At the same time, switch_theme runs only after the stylesheet option was updated to $new_theme.
600 // To retrieve theme mods of the old theme, the getter is hooked to get_option( 'stylesheet' ) so that we
601 // get the old theme, which causes the get_nav_menu_locations to get the locations of the old theme.
602 $get_old_theme_stylesheet = function() use ( $old_theme ) {
603 return $old_theme->get_stylesheet();
604 };
605 add_filter( 'option_stylesheet', $get_old_theme_stylesheet );
606
607 $locations = get_nav_menu_locations();
608 $area_mapping = get_option( 'fse_navigation_areas', array() );
609
610 foreach ( $locations as $location_name => $menu_id ) {
611 // Get the menu from the location, skipping if there is no
612 // menu or there was an error.
613 $menu = wp_get_nav_menu_object( $menu_id );
614 if ( ! $menu || is_wp_error( $menu ) ) {
615 continue;
616 }
617
618 $menu_items = gutenberg_global_get_menu_items_at_location( $location_name );
619 if ( empty( $menu_items ) ) {
620 continue;
621 }
622
623 $post_name = 'classic_menu_' . $menu_id;
624 $post_status = 'publish';
625
626 // Get or create to avoid creating too many wp_navigation posts.
627 $query = new WP_Query;
628 $matching_posts = $query->query(
629 array(
630 'name' => $post_name,
631 'post_status' => $post_status,
632 'post_type' => 'wp_navigation',
633 'posts_per_page' => 1,
634 )
635 );
636
637 if ( count( $matching_posts ) ) {
638 $navigation_post_id = $matching_posts[0]->ID;
639 } else {
640 $menu_items_by_parent_id = gutenberg_global_sort_menu_items_by_parent_id( $menu_items );
641 $parsed_blocks = gutenberg_global_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
642 $post_data = array(
643 'post_type' => 'wp_navigation',
644 'post_title' => sprintf(
645 /* translators: %s: the name of the menu, e.g. "Main Menu". */
646 __( 'Classic menu: %s', 'gutenberg' ),
647 $menu->name
648 ),
649 'post_name' => $post_name,
650 'post_content' => serialize_blocks( $parsed_blocks ),
651 'post_status' => $post_status,
652 );
653 $navigation_post_id = wp_insert_post( $post_data );
654 }
655
656 $area_mapping[ $location_name ] = $navigation_post_id;
657 }
658 remove_filter( 'option_stylesheet', $get_old_theme_stylesheet );
659
660 update_option( 'fse_navigation_areas', $area_mapping );
661 }
662
663 add_action( 'switch_theme', 'gutenberg_migrate_nav_on_theme_switch', 200, 3 );
664
665 // The functions below are copied over from packages/block-library/src/navigation/index.php
666 // Let's figure out a better way of managing these global PHP dependencies.
667
668 /**
669 * Returns the menu items for a WordPress menu location.
670 *
671 * @param string $location The menu location.
672 * @return array Menu items for the location.
673 */
674 function gutenberg_global_get_menu_items_at_location( $location ) {
675 if ( empty( $location ) ) {
676 return;
677 }
678
679 // Build menu data. The following approximates the code in
680 // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.
681
682 // Find the location in the list of locations, returning early if the
683 // location can't be found.
684 $locations = get_nav_menu_locations();
685 if ( ! isset( $locations[ $location ] ) ) {
686 return;
687 }
688
689 // Get the menu from the location, returning early if there is no
690 // menu or there was an error.
691 $menu = wp_get_nav_menu_object( $locations[ $location ] );
692 if ( ! $menu || is_wp_error( $menu ) ) {
693 return;
694 }
695
696 $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
697 _wp_menu_item_classes_by_context( $menu_items );
698
699 return $menu_items;
700 }
701
702
703 /**
704 * Sorts a standard array of menu items into a nested structure keyed by the
705 * id of the parent menu.
706 *
707 * @param array $menu_items Menu items to sort.
708 * @return array An array keyed by the id of the parent menu where each element
709 * is an array of menu items that belong to that parent.
710 */
711 function gutenberg_global_sort_menu_items_by_parent_id( $menu_items ) {
712 $sorted_menu_items = array();
713 foreach ( (array) $menu_items as $menu_item ) {
714 $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
715 }
716 unset( $menu_items, $menu_item );
717
718 $menu_items_by_parent_id = array();
719 foreach ( $sorted_menu_items as $menu_item ) {
720 $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
721 }
722
723 return $menu_items_by_parent_id;
724 }
725
726 /**
727 * Turns menu item data into a nested array of parsed blocks
728 *
729 * @param array $menu_items An array of menu items that represent
730 * an individual level of a menu.
731 * @param array $menu_items_by_parent_id An array keyed by the id of the
732 * parent menu where each element is an
733 * array of menu items that belong to
734 * that parent.
735 * @return array An array of parsed block data.
736 */
737 function gutenberg_global_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
738 if ( empty( $menu_items ) ) {
739 return array();
740 }
741
742 $blocks = array();
743
744 foreach ( $menu_items as $menu_item ) {
745 $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
746 $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
747 $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
748 $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
749 $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
750
751 $block = array(
752 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link',
753 'attrs' => array(
754 'className' => $class_name,
755 'description' => $menu_item->description,
756 'id' => $id,
757 'kind' => $kind,
758 'label' => $menu_item->title,
759 'opensInNewTab' => $opens_in_new_tab,
760 'rel' => $rel,
761 'title' => $menu_item->attr_title,
762 'type' => $menu_item->object,
763 'url' => $menu_item->url,
764 ),
765 );
766
767 $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] )
768 ? gutenberg_global_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id )
769 : array();
770 $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] );
771
772 $blocks[] = $block;
773 }
774
775 return $blocks;
776 }
777