386 lines
14 KiB
PHP
386 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Extend WordPress core's rendering of menus to support block-based menus.
|
|
*
|
|
* @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 ) {
|
|
// We should uncomment the line below when the block-nav-menus feature becomes stable.
|
|
// @see https://github.com/WordPress/gutenberg/issues/34265.
|
|
/*if ( current_theme_supports( 'block-nav-menus' ) ) {*/
|
|
if ( false ) {
|
|
return $menu_items;
|
|
}
|
|
|
|
return array_filter(
|
|
$menu_items,
|
|
static 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 ) {
|
|
// We should uncomment the line below when the block-nav-menus feature becomes stable.
|
|
// @see https://github.com/WordPress/gutenberg/issues/34265.
|
|
/*if ( ! current_theme_supports( 'block-nav-menus' ) ) {*/
|
|
if ( true ) {
|
|
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;
|
|
}
|
|
|
|
$block_attributes = array();
|
|
if ( isset( $args->block_attributes ) ) {
|
|
$block_attributes = $args->block_attributes;
|
|
}
|
|
|
|
$navigation_block = array(
|
|
'blockName' => 'core/navigation',
|
|
'attrs' => $block_attributes,
|
|
'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' );
|