2 /** |
2 /** |
3 * Server-side rendering of the `core/navigation` block. |
3 * Server-side rendering of the `core/navigation` block. |
4 * |
4 * |
5 * @package WordPress |
5 * @package WordPress |
6 */ |
6 */ |
|
7 |
|
8 /** |
|
9 * Helper functions used to render the navigation block. |
|
10 * |
|
11 * @since 6.5.0 |
|
12 */ |
|
13 class WP_Navigation_Block_Renderer { |
|
14 |
|
15 /** |
|
16 * Used to determine whether or not a navigation has submenus. |
|
17 * |
|
18 * @since 6.5.0 |
|
19 */ |
|
20 private static $has_submenus = false; |
|
21 |
|
22 /** |
|
23 * Used to determine which blocks need an <li> wrapper. |
|
24 * |
|
25 * @since 6.5.0 |
|
26 * |
|
27 * @var array |
|
28 */ |
|
29 private static $needs_list_item_wrapper = array( |
|
30 'core/site-title', |
|
31 'core/site-logo', |
|
32 'core/social-links', |
|
33 ); |
|
34 |
|
35 /** |
|
36 * Keeps track of all the navigation names that have been seen. |
|
37 * |
|
38 * @since 6.5.0 |
|
39 * |
|
40 * @var array |
|
41 */ |
|
42 private static $seen_menu_names = array(); |
|
43 |
|
44 /** |
|
45 * Returns whether or not this is responsive navigation. |
|
46 * |
|
47 * @since 6.5.0 |
|
48 * |
|
49 * @param array $attributes The block attributes. |
|
50 * @return bool Returns whether or not this is responsive navigation. |
|
51 */ |
|
52 private static function is_responsive( $attributes ) { |
|
53 /** |
|
54 * This is for backwards compatibility after the `isResponsive` attribute was been removed. |
|
55 */ |
|
56 |
|
57 $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive']; |
|
58 return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute; |
|
59 } |
|
60 |
|
61 /** |
|
62 * Returns whether or not a navigation has a submenu. |
|
63 * |
|
64 * @since 6.5.0 |
|
65 * |
|
66 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
67 * @return bool Returns whether or not a navigation has a submenu and also sets the member variable. |
|
68 */ |
|
69 private static function has_submenus( $inner_blocks ) { |
|
70 if ( true === static::$has_submenus ) { |
|
71 return static::$has_submenus; |
|
72 } |
|
73 |
|
74 foreach ( $inner_blocks as $inner_block ) { |
|
75 // If this is a page list then work out if any of the pages have children. |
|
76 if ( 'core/page-list' === $inner_block->name ) { |
|
77 $all_pages = get_pages( |
|
78 array( |
|
79 'sort_column' => 'menu_order,post_title', |
|
80 'order' => 'asc', |
|
81 ) |
|
82 ); |
|
83 foreach ( (array) $all_pages as $page ) { |
|
84 if ( $page->post_parent ) { |
|
85 static::$has_submenus = true; |
|
86 break; |
|
87 } |
|
88 } |
|
89 } |
|
90 // If this is a navigation submenu then we know we have submenus. |
|
91 if ( 'core/navigation-submenu' === $inner_block->name ) { |
|
92 static::$has_submenus = true; |
|
93 break; |
|
94 } |
|
95 } |
|
96 |
|
97 return static::$has_submenus; |
|
98 } |
|
99 |
|
100 /** |
|
101 * Determine whether the navigation blocks is interactive. |
|
102 * |
|
103 * @since 6.5.0 |
|
104 * |
|
105 * @param array $attributes The block attributes. |
|
106 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
107 * @return bool Returns whether or not to load the view script. |
|
108 */ |
|
109 private static function is_interactive( $attributes, $inner_blocks ) { |
|
110 $has_submenus = static::has_submenus( $inner_blocks ); |
|
111 $is_responsive_menu = static::is_responsive( $attributes ); |
|
112 return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu; |
|
113 } |
|
114 |
|
115 /** |
|
116 * Returns whether or not a block needs a list item wrapper. |
|
117 * |
|
118 * @since 6.5.0 |
|
119 * |
|
120 * @param WP_Block $block The block. |
|
121 * @return bool Returns whether or not a block needs a list item wrapper. |
|
122 */ |
|
123 private static function does_block_need_a_list_item_wrapper( $block ) { |
|
124 |
|
125 /** |
|
126 * Filter the list of blocks that need a list item wrapper. |
|
127 * |
|
128 * Affords the ability to customize which blocks need a list item wrapper when rendered |
|
129 * within a core/navigation block. |
|
130 * This is useful for blocks that are not list items but should be wrapped in a list |
|
131 * item when used as a child of a navigation block. |
|
132 * |
|
133 * @since 6.5.0 |
|
134 * |
|
135 * @param array $needs_list_item_wrapper The list of blocks that need a list item wrapper. |
|
136 * @return array The list of blocks that need a list item wrapper. |
|
137 */ |
|
138 $needs_list_item_wrapper = apply_filters( 'block_core_navigation_listable_blocks', static::$needs_list_item_wrapper ); |
|
139 |
|
140 return in_array( $block->name, $needs_list_item_wrapper, true ); |
|
141 } |
|
142 |
|
143 /** |
|
144 * Returns the markup for a single inner block. |
|
145 * |
|
146 * @since 6.5.0 |
|
147 * |
|
148 * @param WP_Block $inner_block The inner block. |
|
149 * @return string Returns the markup for a single inner block. |
|
150 */ |
|
151 private static function get_markup_for_inner_block( $inner_block ) { |
|
152 $inner_block_content = $inner_block->render(); |
|
153 if ( ! empty( $inner_block_content ) ) { |
|
154 if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) { |
|
155 return '<li class="wp-block-navigation-item">' . $inner_block_content . '</li>'; |
|
156 } |
|
157 } |
|
158 |
|
159 return $inner_block_content; |
|
160 } |
|
161 |
|
162 /** |
|
163 * Returns the html for the inner blocks of the navigation block. |
|
164 * |
|
165 * @since 6.5.0 |
|
166 * |
|
167 * @param array $attributes The block attributes. |
|
168 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
169 * @return string Returns the html for the inner blocks of the navigation block. |
|
170 */ |
|
171 private static function get_inner_blocks_html( $attributes, $inner_blocks ) { |
|
172 $has_submenus = static::has_submenus( $inner_blocks ); |
|
173 $is_interactive = static::is_interactive( $attributes, $inner_blocks ); |
|
174 |
|
175 $style = static::get_styles( $attributes ); |
|
176 $class = static::get_classes( $attributes ); |
|
177 $container_attributes = get_block_wrapper_attributes( |
|
178 array( |
|
179 'class' => 'wp-block-navigation__container ' . $class, |
|
180 'style' => $style, |
|
181 ) |
|
182 ); |
|
183 |
|
184 $inner_blocks_html = ''; |
|
185 $is_list_open = false; |
|
186 |
|
187 foreach ( $inner_blocks as $inner_block ) { |
|
188 $inner_block_markup = static::get_markup_for_inner_block( $inner_block ); |
|
189 $p = new WP_HTML_Tag_Processor( $inner_block_markup ); |
|
190 $is_list_item = $p->next_tag( 'LI' ); |
|
191 |
|
192 if ( $is_list_item && ! $is_list_open ) { |
|
193 $is_list_open = true; |
|
194 $inner_blocks_html .= sprintf( |
|
195 '<ul %1$s>', |
|
196 $container_attributes |
|
197 ); |
|
198 } |
|
199 |
|
200 if ( ! $is_list_item && $is_list_open ) { |
|
201 $is_list_open = false; |
|
202 $inner_blocks_html .= '</ul>'; |
|
203 } |
|
204 |
|
205 $inner_blocks_html .= $inner_block_markup; |
|
206 } |
|
207 |
|
208 if ( $is_list_open ) { |
|
209 $inner_blocks_html .= '</ul>'; |
|
210 } |
|
211 |
|
212 // Add directives to the submenu if needed. |
|
213 if ( $has_submenus && $is_interactive ) { |
|
214 $tags = new WP_HTML_Tag_Processor( $inner_blocks_html ); |
|
215 $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes ); |
|
216 } |
|
217 |
|
218 return $inner_blocks_html; |
|
219 } |
|
220 |
|
221 /** |
|
222 * Gets the inner blocks for the navigation block from the navigation post. |
|
223 * |
|
224 * @since 6.5.0 |
|
225 * |
|
226 * @param array $attributes The block attributes. |
|
227 * @return WP_Block_List Returns the inner blocks for the navigation block. |
|
228 */ |
|
229 private static function get_inner_blocks_from_navigation_post( $attributes ) { |
|
230 $navigation_post = get_post( $attributes['ref'] ); |
|
231 if ( ! isset( $navigation_post ) ) { |
|
232 return new WP_Block_List( array(), $attributes ); |
|
233 } |
|
234 |
|
235 // Only published posts are valid. If this is changed then a corresponding change |
|
236 // must also be implemented in `use-navigation-menu.js`. |
|
237 if ( 'publish' === $navigation_post->post_status ) { |
|
238 $parsed_blocks = parse_blocks( $navigation_post->post_content ); |
|
239 |
|
240 // 'parse_blocks' includes a null block with '\n\n' as the content when |
|
241 // it encounters whitespace. This code strips it. |
|
242 $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); |
|
243 |
|
244 if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) { |
|
245 // Run Block Hooks algorithm to inject hooked blocks. |
|
246 $markup = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post ); |
|
247 $root_nav_block = parse_blocks( $markup )[0]; |
|
248 |
|
249 $blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks; |
|
250 } |
|
251 |
|
252 // TODO - this uses the full navigation block attributes for the |
|
253 // context which could be refined. |
|
254 return new WP_Block_List( $blocks, $attributes ); |
|
255 } |
|
256 } |
|
257 |
|
258 /** |
|
259 * Gets the inner blocks for the navigation block from the fallback. |
|
260 * |
|
261 * @since 6.5.0 |
|
262 * |
|
263 * @param array $attributes The block attributes. |
|
264 * @return WP_Block_List Returns the inner blocks for the navigation block. |
|
265 */ |
|
266 private static function get_inner_blocks_from_fallback( $attributes ) { |
|
267 $fallback_blocks = block_core_navigation_get_fallback_blocks(); |
|
268 |
|
269 // Fallback my have been filtered so do basic test for validity. |
|
270 if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { |
|
271 return new WP_Block_List( array(), $attributes ); |
|
272 } |
|
273 |
|
274 return new WP_Block_List( $fallback_blocks, $attributes ); |
|
275 } |
|
276 |
|
277 /** |
|
278 * Gets the inner blocks for the navigation block. |
|
279 * |
|
280 * @since 6.5.0 |
|
281 * |
|
282 * @param array $attributes The block attributes. |
|
283 * @param WP_Block $block The parsed block. |
|
284 * @return WP_Block_List Returns the inner blocks for the navigation block. |
|
285 */ |
|
286 private static function get_inner_blocks( $attributes, $block ) { |
|
287 $inner_blocks = $block->inner_blocks; |
|
288 |
|
289 // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render. |
|
290 if ( array_key_exists( 'navigationMenuId', $attributes ) ) { |
|
291 $attributes['ref'] = $attributes['navigationMenuId']; |
|
292 } |
|
293 |
|
294 // If: |
|
295 // - the gutenberg plugin is active |
|
296 // - `__unstableLocation` is defined |
|
297 // - we have menu items at the defined location |
|
298 // - we don't have a relationship to a `wp_navigation` Post (via `ref`). |
|
299 // ...then create inner blocks from the classic menu assigned to that location. |
|
300 if ( |
|
301 defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && |
|
302 array_key_exists( '__unstableLocation', $attributes ) && |
|
303 ! array_key_exists( 'ref', $attributes ) && |
|
304 ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) |
|
305 ) { |
|
306 $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ); |
|
307 } |
|
308 |
|
309 // Load inner blocks from the navigation post. |
|
310 if ( array_key_exists( 'ref', $attributes ) ) { |
|
311 $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes ); |
|
312 } |
|
313 |
|
314 // If there are no inner blocks then fallback to rendering an appropriate fallback. |
|
315 if ( empty( $inner_blocks ) ) { |
|
316 $inner_blocks = static::get_inner_blocks_from_fallback( $attributes ); |
|
317 } |
|
318 |
|
319 /** |
|
320 * Filter navigation block $inner_blocks. |
|
321 * Allows modification of a navigation block menu items. |
|
322 * |
|
323 * @since 6.1.0 |
|
324 * |
|
325 * @param \WP_Block_List $inner_blocks |
|
326 */ |
|
327 $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks ); |
|
328 |
|
329 $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); |
|
330 if ( $post_ids ) { |
|
331 _prime_post_caches( $post_ids, false, false ); |
|
332 } |
|
333 |
|
334 return $inner_blocks; |
|
335 } |
|
336 |
|
337 /** |
|
338 * Gets the name of the current navigation, if it has one. |
|
339 * |
|
340 * @since 6.5.0 |
|
341 * |
|
342 * @param array $attributes The block attributes. |
|
343 * @return string Returns the name of the navigation. |
|
344 */ |
|
345 private static function get_navigation_name( $attributes ) { |
|
346 |
|
347 $navigation_name = $attributes['ariaLabel'] ?? ''; |
|
348 |
|
349 // Load the navigation post. |
|
350 if ( array_key_exists( 'ref', $attributes ) ) { |
|
351 $navigation_post = get_post( $attributes['ref'] ); |
|
352 if ( ! isset( $navigation_post ) ) { |
|
353 return $navigation_name; |
|
354 } |
|
355 |
|
356 // Only published posts are valid. If this is changed then a corresponding change |
|
357 // must also be implemented in `use-navigation-menu.js`. |
|
358 if ( 'publish' === $navigation_post->post_status ) { |
|
359 $navigation_name = $navigation_post->post_title; |
|
360 |
|
361 // This is used to count the number of times a navigation name has been seen, |
|
362 // so that we can ensure every navigation has a unique id. |
|
363 if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) { |
|
364 ++static::$seen_menu_names[ $navigation_name ]; |
|
365 } else { |
|
366 static::$seen_menu_names[ $navigation_name ] = 1; |
|
367 } |
|
368 } |
|
369 } |
|
370 |
|
371 return $navigation_name; |
|
372 } |
|
373 |
|
374 /** |
|
375 * Returns the layout class for the navigation block. |
|
376 * |
|
377 * @since 6.5.0 |
|
378 * |
|
379 * @param array $attributes The block attributes. |
|
380 * @return string Returns the layout class for the navigation block. |
|
381 */ |
|
382 private static function get_layout_class( $attributes ) { |
|
383 $layout_justification = array( |
|
384 'left' => 'items-justified-left', |
|
385 'right' => 'items-justified-right', |
|
386 'center' => 'items-justified-center', |
|
387 'space-between' => 'items-justified-space-between', |
|
388 ); |
|
389 |
|
390 $layout_class = ''; |
|
391 if ( |
|
392 isset( $attributes['layout']['justifyContent'] ) && |
|
393 isset( $layout_justification[ $attributes['layout']['justifyContent'] ] ) |
|
394 ) { |
|
395 $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ]; |
|
396 } |
|
397 if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) { |
|
398 $layout_class .= ' is-vertical'; |
|
399 } |
|
400 |
|
401 if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) { |
|
402 $layout_class .= ' no-wrap'; |
|
403 } |
|
404 return $layout_class; |
|
405 } |
|
406 |
|
407 /** |
|
408 * Return classes for the navigation block. |
|
409 * |
|
410 * @since 6.5.0 |
|
411 * |
|
412 * @param array $attributes The block attributes. |
|
413 * @return string Returns the classes for the navigation block. |
|
414 */ |
|
415 private static function get_classes( $attributes ) { |
|
416 // Restore legacy classnames for submenu positioning. |
|
417 $layout_class = static::get_layout_class( $attributes ); |
|
418 $colors = block_core_navigation_build_css_colors( $attributes ); |
|
419 $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); |
|
420 $is_responsive_menu = static::is_responsive( $attributes ); |
|
421 |
|
422 // Manually add block support text decoration as CSS class. |
|
423 $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null; |
|
424 $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); |
|
425 |
|
426 $classes = array_merge( |
|
427 $colors['css_classes'], |
|
428 $font_sizes['css_classes'], |
|
429 $is_responsive_menu ? array( 'is-responsive' ) : array(), |
|
430 $layout_class ? array( $layout_class ) : array(), |
|
431 $text_decoration ? array( $text_decoration_class ) : array() |
|
432 ); |
|
433 return implode( ' ', $classes ); |
|
434 } |
|
435 |
|
436 /** |
|
437 * Get styles for the navigation block. |
|
438 * |
|
439 * @since 6.5.0 |
|
440 * |
|
441 * @param array $attributes The block attributes. |
|
442 * @return string Returns the styles for the navigation block. |
|
443 */ |
|
444 private static function get_styles( $attributes ) { |
|
445 $colors = block_core_navigation_build_css_colors( $attributes ); |
|
446 $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); |
|
447 $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; |
|
448 return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles']; |
|
449 } |
|
450 |
|
451 /** |
|
452 * Get the responsive container markup |
|
453 * |
|
454 * @since 6.5.0 |
|
455 * |
|
456 * @param array $attributes The block attributes. |
|
457 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
458 * @param string $inner_blocks_html The markup for the inner blocks. |
|
459 * @return string Returns the container markup. |
|
460 */ |
|
461 private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) { |
|
462 $is_interactive = static::is_interactive( $attributes, $inner_blocks ); |
|
463 $colors = block_core_navigation_build_css_colors( $attributes ); |
|
464 $modal_unique_id = wp_unique_id( 'modal-' ); |
|
465 |
|
466 $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; |
|
467 |
|
468 $responsive_container_classes = array( |
|
469 'wp-block-navigation__responsive-container', |
|
470 $is_hidden_by_default ? 'hidden-by-default' : '', |
|
471 implode( ' ', $colors['overlay_css_classes'] ), |
|
472 ); |
|
473 $open_button_classes = array( |
|
474 'wp-block-navigation__responsive-container-open', |
|
475 $is_hidden_by_default ? 'always-shown' : '', |
|
476 ); |
|
477 |
|
478 $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; |
|
479 $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>'; |
|
480 if ( isset( $attributes['icon'] ) ) { |
|
481 if ( 'menu' === $attributes['icon'] ) { |
|
482 $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5zm0 7.8h14v-1.5H5v1.5zM5 19h14v-1.5H5V19z" /></svg>'; |
|
483 } |
|
484 } |
|
485 $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' ); |
|
486 $toggle_close_button_icon = '<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>'; |
|
487 $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' ); |
|
488 $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label. |
|
489 $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label. |
|
490 |
|
491 // Add Interactivity API directives to the markup if needed. |
|
492 $open_button_directives = ''; |
|
493 $responsive_container_directives = ''; |
|
494 $responsive_dialog_directives = ''; |
|
495 $close_button_directives = ''; |
|
496 if ( $is_interactive ) { |
|
497 $open_button_directives = ' |
|
498 data-wp-on-async--click="actions.openMenuOnClick" |
|
499 data-wp-on--keydown="actions.handleMenuKeydown" |
|
500 '; |
|
501 $responsive_container_directives = ' |
|
502 data-wp-class--has-modal-open="state.isMenuOpen" |
|
503 data-wp-class--is-menu-open="state.isMenuOpen" |
|
504 data-wp-watch="callbacks.initMenu" |
|
505 data-wp-on--keydown="actions.handleMenuKeydown" |
|
506 data-wp-on-async--focusout="actions.handleMenuFocusout" |
|
507 tabindex="-1" |
|
508 '; |
|
509 $responsive_dialog_directives = ' |
|
510 data-wp-bind--aria-modal="state.ariaModal" |
|
511 data-wp-bind--aria-label="state.ariaLabel" |
|
512 data-wp-bind--role="state.roleAttribute" |
|
513 '; |
|
514 $close_button_directives = ' |
|
515 data-wp-on-async--click="actions.closeMenuOnClick" |
|
516 '; |
|
517 $responsive_container_content_directives = ' |
|
518 data-wp-watch="callbacks.focusFirstElement" |
|
519 '; |
|
520 } |
|
521 |
|
522 $overlay_inline_styles = esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ); |
|
523 |
|
524 return sprintf( |
|
525 '<button aria-haspopup="dialog" %3$s class="%6$s" %10$s>%8$s</button> |
|
526 <div class="%5$s" %7$s id="%1$s" %11$s> |
|
527 <div class="wp-block-navigation__responsive-close" tabindex="-1"> |
|
528 <div class="wp-block-navigation__responsive-dialog" %12$s> |
|
529 <button %4$s class="wp-block-navigation__responsive-container-close" %13$s>%9$s</button> |
|
530 <div class="wp-block-navigation__responsive-container-content" %14$s id="%1$s-content"> |
|
531 %2$s |
|
532 </div> |
|
533 </div> |
|
534 </div> |
|
535 </div>', |
|
536 esc_attr( $modal_unique_id ), |
|
537 $inner_blocks_html, |
|
538 $toggle_aria_label_open, |
|
539 $toggle_aria_label_close, |
|
540 esc_attr( implode( ' ', $responsive_container_classes ) ), |
|
541 esc_attr( implode( ' ', $open_button_classes ) ), |
|
542 ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '', |
|
543 $toggle_button_content, |
|
544 $toggle_close_button_content, |
|
545 $open_button_directives, |
|
546 $responsive_container_directives, |
|
547 $responsive_dialog_directives, |
|
548 $close_button_directives, |
|
549 $responsive_container_content_directives |
|
550 ); |
|
551 } |
|
552 |
|
553 /** |
|
554 * Get the wrapper attributes |
|
555 * |
|
556 * @since 6.5.0 |
|
557 * |
|
558 * @param array $attributes The block attributes. |
|
559 * @param WP_Block_List $inner_blocks A list of inner blocks. |
|
560 * @return string Returns the navigation block markup. |
|
561 */ |
|
562 private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) { |
|
563 $nav_menu_name = static::get_unique_navigation_name( $attributes ); |
|
564 $is_interactive = static::is_interactive( $attributes, $inner_blocks ); |
|
565 $is_responsive_menu = static::is_responsive( $attributes ); |
|
566 $style = static::get_styles( $attributes ); |
|
567 $class = static::get_classes( $attributes ); |
|
568 $wrapper_attributes = get_block_wrapper_attributes( |
|
569 array( |
|
570 'class' => $class, |
|
571 'style' => $style, |
|
572 'aria-label' => $nav_menu_name, |
|
573 ) |
|
574 ); |
|
575 |
|
576 if ( $is_responsive_menu ) { |
|
577 $nav_element_directives = static::get_nav_element_directives( $is_interactive ); |
|
578 $wrapper_attributes .= ' ' . $nav_element_directives; |
|
579 } |
|
580 |
|
581 return $wrapper_attributes; |
|
582 } |
|
583 |
|
584 /** |
|
585 * Gets the nav element directives. |
|
586 * |
|
587 * @since 6.5.0 |
|
588 * |
|
589 * @param bool $is_interactive Whether the block is interactive. |
|
590 * @return string the directives for the navigation element. |
|
591 */ |
|
592 private static function get_nav_element_directives( $is_interactive ) { |
|
593 if ( ! $is_interactive ) { |
|
594 return ''; |
|
595 } |
|
596 // When adding to this array be mindful of security concerns. |
|
597 $nav_element_context = wp_interactivity_data_wp_context( |
|
598 array( |
|
599 'overlayOpenedBy' => array( |
|
600 'click' => false, |
|
601 'hover' => false, |
|
602 'focus' => false, |
|
603 ), |
|
604 'type' => 'overlay', |
|
605 'roleAttribute' => '', |
|
606 'ariaLabel' => __( 'Menu' ), |
|
607 ) |
|
608 ); |
|
609 $nav_element_directives = ' |
|
610 data-wp-interactive="core/navigation" ' |
|
611 . $nav_element_context; |
|
612 |
|
613 return $nav_element_directives; |
|
614 } |
|
615 |
|
616 /** |
|
617 * Handle view script module loading. |
|
618 * |
|
619 * @since 6.5.0 |
|
620 * |
|
621 * @param array $attributes The block attributes. |
|
622 * @param WP_Block $block The parsed block. |
|
623 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
624 */ |
|
625 private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) { |
|
626 if ( static::is_interactive( $attributes, $inner_blocks ) ) { |
|
627 $suffix = wp_scripts_get_suffix(); |
|
628 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { |
|
629 $module_url = gutenberg_url( '/build/interactivity/navigation.min.js' ); |
|
630 } |
|
631 |
|
632 wp_register_script_module( |
|
633 '@wordpress/block-library/navigation', |
|
634 isset( $module_url ) ? $module_url : includes_url( "blocks/navigation/view{$suffix}.js" ), |
|
635 array( '@wordpress/interactivity' ), |
|
636 defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) |
|
637 ); |
|
638 wp_enqueue_script_module( '@wordpress/block-library/navigation' ); |
|
639 } |
|
640 } |
|
641 |
|
642 /** |
|
643 * Returns the markup for the navigation block. |
|
644 * |
|
645 * @since 6.5.0 |
|
646 * |
|
647 * @param array $attributes The block attributes. |
|
648 * @param WP_Block_List $inner_blocks The list of inner blocks. |
|
649 * @return string Returns the navigation wrapper markup. |
|
650 */ |
|
651 private static function get_wrapper_markup( $attributes, $inner_blocks ) { |
|
652 $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks ); |
|
653 if ( static::is_responsive( $attributes ) ) { |
|
654 return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ); |
|
655 } |
|
656 return $inner_blocks_html; |
|
657 } |
|
658 |
|
659 /** |
|
660 * Returns a unique name for the navigation. |
|
661 * |
|
662 * @since 6.5.0 |
|
663 * |
|
664 * @param array $attributes The block attributes. |
|
665 * @return string Returns a unique name for the navigation. |
|
666 */ |
|
667 private static function get_unique_navigation_name( $attributes ) { |
|
668 $nav_menu_name = static::get_navigation_name( $attributes ); |
|
669 |
|
670 // If the menu name has been used previously then append an ID |
|
671 // to the name to ensure uniqueness across a given post. |
|
672 if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) { |
|
673 $count = static::$seen_menu_names[ $nav_menu_name ]; |
|
674 $nav_menu_name = $nav_menu_name . ' ' . ( $count ); |
|
675 } |
|
676 |
|
677 return $nav_menu_name; |
|
678 } |
|
679 |
|
680 /** |
|
681 * Renders the navigation block. |
|
682 * |
|
683 * @since 6.5.0 |
|
684 * |
|
685 * @param array $attributes The block attributes. |
|
686 * @param string $content The saved content. |
|
687 * @param WP_Block $block The parsed block. |
|
688 * @return string Returns the navigation block markup. |
|
689 */ |
|
690 public static function render( $attributes, $content, $block ) { |
|
691 /** |
|
692 * Deprecated: |
|
693 * The rgbTextColor and rgbBackgroundColor attributes |
|
694 * have been deprecated in favor of |
|
695 * customTextColor and customBackgroundColor ones. |
|
696 * Move the values from old attrs to the new ones. |
|
697 */ |
|
698 if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) { |
|
699 $attributes['customTextColor'] = $attributes['rgbTextColor']; |
|
700 } |
|
701 |
|
702 if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) { |
|
703 $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor']; |
|
704 } |
|
705 |
|
706 unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] ); |
|
707 |
|
708 $inner_blocks = static::get_inner_blocks( $attributes, $block ); |
|
709 // Prevent navigation blocks referencing themselves from rendering. |
|
710 if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { |
|
711 return ''; |
|
712 } |
|
713 |
|
714 static::handle_view_script_module_loading( $attributes, $block, $inner_blocks ); |
|
715 |
|
716 return sprintf( |
|
717 '<nav %1$s>%2$s</nav>', |
|
718 static::get_nav_wrapper_attributes( $attributes, $inner_blocks ), |
|
719 static::get_wrapper_markup( $attributes, $inner_blocks ) |
|
720 ); |
|
721 } |
|
722 } |
7 |
723 |
8 // These functions are used for the __unstableLocation feature and only active |
724 // These functions are used for the __unstableLocation feature and only active |
9 // when the gutenberg plugin is active. |
725 // when the gutenberg plugin is active. |
10 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { |
726 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { |
11 /** |
727 /** |
12 * Returns the menu items for a WordPress menu location. |
728 * Returns the menu items for a WordPress menu location. |
|
729 * |
|
730 * @since 5.9.0 |
13 * |
731 * |
14 * @param string $location The menu location. |
732 * @param string $location The menu location. |
15 * @return array Menu items for the location. |
733 * @return array Menu items for the location. |
16 */ |
734 */ |
17 function block_core_navigation_get_menu_items_at_location( $location ) { |
735 function block_core_navigation_get_menu_items_at_location( $location ) { |
65 |
785 |
66 return $menu_items_by_parent_id; |
786 return $menu_items_by_parent_id; |
67 } |
787 } |
68 |
788 |
69 /** |
789 /** |
70 * Turns menu item data into a nested array of parsed blocks |
790 * Gets the inner blocks for the navigation block from the unstable location attribute. |
71 * |
791 * |
72 * @param array $menu_items An array of menu items that represent |
792 * @since 6.5.0 |
73 * an individual level of a menu. |
793 * |
74 * @param array $menu_items_by_parent_id An array keyed by the id of the |
794 * @param array $attributes The block attributes. |
75 * parent menu where each element is an |
795 * @return WP_Block_List Returns the inner blocks for the navigation block. |
76 * array of menu items that belong to |
796 */ |
77 * that parent. |
797 function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) { |
78 * @return array An array of parsed block data. |
798 $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ); |
79 */ |
|
80 function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { |
|
81 if ( empty( $menu_items ) ) { |
799 if ( empty( $menu_items ) ) { |
82 return array(); |
800 return new WP_Block_List( array(), $attributes ); |
83 } |
801 } |
84 |
802 |
85 $blocks = array(); |
803 $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items ); |
86 |
804 $parsed_blocks = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); |
87 foreach ( $menu_items as $menu_item ) { |
805 return new WP_Block_List( $parsed_blocks, $attributes ); |
88 $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; |
806 } |
89 $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; |
807 } |
90 $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; |
808 |
91 $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; |
809 /** |
92 $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; |
810 * Add Interactivity API directives to the navigation-submenu and page-list |
93 |
811 * blocks markup using the Tag Processor. |
94 $block = array( |
812 * |
95 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', |
813 * @since 6.3.0 |
96 'attrs' => array( |
814 * |
97 'className' => $class_name, |
815 * @param WP_HTML_Tag_Processor $tags Markup of the navigation block. |
98 'description' => $menu_item->description, |
816 * @param array $block_attributes Block attributes. |
99 'id' => $id, |
817 * |
100 'kind' => $kind, |
818 * @return string Submenu markup with the directives injected. |
101 'label' => $menu_item->title, |
819 */ |
102 'opensInNewTab' => $opens_in_new_tab, |
820 function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) { |
103 'rel' => $rel, |
821 while ( $tags->next_tag( |
104 'title' => $menu_item->attr_title, |
822 array( |
105 'type' => $menu_item->object, |
823 'tag_name' => 'LI', |
106 'url' => $menu_item->url, |
824 'class_name' => 'has-child', |
107 ), |
825 ) |
108 ); |
826 ) ) { |
109 |
827 // Add directives to the parent `<li>`. |
110 $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) |
828 $tags->set_attribute( 'data-wp-interactive', 'core/navigation' ); |
111 ? block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) |
829 $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' ); |
112 : array(); |
830 $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' ); |
113 $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); |
831 $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' ); |
114 |
832 $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' ); |
115 $blocks[] = $block; |
833 |
116 } |
834 // This is a fix for Safari. Without it, Safari doesn't change the active |
117 |
835 // element when the user clicks on a button. It can be removed once we add |
118 return $blocks; |
836 // an overlay to capture the clicks, instead of relying on the focusout |
119 } |
837 // event. |
|
838 $tags->set_attribute( 'tabindex', '-1' ); |
|
839 |
|
840 if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { |
|
841 $tags->set_attribute( 'data-wp-on-async--mouseenter', 'actions.openMenuOnHover' ); |
|
842 $tags->set_attribute( 'data-wp-on-async--mouseleave', 'actions.closeMenuOnHover' ); |
|
843 } |
|
844 |
|
845 // Add directives to the toggle submenu button. |
|
846 if ( $tags->next_tag( |
|
847 array( |
|
848 'tag_name' => 'BUTTON', |
|
849 'class_name' => 'wp-block-navigation-submenu__toggle', |
|
850 ) |
|
851 ) ) { |
|
852 $tags->set_attribute( 'data-wp-on-async--click', 'actions.toggleMenuOnClick' ); |
|
853 $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' ); |
|
854 // The `aria-expanded` attribute for SSR is already added in the submenu block. |
|
855 } |
|
856 // Add directives to the submenu. |
|
857 if ( $tags->next_tag( |
|
858 array( |
|
859 'tag_name' => 'UL', |
|
860 'class_name' => 'wp-block-navigation__submenu-container', |
|
861 ) |
|
862 ) ) { |
|
863 $tags->set_attribute( 'data-wp-on-async--focus', 'actions.openMenuOnFocus' ); |
|
864 } |
|
865 |
|
866 // Iterate through subitems if exist. |
|
867 block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ); |
|
868 } |
|
869 return $tags->get_updated_html(); |
120 } |
870 } |
121 |
871 |
122 /** |
872 /** |
123 * Build an array with CSS classes and inline styles defining the colors |
873 * 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. |
874 * which will be applied to the navigation markup in the front-end. |
|
875 * |
|
876 * @since 5.9.0 |
125 * |
877 * |
126 * @param array $attributes Navigation block attributes. |
878 * @param array $attributes Navigation block attributes. |
127 * |
879 * |
128 * @return array Colors CSS classes and inline styles. |
880 * @return array Colors CSS classes and inline styles. |
129 */ |
881 */ |
240 } |
994 } |
241 |
995 |
242 /** |
996 /** |
243 * Returns the top-level submenu SVG chevron icon. |
997 * Returns the top-level submenu SVG chevron icon. |
244 * |
998 * |
|
999 * @since 5.9.0 |
|
1000 * |
245 * @return string |
1001 * @return string |
246 */ |
1002 */ |
247 function block_core_navigation_render_submenu_icon() { |
1003 function 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>'; |
1004 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 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 } |
1005 } |
280 |
1006 |
281 /** |
1007 /** |
282 * Filter out empty "null" blocks from the block list. |
1008 * Filter out empty "null" blocks from the block list. |
283 * 'parse_blocks' includes a null block with '\n\n' as the content when |
1009 * '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 |
1010 * it encounters whitespace. This is not a bug but rather how the parser |
285 * is designed. |
1011 * is designed. |
286 * |
1012 * |
|
1013 * @since 5.9.0 |
|
1014 * |
287 * @param array $parsed_blocks the parsed blocks to be normalized. |
1015 * @param array $parsed_blocks the parsed blocks to be normalized. |
288 * @return array the normalized parsed blocks. |
1016 * @return array the normalized parsed blocks. |
289 */ |
1017 */ |
290 function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) { |
1018 function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) { |
291 $filtered = array_filter( |
1019 $filtered = array_filter( |
292 $parsed_blocks, |
1020 $parsed_blocks, |
293 function( $block ) { |
1021 static function ( $block ) { |
294 return isset( $block['blockName'] ); |
1022 return isset( $block['blockName'] ); |
295 } |
1023 } |
296 ); |
1024 ); |
297 |
1025 |
298 // Reset keys. |
1026 // Reset keys. |
299 return array_values( $filtered ); |
1027 return array_values( $filtered ); |
300 } |
1028 } |
301 |
1029 |
302 /** |
1030 /** |
|
1031 * Returns true if the navigation block contains a nested navigation block. |
|
1032 * |
|
1033 * @since 6.2.0 |
|
1034 * |
|
1035 * @param WP_Block_List $inner_blocks Inner block instance to be normalized. |
|
1036 * @return bool true if the navigation block contains a nested navigation block. |
|
1037 */ |
|
1038 function block_core_navigation_block_contains_core_navigation( $inner_blocks ) { |
|
1039 foreach ( $inner_blocks as $block ) { |
|
1040 if ( 'core/navigation' === $block->name ) { |
|
1041 return true; |
|
1042 } |
|
1043 if ( $block->inner_blocks && block_core_navigation_block_contains_core_navigation( $block->inner_blocks ) ) { |
|
1044 return true; |
|
1045 } |
|
1046 } |
|
1047 |
|
1048 return false; |
|
1049 } |
|
1050 |
|
1051 /** |
303 * Retrieves the appropriate fallback to be used on the front of the |
1052 * Retrieves the appropriate fallback to be used on the front of the |
304 * site when there is no menu assigned to the Nav block. |
1053 * site when there is no menu assigned to the Nav block. |
305 * |
1054 * |
306 * This aims to mirror how the fallback mechanic for wp_nav_menu works. |
1055 * 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. |
1056 * See https://developer.wordpress.org/reference/functions/wp_nav_menu/#more-information. |
|
1057 * |
|
1058 * @since 5.9.0 |
308 * |
1059 * |
309 * @return array the array of blocks to be used as a fallback. |
1060 * @return array the array of blocks to be used as a fallback. |
310 */ |
1061 */ |
311 function block_core_navigation_get_fallback_blocks() { |
1062 function block_core_navigation_get_fallback_blocks() { |
312 $page_list_fallback = array( |
1063 $page_list_fallback = array( |
313 array( |
1064 array( |
314 'blockName' => 'core/page-list', |
1065 'blockName' => 'core/page-list', |
315 'attrs' => array( |
1066 'innerContent' => array(), |
316 '__unstableMaxPages' => 4, |
1067 'attrs' => array(), |
317 ), |
|
318 ), |
1068 ), |
319 ); |
1069 ); |
320 |
1070 |
321 $registry = WP_Block_Type_Registry::get_instance(); |
1071 $registry = WP_Block_Type_Registry::get_instance(); |
322 |
1072 |
323 // If `core/page-list` is not registered then return empty blocks. |
1073 // If `core/page-list` is not registered then return empty blocks. |
324 $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array(); |
1074 $fallback_blocks = $registry->is_registered( 'core/page-list' ) ? $page_list_fallback : array(); |
325 |
1075 $navigation_post = WP_Navigation_Fallback::get_fallback(); |
326 // Default to a list of Pages. |
1076 |
327 |
1077 // Use the first non-empty Navigation as fallback if available. |
328 $navigation_post = 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 ) { |
1078 if ( $navigation_post ) { |
332 $maybe_fallback = block_core_navigation_filter_out_empty_blocks( parse_blocks( $navigation_post->post_content ) ); |
1079 $parsed_blocks = parse_blocks( $navigation_post->post_content ); |
|
1080 $maybe_fallback = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); |
333 |
1081 |
334 // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks. |
1082 // 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. |
1083 // In this case default to the (Page List) fallback. |
336 $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks; |
1084 $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks; |
|
1085 |
|
1086 if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) { |
|
1087 // Run Block Hooks algorithm to inject hooked blocks. |
|
1088 // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks. |
|
1089 $markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post ); |
|
1090 $blocks = parse_blocks( $markup ); |
|
1091 |
|
1092 if ( isset( $blocks[0]['innerBlocks'] ) ) { |
|
1093 $fallback_blocks = $blocks[0]['innerBlocks']; |
|
1094 } |
|
1095 } |
337 } |
1096 } |
338 |
1097 |
339 /** |
1098 /** |
340 * Filters the fallback experience for the Navigation block. |
1099 * Filters the fallback experience for the Navigation block. |
341 * |
1100 * |
375 if ( $block->inner_blocks ) { |
1138 if ( $block->inner_blocks ) { |
376 $post_ids = block_core_navigation_get_post_ids( $block->inner_blocks ); |
1139 $post_ids = block_core_navigation_get_post_ids( $block->inner_blocks ); |
377 } |
1140 } |
378 |
1141 |
379 if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) { |
1142 if ( 'core/navigation-link' === $block->name || 'core/navigation-submenu' === $block->name ) { |
380 if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] ) { |
1143 if ( $block->attributes && isset( $block->attributes['kind'] ) && 'post-type' === $block->attributes['kind'] && isset( $block->attributes['id'] ) ) { |
381 $post_ids[] = $block->attributes['id']; |
1144 $post_ids[] = $block->attributes['id']; |
382 } |
1145 } |
383 } |
1146 } |
384 |
1147 |
385 return $post_ids; |
1148 return $post_ids; |
386 } |
1149 } |
387 |
1150 |
388 /** |
1151 /** |
389 * Renders the `core/navigation` block on server. |
1152 * Renders the `core/navigation` block on server. |
|
1153 * |
|
1154 * @since 5.9.0 |
390 * |
1155 * |
391 * @param array $attributes The block attributes. |
1156 * @param array $attributes The block attributes. |
392 * @param string $content The saved content. |
1157 * @param string $content The saved content. |
393 * @param WP_Block $block The parsed block. |
1158 * @param WP_Block $block The parsed block. |
394 * |
1159 * |
395 * @return string Returns the post content with the legacy widget added. |
1160 * @return string Returns the navigation block markup. |
396 */ |
1161 */ |
397 function render_block_core_navigation( $attributes, $content, $block ) { |
1162 function render_block_core_navigation( $attributes, $content, $block ) { |
398 |
1163 return WP_Navigation_Block_Renderer::render( $attributes, $content, $block ); |
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 $inner_blocks = $block->inner_blocks; |
|
435 |
|
436 // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render. |
|
437 if ( array_key_exists( 'navigationMenuId', $attributes ) ) { |
|
438 $attributes['ref'] = $attributes['navigationMenuId']; |
|
439 } |
|
440 |
|
441 // If: |
|
442 // - the gutenberg plugin is active |
|
443 // - `__unstableLocation` is defined |
|
444 // - we have menu items at the defined location |
|
445 // - we don't have a relationship to a `wp_navigation` Post (via `ref`). |
|
446 // ...then create inner blocks from the classic menu assigned to that location. |
|
447 if ( |
|
448 defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && |
|
449 array_key_exists( '__unstableLocation', $attributes ) && |
|
450 ! array_key_exists( 'ref', $attributes ) && |
|
451 ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) |
|
452 ) { |
|
453 $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ); |
|
454 if ( empty( $menu_items ) ) { |
|
455 return ''; |
|
456 } |
|
457 |
|
458 $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items ); |
|
459 $parsed_blocks = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); |
|
460 $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes ); |
|
461 } |
|
462 |
|
463 // Load inner blocks from the navigation post. |
|
464 if ( array_key_exists( 'ref', $attributes ) ) { |
|
465 $navigation_post = get_post( $attributes['ref'] ); |
|
466 if ( ! isset( $navigation_post ) ) { |
|
467 return ''; |
|
468 } |
|
469 |
|
470 $nav_menu_name = $navigation_post->post_title; |
|
471 |
|
472 if ( isset( $seen_menu_names[ $nav_menu_name ] ) ) { |
|
473 ++$seen_menu_names[ $nav_menu_name ]; |
|
474 } else { |
|
475 $seen_menu_names[ $nav_menu_name ] = 1; |
|
476 } |
|
477 |
|
478 $parsed_blocks = parse_blocks( $navigation_post->post_content ); |
|
479 |
|
480 // 'parse_blocks' includes a null block with '\n\n' as the content when |
|
481 // it encounters whitespace. This code strips it. |
|
482 $compacted_blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); |
|
483 |
|
484 // TODO - this uses the full navigation block attributes for the |
|
485 // context which could be refined. |
|
486 $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes ); |
|
487 } |
|
488 |
|
489 // If there are no inner blocks then fallback to rendering an appropriate fallback. |
|
490 if ( empty( $inner_blocks ) ) { |
|
491 $is_fallback = true; // indicate we are rendering the fallback. |
|
492 |
|
493 $fallback_blocks = block_core_navigation_get_fallback_blocks(); |
|
494 |
|
495 // Fallback my have been filtered so do basic test for validity. |
|
496 if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { |
|
497 return ''; |
|
498 } |
|
499 |
|
500 $inner_blocks = new WP_Block_List( $fallback_blocks, $attributes ); |
|
501 |
|
502 } |
|
503 |
|
504 $layout_justification = array( |
|
505 'left' => 'items-justified-left', |
|
506 'right' => 'items-justified-right', |
|
507 'center' => 'items-justified-center', |
|
508 'space-between' => 'items-justified-space-between', |
|
509 ); |
|
510 |
|
511 // Restore legacy classnames for submenu positioning. |
|
512 $layout_class = ''; |
|
513 if ( isset( $attributes['layout']['justifyContent'] ) ) { |
|
514 $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ]; |
|
515 } |
|
516 if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) { |
|
517 $layout_class .= ' is-vertical'; |
|
518 } |
|
519 |
|
520 if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) { |
|
521 $layout_class .= ' no-wrap'; |
|
522 } |
|
523 |
|
524 // Manually add block support text decoration as CSS class. |
|
525 $text_decoration = _wp_array_get( $attributes, array( 'style', 'typography', 'textDecoration' ), null ); |
|
526 $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); |
|
527 |
|
528 $colors = block_core_navigation_build_css_colors( $attributes ); |
|
529 $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); |
|
530 $classes = array_merge( |
|
531 $colors['css_classes'], |
|
532 $font_sizes['css_classes'], |
|
533 $is_responsive_menu ? array( 'is-responsive' ) : array(), |
|
534 $layout_class ? array( $layout_class ) : array(), |
|
535 $is_fallback ? array( 'is-fallback' ) : array(), |
|
536 $text_decoration ? array( $text_decoration_class ) : array() |
|
537 ); |
|
538 |
|
539 $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); |
|
540 if ( $post_ids ) { |
|
541 _prime_post_caches( $post_ids, false, false ); |
|
542 } |
|
543 |
|
544 $inner_blocks_html = ''; |
|
545 $is_list_open = false; |
|
546 foreach ( $inner_blocks as $inner_block ) { |
|
547 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 ) { |
|
548 $is_list_open = true; |
|
549 $inner_blocks_html .= '<ul class="wp-block-navigation__container">'; |
|
550 } |
|
551 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 ) { |
|
552 $is_list_open = false; |
|
553 $inner_blocks_html .= '</ul>'; |
|
554 } |
|
555 if ( 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name ) { |
|
556 $inner_blocks_html .= '<li class="wp-block-navigation-item">' . $inner_block->render() . '</li>'; |
|
557 } else { |
|
558 $inner_blocks_html .= $inner_block->render(); |
|
559 } |
|
560 } |
|
561 |
|
562 if ( $is_list_open ) { |
|
563 $inner_blocks_html .= '</ul>'; |
|
564 } |
|
565 |
|
566 $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; |
|
567 |
|
568 // If the menu name has been used previously then append an ID |
|
569 // to the name to ensure uniqueness across a given post. |
|
570 if ( isset( $seen_menu_names[ $nav_menu_name ] ) && $seen_menu_names[ $nav_menu_name ] > 1 ) { |
|
571 $count = $seen_menu_names[ $nav_menu_name ]; |
|
572 $nav_menu_name = $nav_menu_name . ' ' . ( $count ); |
|
573 } |
|
574 |
|
575 $wrapper_attributes = get_block_wrapper_attributes( |
|
576 array( |
|
577 'class' => implode( ' ', $classes ), |
|
578 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'], |
|
579 'aria-label' => $nav_menu_name, |
|
580 ) |
|
581 ); |
|
582 |
|
583 $modal_unique_id = wp_unique_id( 'modal-' ); |
|
584 |
|
585 // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive, |
|
586 // return early if they don't. |
|
587 if ( ! $is_responsive_menu ) { |
|
588 return sprintf( |
|
589 '<nav %1$s>%2$s</nav>', |
|
590 $wrapper_attributes, |
|
591 $inner_blocks_html |
|
592 ); |
|
593 } |
|
594 |
|
595 $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; |
|
596 |
|
597 $responsive_container_classes = array( |
|
598 'wp-block-navigation__responsive-container', |
|
599 $is_hidden_by_default ? 'hidden-by-default' : '', |
|
600 implode( ' ', $colors['overlay_css_classes'] ), |
|
601 ); |
|
602 $open_button_classes = array( |
|
603 'wp-block-navigation__responsive-container-open', |
|
604 $is_hidden_by_default ? 'always-shown' : '', |
|
605 ); |
|
606 |
|
607 $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>'; |
|
608 $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; |
|
609 $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : 'Menu'; |
|
610 |
|
611 $responsive_container_markup = sprintf( |
|
612 '<button aria-haspopup="true" aria-label="%3$s" class="%6$s" data-micromodal-trigger="%1$s">%9$s</button> |
|
613 <div class="%5$s" style="%7$s" id="%1$s"> |
|
614 <div class="wp-block-navigation__responsive-close" tabindex="-1" data-micromodal-close> |
|
615 <div class="wp-block-navigation__responsive-dialog" aria-label="%8$s"> |
|
616 <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> |
|
617 <div class="wp-block-navigation__responsive-container-content" id="%1$s-content"> |
|
618 %2$s |
|
619 </div> |
|
620 </div> |
|
621 </div> |
|
622 </div>', |
|
623 esc_attr( $modal_unique_id ), |
|
624 $inner_blocks_html, |
|
625 __( 'Open menu' ), // Open button label. |
|
626 __( 'Close menu' ), // Close button label. |
|
627 esc_attr( implode( ' ', $responsive_container_classes ) ), |
|
628 esc_attr( implode( ' ', $open_button_classes ) ), |
|
629 safecss_filter_attr( $colors['overlay_inline_styles'] ), |
|
630 __( 'Menu' ), |
|
631 $toggle_button_content |
|
632 ); |
|
633 |
|
634 return sprintf( |
|
635 '<nav %1$s>%2$s</nav>', |
|
636 $wrapper_attributes, |
|
637 $responsive_container_markup |
|
638 ); |
|
639 } |
1164 } |
640 |
1165 |
641 /** |
1166 /** |
642 * Register the navigation block. |
1167 * Register the navigation block. |
|
1168 * |
|
1169 * @since 5.9.0 |
643 * |
1170 * |
644 * @uses render_block_core_navigation() |
1171 * @uses render_block_core_navigation() |
645 * @throws WP_Error An WP_Error exception parsing the block definition. |
1172 * @throws WP_Error An WP_Error exception parsing the block definition. |
646 */ |
1173 */ |
647 function register_block_core_navigation() { |
1174 function register_block_core_navigation() { |
686 |
1215 |
687 return $parsed_block; |
1216 return $parsed_block; |
688 } |
1217 } |
689 |
1218 |
690 add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); |
1219 add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); |
|
1220 |
|
1221 /** |
|
1222 * Turns menu item data into a nested array of parsed blocks |
|
1223 * |
|
1224 * @since 5.9.0 |
|
1225 * |
|
1226 * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead. |
|
1227 * |
|
1228 * @param array $menu_items An array of menu items that represent |
|
1229 * an individual level of a menu. |
|
1230 * @param array $menu_items_by_parent_id An array keyed by the id of the |
|
1231 * parent menu where each element is an |
|
1232 * array of menu items that belong to |
|
1233 * that parent. |
|
1234 * @return array An array of parsed block data. |
|
1235 */ |
|
1236 function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { |
|
1237 |
|
1238 _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::parse_blocks_from_menu_items' ); |
|
1239 |
|
1240 if ( empty( $menu_items ) ) { |
|
1241 return array(); |
|
1242 } |
|
1243 |
|
1244 $blocks = array(); |
|
1245 |
|
1246 foreach ( $menu_items as $menu_item ) { |
|
1247 $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; |
|
1248 $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; |
|
1249 $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; |
|
1250 $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; |
|
1251 $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; |
|
1252 |
|
1253 $block = array( |
|
1254 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', |
|
1255 'attrs' => array( |
|
1256 'className' => $class_name, |
|
1257 'description' => $menu_item->description, |
|
1258 'id' => $id, |
|
1259 'kind' => $kind, |
|
1260 'label' => $menu_item->title, |
|
1261 'opensInNewTab' => $opens_in_new_tab, |
|
1262 'rel' => $rel, |
|
1263 'title' => $menu_item->attr_title, |
|
1264 'type' => $menu_item->object, |
|
1265 'url' => $menu_item->url, |
|
1266 ), |
|
1267 ); |
|
1268 |
|
1269 $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) |
|
1270 ? block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) |
|
1271 : array(); |
|
1272 $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); |
|
1273 |
|
1274 $blocks[] = $block; |
|
1275 } |
|
1276 |
|
1277 return $blocks; |
|
1278 } |
|
1279 |
|
1280 /** |
|
1281 * Get the classic navigation menu to use as a fallback. |
|
1282 * |
|
1283 * @since 6.2.0 |
|
1284 * |
|
1285 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback() instead. |
|
1286 * |
|
1287 * @return object WP_Term The classic navigation. |
|
1288 */ |
|
1289 function block_core_navigation_get_classic_menu_fallback() { |
|
1290 |
|
1291 _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback' ); |
|
1292 |
|
1293 $classic_nav_menus = wp_get_nav_menus(); |
|
1294 |
|
1295 // If menus exist. |
|
1296 if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) ) { |
|
1297 // Handles simple use case where user has a classic menu and switches to a block theme. |
|
1298 |
|
1299 // Returns the menu assigned to location `primary`. |
|
1300 $locations = get_nav_menu_locations(); |
|
1301 if ( isset( $locations['primary'] ) ) { |
|
1302 $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); |
|
1303 if ( $primary_menu ) { |
|
1304 return $primary_menu; |
|
1305 } |
|
1306 } |
|
1307 |
|
1308 // Returns a menu if `primary` is its slug. |
|
1309 foreach ( $classic_nav_menus as $classic_nav_menu ) { |
|
1310 if ( 'primary' === $classic_nav_menu->slug ) { |
|
1311 return $classic_nav_menu; |
|
1312 } |
|
1313 } |
|
1314 |
|
1315 // Otherwise return the most recently created classic menu. |
|
1316 usort( |
|
1317 $classic_nav_menus, |
|
1318 static function ( $a, $b ) { |
|
1319 return $b->term_id - $a->term_id; |
|
1320 } |
|
1321 ); |
|
1322 return $classic_nav_menus[0]; |
|
1323 } |
|
1324 } |
|
1325 |
|
1326 /** |
|
1327 * Converts a classic navigation to blocks. |
|
1328 * |
|
1329 * @since 6.2.0 |
|
1330 * |
|
1331 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead. |
|
1332 * |
|
1333 * @param object $classic_nav_menu WP_Term The classic navigation object to convert. |
|
1334 * @return array the normalized parsed blocks. |
|
1335 */ |
|
1336 function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) { |
|
1337 |
|
1338 _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks' ); |
|
1339 |
|
1340 // BEGIN: Code that already exists in wp_nav_menu(). |
|
1341 $menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) ); |
|
1342 |
|
1343 // Set up the $menu_item variables. |
|
1344 _wp_menu_item_classes_by_context( $menu_items ); |
|
1345 |
|
1346 $sorted_menu_items = array(); |
|
1347 foreach ( (array) $menu_items as $menu_item ) { |
|
1348 $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; |
|
1349 } |
|
1350 |
|
1351 unset( $menu_items, $menu_item ); |
|
1352 |
|
1353 // END: Code that already exists in wp_nav_menu(). |
|
1354 |
|
1355 $menu_items_by_parent_id = array(); |
|
1356 foreach ( $sorted_menu_items as $menu_item ) { |
|
1357 $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; |
|
1358 } |
|
1359 |
|
1360 $inner_blocks = block_core_navigation_parse_blocks_from_menu_items( |
|
1361 isset( $menu_items_by_parent_id[0] ) |
|
1362 ? $menu_items_by_parent_id[0] |
|
1363 : array(), |
|
1364 $menu_items_by_parent_id |
|
1365 ); |
|
1366 |
|
1367 return serialize_blocks( $inner_blocks ); |
|
1368 } |
|
1369 |
|
1370 /** |
|
1371 * If there's a classic menu then use it as a fallback. |
|
1372 * |
|
1373 * @since 6.2.0 |
|
1374 * |
|
1375 * @deprecated 6.3.0 Use WP_Navigation_Fallback::create_classic_menu_fallback() instead. |
|
1376 * |
|
1377 * @return array the normalized parsed blocks. |
|
1378 */ |
|
1379 function block_core_navigation_maybe_use_classic_menu_fallback() { |
|
1380 |
|
1381 _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::create_classic_menu_fallback' ); |
|
1382 |
|
1383 // See if we have a classic menu. |
|
1384 $classic_nav_menu = block_core_navigation_get_classic_menu_fallback(); |
|
1385 |
|
1386 if ( ! $classic_nav_menu ) { |
|
1387 return; |
|
1388 } |
|
1389 |
|
1390 // If we have a classic menu then convert it to blocks. |
|
1391 $classic_nav_menu_blocks = block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ); |
|
1392 |
|
1393 if ( empty( $classic_nav_menu_blocks ) ) { |
|
1394 return; |
|
1395 } |
|
1396 |
|
1397 // Create a new navigation menu from the classic menu. |
|
1398 $wp_insert_post_result = wp_insert_post( |
|
1399 array( |
|
1400 'post_content' => $classic_nav_menu_blocks, |
|
1401 'post_title' => $classic_nav_menu->name, |
|
1402 'post_name' => $classic_nav_menu->slug, |
|
1403 'post_status' => 'publish', |
|
1404 'post_type' => 'wp_navigation', |
|
1405 ), |
|
1406 true // So that we can check whether the result is an error. |
|
1407 ); |
|
1408 |
|
1409 if ( is_wp_error( $wp_insert_post_result ) ) { |
|
1410 return; |
|
1411 } |
|
1412 |
|
1413 // Fetch the most recently published navigation which will be the classic one created above. |
|
1414 return block_core_navigation_get_most_recently_published_navigation(); |
|
1415 } |
|
1416 |
|
1417 /** |
|
1418 * Finds the most recently published `wp_navigation` Post. |
|
1419 * |
|
1420 * @since 6.1.0 |
|
1421 * |
|
1422 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_most_recently_published_navigation() instead. |
|
1423 * |
|
1424 * @return WP_Post|null the first non-empty Navigation or null. |
|
1425 */ |
|
1426 function block_core_navigation_get_most_recently_published_navigation() { |
|
1427 |
|
1428 _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_most_recently_published_navigation' ); |
|
1429 |
|
1430 // Default to the most recently created menu. |
|
1431 $parsed_args = array( |
|
1432 'post_type' => 'wp_navigation', |
|
1433 'no_found_rows' => true, |
|
1434 'update_post_meta_cache' => false, |
|
1435 'update_post_term_cache' => false, |
|
1436 'order' => 'DESC', |
|
1437 'orderby' => 'date', |
|
1438 'post_status' => 'publish', |
|
1439 'posts_per_page' => 1, // get only the most recent. |
|
1440 ); |
|
1441 |
|
1442 $navigation_post = new WP_Query( $parsed_args ); |
|
1443 if ( count( $navigation_post->posts ) > 0 ) { |
|
1444 return $navigation_post->posts[0]; |
|
1445 } |
|
1446 |
|
1447 return null; |
|
1448 } |
|
1449 |
|
1450 /** |
|
1451 * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks. |
|
1452 * |
|
1453 * @since 6.5.0 |
|
1454 * |
|
1455 * @param string $serialized_block The serialized markup of a block and its inner blocks. |
|
1456 * @return string |
|
1457 */ |
|
1458 function block_core_navigation_remove_serialized_parent_block( $serialized_block ) { |
|
1459 $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); |
|
1460 $end = strrpos( $serialized_block, '<!--' ); |
|
1461 return substr( $serialized_block, $start, $end - $start ); |
|
1462 } |
|
1463 |
|
1464 /** |
|
1465 * Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object. |
|
1466 * The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute. |
|
1467 * |
|
1468 * @since 6.5.0 |
|
1469 * |
|
1470 * @param array $inner_blocks Parsed inner blocks of a Navigation block. |
|
1471 * @param WP_Post $post `wp_navigation` post object corresponding to the block. |
|
1472 * |
|
1473 * @return array the normalized parsed blocks. |
|
1474 */ |
|
1475 function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) { |
|
1476 $attributes = array(); |
|
1477 |
|
1478 if ( isset( $post->ID ) ) { |
|
1479 $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); |
|
1480 if ( ! empty( $ignored_hooked_blocks ) ) { |
|
1481 $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); |
|
1482 $attributes['metadata'] = array( |
|
1483 'ignoredHookedBlocks' => $ignored_hooked_blocks, |
|
1484 ); |
|
1485 } |
|
1486 } |
|
1487 |
|
1488 $mock_anchor_parent_block = array( |
|
1489 'blockName' => 'core/navigation', |
|
1490 'attrs' => $attributes, |
|
1491 'innerBlocks' => $inner_blocks, |
|
1492 'innerContent' => array_fill( 0, count( $inner_blocks ), null ), |
|
1493 ); |
|
1494 |
|
1495 return $mock_anchor_parent_block; |
|
1496 } |
|
1497 |
|
1498 /** |
|
1499 * Insert hooked blocks into a Navigation block. |
|
1500 * |
|
1501 * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, |
|
1502 * this function inserts hooked blocks into it, and returns the serialized inner blocks in a |
|
1503 * mock Navigation block wrapper. |
|
1504 * |
|
1505 * If there are any hooked blocks that need to be inserted as the Navigation block's first or last |
|
1506 * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any |
|
1507 * of those hooked blocks should be exempted from insertion. |
|
1508 * |
|
1509 * @since 6.5.0 |
|
1510 * |
|
1511 * @param array $inner_blocks Parsed inner blocks of a Navigation block. |
|
1512 * @param WP_Post $post `wp_navigation` post object corresponding to the block. |
|
1513 * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any. |
|
1514 */ |
|
1515 function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) { |
|
1516 $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post ); |
|
1517 $hooked_blocks = get_hooked_blocks(); |
|
1518 $before_block_visitor = null; |
|
1519 $after_block_visitor = null; |
|
1520 |
|
1521 if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { |
|
1522 $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' ); |
|
1523 $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' ); |
|
1524 } |
|
1525 |
|
1526 return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); |
|
1527 } |
|
1528 |
|
1529 /** |
|
1530 * Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks. |
|
1531 * |
|
1532 * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, |
|
1533 * this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a |
|
1534 * mock Navigation block wrapper. |
|
1535 * |
|
1536 * @since 6.5.0 |
|
1537 * |
|
1538 * @param array $inner_blocks Parsed inner blocks of a Navigation block. |
|
1539 * @param WP_Post $post `wp_navigation` post object corresponding to the block. |
|
1540 * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any. |
|
1541 */ |
|
1542 function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) { |
|
1543 $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post ); |
|
1544 $hooked_blocks = get_hooked_blocks(); |
|
1545 $before_block_visitor = null; |
|
1546 $after_block_visitor = null; |
|
1547 |
|
1548 if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { |
|
1549 $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); |
|
1550 $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); |
|
1551 } |
|
1552 |
|
1553 return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); |
|
1554 } |
|
1555 |
|
1556 /** |
|
1557 * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API. |
|
1558 * |
|
1559 * @access private |
|
1560 * @since 6.5.0 |
|
1561 * |
|
1562 * @param stdClass $post Post object. |
|
1563 * @return stdClass The updated post object. |
|
1564 */ |
|
1565 function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) { |
|
1566 /* |
|
1567 * In this scenario the user has likely tried to create a navigation via the REST API. |
|
1568 * In which case we won't have a post ID to work with and store meta against. |
|
1569 */ |
|
1570 if ( empty( $post->ID ) ) { |
|
1571 return $post; |
|
1572 } |
|
1573 |
|
1574 /** |
|
1575 * Skip meta generation when consumers intentionally update specific Navigation fields |
|
1576 * and omit the content update. |
|
1577 */ |
|
1578 if ( ! isset( $post->post_content ) ) { |
|
1579 return $post; |
|
1580 } |
|
1581 |
|
1582 /* |
|
1583 * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into |
|
1584 * all anchor blocks. For the root level, we create a mock Navigation and extract them from there. |
|
1585 */ |
|
1586 $blocks = parse_blocks( $post->post_content ); |
|
1587 |
|
1588 /* |
|
1589 * Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that |
|
1590 * we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be |
|
1591 * used as context for hooked blocks insertion). |
|
1592 * We thus have to look it up from the DB,based on `$post->ID`. |
|
1593 */ |
|
1594 $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) ); |
|
1595 |
|
1596 $root_nav_block = parse_blocks( $markup )[0]; |
|
1597 $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] ) |
|
1598 ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] |
|
1599 : array(); |
|
1600 |
|
1601 if ( ! empty( $ignored_hooked_blocks ) ) { |
|
1602 $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); |
|
1603 if ( ! empty( $existing_ignored_hooked_blocks ) ) { |
|
1604 $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); |
|
1605 $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); |
|
1606 } |
|
1607 update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) ); |
|
1608 } |
|
1609 |
|
1610 $post->post_content = block_core_navigation_remove_serialized_parent_block( $markup ); |
|
1611 return $post; |
|
1612 } |
|
1613 |
|
1614 /* |
|
1615 * Before adding our filter, we verify if it's already added in Core. |
|
1616 * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". |
|
1617 * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. |
|
1618 */ |
|
1619 $rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found |
|
1620 |
|
1621 /* |
|
1622 * Do not add the `block_core_navigation_update_ignore_hooked_blocks_meta` filter in the following cases: |
|
1623 * - If Core has added the `update_ignored_hooked_blocks_postmeta` filter already (WP >= 6.6); |
|
1624 * - or if the `set_ignored_hooked_blocks_metadata` function is unavailable (which is required for the filter to work. It was introduced by WP 6.5 but is not present in Gutenberg's WP 6.5 compatibility layer); |
|
1625 * - or if the `$rest_insert_wp_navigation_core_callback` filter has already been added. |
|
1626 */ |
|
1627 if ( |
|
1628 ! has_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' ) && |
|
1629 function_exists( 'set_ignored_hooked_blocks_metadata' ) && |
|
1630 ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) |
|
1631 ) { |
|
1632 add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' ); |
|
1633 } |
|
1634 |
|
1635 /* |
|
1636 * Previous versions of Gutenberg were attaching the block_core_navigation_update_ignore_hooked_blocks_meta |
|
1637 * function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_). |
|
1638 * To avoid collisions, we need to remove the filter from that action if it's present. |
|
1639 */ |
|
1640 if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) { |
|
1641 remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ); |
|
1642 } |
|
1643 |
|
1644 /** |
|
1645 * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks. |
|
1646 * |
|
1647 * @since 6.5.0 |
|
1648 * |
|
1649 * @param WP_REST_Response $response The response object. |
|
1650 * @param WP_Post $post Post object. |
|
1651 * @return WP_REST_Response The response object. |
|
1652 */ |
|
1653 function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) { |
|
1654 if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) { |
|
1655 return $response; |
|
1656 } |
|
1657 $parsed_blocks = parse_blocks( $response->data['content']['raw'] ); |
|
1658 $content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post ); |
|
1659 |
|
1660 // Remove mock Navigation block wrapper. |
|
1661 $content = block_core_navigation_remove_serialized_parent_block( $content ); |
|
1662 |
|
1663 $response->data['content']['raw'] = $content; |
|
1664 |
|
1665 /** This filter is documented in wp-includes/post-template.php */ |
|
1666 $response->data['content']['rendered'] = apply_filters( 'the_content', $content ); |
|
1667 |
|
1668 return $response; |
|
1669 } |
|
1670 |
|
1671 /* |
|
1672 * Before adding our filter, we verify if it's already added in Core. |
|
1673 * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". |
|
1674 * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. |
|
1675 */ |
|
1676 $rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response'; |
|
1677 |
|
1678 /* |
|
1679 * Do not add the `block_core_navigation_insert_hooked_blocks_into_rest_response` filter in the following cases: |
|
1680 * - If Core has added the `insert_hooked_blocks_into_rest_response` filter already (WP >= 6.6); |
|
1681 * - or if the `set_ignored_hooked_blocks_metadata` function is unavailable (which is required for the filter to work. It was introduced by WP 6.5 but is not present in Gutenberg's WP 6.5 compatibility layer); |
|
1682 * - or if the `$rest_prepare_wp_navigation_core_callback` filter has already been added. |
|
1683 */ |
|
1684 if ( |
|
1685 ! has_filter( 'rest_prepare_wp_navigation', 'insert_hooked_blocks_into_rest_response' ) && |
|
1686 function_exists( 'set_ignored_hooked_blocks_metadata' ) && |
|
1687 ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) |
|
1688 ) { |
|
1689 add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 ); |
|
1690 } |