PluginProbe
Gutenberg / 23.3.2
Gutenberg v23.3.2
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 / scripts / block-library / navigation-submenu.php

navigation-submenu.php in Gutenberg 23.3.2, at build/scripts/block-library/navigation-submenu.php

334 lines 11.5 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-submenu` block.
4 *
5 * @package WordPress
6 */
7
8 require_once __DIR__ . '/navigation-link/shared/item-should-render.php';
9 require_once __DIR__ . '/navigation-link/shared/render-submenu-icon.php';
10 require_once __DIR__ . '/navigation-link/shared/build-css-font-sizes.php';
11
12 /**
13 * Renders the submenu icon SVG for the Navigation Submenu block.
14 *
15 * @since 5.9.0
16 * @deprecated 7.0.0 Use block_core_shared_navigation_render_submenu_icon() instead.
17 *
18 * @return string SVG markup for the submenu icon.
19 */
20 function gutenberg_block_core_navigation_submenu_render_submenu_icon() {
21 _deprecated_function( __FUNCTION__, '7.0.0', 'block_core_shared_navigation_render_submenu_icon()' );
22 return block_core_shared_navigation_render_submenu_icon();
23 }
24
25 /**
26 * Returns the submenu visibility value with backward compatibility
27 * for the deprecated openSubmenusOnClick attribute.
28 *
29 * This function centralizes the migration logic from the boolean
30 * openSubmenusOnClick to the new submenuVisibility enum.
31 *
32 * Backward compatibility handling:
33 * - Legacy blocks (saved before migration, never opened in editor):
34 * Have openSubmenusOnClick in database. Parent Navigation block passes it via context.
35 * We prioritize openSubmenusOnClick to preserve the original behavior.
36 *
37 * - Migrated blocks (opened in editor after migration):
38 * JavaScript deprecation removes openSubmenusOnClick and sets submenuVisibility.
39 * We use submenuVisibility since openSubmenusOnClick is null.
40 *
41 * - New blocks (created after migration):
42 * Only have submenuVisibility, openSubmenusOnClick is null.
43 * We use submenuVisibility.
44 *
45 * @since 6.9.0
46 *
47 * @param array $context Block context from parent Navigation block.
48 * @return string The visibility mode: 'hover', 'click', or 'always'.
49 */
50 function gutenberg_block_core_navigation_submenu_get_submenu_visibility( $context ) {
51 $deprecated_open_submenus_on_click = $context['openSubmenusOnClick'] ?? null;
52
53 // For backward compatibility, prioritize the legacy attribute if present. If it has been loaded and saved in the editor, then
54 // the deprecated attribute will be replaced by submenuVisibility.
55 if ( null !== $deprecated_open_submenus_on_click ) {
56 // Convert boolean to string: true -> 'click', false -> 'hover'.
57 return ! empty( $deprecated_open_submenus_on_click ) ? 'click' : 'hover';
58 }
59
60 $submenu_visibility = $context['submenuVisibility'] ?? null;
61
62 // Use submenuVisibility for migrated/new blocks.
63 return $submenu_visibility ?? 'hover';
64 }
65
66 /**
67 * Renders the `core/navigation-submenu` block.
68 *
69 * @since 5.9.0
70 *
71 * @param array $attributes The block attributes.
72 * @param string $content The saved content.
73 * @param WP_Block $block The parsed block.
74 *
75 * @return string Returns the post content with the legacy widget added.
76 */
77 function gutenberg_render_block_core_navigation_submenu( $attributes, $content, $block ) {
78 // Check if this navigation item should render based on post status.
79 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
80 if ( ! gutenberg_block_core_shared_navigation_item_should_render( $attributes, $block ) ) {
81 return '';
82 }
83 }
84
85 // Don't render the block's subtree if it has no label.
86 if ( empty( $attributes['label'] ) ) {
87 return '';
88 }
89
90 // The build system prefixes this function with "gutenberg_" to avoid
91 // collisions with the core version. Until this function is backported to
92 // core, we need to guard its use and only call the prefixed name in
93 // the plugin.
94 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
95 $font_sizes = gutenberg_block_core_shared_navigation_build_css_font_sizes( $block->context );
96 } else {
97 $font_sizes = block_core_shared_navigation_build_css_font_sizes( $block->context );
98 }
99 $style_attribute = $font_sizes['inline_styles'];
100
101 // Render inner blocks first to check if any menu items will actually display.
102 $inner_blocks_html = '';
103 foreach ( $block->inner_blocks as $inner_block ) {
104 $inner_blocks_html .= $inner_block->render();
105 }
106 $has_submenu = ! empty( trim( $inner_blocks_html ) );
107
108 $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] );
109 $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind );
110
111 if ( is_post_type_archive() && ! empty( $attributes['url'] ) ) {
112 $queried_archive_link = get_post_type_archive_link( get_queried_object()->name );
113 if ( $attributes['url'] === $queried_archive_link ) {
114 $is_active = true;
115 }
116 }
117
118 $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'];
119 $computed_visibility = gutenberg_block_core_navigation_submenu_get_submenu_visibility( $block->context );
120 $open_on_click = 'click' === $computed_visibility;
121 $open_on_hover = 'hover' === $computed_visibility;
122 $open_on_hover_and_click = $open_on_hover && $show_submenu_indicators;
123
124 $classes = array(
125 'wp-block-navigation-item',
126 );
127 $classes = array_merge(
128 $classes,
129 $font_sizes['css_classes']
130 );
131 if ( $has_submenu ) {
132 $classes[] = 'has-child';
133 }
134 if ( $open_on_click ) {
135 $classes[] = 'open-on-click';
136 }
137 if ( $open_on_hover_and_click ) {
138 $classes[] = 'open-on-hover-click';
139 }
140 if ( 'always' === $computed_visibility ) {
141 $classes[] = 'open-always';
142 }
143 if ( $is_active ) {
144 $classes[] = 'current-menu-item';
145 }
146
147 $wrapper_attributes = get_block_wrapper_attributes(
148 array(
149 'class' => implode( ' ', $classes ),
150 'style' => $style_attribute,
151 )
152 );
153
154 $label = '';
155
156 if ( isset( $attributes['label'] ) ) {
157 $label .= wp_kses_post( $attributes['label'] );
158 }
159
160 $aria_label = sprintf(
161 /* translators: Accessibility text. %s: Parent page title. */
162 __( '%s submenu' ),
163 wp_strip_all_tags( $label )
164 );
165
166 $html = '<li ' . $wrapper_attributes . '>';
167
168 // If Submenus open on hover or are always open, we render an anchor tag with attributes.
169 // If submenu icons are set to show, we also render a submenu button, so the submenu can be opened on click.
170 if ( ! $open_on_click ) {
171 $item_url = $attributes['url'] ?? '';
172 // Start appending HTML attributes to anchor tag.
173 $html .= '<a class="wp-block-navigation-item__content"';
174
175 // The href attribute on a and area elements is not required;
176 // when those elements do not have href attributes they do not create hyperlinks.
177 // But also The href attribute must have a value that is a valid URL potentially
178 // surrounded by spaces.
179 // see: https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements.
180 if ( ! empty( $item_url ) ) {
181 $html .= ' href="' . esc_url( $item_url ) . '"';
182 }
183
184 if ( $is_active ) {
185 $html .= ' aria-current="page"';
186 }
187
188 if ( isset( $attributes['opensInNewTab'] ) && true === $attributes['opensInNewTab'] ) {
189 $html .= ' target="_blank" ';
190 }
191
192 if ( isset( $attributes['rel'] ) ) {
193 $html .= ' rel="' . esc_attr( $attributes['rel'] ) . '"';
194 } elseif ( isset( $attributes['nofollow'] ) && $attributes['nofollow'] ) {
195 $html .= ' rel="nofollow"';
196 }
197
198 if ( isset( $attributes['title'] ) ) {
199 $html .= ' title="' . esc_attr( $attributes['title'] ) . '"';
200 }
201
202 $html .= '>';
203 // End appending HTML attributes to anchor tag.
204
205 $html .= '<span class="wp-block-navigation-item__label">';
206 $html .= $label;
207 $html .= '</span>';
208
209 // Add description if available.
210 if ( ! empty( $attributes['description'] ) ) {
211 $html .= '<span class="wp-block-navigation-item__description">';
212 $html .= wp_kses_post( $attributes['description'] );
213 $html .= '</span>';
214 }
215
216 $html .= '</a>';
217 // End anchor tag content.
218
219 if ( $show_submenu_indicators && $has_submenu ) {
220 // The submenu icon is rendered in a button here
221 // so that there's a clickable element to open the submenu.
222 $html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation__submenu-icon wp-block-navigation-submenu__toggle" aria-expanded="false">';
223 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
224 $html .= gutenberg_block_core_shared_navigation_render_submenu_icon();
225 } else {
226 $html .= block_core_shared_navigation_render_submenu_icon();
227 }
228 $html .= '</button>';
229 }
230 } else {
231 $html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation-item__content wp-block-navigation-submenu__toggle" aria-expanded="false">';
232
233 // Wrap title with span to isolate it from submenu icon.
234 $html .= '<span class="wp-block-navigation-item__label">';
235
236 $html .= $label;
237
238 $html .= '</span>';
239
240 // Add description if available.
241 if ( ! empty( $attributes['description'] ) ) {
242 $html .= '<span class="wp-block-navigation-item__description">';
243 $html .= wp_kses_post( $attributes['description'] );
244 $html .= '</span>';
245 }
246
247 $html .= '</button>';
248
249 if ( $has_submenu ) {
250 $html .= '<span class="wp-block-navigation__submenu-icon">';
251 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
252 $html .= gutenberg_block_core_shared_navigation_render_submenu_icon();
253 } else {
254 $html .= block_core_shared_navigation_render_submenu_icon();
255 }
256 $html .= '</span>';
257 }
258 }
259
260 if ( $has_submenu ) {
261 // Copy some attributes from the parent block to this one.
262 // Ideally this would happen in the client when the block is created.
263 if ( array_key_exists( 'overlayTextColor', $block->context ) ) {
264 $attributes['textColor'] = $block->context['overlayTextColor'];
265 }
266 if ( array_key_exists( 'overlayBackgroundColor', $block->context ) ) {
267 $attributes['backgroundColor'] = $block->context['overlayBackgroundColor'];
268 }
269 if ( array_key_exists( 'customOverlayTextColor', $block->context ) ) {
270 $attributes['style']['color']['text'] = $block->context['customOverlayTextColor'];
271 }
272 if ( array_key_exists( 'customOverlayBackgroundColor', $block->context ) ) {
273 $attributes['style']['color']['background'] = $block->context['customOverlayBackgroundColor'];
274 }
275
276 // This allows us to be able to get a response from gutenberg_apply_colors_support.
277 $block->block_type->supports['color'] = true;
278 $colors_supports = gutenberg_apply_colors_support( $block->block_type, $attributes );
279 $css_classes = 'wp-block-navigation__submenu-container';
280 if ( array_key_exists( 'class', $colors_supports ) ) {
281 $css_classes .= ' ' . $colors_supports['class'];
282 }
283
284 $style_attribute = '';
285 if ( array_key_exists( 'style', $colors_supports ) ) {
286 $style_attribute = $colors_supports['style'];
287 }
288
289 if ( strpos( $inner_blocks_html, 'current-menu-item' ) ) {
290 $tag_processor = new WP_HTML_Tag_Processor( $html );
291 while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item' ) ) ) {
292 $tag_processor->add_class( 'current-menu-ancestor' );
293 }
294 $html = $tag_processor->get_updated_html();
295 }
296
297 $wrapper_attributes = get_block_wrapper_attributes(
298 array(
299 'class' => $css_classes,
300 'style' => $style_attribute,
301 )
302 );
303
304 $html .= sprintf(
305 '<ul %s>%s</ul>',
306 $wrapper_attributes,
307 $inner_blocks_html
308 );
309
310 }
311
312 $html .= '</li>';
313
314 return $html;
315 }
316
317 /**
318 * Register the navigation submenu block.
319 *
320 * @since 5.9.0
321 *
322 * @uses gutenberg_render_block_core_navigation_submenu()
323 * @throws WP_Error An WP_Error exception parsing the block definition.
324 */
325 function gutenberg_register_block_core_navigation_submenu() {
326 register_block_type_from_metadata(
327 __DIR__ . '/navigation-submenu',
328 array(
329 'render_callback' => 'gutenberg_render_block_core_navigation_submenu',
330 )
331 );
332 }
333 add_action( 'init', 'gutenberg_register_block_core_navigation_submenu', 20 );
334