Resaltando la Clase Ancestral wp_nav_menu() sin Elementos Hijos en la Estructura de Navegación
(Nota de los moderadores: Originalmente titulado "Clase ancestral wp_nav_menu sin hijos en la estructura de navegación")
Tengo un wp_nav_menu
en mi encabezado que tiene tres páginas. Cuando estoy en una de esas páginas, el li
que contiene esa página en el menú obtiene la clase .current_page_item
. Estas tres páginas tienen plantillas, y estas plantillas contienen consultas personalizadas para obtener todas las entradas de un cierto tipo de contenido. En efecto, los "hijos" percibidos de esta página de nivel superior no son realmente hijos, son simplemente de un tipo de contenido que he asociado con esa página de nivel superior usando una plantilla.
Me gustaría que los elementos del menú de nivel superior obtengan una clase 'current-ancestor'
cuando el usuario está navegando por una página individual de un tipo de entrada específico, nuevamente, asociado con esa página solo en una consulta personalizada en el archivo de plantilla.
Espero que tenga sentido - si no, ¡háganme saber dónde los perdí! Agradezco mucho cualquier ayuda.
--Editado para especificaciones: Por ejemplo, tengo una página estática llamada Talleres que está usando una plantilla. Su slug es talleres. La plantilla tiene una función personalizada get_posts y un bucle dentro de ella, que extrae y muestra todas las entradas de un tipo de contenido personalizado llamado talleres. Si hago clic en el título de uno de estos talleres, me lleva al contenido completo de esa pieza de contenido. La estructura de enlaces permanentes del tipo de entrada personalizada está configurada como talleres/nombre-entrada, por lo que según lo ve el usuario, estas piezas de contenido son hijas de la página Talleres, cuando en realidad son todas de un tipo de contenido pero no están relacionadas con la página. Es esa brecha la que necesito cerrar efectivamente en el menú, resaltando el elemento del menú 'Talleres' cuando se navega por contenido de tipo 'taller'.
Nuevamente, espero que tenga sentido, ¡creo que dije 'taller' más de 20 veces en un párrafo!

Hay una solución más sencilla. Olvídate de crear páginas para cada tipo de publicación solo para tener elementos de navegación, porque como has aprendido, WordPress no tiene forma de reconocer que los tipos personalizados que estás viendo están relacionados con esa página.
En su lugar, crea un enlace personalizado en Apariencia->Menús. Simplemente coloca la URL que devolverá tu tipo personalizado y dale una etiqueta, luego presiona "Añadir al menú".
http://example.com/talleres/
o para enlaces no amigables:
http://example.com/?post_type=talleres
esto solo creará un botón de navegación que muestra todas las publicaciones con ese tipo de publicación personalizado, y también agregará la clase current-menu-item cuando hayas hecho clic en ese elemento de navegación - pero aún no agregará la clase de navegación en ninguna URL que no sea esta
Luego, una vez creado, ve a la configuración de ese nuevo elemento e ingresa el slug del tipo de publicación personalizado en el campo "Atributo del título" (también podrías usar el campo de descripción, pero ese está oculto en las opciones de la pantalla de administración por defecto).
Ahora, necesitas engancharte al filtro nav_menu_css_class
(que se ejecuta para cada elemento de navegación) y verificar si el contenido que se está viendo es del tipo de publicación indicado en tu elemento de navegación personalizado:
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
$post_type = get_query_var('post_type');
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($classes, 'current-menu-item');
};
return $classes;
}
En este caso, vamos a verificar que el contenido del campo Atributo del título no esté vacío y si coincide con el post_type que se está consultando actualmente. Si es así, agregamos la clase current-menu-item a su array de clases, luego devolvemos el array modificado.
Podrías modificar esto para simplemente coincidir con el título del elemento de navegación, pero si por alguna razón deseas titular el elemento de navegación de manera diferente al slug simple del tipo de publicación, usar el campo Atributo del título o Descripción te da esa flexibilidad.
Ahora, cada vez que estés viendo un elemento individual (o probablemente incluso listados de archivo) de un tipo de publicación que coincida con un elemento del menú de navegación, a ese elemento se le asignará la clase CSS current-menu-item para que tu resaltado funcione.
No se necesitan páginas ni plantillas de página ;-) La consulta de URL se encarga de obtener las publicaciones correctas. Tu plantilla de loop se encarga de mostrar la salida de la consulta. Esta función se encarga de reconocer lo que se está mostrando y agregar la clase CSS.
BONUS
Incluso puedes automatizar el proceso usando wp_update_nav_menu_item
, haciendo que los elementos del menú se generen automáticamente para todos tus tipos de publicaciones. Para este ejemplo, primero necesitarías haber recuperado el $menu_id
del menú de navegación al que deseas agregar estos elementos.
$types = get_post_types( array( 'exclude_from_search' => false, '_builtin' => false ), 'objects' );
foreach ($types as $type) {
wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'custom',
'menu-item-title' => $type->labels->name,
'menu-item-url' => get_bloginfo('url') . '/?post_type=' . $type->rewrite['slug'],
'menu-item-attr-title' => $type->rewrite['slug'],
'menu-item-status' => 'publish'
)
);
}

¡Eso es lo que necesitaba! Estoy usando plantillas de página solo porque los diseños son bastante complejos para esas páginas y no solo listan las páginas, pero aún puedo utilizar ese filtro que proporcionaste para verificar el ID de la página. La naturaleza de este tema es que las opciones del tema te permiten emparejar páginas ('home' es esta página, 'about' es esta página, etc.), así que eso debería funcionar perfectamente. ¡Gracias por la asistencia (increíblemente detallada)!

Tuve que eliminar el current_page_parent
del elemento de navegación que era mi blog, pero por lo demás funcionó. Gracias.

Esto no funcionaba para mí, ya que $item->attr_title
extraía el TÍTULO, y yo había escrito el título en mayúsculas. Así que cambié el atributo a $item->post_name
y ahora funciona bien para mí.

en lugar de usar
$post_type = get_query_var('post_type');
Podrías probar:
$post_type = get_post_type();
A veces el tipo de publicación no está establecido en la variable de consulta. Este es el caso para el tipo de publicación predeterminado "post", por lo que si deseas resaltar una publicación que se mostró desde una página de listado, necesitarás usar esto. get_query_var() solo devuelve una cadena vacía para tipos de publicaciones que no son personalizados.
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
$post_type = get_post_type();
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($classes, 'current-menu-item');
};
return $classes;
}

@Somatic - ¡eso es fantástico! Modifiqué un poco tu código para que también funcione con una Taxonomía específica (que estoy usando solo para el post_type relacionado). La idea es usar el atributo Title del ítem del menú para almacenar tanto el nombre del post_type COMO el nombre de la taxonomía, separados por punto y coma, y luego procesados por la función.
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
# Obtener variables de consulta
$post_type = get_query_var('post_type');
$taxonomy = get_query_var('taxonomy');
# Obtener y procesar atributo Title del ítem del menú
$title = $item->attr_title; // atributo Title del ítem del menú, como post_type;taxonomy
$title_array = explode(";", $title);
$title_posttype = $title_array[0];
$title_taxonomy = $title_array[1];
# Agregar clase si es necesario
if ($title != '' && ($title_posttype == $post_type || $title_taxonomy == $taxonomy)) {
array_push($classes, 'current-menu-item');
};
return $classes;
}

Aquí está mi solución si quieres trabajar con wp_list_pages.
Añade esto en tu functions.php
add_filter('page_css_class', 'my_page_css_class', 10, 2);
function my_page_css_class($css_class, $page){
$post_type = get_post_type();
if($post_type != "page"){
$parent_page = get_option('page_for_custom_post_type-'.$post_type);
if($page->ID == $parent_page)
$css_class[] = 'current_page_parent';
}
return $css_class;
}
Ahora solo añade en la tabla wp_options una nueva fila con un option_name de page_for_custom_post_type-xxxx y un option_value con el ID de la página que quieras conectar.
Quizás hayas notado que ya existe una opción llamada page_for_posts. Si solo tienes 1 tipo de entrada personalizada, puedes configurar tu página en /wp-admin/options-reading.php en el menú desplegable y la navegación establecerá correctamente el current_page.
Creo que el núcleo de WordPress debería extender esta sección con un menú desplegable para cada tipo de entrada registrado.

Decidí continuar usando páginas y utilizar el nombre de la plantilla de página como una clase en el elemento de navegación. Esto me permite evitar saturar el atributo title, algo que no me gustaba de otras soluciones.
add_filter('nav_menu_css_class', 'mbudm_add_page_type_to_menu', 10, 2 );
// Si un elemento del menú es una página, añade el nombre de la plantilla como clase CSS
function mbudm_add_page_type_to_menu($classes, $item) {
if($item->object == 'page'){
$template_name = get_post_meta( $item->object_id, '_wp_page_template', true );
$new_class =str_replace(".php","",$template_name);
array_push($classes, $new_class);
return $classes;
}
}
También tengo clases para el body añadidas en header.php
<body <?php body_class(); ?>>
Finalmente, esta solución requiere CSS adicional para aplicar el estado seleccionado/activo a los elementos del menú de navegación. Lo uso para mostrar archivos de taxonomía y tipos de contenido personalizado relacionados con la página como hijos de esta página:
/* estados seleccionados - incluye subpáginas para todo lo relacionado con productos */
#nav-main li.current-menu-item a,
body.single-mbudm_product #nav-main li.lp_products a,
body.tax-mbudm_product_category #nav-main li.lp_products a,
#nav-main li.current_page_parent a{color:#c00;}

Esto me dio el siguiente error:
Warning: join() [function.join]: Invalid arguments passed in /home/path/to/wp-includes/nav-menu-template.php on line 76
¿Alguna idea de qué pasó aquí?

@Somatic - ¡Excelente código! Hice un cambio por mi parte. Quería mantener el Atributo de Título para su propósito original, así que en su lugar coloqué el slug del Tipo de Entrada Personalizado en la Relación de Enlace (XFN) dentro de las propiedades avanzadas del menú que puedes activar en Opciones de Pantalla. Modifiqué
if ($item->attr_title != '' && $item->attr_title == $post_type) {
y lo cambié a
if ($item->xfn != '' && $item->xfn == $post_type) {

Buen trabajo Somatic.
Desafortunadamente, no entiendo cómo puedes listar tus tipos de posts personalizados en una página de la manera que explicas. Si no uso un page-portfolio.php y lo añado a una página, solo obtengo un error 404.
Si hago como Gavin, he modificado un poco tu función para también eliminar la clase "current_page_parent" de la página del blog así:
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2);
function current_type_nav_class($css_class, $item) {
$post_type = get_query_var('post_type');
if (get_post_type()=='portfolio') {
$current_value = "current_page_parent";
$css_class = array_filter($css_class, function ($element) use ($current_value) { return ($element != $current_value); } );
}
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($css_class, 'current_page_parent');
};
return $css_class;
}
