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 / lib / experimental / navigation-theme-opt-in.php

navigation-theme-opt-in.php in Gutenberg 23.3.2, at lib/experimental/navigation-theme-opt-in.php

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