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