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

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

376 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions used in making nav menus interopable with block editors.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Shim that hooks into `wp_update_nav_menu_item` and makes it so that nav menu
10 * items support a 'content' field. This field contains HTML and is used by nav
11 * menu items with `type` set to `'block'`.
12 *
13 * Specifically, this shim makes it so that:
14 *
15 * 1) The `wp_update_nav_menu_item()` function supports setting
16 * `'menu-item-content'` on a menu item. When merged to Core, this functionality
17 * should exist in `wp_update_nav_menu_item()`.
18 *
19 * 2) Updating a menu via nav-menus.php supports setting `'menu-item-content'`
20 * on a menu item. When merged to Core, this functionality should exist in
21 * `wp_nav_menu_update_menu_items()`.
22 *
23 * 3) The `customize_save` ajax action supports setting `'content'` on a nav
24 * menu item. When merged to Core, this functionality should exist in
25 * `WP_Customize_Manager::save()`.
26 *
27 * This shim can be removed when the Gutenberg plugin requires a WordPress
28 * version that has the ticket below.
29 *
30 * @see https://core.trac.wordpress.org/ticket/50544
31 *
32 * @param int $menu_id ID of the updated menu.
33 * @param int $menu_item_db_id ID of the new menu item.
34 * @param array $args An array of arguments used to update/add the menu item.
35 */
36 function gutenberg_update_nav_menu_item_content( $menu_id, $menu_item_db_id, $args ) {
37 global $wp_customize;
38
39 // Support setting content in nav-menus.php by grabbing the value from
40 // $_POST. This belongs in `wp_nav_menu_update_menu_items()`.
41 if ( isset( $_POST['menu-item-content'][ $menu_item_db_id ] ) ) {
42 $args['menu-item-content'] = wp_unslash( $_POST['menu-item-content'][ $menu_item_db_id ] );
43 }
44
45 // Support setting content in customize_save admin-ajax.php requests by
46 // grabbing the unsanitized $_POST values. This belongs in
47 // `WP_Customize_Manager::save()`.
48 if ( isset( $wp_customize ) ) {
49 $values = $wp_customize->unsanitized_post_values();
50 if ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
51 if ( is_string( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
52 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content'];
53 } elseif ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'] ) ) {
54 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'];
55 }
56 }
57 }
58
59 // Everything else belongs in `wp_update_nav_menu_item()`.
60
61 $defaults = array(
62 'menu-item-content' => '',
63 );
64
65 $args = wp_parse_args( $args, $defaults );
66
67 update_post_meta( $menu_item_db_id, '_menu_item_content', wp_slash( $args['menu-item-content'] ) );
68 }
69 add_action( 'wp_update_nav_menu_item', 'gutenberg_update_nav_menu_item_content', 10, 3 );
70
71 /**
72 * Shim that hooks into `wp_setup_nav_menu_items` and makes it so that nav menu
73 * items have a 'content' field. This field contains HTML and is used by nav
74 * menu items with `type` set to `'block'`.
75 *
76 * Specifically, this shim makes it so that the `wp_setup_nav_menu_item()`
77 * function sets `content` on the returned menu item. When merged to Core, this
78 * functionality should exist in `wp_setup_nav_menu_item()`.
79 *
80 * This shim can be removed when the Gutenberg plugin requires a WordPress
81 * version that has the ticket below.
82 *
83 * @see https://core.trac.wordpress.org/ticket/50544
84 *
85 * @param object $menu_item The menu item object.
86 *
87 * @return object Updated menu item object.
88 */
89 function gutenberg_setup_block_nav_menu_item( $menu_item ) {
90 if ( 'block' === $menu_item->type ) {
91 $menu_item->type_label = __( 'Block', 'gutenberg' );
92 $menu_item->content = ! isset( $menu_item->content ) ? get_post_meta( $menu_item->db_id, '_menu_item_content', true ) : $menu_item->content;
93
94 // Set to make the menu item display nicely in nav-menus.php.
95 $menu_item->object = 'block';
96 $menu_item->title = __( 'Block', 'gutenberg' );
97 }
98
99 return $menu_item;
100 }
101 add_filter( 'wp_setup_nav_menu_item', 'gutenberg_setup_block_nav_menu_item' );
102
103 /**
104 * Shim that hooks into `walker_nav_menu_start_el` and makes it so that the
105 * default walker which renders a menu will correctly render the HTML associated
106 * with any navigation menu item that has `type` set to `'block`'.
107 *
108 * Specifically, this shim makes it so that `Walker_Nav_Menu::start_el()`
109 * renders the `content` of a nav menu item when its `type` is `'block'`. When
110 * merged to Core, this functionality should exist in
111 * `Walker_Nav_Menu::start_el()`.
112 *
113 * This shim can be removed when the Gutenberg plugin requires a WordPress
114 * version that has the ticket below.
115 *
116 * @see https://core.trac.wordpress.org/ticket/50544
117 *
118 * @param string $item_output The menu item's starting HTML output.
119 * @param WP_Post $item Menu item data object.
120 * @param int $depth Depth of menu item. Used for padding.
121 * @param stdClass $args An object of wp_nav_menu() arguments.
122 *
123 * @return string The menu item's updated HTML output.
124 */
125 function gutenberg_output_block_nav_menu_item( $item_output, $item, $depth, $args ) {
126 if ( 'block' === $item->type ) {
127 $item_output = $args->before;
128 /** This filter is documented in wp-includes/post-template.php */
129 $item_output .= apply_filters( 'the_content', $item->content );
130 $item_output .= $args->after;
131 }
132
133 return $item_output;
134 }
135 add_filter( 'walker_nav_menu_start_el', 'gutenberg_output_block_nav_menu_item', 10, 4 );
136
137 /**
138 * Shim that prevents menu items with type `'block'` from being rendered in the
139 * frontend when the theme does not support block menus.
140 *
141 * Specifically, this shim makes it so that `wp_nav_menu()` will remove any menu
142 * items that have a `type` of `'block'` from `$sorted_menu_items`. When merged
143 * to Core, this functionality should exist in `wp_nav_menu()`.
144 *
145 * This shim can be removed when the Gutenberg plugin requires a WordPress
146 * version that has the ticket below.
147 *
148 * @see https://core.trac.wordpress.org/ticket/50544
149 *
150 * @param array $menu_items The menu items, sorted by each menu item's menu order.
151 *
152 * @return array Updated menu items, sorted by each menu item's menu order.
153 */
154 function gutenberg_remove_block_nav_menu_items( $menu_items ) {
155 if ( current_theme_supports( 'block-nav-menus' ) ) {
156 return $menu_items;
157 }
158
159 return array_filter(
160 $menu_items,
161 function( $menu_item ) {
162 return 'block' !== $menu_item->type;
163 }
164 );
165 }
166 add_filter( 'wp_nav_menu_objects', 'gutenberg_remove_block_nav_menu_items', 10 );
167
168 /**
169 * Recursively converts a list of menu items into a list of blocks. This is a
170 * helper function used by `gutenberg_output_block_nav_menu()`.
171 *
172 * Transformation depends on the menu item type. Link menu items are turned into
173 * a `core/navigation-link` block. Block menu items are simply parsed.
174 *
175 * @param array $menu_items The menu items to convert, sorted by each menu item's menu order.
176 * @param array $menu_items_by_parent_id All menu items, indexed by their parent's ID.
177
178 * @return array Updated menu items, sorted by each menu item's menu order.
179 */
180 function gutenberg_convert_menu_items_to_blocks(
181 $menu_items,
182 &$menu_items_by_parent_id
183 ) {
184 if ( empty( $menu_items ) ) {
185 return array();
186 }
187
188 $blocks = array();
189
190 foreach ( $menu_items as $menu_item ) {
191 if ( 'block' === $menu_item->type ) {
192 $parsed_blocks = parse_blocks( $menu_item->content );
193
194 if ( count( $parsed_blocks ) ) {
195 $block = $parsed_blocks[0];
196 } else {
197 $block = array(
198 'blockName' => 'core/freeform',
199 'attrs' => array(
200 'originalContent' => $menu_item->content,
201 ),
202 );
203 }
204 } else {
205 $block = array(
206 'blockName' => 'core/navigation-link',
207 'attrs' => array(
208 'label' => $menu_item->title,
209 'url' => $menu_item->url,
210 ),
211 );
212 }
213
214 $block['innerBlocks'] = gutenberg_convert_menu_items_to_blocks(
215 isset( $menu_items_by_parent_id[ $menu_item->ID ] )
216 ? $menu_items_by_parent_id[ $menu_item->ID ]
217 : array(),
218 $menu_items_by_parent_id
219 );
220
221 $blocks[] = $block;
222 }
223
224 return $blocks;
225 };
226
227 /**
228 * Shim that causes `wp_nav_menu()` to output a Navigation block instead of a
229 * nav menu when the theme supports block menus. The Navigation block is
230 * constructed by transforming the stored tree of menu items into a tree of
231 * blocks.
232 *
233 * Specifically, this shim makes it so that `wp_nav_menu()` returns early when
234 * the theme supports block menus. When merged to Core, this functionality
235 * should exist in `wp_nav_menu()` after `$sorted_menu_items` is set. The
236 * duplicated code (marked using BEGIN and END) can be deleted.
237 *
238 * This shim can be removed when the Gutenberg plugin requires a WordPress
239 * version that has the ticket below.
240 *
241 * @see https://core.trac.wordpress.org/ticket/50544
242 *
243 * @param string|null $output Nav menu output to short-circuit with. Default null.
244 * @param stdClass $args An object containing wp_nav_menu() arguments.
245 *
246 * @return string|null Nav menu output to short-circuit with.
247 */
248 function gutenberg_output_block_nav_menu( $output, $args ) {
249 if ( ! current_theme_supports( 'block-nav-menus' ) ) {
250 return null;
251 }
252
253 // BEGIN: Code that already exists in wp_nav_menu().
254
255 // Get the nav menu based on the requested menu.
256 $menu = wp_get_nav_menu_object( $args->menu );
257
258 // Get the nav menu based on the theme_location.
259 $locations = get_nav_menu_locations();
260 if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
261 $menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
262 }
263
264 // Get the first menu that has items if we still can't find a menu.
265 if ( ! $menu && ! $args->theme_location ) {
266 $menus = wp_get_nav_menus();
267 foreach ( $menus as $menu_maybe ) {
268 $menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
269 if ( $menu_items ) {
270 $menu = $menu_maybe;
271 break;
272 }
273 }
274 }
275
276 if ( empty( $args->menu ) ) {
277 $args->menu = $menu;
278 }
279
280 // If the menu exists, get its items.
281 if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
282 $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
283 }
284
285 // Set up the $menu_item variables.
286 _wp_menu_item_classes_by_context( $menu_items );
287
288 $sorted_menu_items = array();
289 foreach ( (array) $menu_items as $menu_item ) {
290 $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
291 }
292
293 unset( $menu_items, $menu_item );
294
295 // END: Code that already exists in wp_nav_menu().
296
297 $menu_items_by_parent_id = array();
298 foreach ( $sorted_menu_items as $menu_item ) {
299 $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
300 }
301
302 $navigation_block = array(
303 'blockName' => 'core/navigation',
304 'attrs' => array(),
305 'innerBlocks' => gutenberg_convert_menu_items_to_blocks(
306 isset( $menu_items_by_parent_id[0] )
307 ? $menu_items_by_parent_id[0]
308 : array(),
309 $menu_items_by_parent_id
310 ),
311 );
312
313 return render_block( $navigation_block );
314 }
315 add_filter( 'pre_wp_nav_menu', 'gutenberg_output_block_nav_menu', 10, 2 );
316
317 /**
318 * Shim that makes nav-menus.php nicely display a menu item with its `type` set to
319 * `'block'`.
320 *
321 * Specifically, this shim makes it so that `Walker_Nav_Menu_Edit::start_el()`
322 * outputs extra form fields. When merged to Core, this markup should exist in
323 * `Walker_Nav_Menu_Edit::start_el()`.
324 *
325 * This shim can be removed when the Gutenberg plugin requires a WordPress
326 * version that has the ticket below.
327 *
328 * @see https://core.trac.wordpress.org/ticket/50544
329 *
330 * @param int $item_id Menu item ID.
331 * @param WP_Post $item Menu item data object.
332 */
333 function gutenberg_output_block_menu_item_custom_fields( $item_id, $item ) {
334 if ( 'block' === $item->type ) {
335 ?>
336 <p class="field-content description description-wide">
337 <label for="edit-menu-item-content-<?php echo $item_id; ?>">
338 <?php _e( 'Content', 'gutenberg' ); ?><br />
339 <textarea id="edit-menu-item-content-<?php echo $item_id; ?>" class="widefat" rows="3" cols="20" name="menu-item-content[<?php echo $item_id; ?>]" readonly><?php echo esc_textarea( trim( $item->content ) ); ?></textarea>
340 </label>
341 </p>
342 <?php
343 }
344 }
345 add_action( 'wp_nav_menu_item_custom_fields', 'gutenberg_output_block_menu_item_custom_fields', 10, 2 );
346
347 /**
348 * Shim that adds extra styling to nav-menus.php. This lets us style menu items
349 * that have a `type` set to `'block'`. When merged to Core, this CSS should be
350 * moved to nav-menus.css.
351 *
352 * This shim can be removed when the Gutenberg plugin requires a WordPress
353 * version that has the ticket below.
354 *
355 * @see https://core.trac.wordpress.org/ticket/50544
356 *
357 * @param string $hook The current admin page.
358 */
359 function gutenberg_add_block_menu_item_styles_to_nav_menus( $hook ) {
360 if ( 'nav-menus.php' === $hook ) {
361 $css = <<<CSS
362 /**
363 * HACK: We're hiding the description field using CSS because this
364 * cannot be done using a filter. When merged to Core, we should
365 * actually remove the field from
366 * `Walker_Nav_Menu_Edit::start_el()`.
367 */
368 .menu-item-block .description:not(.field-content) {
369 display: none;
370 }
371 CSS;
372 wp_add_inline_style( 'nav-menus', $css );
373 }
374 }
375 add_action( 'admin_enqueue_scripts', 'gutenberg_add_block_menu_item_styles_to_nav_menus' );
376