¿Descripción de elementos del menú? Walker personalizado para wp_nav_menu()
El menú normal de WordPress se ve así:
Inicio | Blog | Sobre nosotros | Contacto
Pero he visto muchas páginas con descripciones debajo de estos enlaces:
Página de inicio | Nuestros Blogs | Sobre nosotros | Contacto
...conócenos... | leer más | información básica | formulario de contacto
¿Cómo se puede lograr esto?
(Quiero que sea una función principal para todos mis temas, así que no necesito plugins, solo quiero saber cómo se hace)

Necesitas un walker personalizado para el menú de navegación.
Básicamente, agregas un parámetro 'walker'
a las opciones de wp_nav_menu()
y llamas a una instancia de una clase mejorada:
wp_nav_menu(
array (
'menu' => 'main-menu',
'container' => FALSE,
'container_id' => FALSE,
'menu_class' => '',
'menu_id' => FALSE,
'depth' => 1,
'walker' => new Description_Walker
)
);
La clase Description_Walker
extiende Walker_Nav_Menu
y modifica la función start_el( &$output, $item, $depth, $args )
para buscar $item->description
.
Un ejemplo básico:
/**
* Crea una lista HTML de elementos del menú de navegación.
* Reemplazo para el Walker nativo, utilizando la descripción.
*
* @see https://wordpress.stackexchange.com/q/14037/
* @author fuxia
*/
class Description_Walker extends Walker_Nav_Menu
{
/**
* Inicia la salida del elemento.
*
* @param string $output Pasado por referencia. Se utiliza para añadir contenido adicional.
* @param object $item Objeto de datos del elemento del menú.
* @param int $depth Profundidad del elemento del menú. Puede usarse para relleno.
* @param array|object $args Cadenas adicionales. En realidad siempre una
instancia de stdClass. Pero esto es WordPress.
* @return void
*/
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 )
{
$classes = empty ( $item->classes ) ? array () : (array) $item->classes;
$class_names = join(
' '
, apply_filters(
'nav_menu_css_class'
, array_filter( $classes ), $item
)
);
! empty ( $class_names )
and $class_names = ' class="'. esc_attr( $class_names ) . '"';
$output .= "<li id='menu-item-$item->ID' $class_names>";
$attributes = '';
! empty( $item->attr_title )
and $attributes .= ' title="' . esc_attr( $item->attr_title ) .'"';
! empty( $item->target )
and $attributes .= ' target="' . esc_attr( $item->target ) .'"';
! empty( $item->xfn )
and $attributes .= ' rel="' . esc_attr( $item->xfn ) .'"';
! empty( $item->url )
and $attributes .= ' href="' . esc_attr( $item->url ) .'"';
// inserta la descripción solo para elementos de nivel superior
// puedes cambiar esto
$description = ( ! empty ( $item->description ) and 0 == $depth )
? '<small class="nav_desc">' . esc_attr( $item->description ) . '</small>' : '';
$title = apply_filters( 'the_title', $item->title, $item->ID );
$item_output = $args->before
. "<a $attributes>"
. $args->link_before
. $title
. '</a> '
. $args->link_after
. $description
. $args->after;
// Como $output se pasa por referencia, no necesitamos devolver nada.
$output .= apply_filters(
'walker_nav_menu_start_el'
, $item_output
, $item
, $depth
, $args
);
}
}
O, alternativamente como comentó @nevvermind, podrías heredar todas las funcionalidades de la función start_el
del padre y simplemente añadir la descripción a $output
:
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 )
{
parent::start_el( $output, $item, $depth, $args );
$output .= sprintf(
'<i>%s</i>',
esc_html( $item->description )
);
}
Ejemplo de salida:
Ahora habilita el campo de descripción en wp-admin/nav-menus.php
para tener la posibilidad de editar este campo. Si no lo haces, WP simplemente volcará todo el contenido de tu publicación en él.
Lecturas adicionales:
- Errores asociados
- Ejemplo similar con diferente marcado y formato
- Habilitar la caja de descripción en la pantalla de gestión de menús programáticamente
- Recopilar la descripción del elemento para uso posterior
Y eso es todo.

Si para ti herencia != reescribir todo el método, solo mantener el mismo nombre, prueba esto: public function start_el(&$output, $item, $depth, $args)
{
parent::start_el($output, $item, $depth, $args);
$output .= sprintf('<i>%s</i>', esc_html($item->description));
}

@nevvermind Deberías al menos verificar si la descripción tiene algún contenido. ;) La posición de la descripción en mi código de ejemplo es solo la forma más simple de ilustrar la solución. Si necesitas colocar la descripción dentro del enlace, tendrás que reconstruir toda la función.

sí, tendrías que escribir el método completo, sin duda, pero para las personas que necesitan (digamos...) añadirlo, esto podría ahorrarles muchos dolores de cabeza. ¡Y todo esto es culpa de WP! ¡Arggg!

Buen trabajo y lo he usado en esta respuesta modificándolo un poco, tal vez puedas mejorarlo si me faltó algo, gracias.

Lo que realmente necesitaba era el wp_nav_menu, pero necesitaba cambiar el parámetro 'container_class', para que funcionara en mi caso particular, donde bajo ciertas condiciones reemplazaba el menú principal por otro, pero necesitaba que las clases fueran consistentes para el css.

Desde WordPress 3.0, ¡ya no necesitas un walker personalizado!
Existe el filtro walker_nav_menu_start_el
, consulta https://developer.wordpress.org/reference/hooks/walker_nav_menu_start_el/
Ejemplo:
function add_description_to_menu($item_output, $item, $depth, $args) {
if (strlen($item->description) > 0 ) {
// agregar descripción después del enlace
$item_output .= sprintf('<span class="description">%s</span>', esc_html($item->description));
// o.. insertar descripción como último elemento dentro del enlace ($item_output termina con "</a>{$args->after}")
// $item_output = substr($item_output, 0, -strlen("</a>{$args->after}")) . sprintf('<span class="description">%s</span >', esc_html($item->description)) . "</a>{$args->after}";
}
return $item_output;
}
add_filter('walker_nav_menu_start_el', 'add_description_to_menu', 10, 4);

Esto no es mejor ni peor que otras sugerencias; simplemente es diferente. Además, es corto y sencillo.
En lugar de usar el campo de descripción como sugiere @toscho, podrías completar el campo "Título" en cada elemento del menú con el texto que deseas, y luego usar este CSS:
.menu-item a:after { content: attr(title); }
También sería fácil usar jQuery para añadirlo, pero como el texto es meramente ornamental, CSS parece más apropiado.
