PluginProbe
Gutenberg / 13.5.1
Gutenberg v13.5.1
24.0.0 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 All 403 releases
gutenberg / build / block-library / blocks / navigation.php

navigation.php in Gutenberg 13.5.1, at build/block-library/blocks/navigation.php

696 lines 25.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/navigation` block.
4 *
5 * @package WordPress
6 */
7
8 // These functions are used for the __unstableLocation feature and only active
9 // when the gutenberg plugin is active.
10 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
11 /**
12 * Returns the menu items for a WordPress menu location.
13 *
14 * @param string $location The menu location.
15 * @return array Menu items for the location.
16 */
17 function gutenberg_block_core_navigation_get_menu_items_at_location( $location ) {
18 if ( empty( $location ) ) {
19 return;
20 }
21
22 // Build menu data. The following approximates the code in
23 // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.
24
25 // Find the location in the list of locations, returning early if the
26 // location can't be found.
27 $locations = get_nav_menu_locations();
28 if ( ! isset( $locations[ $location ] ) ) {
29 return;
30 }
31
32 // Get the menu from the location, returning early if there is no
33 // menu or there was an error.
34 $menu = wp_get_nav_menu_object( $locations[ $location ] );
35 if ( ! $menu || is_wp_error( $menu ) ) {
36 return;
37 }
38
39 $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
40 _wp_menu_item_classes_by_context( $menu_items );
41
42 return $menu_items;
43 }
44
45
46 /**
47 * Sorts a standard array of menu items into a nested structure keyed by the
48 * id of the parent menu.
49 *
50 * @param array $menu_items Menu items to sort.
51 * @return array An array keyed by the id of the parent menu where each element
52 * is an array of menu items that belong to that parent.
53 */
54 function gutenberg_block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) {
55 $sorted_menu_items = array();
56 foreach ( (array) $menu_items as $menu_item ) {
57 $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
58 }
59 unset( $menu_items, $menu_item );
60
61 $menu_items_by_parent_id = array();
62 foreach ( $sorted_menu_items as $menu_item ) {
63 $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
64 }
65
66 return $menu_items_by_parent_id;
67 }
68
69 /**
70 * Turns menu item data into a nested array of parsed blocks
71 *
72 * @param array $menu_items An array of menu items that represent
73 * an individual level of a menu.
74 * @param array $menu_items_by_parent_id An array keyed by the id of the
75 * parent menu where each element is an
76 * array of menu items that belong to
77 * that parent.
78 * @return array An array of parsed block data.
79 */
80 function gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
81 if ( empty( $menu_items ) ) {
82 return array();
83 }
84
85 $blocks = array();
86
87 foreach ( $menu_items as $menu_item ) {
88 $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
89 $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
90 $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
91 $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
92 $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
93
94 $block = array(
95 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link',
96 'attrs' => array(
97 'className' => $class_name,
98 'description' => $menu_item->description,
99 'id' => $id,
100 'kind' => $kind,
101 'label' => $menu_item->title,
102 'opensInNewTab' => $opens_in_new_tab,
103 'rel' => $rel,
104 'title' => $menu_item->attr_title,
105 'type' => $menu_item->object,
106 'url' => $menu_item->url,
107 ),
108 );
109
110 $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] )
111 ? gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id )
112 : array();
113 $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] );
114
115 $blocks[] = $block;
116 }
117
118 return $blocks;
119 }
120 }
121
122 /**
123 * Build an array with CSS classes and inline styles defining the colors
124 * which will be applied to the navigation markup in the front-end.
125 *
126 * @param array $attributes Navigation block attributes.
127 *
128 * @return array Colors CSS classes and inline styles.
129 */
130 function gutenberg_block_core_navigation_build_css_colors( $attributes ) {
131 $colors = array(
132 'css_classes' => array(),
133 'inline_styles' => '',
134 'overlay_css_classes' => array(),
135 'overlay_inline_styles' => '',
136 );
137
138 // Text color.
139 $has_named_text_color = array_key_exists( 'textColor', $attributes );
140 $has_custom_text_color = array_key_exists( 'customTextColor', $attributes );
141
142 // If has text color.
143 if ( $has_custom_text_color || $has_named_text_color ) {
144 // Add has-text-color class.
145 $colors['css_classes'][] = 'has-text-color';
146 }
147
148 if ( $has_named_text_color ) {
149 // Add the color class.
150 $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] );
151 } elseif ( $has_custom_text_color ) {
152 // Add the custom color inline style.
153 $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] );
154 }
155
156 // Background color.
157 $has_named_background_color = array_key_exists( 'backgroundColor', $attributes );
158 $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes );
159
160 // If has background color.
161 if ( $has_custom_background_color || $has_named_background_color ) {
162 // Add has-background class.
163 $colors['css_classes'][] = 'has-background';
164 }
165
166 if ( $has_named_background_color ) {
167 // Add the background-color class.
168 $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
169 } elseif ( $has_custom_background_color ) {
170 // Add the custom background-color inline style.
171 $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] );
172 }
173
174 // Overlay text color.
175 $has_named_overlay_text_color = array_key_exists( 'overlayTextColor', $attributes );
176 $has_custom_overlay_text_color = array_key_exists( 'customOverlayTextColor', $attributes );
177
178 // If has overlay text color.
179 if ( $has_custom_overlay_text_color || $has_named_overlay_text_color ) {
180 // Add has-text-color class.
181 $colors['overlay_css_classes'][] = 'has-text-color';
182 }
183
184 if ( $has_named_overlay_text_color ) {
185 // Add the overlay color class.
186 $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', $attributes['overlayTextColor'] );
187 } elseif ( $has_custom_overlay_text_color ) {
188 // Add the custom overlay color inline style.
189 $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $attributes['customOverlayTextColor'] );
190 }
191
192 // Overlay background color.
193 $has_named_overlay_background_color = array_key_exists( 'overlayBackgroundColor', $attributes );
194 $has_custom_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $attributes );
195
196 // If has overlay background color.
197 if ( $has_custom_overlay_background_color || $has_named_overlay_background_color ) {
198 // Add has-background class.
199 $colors['overlay_css_classes'][] = 'has-background';
200 }
201
202 if ( $has_named_overlay_background_color ) {
203 // Add the overlay background-color class.
204 $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', $attributes['overlayBackgroundColor'] );
205 } elseif ( $has_custom_overlay_background_color ) {
206 // Add the custom overlay background-color inline style.
207 $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customOverlayBackgroundColor'] );
208 }
209
210 return $colors;
211 }
212
213 /**
214 * Build an array with CSS classes and inline styles defining the font sizes
215 * which will be applied to the navigation markup in the front-end.
216 *
217 * @param array $attributes Navigation block attributes.
218 *
219 * @return array Font size CSS classes and inline styles.
220 */
221 function gutenberg_block_core_navigation_build_css_font_sizes( $attributes ) {
222 // CSS classes.
223 $font_sizes = array(
224 'css_classes' => array(),
225 'inline_styles' => '',
226 );
227
228 $has_named_font_size = array_key_exists( 'fontSize', $attributes );
229 $has_custom_font_size = array_key_exists( 'customFontSize', $attributes );
230
231 if ( $has_named_font_size ) {
232 // Add the font size class.
233 $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] );
234 } elseif ( $has_custom_font_size ) {
235 // Add the custom font size inline style.
236 $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] );
237 }
238
239 return $font_sizes;
240 }
241
242 /**
243 * Returns the top-level submenu SVG chevron icon.
244 *
245 * @return string
246 */
247 function gutenberg_block_core_navigation_render_submenu_icon() {
248 return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"></path></svg>';
249 }
250
251
252 /**
253 * Finds the first non-empty `wp_navigation` Post.
254 *
255 * @return WP_Post|null the first non-empty Navigation or null.
256 */
257 function gutenberg_block_core_navigation_get_first_non_empty_navigation() {
258 // Order and orderby args set to mirror those in `wp_get_nav_menus`
259 // see:
260 // - https://github.com/WordPress/wordpress-develop/blob/ba943e113d3b31b121f77a2d30aebe14b047c69d/src/wp-includes/nav-menu.php#L613-L619.
261 // - https://developer.wordpress.org/reference/classes/wp_query/#order-orderby-parameters.
262 $parsed_args = array(
263 'post_type' => 'wp_navigation',
264 'no_found_rows' => true,
265 'order' => 'ASC',
266 'orderby' => 'name',
267 'post_status' => 'publish',
268 'posts_per_page' => 20, // Try the first 20 posts.
269 );
270
271 $navigation_posts = new WP_Query( $parsed_args );
272 foreach ( $navigation_posts->posts as $navigation_post ) {
273 if ( has_blocks( $navigation_post ) ) {
274 return $navigation_post;
275 }
276 }
277
278 return null;
279 }
280
281 /**
282 * Filter out empty "null" blocks from the block list.
283 * 'parse_blocks' includes a null block with '\n\n' as the content when
284 * it encounters whitespace. This is not a bug but rather how the parser
285 * is designed.
286 *
287 * @param array $parsed_blocks the parsed blocks to be normalized.
288 * @return array the normalized parsed blocks.
289 */
290 function gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) {
291 $filtered = array_filter(
292 $parsed_blocks,
293 function( $block ) {
294 return isset( $block['blockName'] );
295 }
296 );
297
298 // Reset keys.
299 return array_values( $filtered );
300 }
301
302 /**
303 * Retrieves the appropriate fallback to be used on the front of the
304 * site when there is no menu assigned to the Nav block.
305 *
306 * This aims to mirror how the fallback mechanic for wp_nav_menu works.
307 * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information.
308 *
309 * @return array the array of blocks to be used as a fallback.
310 */
311 function gutenberg_block_core_navigation_get_fallback_blocks() {
312 $page_list_fallback = array(
313 array(
314 'blockName' => 'core/page-list',
315 'attrs' => array(
316 '__unstableMaxPages' => 4,
317 ),
318 ),
319 );
320
321 $registry = WP_Block_Type_Registry::get_instance();
322
323 // If `core/page-list` is not registered then return empty blocks.
324 $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array();
325
326 // Default to a list of Pages.
327
328 $navigation_post = gutenberg_block_core_navigation_get_first_non_empty_navigation();
329
330 // Prefer using the first non-empty Navigation as fallback if available.
331 if ( $navigation_post ) {
332 $maybe_fallback = gutenberg_block_core_navigation_filter_out_empty_blocks( parse_blocks( $navigation_post->post_content ) );
333
334 // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
335 // In this case default to the (Page List) fallback.
336 $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
337 }
338
339 /**
340 * Filters the fallback experience for the Navigation block.
341 *
342 * Returning a falsey value will opt out of the fallback and cause the block not to render.
343 * To customise the blocks provided return an array of blocks - these should be valid
344 * children of the `core/navigation` block.
345 *
346 * @since 5.9.0
347 *
348 * @param array[] default fallback blocks provided by the default block mechanic.
349 */
350 return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks );
351 }
352
353 /**
354 * Iterate through all inner blocks recursively and get navigation link block's post IDs.
355 *
356 * @param WP_Block_List $inner_blocks Block list class instance.
357 *
358 * @return array Array of post IDs.
359 */
360 function gutenberg_block_core_navigation_get_post_ids( $inner_blocks ) {
361 $post_ids = array_map( 'gutenberg_block_core_navigation_from_block_get_post_ids', iterator_to_array( $inner_blocks ) );
362 return array_unique( array_merge( ...$post_ids ) );
363 }
364
365 /**
366 * Get post IDs from a navigation link block instance.
367 *
368 * @param WP_Block $block Instance of a block.
369 *
370 * @return array Array of post IDs.
371 */
372 function gutenberg_block_core_navigation_from_block_get_post_ids( $block ) {
373 $post_ids = array();
374
375 if ( $block->inner_blocks ) {
376 $post_ids = gutenberg_block_core_navigation_get_post_ids( $block->inner_blocks );
377 }
378
379 if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) {
380 if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] ) {
381 $post_ids[] = $block->attributes['id'];
382 }
383 }
384
385 return $post_ids;
386 }
387
388 /**
389 * Renders the `core/navigation` block on server.
390 *
391 * @param array $attributes The block attributes.
392 * @param string $content The saved content.
393 * @param WP_Block $block The parsed block.
394 *
395 * @return string Returns the post content with the legacy widget added.
396 */
397 function gutenberg_render_block_core_navigation( $attributes, $content, $block ) {
398
399 static $seen_menu_names = array();
400
401 // Flag used to indicate whether the rendered output is considered to be
402 // a fallback (i.e. the block has no menu associated with it).
403 $is_fallback = false;
404
405 $nav_menu_name = '';
406
407 /**
408 * Deprecated:
409 * The rgbTextColor and rgbBackgroundColor attributes
410 * have been deprecated in favor of
411 * customTextColor and customBackgroundColor ones.
412 * Move the values from old attrs to the new ones.
413 */
414 if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
415 $attributes['customTextColor'] = $attributes['rgbTextColor'];
416 }
417
418 if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
419 $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
420 }
421
422 unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
423
424 /**
425 * This is for backwards compatibility after `isResponsive` attribute has been removed.
426 */
427 $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
428 $is_responsive_menu = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
429 $should_load_view_script = ! wp_script_is( 'wp-block-navigation-view' ) && ( $is_responsive_menu || $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] );
430 if ( $should_load_view_script ) {
431 wp_enqueue_script( 'wp-block-navigation-view' );
432 }
433
434 $should_load_modal_view_script = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'];
435 if ( $should_load_modal_view_script ) {
436 wp_enqueue_script( 'wp-block-navigation-view-modal' );
437 }
438
439 $inner_blocks = $block->inner_blocks;
440
441 // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
442 if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
443 $attributes['ref'] = $attributes['navigationMenuId'];
444 }
445
446 // If:
447 // - the gutenberg plugin is active
448 // - `__unstableLocation` is defined
449 // - we have menu items at the defined location
450 // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
451 // ...then create inner blocks from the classic menu assigned to that location.
452 if (
453 defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
454 array_key_exists( '__unstableLocation', $attributes ) &&
455 ! array_key_exists( 'ref', $attributes ) &&
456 ! empty( gutenberg_block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
457 ) {
458 $menu_items = gutenberg_block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] );
459 if ( empty( $menu_items ) ) {
460 return '';
461 }
462
463 $menu_items_by_parent_id = gutenberg_block_core_navigation_sort_menu_items_by_parent_id( $menu_items );
464 $parsed_blocks = gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
465 $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes );
466 }
467
468 // Load inner blocks from the navigation post.
469 if ( array_key_exists( 'ref', $attributes ) ) {
470 $navigation_post = get_post( $attributes['ref'] );
471 if ( ! isset( $navigation_post ) ) {
472 return '';
473 }
474
475 $nav_menu_name = $navigation_post->post_title;
476
477 if ( isset( $seen_menu_names[ $nav_menu_name ] ) ) {
478 ++$seen_menu_names[ $nav_menu_name ];
479 } else {
480 $seen_menu_names[ $nav_menu_name ] = 1;
481 }
482
483 $parsed_blocks = parse_blocks( $navigation_post->post_content );
484
485 // 'parse_blocks' includes a null block with '\n\n' as the content when
486 // it encounters whitespace. This code strips it.
487 $compacted_blocks = gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
488
489 // TODO - this uses the full navigation block attributes for the
490 // context which could be refined.
491 $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes );
492 }
493
494 // If there are no inner blocks then fallback to rendering an appropriate fallback.
495 if ( empty( $inner_blocks ) ) {
496 $is_fallback = true; // indicate we are rendering the fallback.
497
498 $fallback_blocks = gutenberg_block_core_navigation_get_fallback_blocks();
499
500 // Fallback my have been filtered so do basic test for validity.
501 if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
502 return '';
503 }
504
505 $inner_blocks = new WP_Block_List( $fallback_blocks, $attributes );
506
507 }
508
509 $layout_justification = array(
510 'left' => 'items-justified-left',
511 'right' => 'items-justified-right',
512 'center' => 'items-justified-center',
513 'space-between' => 'items-justified-space-between',
514 );
515
516 // Restore legacy classnames for submenu positioning.
517 $layout_class = '';
518 if ( isset( $attributes['layout']['justifyContent'] ) ) {
519 $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
520 }
521 if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
522 $layout_class .= ' is-vertical';
523 }
524
525 if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
526 $layout_class .= ' no-wrap';
527 }
528
529 // Manually add block support text decoration as CSS class.
530 $text_decoration = _wp_array_get( $attributes, array( 'style', 'typography', 'textDecoration' ), null );
531 $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
532
533 $colors = gutenberg_block_core_navigation_build_css_colors( $attributes );
534 $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes );
535 $classes = array_merge(
536 $colors['css_classes'],
537 $font_sizes['css_classes'],
538 $is_responsive_menu ? array( 'is-responsive' ) : array(),
539 $layout_class ? array( $layout_class ) : array(),
540 $is_fallback ? array( 'is-fallback' ) : array(),
541 $text_decoration ? array( $text_decoration_class ) : array()
542 );
543
544 $post_ids = gutenberg_block_core_navigation_get_post_ids( $inner_blocks );
545 if ( $post_ids ) {
546 _prime_post_caches( $post_ids, false, false );
547 }
548
549 $inner_blocks_html = '';
550 $is_list_open = false;
551 foreach ( $inner_blocks as $inner_block ) {
552 if ( ( 'core/navigation-link' === $inner_block->name || 'core/home-link' === $inner_block->name || 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name || 'core/navigation-submenu' === $inner_block->name ) && ! $is_list_open ) {
553 $is_list_open = true;
554 $inner_blocks_html .= '<ul class="wp-block-navigation__container">';
555 }
556 if ( 'core/navigation-link' !== $inner_block->name && 'core/home-link' !== $inner_block->name && 'core/site-title' !== $inner_block->name && 'core/site-logo' !== $inner_block->name && 'core/navigation-submenu' !== $inner_block->name && $is_list_open ) {
557 $is_list_open = false;
558 $inner_blocks_html .= '</ul>';
559 }
560 if ( 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name ) {
561 $inner_blocks_html .= '<li class="wp-block-navigation-item">' . $inner_block->render() . '</li>';
562 } else {
563 $inner_blocks_html .= $inner_block->render();
564 }
565 }
566
567 if ( $is_list_open ) {
568 $inner_blocks_html .= '</ul>';
569 }
570
571 $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
572
573 // If the menu name has been used previously then append an ID
574 // to the name to ensure uniqueness across a given post.
575 if ( isset( $seen_menu_names[ $nav_menu_name ] ) && $seen_menu_names[ $nav_menu_name ] > 1 ) {
576 $count = $seen_menu_names[ $nav_menu_name ];
577 $nav_menu_name = $nav_menu_name . ' ' . ( $count );
578 }
579
580 $wrapper_attributes = get_block_wrapper_attributes(
581 array(
582 'class' => implode( ' ', $classes ),
583 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'],
584 'aria-label' => $nav_menu_name,
585 )
586 );
587
588 $modal_unique_id = wp_unique_id( 'modal-' );
589
590 // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive,
591 // return early if they don't.
592 if ( ! $is_responsive_menu ) {
593 return sprintf(
594 '<nav %1$s>%2$s</nav>',
595 $wrapper_attributes,
596 $inner_blocks_html
597 );
598 }
599
600 $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
601
602 $responsive_container_classes = array(
603 'wp-block-navigation__responsive-container',
604 $is_hidden_by_default ? 'hidden-by-default' : '',
605 implode( ' ', $colors['overlay_css_classes'] ),
606 );
607 $open_button_classes = array(
608 'wp-block-navigation__responsive-container-open',
609 $is_hidden_by_default ? 'always-shown' : '',
610 );
611
612 $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="7.5" width="16" height="1.5" /><rect x="4" y="15" width="16" height="1.5" /></svg>';
613 $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
614 $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : 'Menu';
615
616 $responsive_container_markup = sprintf(
617 '<button aria-haspopup="true" aria-label="%3$s" class="%6$s" data-micromodal-trigger="%1$s">%9$s</button>
618 <div class="%5$s" style="%7$s" id="%1$s">
619 <div class="wp-block-navigation__responsive-close" tabindex="-1" data-micromodal-close>
620 <div class="wp-block-navigation__responsive-dialog" aria-label="%8$s">
621 <button aria-label="%4$s" data-micromodal-close class="wp-block-navigation__responsive-container-close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg></button>
622 <div class="wp-block-navigation__responsive-container-content" id="%1$s-content">
623 %2$s
624 </div>
625 </div>
626 </div>
627 </div>',
628 esc_attr( $modal_unique_id ),
629 $inner_blocks_html,
630 __( 'Open menu' ), // Open button label.
631 __( 'Close menu' ), // Close button label.
632 esc_attr( implode( ' ', $responsive_container_classes ) ),
633 esc_attr( implode( ' ', $open_button_classes ) ),
634 safecss_filter_attr( $colors['overlay_inline_styles'] ),
635 __( 'Menu' ),
636 $toggle_button_content
637 );
638
639 return sprintf(
640 '<nav %1$s>%2$s</nav>',
641 $wrapper_attributes,
642 $responsive_container_markup
643 );
644 }
645
646 /**
647 * Register the navigation block.
648 *
649 * @uses gutenberg_render_block_core_navigation()
650 * @throws WP_Error An WP_Error exception parsing the block definition.
651 */
652 function gutenberg_register_block_core_navigation() {
653 register_block_type_from_metadata(
654 __DIR__ . '/navigation',
655 array(
656 'render_callback' => 'gutenberg_render_block_core_navigation',
657 )
658 );
659 }
660
661 add_action( 'init', 'gutenberg_register_block_core_navigation', 20 );
662
663 /**
664 * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
665 *
666 * @param array $parsed_block The block being rendered.
667 *
668 * @return array The block being rendered without typographic presets.
669 */
670 function gutenberg_block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) {
671 if ( 'core/navigation' === $parsed_block['blockName'] ) {
672 $attribute_to_prefix_map = array(
673 'fontStyle' => 'var:preset|font-style|',
674 'fontWeight' => 'var:preset|font-weight|',
675 'textDecoration' => 'var:preset|text-decoration|',
676 'textTransform' => 'var:preset|text-transform|',
677 );
678 foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) {
679 if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) {
680 $prefix_len = strlen( $prefix );
681 $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ];
682 if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) {
683 $attribute_value = substr( $attribute_value, $prefix_len );
684 }
685 if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) {
686 $attribute_value = 'line-through';
687 }
688 }
689 }
690 }
691
692 return $parsed_block;
693 }
694
695 add_filter( 'render_block_data', 'gutenberg_block_core_navigation_typographic_presets_backcompatibility' );
696