Excluir dinámicamente elementos del menú desde wp_nav_menu

21 oct 2011, 20:14:45
Vistas: 29.5K
Votos: 22

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?

9
Comentarios

¿Puedes mostrarnos tu clase walker child personalizada?

soulseekah soulseekah
21 oct 2011 20:42:56

Hola Souleseekah, acabo de agregarla a mi publicación original. ¡Gracias!

Marventus Marventus
21 oct 2011 20:52:42

Pensé en pasar un argumento exclude también, pero, al contrario de wp_list_pages y muchas otras funciones de WP, wp_nav_menu no incluye uno. Así que incluso si lo especifico al llamar al menú o en el walker, no sería reconocido dentro de wp_nav_menu, ¿verdad?

Marventus Marventus
21 oct 2011 21:08:15

Lo siento, no estaba pensando con claridad cuando escribí eso, lo borré inmediatamente.

soulseekah soulseekah
21 oct 2011 21:11:02

¡No te preocupes por eso!

Marventus Marventus
21 oct 2011 21:16:34

Confirmé que las etiquetas condicionales no funcionan en filtros aplicados a wp_nav_menu_items. Este código imprime la misma imagen: function bla ($items) { $image_number = ( is_user_logged_in ) ? '2' : '1'; $items .= ( is_user_logged_in ) ? '<a href="'.wp_logout_url( $_SERVER['REQUEST_URI'] ).'"><img src="'.get_bloginfo('template_url').'/images/skin1/login_'.$image_number.'_normal.png" alt="Cerrar sesión" title="Cerrar sesión" /></a>' : '<a href="'.wp_login_url( $_SERVER['REQUEST_URI'] ).'"><img src="'.get_bloginfo('template_url').'/images/skin1/login_'.$image_number.'_normal.png" alt="Iniciar sesión" title="Iniciar sesión"/></a>';}

Marventus Marventus
22 oct 2011 06:29:59

Tienes que LLAMAR a la función is_user_logged_in() con los paréntesis... ¿se está haciendo tarde allí?

soulseekah soulseekah
22 oct 2011 08:21:39

Agregué un método de filtro wp_nav_menu_items a mi respuesta.

soulseekah soulseekah
22 oct 2011 08:41:14

Rayos... Jajaja. Bueno, en mi defensa, debo admitir que era bastante tarde. Pero aún así...

Marventus Marventus
22 oct 2011 15:28:55
Mostrar los 4 comentarios restantes
Todas las respuestas a la pregunta 2
12
41

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.

21 oct 2011 21:08:38
Comentarios

¡Gracias! Eso podría funcionar. Lo intentaré y te aviso.

Marventus Marventus
21 oct 2011 21:14:18

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?

Marventus Marventus
21 oct 2011 21:41:28

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.

soulseekah soulseekah
21 oct 2011 21:43:56

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.

Marventus Marventus
22 oct 2011 02:20:54

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

Marventus Marventus
22 oct 2011 02:40:03

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.

soulseekah soulseekah
22 oct 2011 08:20:08

Eso tiene sentido. Probaré la nueva versión y te aviso.

Marventus Marventus
22 oct 2011 16:25:09

Souseekah, eres un crack: funcionó de maravilla. ¡Muchas gracias!

Marventus Marventus
22 oct 2011 20:31:48

Genial, me alegro de haber sido de ayuda.

soulseekah soulseekah
23 oct 2011 10:30:50

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?

Naweed Chougle Naweed Chougle
18 nov 2015 14:25:12

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

Naweed Chougle Naweed Chougle
18 nov 2015 14:32:29

El método 2 funcionó bien, incluyendo la verificación is_admin(). Me sorprendió un poco que ocultar el enlace 'Inicio' cuando estás en la página de inicio no sea una opción estándar, pero este código y usar is_front_page() me dieron lo que quería.

Ron Burk Ron Burk
13 oct 2016 22:28:44
Mostrar los 7 comentarios restantes
4

¿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' ) ); ?>
22 oct 2011 03:11:35
Comentarios

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.

Marventus Marventus
22 oct 2011 04:35:24

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

Marventus Marventus
22 oct 2011 05:05:48

¿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?

saq saq
22 oct 2011 06:45:59

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ú.

Marventus Marventus
22 oct 2011 14:53:04