Saves the properties of a menu item or create a new one.
Description
The menu-item-title, menu-item-description and menu-item-attr-title are expected to be pre-slashed since they are passed directly to APIs that expect slashed data.
Parameters
$menu_id
intrequired- The ID of the menu. If 0, makes the menu item a draft orphan.
$menu_item_db_id
intrequired- The ID of the menu item. If 0, creates a new menu item.
$menu_item_data
arrayoptional- The menu item’s data.
Default:
array()
$fire_after_hooks
booloptional- Whether to fire the after insert hooks.
Default:
true
Source
function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item_data = array(), $fire_after_hooks = true ) {
$menu_id = (int) $menu_id;
$menu_item_db_id = (int) $menu_item_db_id;
// Make sure that we don't convert non-nav_menu_item objects into nav_menu_item objects.
if ( ! empty( $menu_item_db_id ) && ! is_nav_menu_item( $menu_item_db_id ) ) {
return new WP_Error( 'update_nav_menu_item_failed', __( 'The given object ID is not that of a menu item.' ) );
}
$menu = wp_get_nav_menu_object( $menu_id );
if ( ! $menu && 0 !== $menu_id ) {
return new WP_Error( 'invalid_menu_id', __( 'Invalid menu ID.' ) );
}
if ( is_wp_error( $menu ) ) {
return $menu;
}
$defaults = array(
'menu-item-db-id' => $menu_item_db_id,
'menu-item-object-id' => 0,
'menu-item-object' => '',
'menu-item-parent-id' => 0,
'menu-item-position' => 0,
'menu-item-type' => 'custom',
'menu-item-title' => '',
'menu-item-url' => '',
'menu-item-description' => '',
'menu-item-attr-title' => '',
'menu-item-target' => '',
'menu-item-classes' => '',
'menu-item-xfn' => '',
'menu-item-status' => '',
'menu-item-post-date' => '',
'menu-item-post-date-gmt' => '',
);
$args = wp_parse_args( $menu_item_data, $defaults );
if ( 0 === $menu_id ) {
$args['menu-item-position'] = 1;
} elseif ( 0 === (int) $args['menu-item-position'] ) {
$menu_items = array();
if ( 0 !== $menu_id ) {
$menu_items = (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
}
$last_item = array_pop( $menu_items );
if ( $last_item && isset( $last_item->menu_order ) ) {
$args['menu-item-position'] = 1 + $last_item->menu_order;
} else {
$args['menu-item-position'] = count( $menu_items );
}
}
$original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0;
if ( 'custom' === $args['menu-item-type'] ) {
// If custom menu item, trim the URL.
$args['menu-item-url'] = trim( $args['menu-item-url'] );
} else {
/*
* If non-custom menu item, then:
* - use the original object's URL.
* - blank default title to sync with the original object's title.
*/
$args['menu-item-url'] = '';
$original_title = '';
if ( 'taxonomy' === $args['menu-item-type'] ) {
$original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
$original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
} elseif ( 'post_type' === $args['menu-item-type'] ) {
$original_object = get_post( $args['menu-item-object-id'] );
$original_parent = (int) $original_object->post_parent;
$original_title = $original_object->post_title;
} elseif ( 'post_type_archive' === $args['menu-item-type'] ) {
$original_object = get_post_type_object( $args['menu-item-object'] );
if ( $original_object ) {
$original_title = $original_object->labels->archives;
}
}
if ( wp_unslash( $args['menu-item-title'] ) === wp_specialchars_decode( $original_title ) ) {
$args['menu-item-title'] = '';
}
// Hack to get wp to create a post object when too many properties are empty.
if ( '' === $args['menu-item-title'] && '' === $args['menu-item-description'] ) {
$args['menu-item-description'] = ' ';
}
}
// Populate the menu item object.
$post = array(
'menu_order' => $args['menu-item-position'],
'ping_status' => 0,
'post_content' => $args['menu-item-description'],
'post_excerpt' => $args['menu-item-attr-title'],
'post_parent' => $original_parent,
'post_title' => $args['menu-item-title'],
'post_type' => 'nav_menu_item',
);
$post_date = wp_resolve_post_date( $args['menu-item-post-date'], $args['menu-item-post-date-gmt'] );
if ( $post_date ) {
$post['post_date'] = $post_date;
}
$update = 0 !== $menu_item_db_id;
// New menu item. Default is draft status.
if ( ! $update ) {
$post['ID'] = 0;
$post['post_status'] = 'publish' === $args['menu-item-status'] ? 'publish' : 'draft';
$menu_item_db_id = wp_insert_post( $post, true, $fire_after_hooks );
if ( ! $menu_item_db_id || is_wp_error( $menu_item_db_id ) ) {
return $menu_item_db_id;
}
/**
* Fires immediately after a new navigation menu item has been added.
*
* @since 4.4.0
*
* @see wp_update_nav_menu_item()
*
* @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.
*/
do_action( 'wp_add_nav_menu_item', $menu_id, $menu_item_db_id, $args );
}
/*
* Associate the menu item with the menu term.
* Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms().
*/
if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) {
$update_terms = wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' );
if ( is_wp_error( $update_terms ) ) {
return $update_terms;
}
}
if ( 'custom' === $args['menu-item-type'] ) {
$args['menu-item-object-id'] = $menu_item_db_id;
$args['menu-item-object'] = 'custom';
}
$menu_item_db_id = (int) $menu_item_db_id;
// Reset invalid `menu_item_parent`.
if ( (int) $args['menu-item-parent-id'] === $menu_item_db_id ) {
$args['menu-item-parent-id'] = 0;
}
update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key( $args['menu-item-type'] ) );
update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', (string) ( (int) $args['menu-item-parent-id'] ) );
update_post_meta( $menu_item_db_id, '_menu_item_object_id', (string) ( (int) $args['menu-item-object-id'] ) );
update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key( $args['menu-item-object'] ) );
update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key( $args['menu-item-target'] ) );
$args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) );
$args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) );
update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] );
update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] );
update_post_meta( $menu_item_db_id, '_menu_item_url', sanitize_url( $args['menu-item-url'] ) );
if ( 0 === $menu_id ) {
update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() );
} elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) ) {
delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' );
}
// Update existing menu item. Default is publish status.
if ( $update ) {
$post['ID'] = $menu_item_db_id;
$post['post_status'] = ( 'draft' === $args['menu-item-status'] ) ? 'draft' : 'publish';
$update_post = wp_update_post( $post, true );
if ( is_wp_error( $update_post ) ) {
return $update_post;
}
}
/**
* Fires after a navigation menu item has been updated.
*
* @since 3.0.0
*
* @see wp_update_nav_menu_item()
*
* @param int $menu_id ID of the updated menu.
* @param int $menu_item_db_id ID of the updated menu item.
* @param array $args An array of arguments used to update a menu item.
*/
do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
return $menu_item_db_id;
}
Hooks
- do_action( ‘wp_add_nav_menu_item’,
int $menu_id ,int $menu_item_db_id ,array $args ) Fires immediately after a new navigation menu item has been added.
- do_action( ‘wp_update_nav_menu_item’,
int $menu_id ,int $menu_item_db_id ,array $args ) Fires after a navigation menu item has been updated.
The
$menu_item_data
argument accepts an array of data. Depending upon the type of nav menu item you are attempting to add, it requires different parameters:Note that in several cases,
menu-item-object
must be included, and it must be set to the post type of the nav item you are referencing.Page
Post
Custom
When adding a custom link to your nav menu, you can omit the
menu-item-type
because it defaults tocustom
.When adding a link for a custom post type.
In addition to the other minimum required parameters, pass these values to the $menu_item_data parameter:
Example on how to work with taxonomies: automatically add a new menu item when a new woocommerce product category has been created. Also if it’s a subcategory – add it as a respective subitem.
If you’re updating an existing menu item and want to keep the ordering, pass
'menu-item-position'
. Otherwise, the item will move to the end of the menu.