Excluir dinámicamente elementos del menú desde wp_nav_menu
Busqué información sobre cómo excluir/eliminar elementos del menú de navegación en menús personalizados, y el único hilo que encontré no tenía respuestas útiles para mi caso.
1. Antecedentes:
Creé un menú tipo Dock usando menús personalizados de WP (wp_nav_menu) y jqDock en mi sitio. Como jqDock necesita imágenes continuas o enlaces de imagen para funcionar, estoy usando un walker personalizado para que la salida HTML del menú de navegación se vea así:
<div id="menu-first" class="nav">
<a><img src="http://path/to/image-1.png"/></a>
<a><img src="http://path/to/image-2.png"/></a>
<a><img src="http://path/to/image-3.png"/></a>
etc...
</div>
El código de mi walker personalizado es:
class custom_nav_walker extends Walker_Nav_Menu
{
var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<ul class=\"sub-menu\">\n";
}
function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "$indent</ul>\n";
}
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
// $output .= $indent . '<li' . $id . $value . $class_names .'>';
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$description = ! empty( $item->description ) ? esc_attr( strtolower( $item->description )) : '';
$item_title = ! empty( $item->attr_title ) ? esc_attr( $item->attr_title ) : '';
if ( strpos($description, ';') !== false ) {
$description_array = explode (';', $description);
$image_name = $description_array[0];
$image_alt = $description_array[1];
} else {
$image_name = $description;
$image_alt = $item_title;
}
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before .'<img src="'.get_bloginfo('template_url').'/images/skin1/'.$image_name.'" alt="'.$image_alt.'" title="'.$item_title.'" />'.$args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
function end_el(&$output, $item, $depth) {
$output .= "";
}
}
El script jqDock luego captura el ID del menú ('menu-first') y reemplaza la salida de wp_nav_menu con el menú Dock. La salida HTML del menú Dock cambia según las opciones especificadas al cargar jqDock.
2. La Pregunta:
Me gustaría no mostrar (es decir, excluir) ciertos elementos del menú según donde se encuentre el usuario en el sitio. Por ejemplo, me gustaría mostrar el elemento Inicio solo cuando el usuario no está en Inicio, y el elemento Entrada aleatoria solo cuando sí lo está.
3. Soluciones Descartadas:
a. Múltiples menús: Registrar y crear múltiples menús y luego llamarlos condicionalmente podría funcionar; sin embargo, no creo que sea una solución ideal ni limpia por muchas razones. Además, los múltiples menús no son fáciles de mantener o actualizar.
b. Búsqueda y Reemplazo con Regex: Esto podría obligarme a cambiar el parámetro needle cada vez que cambie las opciones de jqDock porque la salida HTML se modifica.
c. Propiedad CSS 'display': Ocultar los elementos a través de la propiedad display de CSS funciona, pero como tiene que aplicarse a la salida del menú jqDock, afecta la renderización visual del menú.
4. Soluciones Fallidas:
a. Filtro para wp_nav_menu_items: Intenté capturar la variable '$items' (string) y asignarle diferentes valores a través de etiquetas condicionales con el siguiente código:
function userf_dynamic_nav_menu ($items) {
$items_array_home = explode('<a', $items);
$items_array_nothome = $items_array_home;
unset($items_array_home[1]);
unset($items_array_nothome[2]);
$items_home = implode('<a', $items_array_home);
$items_nothome = implode('<a', $items_array_nothome);
if ( is_home() ) {
$items = $items_home;
} else {
$items = $items_nothome;
}
return $items;
}
add_filter('wp_nav_menu_first_items', 'userf_dynamic_nav_menu');
Esto funciona solo parcialmente, porque los elementos del menú cambian, pero las etiquetas condicionales son ignoradas. Supongo que esto tiene sentido debido al momento en que se aplica el filtro.
b. Función de menú de navegación personalizada: Intenté crear mi propia función de menú de navegación personalizada para poder agregar un argumento de exclusión al array $defaults y usar este código ligeramente modificado de wp_list_pages
para poblar el argumento adicional:
$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );
¿Alguna idea?

Método 1
Puedes agregar un constructor a tu Walker personalizado para almacenar algunos argumentos de exclusión adicionales, como:
class custom_nav_walker extends Walker_Nav_Menu {
function __construct( $exclude = null ) {
$this->exclude = $exclude;
}
function skip( $item ) {
return in_array($item->ID, (array)$this->exclude);
// o
return in_array($item->title, (array)$this->exclude);
// etc.
}
// ...dentro de start_el, end_el
if ( $this->skip( $item ) ) return;
}
O bien omite el constructor y establece su propiedad $exclude
antes de pasarlo como walker a wp_nav_menu()
de esta manera:
$my_custom_nav_walker = new custom_nav_walker;
$my_custom_nav_walker->exclude = array( ... );
Dependiendo de qué estés excluyendo, proporciona el formato correcto para el parámetro exclude.
Método 2
Así es como podrías hacer esto enganchando al filtro wp_get_nav_menu_items
.
function wpse31748_exclude_menu_items( $items, $menu, $args ) {
// Itera sobre los elementos para buscar y eliminar
foreach ( $items as $key => $item ) {
if ( $item->object_id == 168 ) unset( $items[$key] );
}
return $items;
}
add_filter( 'wp_get_nav_menu_items', 'wpse31748_exclude_menu_items', null, 3 );
Nota: object_id
es el objeto al que apunta el menú, mientras que ID
es el ID del menú, estos son diferentes.
Déjame saber tus comentarios.

Probé el enfoque del constructor y, no importa lo que intente, sigo recibiendo un error "Tipo de dato incorrecto para el segundo argumento" en la función in_array
. ¿Estoy haciendo algo mal?

La propiedad $exclude
debe ser un array. Así que asegúrate de pasar un array al constructor, o revisa el código actualizado en mi respuesta. Específicamente el typecast para $this->exclude
, por si acaso no se pasa un array.

Disculpa por eso: tenía un error tipográfico en mi función. Acabo de probar $exclude = array ('4', '7');
y usando los slugs también, pero no está teniendo ningún efecto en la salida del walker. Intentaré el segundo enfoque y te aviso.

No, eso tampoco funcionó. Creo que estoy mentalmente agotado de intentar resolver esto, así que eso podría estar afectando mi... "rendimiento", :-)

Bueno, una cosa que quizás no estés haciendo es comparar el valor real de $item
, recuerda que $item
es una clase estándar con propiedades, así que tienes que comparar una de sus propiedades con lo que sea que estés proporcionando. Puedes hacer var_dump
de $item
para ver qué tipo de propiedades están disponibles para comparar al excluir. Edité mi código para hacerlo más claro.

Con el método 2, los elementos del menú desaparecen de Apariencia > Menús en el panel de control también. ¿Cómo evitas que eso suceda?

........La respuesta es que necesitas la verificación condicional is_admin() para ver si se está cargando el panel de control.

¿Esto ayuda?
$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );
como ejemplo
<?php wp_nav_menu( array( 'container_class' => 'menu-header', 'theme_location' => 'primary', 'exclude' => '66' ) ); ?>

Hola Saq, olvidé mencionar que una de las soluciones que no funcionó fue crear una función personalizada nav_menu y agregar ese código como un argumento adicional a los valores predeterminados de la función. Lamentablemente, no funcionó. No intenté incluirlo en el walker, pero no creo que eso funcionaría tampoco por la misma razón que mencioné anteriormente, principalmente porque wp_nav_menu
no tiene un argumento "exclude", pero podría estar equivocado.

Actualicé mi publicación original para incluir esto y mayor claridad.

¿Qué tal si no usas un walker personalizado, sino que usas un nav_menu regular y extraes los elementos con wp_get_nav_menu_items() con tu imagen personalizada?

Eso sería una buena solución en general, pero en este caso particular, wp_get_nav_menu_items
no recuperará las imágenes porque las etiquetas img no están almacenadas en el menú personalizado real (solo los nombres de archivo están en el campo de descripción, por ejemplo, "image1.png"). El walker personalizado es lo que me permite insertar las etiquetas img en la salida del menú.
