PluginProbe
Gutenberg / 12.1.0
Gutenberg v12.1.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 7.4.0 All 402 releases
gutenberg / build / block-library / blocks / navigation.php

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

490 lines 18.4 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 /**
9 * Build an array with CSS classes and inline styles defining the colors
10 * which will be applied to the navigation markup in the front-end.
11 *
12 * @param array $attributes Navigation block attributes.
13 *
14 * @return array Colors CSS classes and inline styles.
15 */
16 function gutenberg_block_core_navigation_build_css_colors( $attributes ) {
17 $colors = array(
18 'css_classes' => array(),
19 'inline_styles' => '',
20 'overlay_css_classes' => array(),
21 'overlay_inline_styles' => '',
22 );
23
24 // Text color.
25 $has_named_text_color = array_key_exists( 'textColor', $attributes );
26 $has_custom_text_color = array_key_exists( 'customTextColor', $attributes );
27
28 // If has text color.
29 if ( $has_custom_text_color || $has_named_text_color ) {
30 // Add has-text-color class.
31 $colors['css_classes'][] = 'has-text-color';
32 }
33
34 if ( $has_named_text_color ) {
35 // Add the color class.
36 $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] );
37 } elseif ( $has_custom_text_color ) {
38 // Add the custom color inline style.
39 $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] );
40 }
41
42 // Background color.
43 $has_named_background_color = array_key_exists( 'backgroundColor', $attributes );
44 $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes );
45
46 // If has background color.
47 if ( $has_custom_background_color || $has_named_background_color ) {
48 // Add has-background class.
49 $colors['css_classes'][] = 'has-background';
50 }
51
52 if ( $has_named_background_color ) {
53 // Add the background-color class.
54 $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
55 } elseif ( $has_custom_background_color ) {
56 // Add the custom background-color inline style.
57 $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] );
58 }
59
60 // Overlay text color.
61 $has_named_overlay_text_color = array_key_exists( 'overlayTextColor', $attributes );
62 $has_custom_overlay_text_color = array_key_exists( 'customOverlayTextColor', $attributes );
63
64 // If has overlay text color.
65 if ( $has_custom_overlay_text_color || $has_named_overlay_text_color ) {
66 // Add has-text-color class.
67 $colors['overlay_css_classes'][] = 'has-text-color';
68 }
69
70 if ( $has_named_overlay_text_color ) {
71 // Add the overlay color class.
72 $colors['overlay_css_classes'][] = sprintf( 'has-%s-color', $attributes['overlayTextColor'] );
73 } elseif ( $has_custom_overlay_text_color ) {
74 // Add the custom overlay color inline style.
75 $colors['overlay_inline_styles'] .= sprintf( 'color: %s;', $attributes['customOverlayTextColor'] );
76 }
77
78 // Overlay background color.
79 $has_named_overlay_background_color = array_key_exists( 'overlayBackgroundColor', $attributes );
80 $has_custom_overlay_background_color = array_key_exists( 'customOverlayBackgroundColor', $attributes );
81
82 // If has overlay background color.
83 if ( $has_custom_overlay_background_color || $has_named_overlay_background_color ) {
84 // Add has-background class.
85 $colors['overlay_css_classes'][] = 'has-background';
86 }
87
88 if ( $has_named_overlay_background_color ) {
89 // Add the overlay background-color class.
90 $colors['overlay_css_classes'][] = sprintf( 'has-%s-background-color', $attributes['overlayBackgroundColor'] );
91 } elseif ( $has_custom_overlay_background_color ) {
92 // Add the custom overlay background-color inline style.
93 $colors['overlay_inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customOverlayBackgroundColor'] );
94 }
95
96 return $colors;
97 }
98
99 /**
100 * Build an array with CSS classes and inline styles defining the font sizes
101 * which will be applied to the navigation markup in the front-end.
102 *
103 * @param array $attributes Navigation block attributes.
104 *
105 * @return array Font size CSS classes and inline styles.
106 */
107 function gutenberg_block_core_navigation_build_css_font_sizes( $attributes ) {
108 // CSS classes.
109 $font_sizes = array(
110 'css_classes' => array(),
111 'inline_styles' => '',
112 );
113
114 $has_named_font_size = array_key_exists( 'fontSize', $attributes );
115 $has_custom_font_size = array_key_exists( 'customFontSize', $attributes );
116
117 if ( $has_named_font_size ) {
118 // Add the font size class.
119 $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] );
120 } elseif ( $has_custom_font_size ) {
121 // Add the custom font size inline style.
122 $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] );
123 }
124
125 return $font_sizes;
126 }
127
128 /**
129 * Returns the top-level submenu SVG chevron icon.
130 *
131 * @return string
132 */
133 function gutenberg_block_core_navigation_render_submenu_icon() {
134 return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" role="img" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"></path></svg>';
135 }
136
137
138 /**
139 * Finds the first non-empty `wp_navigation` Post.
140 *
141 * @return WP_Post|null the first non-empty Navigation or null.
142 */
143 function gutenberg_block_core_navigation_get_first_non_empty_navigation() {
144 // Order and orderby args set to mirror those in `wp_get_nav_menus`
145 // see:
146 // - https://github.com/WordPress/wordpress-develop/blob/ba943e113d3b31b121f77a2d30aebe14b047c69d/src/wp-includes/nav-menu.php#L613-L619.
147 // - https://developer.wordpress.org/reference/classes/wp_query/#order-orderby-parameters.
148 $parsed_args = array(
149 'post_type' => 'wp_navigation',
150 'no_found_rows' => true,
151 'order' => 'ASC',
152 'orderby' => 'name',
153 'post_status' => 'publish',
154 'posts_per_page' => 20, // Try the first 20 posts.
155 );
156
157 $navigation_posts = new WP_Query( $parsed_args );
158 foreach ( $navigation_posts->posts as $navigation_post ) {
159 if ( has_blocks( $navigation_post ) ) {
160 return $navigation_post;
161 }
162 }
163
164 return null;
165 }
166
167 /**
168 * Filter out empty "null" blocks from the block list.
169 * 'parse_blocks' includes a null block with '\n\n' as the content when
170 * it encounters whitespace. This is not a bug but rather how the parser
171 * is designed.
172 *
173 * @param array $parsed_blocks the parsed blocks to be normalized.
174 * @return array the normalized parsed blocks.
175 */
176 function gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) {
177 $filtered = array_filter(
178 $parsed_blocks,
179 function( $block ) {
180 return isset( $block['blockName'] );
181 }
182 );
183
184 // Reset keys.
185 return array_values( $filtered );
186 }
187
188 /**
189 * Retrieves the appropriate fallback to be used on the front of the
190 * site when there is no menu assigned to the Nav block.
191 *
192 * This aims to mirror how the fallback mechanic for wp_nav_menu works.
193 * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information.
194 *
195 * @return array the array of blocks to be used as a fallback.
196 */
197 function gutenberg_block_core_navigation_get_fallback_blocks() {
198 $page_list_fallback = array(
199 array(
200 'blockName' => 'core/page-list',
201 'attrs' => array(
202 '__unstableMaxPages' => 4,
203 ),
204 ),
205 );
206
207 $registry = WP_Block_Type_Registry::get_instance();
208
209 // If `core/page-list` is not registered then return empty blocks.
210 $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array();
211
212 // Default to a list of Pages.
213
214 $navigation_post = gutenberg_block_core_navigation_get_first_non_empty_navigation();
215
216 // Prefer using the first non-empty Navigation as fallback if available.
217 if ( $navigation_post ) {
218 $maybe_fallback = gutenberg_block_core_navigation_filter_out_empty_blocks( parse_blocks( $navigation_post->post_content ) );
219
220 // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks.
221 // In this case default to the (Page List) fallback.
222 $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks;
223 }
224
225 /**
226 * Filters the fallback experience for the Navigation block.
227 *
228 * Returning a falsey value will opt out of the fallback and cause the block not to render.
229 * To customise the blocks provided return an array of blocks - these should be valid
230 * children of the `core/navigation` block.
231 *
232 * @param array[] default fallback blocks provided by the default block mechanic.
233 */
234 return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks );
235 }
236
237 /**
238 * Renders the `core/navigation` block on server.
239 *
240 * @param array $attributes The block attributes.
241 * @param array $content The saved content.
242 * @param array $block The parsed block.
243 *
244 * @return string Returns the post content with the legacy widget added.
245 */
246 function gutenberg_render_block_core_navigation( $attributes, $content, $block ) {
247
248 // Flag used to indicate whether the rendered output is considered to be
249 // a fallback (i.e. the block has no menu associated with it).
250 $is_fallback = false;
251
252 /**
253 * Deprecated:
254 * The rgbTextColor and rgbBackgroundColor attributes
255 * have been deprecated in favor of
256 * customTextColor and customBackgroundColor ones.
257 * Move the values from old attrs to the new ones.
258 */
259 if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
260 $attributes['customTextColor'] = $attributes['rgbTextColor'];
261 }
262
263 if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
264 $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
265 }
266
267 unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
268
269 /**
270 * This is for backwards compatibility after `isResponsive` attribute has been removed.
271 */
272 $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
273 $is_responsive_menu = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
274 $should_load_view_script = ! wp_script_is( 'wp-block-navigation-view' ) && ( $is_responsive_menu || $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] );
275 if ( $should_load_view_script ) {
276 wp_enqueue_script( 'wp-block-navigation-view' );
277 }
278
279 $inner_blocks = $block->inner_blocks;
280
281 // If `__unstableLocation` is defined and:
282 // - we have menu items at the defined location
283 // - we don't have a relationship to a `wp_navigation` Post (via `navigationMenuId`).
284 // ...then create inner blocks from the classic menu assigned to that location.
285 if (
286 array_key_exists( '__unstableLocation', $attributes ) &&
287 ! array_key_exists( 'navigationMenuId', $attributes ) &&
288 ! empty( gutenberg_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
289 ) {
290 $menu_items = gutenberg_get_menu_items_at_location( $attributes['__unstableLocation'] );
291 if ( empty( $menu_items ) ) {
292 return '';
293 }
294
295 $menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items );
296 $parsed_blocks = gutenberg_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
297 $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes );
298 }
299
300 // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
301 if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
302 $attributes['ref'] = $attributes['navigationMenuId'];
303 }
304 // Load inner blocks from the navigation post.
305 if ( array_key_exists( 'ref', $attributes ) ) {
306 $navigation_post = get_post( $attributes['ref'] );
307 if ( ! isset( $navigation_post ) ) {
308 return '';
309 }
310
311 $parsed_blocks = parse_blocks( $navigation_post->post_content );
312
313 // 'parse_blocks' includes a null block with '\n\n' as the content when
314 // it encounters whitespace. This code strips it.
315 $compacted_blocks = gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
316
317 // TODO - this uses the full navigation block attributes for the
318 // context which could be refined.
319 $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes );
320 }
321
322 // If there are no inner blocks then fallback to rendering an appropriate fallback.
323 if ( empty( $inner_blocks ) ) {
324 $is_fallback = true; // indicate we are rendering the fallback.
325
326 $fallback_blocks = gutenberg_block_core_navigation_get_fallback_blocks();
327
328 // Fallback my have been filtered so do basic test for validity.
329 if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
330 return '';
331 }
332
333 $inner_blocks = new WP_Block_List( $fallback_blocks, $attributes );
334
335 }
336
337 // Restore legacy classnames for submenu positioning.
338 $layout_class = '';
339 if ( isset( $attributes['layout']['justifyContent'] ) ) {
340 if ( 'right' === $attributes['layout']['justifyContent'] ) {
341 $layout_class .= 'items-justified-right';
342 } elseif ( 'space-between' === $attributes['layout']['justifyContent'] ) {
343 $layout_class .= 'items-justified-space-between';
344 }
345 }
346
347 $colors = gutenberg_block_core_navigation_build_css_colors( $attributes );
348 $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes );
349 $classes = array_merge(
350 $colors['css_classes'],
351 $font_sizes['css_classes'],
352 $is_responsive_menu ? array( 'is-responsive' ) : array(),
353 $layout_class ? array( $layout_class ) : array(),
354 $is_fallback ? array( 'is-fallback' ) : array()
355 );
356
357 $inner_blocks_html = '';
358 $is_list_open = false;
359 foreach ( $inner_blocks as $inner_block ) {
360 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 ) {
361 $is_list_open = true;
362 $inner_blocks_html .= '<ul class="wp-block-navigation__container">';
363 }
364 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 ) {
365 $is_list_open = false;
366 $inner_blocks_html .= '</ul>';
367 }
368 if ( 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name ) {
369 $inner_blocks_html .= '<li class="wp-block-navigation-item">' . $inner_block->render() . '</li>';
370 } else {
371 $inner_blocks_html .= $inner_block->render();
372 }
373 }
374
375 if ( $is_list_open ) {
376 $inner_blocks_html .= '</ul>';
377 }
378
379 $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
380
381 $wrapper_attributes = get_block_wrapper_attributes(
382 array(
383 'class' => implode( ' ', $classes ),
384 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'],
385 )
386 );
387
388 $modal_unique_id = uniqid();
389
390 // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive,
391 // return early if they don't.
392 if ( ! $is_responsive_menu ) {
393 return sprintf(
394 '<nav %1$s>%2$s</nav>',
395 $wrapper_attributes,
396 $inner_blocks_html
397 );
398 }
399
400 $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
401
402 $responsive_container_classes = array(
403 'wp-block-navigation__responsive-container',
404 $is_hidden_by_default ? 'hidden-by-default' : '',
405 implode( ' ', $colors['overlay_css_classes'] ),
406 );
407 $open_button_classes = array(
408 'wp-block-navigation__responsive-container-open',
409 $is_hidden_by_default ? 'always-shown' : '',
410 );
411
412 $responsive_container_markup = sprintf(
413 '<button aria-expanded="false" aria-haspopup="true" aria-label="%3$s" class="%6$s" data-micromodal-trigger="modal-%1$s"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" 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></button>
414 <div class="%5$s" style="%7$s" id="modal-%1$s">
415 <div class="wp-block-navigation__responsive-close" tabindex="-1" data-micromodal-close>
416 <div class="wp-block-navigation__responsive-dialog" role="dialog" aria-modal="true" aria-labelledby="modal-%1$s-title" >
417 <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" role="img" 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>
418 <div class="wp-block-navigation__responsive-container-content" id="modal-%1$s-content">
419 %2$s
420 </div>
421 </div>
422 </div>
423 </div>',
424 $modal_unique_id,
425 $inner_blocks_html,
426 __( 'Open menu' ), // Open button label.
427 __( 'Close menu' ), // Close button label.
428 implode( ' ', $responsive_container_classes ),
429 implode( ' ', $open_button_classes ),
430 $colors['overlay_inline_styles']
431 );
432
433 return sprintf(
434 '<nav %1$s>%2$s</nav>',
435 $wrapper_attributes,
436 $responsive_container_markup
437 );
438 }
439
440 /**
441 * Register the navigation block.
442 *
443 * @uses gutenberg_render_block_core_navigation()
444 * @throws WP_Error An WP_Error exception parsing the block definition.
445 */
446 function gutenberg_register_block_core_navigation() {
447 register_block_type_from_metadata(
448 __DIR__ . '/navigation',
449 array(
450 'render_callback' => 'gutenberg_render_block_core_navigation',
451 )
452 );
453 }
454
455 add_action( 'init', 'gutenberg_register_block_core_navigation', 20 );
456
457 /**
458 * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
459 *
460 * @param array $parsed_block The block being rendered.
461 *
462 * @return array The block being rendered without typographic presets.
463 */
464 function gutenberg_block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) {
465 if ( 'core/navigation' === $parsed_block['blockName'] ) {
466 $attribute_to_prefix_map = array(
467 'fontStyle' => 'var:preset|font-style|',
468 'fontWeight' => 'var:preset|font-weight|',
469 'textDecoration' => 'var:preset|text-decoration|',
470 'textTransform' => 'var:preset|text-transform|',
471 );
472 foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) {
473 if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) {
474 $prefix_len = strlen( $prefix );
475 $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ];
476 if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) {
477 $attribute_value = substr( $attribute_value, $prefix_len );
478 }
479 if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) {
480 $attribute_value = 'line-through';
481 }
482 }
483 }
484 }
485
486 return $parsed_block;
487 }
488
489 add_filter( 'render_block_data', 'gutenberg_block_core_navigation_typographic_presets_backcompatibility' );
490