Agregar programáticamente un menú de navegación y elementos del menú
A través de funciones de la API, quiero definir un nuevo menú de navegación, seleccionarlo en el tema actual y luego insertar algunas Páginas como elementos del menú. Esto se debe hacer, por ejemplo, en la activación de un tema.
A través de un proceso (moderadamente doloroso) de ingeniería inversa de las inserciones y actualizaciones de la base de datos después de configurar manualmente el menú de navegación y los elementos, he reunido los siguientes pasos, donde 'footer-nav' es el ID slug del menú de navegación que estoy creando:
if (!term_exists('footer-nav', 'nav_menu')) {
// Insertar nuevo menú
$menu = wp_insert_term('Footer nav', 'nav_menu', array('slug' => 'footer-nav'));
// Seleccionar este menú en el tema actual
update_option('theme_mods_'.get_current_theme(), array("nav_menu_locations" => array("primary" => $menu['term_id'])));
// Insertar nueva página
$page = wp_insert_post(array('post_title' => 'Blog',
'post_content' => '',
'post_status' => 'publish',
'post_type' => 'page'));
// Insertar nuevo elemento de menú
$nav_item = wp_insert_post(array('post_title' => 'Noticias',
'post_content' => '',
'post_status' => 'publish',
'post_type' => 'nav_menu_item'));
add_post_meta($nav_item, '_menu_item_type', 'post_type');
add_post_meta($nav_item, '_menu_item_menu_item_parent', '0');
add_post_meta($nav_item, '_menu_item_object_id', $page);
add_post_meta($nav_item, '_menu_item_object', 'page');
add_post_meta($nav_item, '_menu_item_target', '');
add_post_meta($nav_item, '_menu_item_classes', 'a:1:{i:0;s:0:"";}');
add_post_meta($nav_item, '_menu_item_xfn', '');
add_post_meta($nav_item, '_menu_item_url', '');
wp_set_object_terms($nav_item, 'footer-nav', 'nav_menu');
}
Esto parece funcionar, pero:
- ¿es una forma robusta y elegante de hacerlo?
- ¿me estoy perdiendo algo totalmente obvio que haría todo esto en una línea de código?

Podría estar entendiéndote mal, pero ¿por qué no usar wp_create_nav_menu()
?
Por ejemplo, esto es lo que hago para crear un menú personalizado de BuddyPress cuando detecto que BP está activo:
$menuname = $lblg_themename . ' BuddyPress Menu';
$bpmenulocation = 'lblgbpmenu';
// ¿Existe ya el menú?
$menu_exists = wp_get_nav_menu_object( $menuname );
// Si no existe, vamos a crearlo.
if( !$menu_exists){
$menu_id = wp_create_nav_menu($menuname);
// Configurar enlaces predeterminados de BuddyPress y agregarlos al menú.
wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => __('Inicio'),
'menu-item-classes' => 'home',
'menu-item-url' => home_url( '/' ),
'menu-item-status' => 'publish'));
wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => __('Actividad'),
'menu-item-classes' => 'activity',
'menu-item-url' => home_url( '/activity/' ),
'menu-item-status' => 'publish'));
wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => __('Miembros'),
'menu-item-classes' => 'members',
'menu-item-url' => home_url( '/members/' ),
'menu-item-status' => 'publish'));
wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => __('Grupos'),
'menu-item-classes' => 'groups',
'menu-item-url' => home_url( '/groups/' ),
'menu-item-status' => 'publish'));
wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => __('Foros'),
'menu-item-classes' => 'forums',
'menu-item-url' => home_url( '/forums/' ),
'menu-item-status' => 'publish'));
// Tomar las ubicaciones del tema y asignar nuestro menú recién creado
// a la ubicación del menú de BuddyPress.
if( !has_nav_menu( $bpmenulocation ) ){
$locations = get_theme_mod('nav_menu_locations');
$locations[$bpmenulocation] = $menu_id;
set_theme_mod( 'nav_menu_locations', $locations );
}

No conocía esta función. Sí, supongo que hará que el código anterior sea mucho más corto. Creo que debería ir más allá del Codex y sumergirme en el código real, ya que encuentro que las funciones de la API a menudo, como en este caso, son de muy bajo nivel. ¡Gracias!

@julien_c si esto está resuelto, márcalo como tal para permitir que quienes vengan después de ti se beneficien de tu experiencia aquí.

Solo quiero probarlo en la vida real para estar seguro de que hace lo que necesito. ¡Recordaré marcarlo como resuelto tan pronto como termine!

Si ves funciones útiles como estas que no están en el codex, es una buena idea agregarlas (hurra por el wiki) =p

Lamento que me tomara tanto tiempo verificar que funcionara en mi caso. ¡Respuesta aceptada! Además, estás definiendo elementos de menú de enlaces personalizados, he agregado una respuesta a continuación para definir enlaces a páginas (que será más robusto ante cambios de URL, por ejemplo).

http://hookr.io/3.9.1/actions/do_action/wp_create_nav_menu/

Tengo algunos problemas con la respuesta aceptada - eso no la hace incorrecta, pero publicaré mi propio código a continuación que creo podría tener un mejor resultado para algunas personas, ya que tuve la misma pregunta pero quise hacer lo mismo con menos código.
Primero, el código anterior crea elementos de navegación de tipo "URL", lo cual está bien para algunos, pero yo quiero enlazar a PÁGINAS, no a URLs porque esta es una característica importante de las navegaciones en WordPress y los clientes inevitablemente mueven cosas, así que nunca uso el tipo de elemento de navegación URL.
Además, solo se maneja una matriz plana de hijos en el código publicado. He creado una función para declarar recursivamente los nuevos elementos de navegación, almacenando sus metadatos devueltos (principalmente el ID después de ser creados en el bucle), y un parámetro para aceptar hijos.
Solo edita $nav_items_to_add
y el resto se maneja recursivamente. Hay 3 claves requeridas en cada matriz. Primero, la clave de la matriz es el slug, así que 'shop' => array( ... )
es lo que quieres para una página con el slug shop
. ['title']
es cómo se etiquetará el elemento de navegación en el front-end. path
es la ruta a la página dentro de la jerarquía de páginas de WordPress, así que esto es idéntico al slug si la página es un padre de nivel superior, y si shop
fuera hijo de home
entonces sería 'path' => 'home/shop'
.
La última clave opcional del array es ['parent']
donde puedes declarar otra clave en el array como padre de la actual. Es importante notar que los elementos se añaden recursivamente, así que el padre debe existir antes de intentar crear un hijo. Esto significa que la declaración debe ocurrir para el elemento de navegación padre antes que para sus hijos.
$locations = get_nav_menu_locations();
if (isset($locations['primary_navigation'])) {
$menu_id = $locations['primary_navigation'];
$new_menu_obj = array();
$nav_items_to_add = array(
'shop' => array(
'title' => 'Tienda',
'path' => 'shop',
),
'shop_l2' => array(
'title' => 'Tienda',
'path' => 'shop',
'parent' => 'shop',
),
'cart' => array(
'title' => 'Carrito',
'path' => 'shop/cart',
'parent' => 'shop',
),
'checkout' => array(
'title' => 'Pago',
'path' => 'shop/checkout',
'parent' => 'shop',
),
'my-account' => array(
'title' => 'Mi Cuenta',
'path' => 'shop/my-account',
'parent' => 'shop',
),
'lost-password' => array(
'title' => 'Contraseña Perdida',
'path' => 'shop/my-account/lost-password',
'parent' => 'my-account',
),
'edit-address' => array(
'title' => 'Editar Mi Dirección',
'path' => 'shop/my-account/edit-address',
'parent' => 'my-account',
),
);
foreach ( $nav_items_to_add as $slug => $nav_item ) {
$new_menu_obj[$slug] = array();
if ( array_key_exists( 'parent', $nav_item ) )
$new_menu_obj[$slug]['parent'] = $nav_item['parent'];
$new_menu_obj[$slug]['id'] = wp_update_nav_menu_item($menu_id, 0, array(
'menu-item-title' => $nav_item['title'],
'menu-item-object' => 'page',
'menu-item-parent-id' => $new_menu_obj[ $nav_item['parent'] ]['id'],
'menu-item-object-id' => get_page_by_path( $nav_item['path'] )->ID,
'menu-item-type' => 'post_type',
'menu-item-status' => 'publish')
);
}
}

Como complemento a la respuesta de ZaMoose, aquí te mostramos cómo crear un elemento de menú de tipo "Página" (no uno "Personalizado"):
wp_update_nav_menu_item($menu_id, 0, array('menu-item-title' => 'Acerca de',
'menu-item-object' => 'page',
'menu-item-object-id' => get_page_by_path('acerca-de')->ID,
'menu-item-type' => 'post_type',
'menu-item-status' => 'publish'));
Asumiendo que solo conoces el slug de la página, por ejemplo.

Para agregar un elemento de menú programáticamente, puedes usar el filtro wp_nav_menu_items
. Coloca el siguiente código en el archivo functions.php de tu tema para agregar un elemento de menú de inicio/cierre de sesión en el menú principal. 'Primary' es el nombre/ID del menú registrado.
/**
* Agregar enlace de inicio/cierre de sesión en el menú principal.
* =============================================================
*/
add_filter( 'wp_nav_menu_items', 'lunchbox_add_loginout_link', 10, 2 );
function lunchbox_add_loginout_link( $items, $args ) {
/**
* Si el menú principal está configurado y el usuario está conectado.
*/
if ( is_user_logged_in() && $args->theme_location == 'primary' ) {
$items .= '<li><a href="'. wp_logout_url() .'">Cerrar Sesión</a></li>';
}
/**
* De lo contrario, mostrar el elemento de menú de inicio de sesión.
*/
elseif ( !is_user_logged_in() && $args->theme_location == 'primary' ) {
$items .= '<li><a href="'. site_url('wp-login.php') .'">Iniciar Sesión</a></li>';
}
return $items;
}
