Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Highlight the CPT (Custom Post Type) Archive & its Parents ("current-menu-parent") & its Ancestors ("current-menu-ancestor") in the WordPress Navigation wp_nav_menu() when viewing the Archive itself or a single detail page of the CPT. Let's say you have CPT called "projects" and you have a WordPress menu set as "My Work (link to a page) > Projec…
/**
* Gist Name: Highlight CPT Archive and wp_nav_menu parents
* Author: VlooMan
* Author URI: http://ishyoboy.com
*
* Highlight the CPT (Custom Post Type) Archive & its Parents ("current-menu-parent")
* & its Ancestors ("current-menu-ancestor") in the WordPress Navigation wp_nav_menu() when viewing the Archive itself
* or a single detail page of the CPT.
*
* Let's say you have CPT called "projects" and you have a WordPress menu set as
* "My Work (link to a page) > Projects (CPT Archive)". If you visit the CPT Archive the "My Work" item would not be
* originally highlighted, only the "Projects" would be. If you visit the detail of a given project nothing would be
* highlighted in the navigation (neither "My Work" nor "Projects"). The code below solves the problem.
*
* The code uses a WordPress hook and 99% of the code is copied from the original WordPress function which for some
* reason does not do this highlighting. Please let me know if the code helped in the comments below.
*
* Simply copy this code to your theme's or child-theme's functions.php file.
*
* P.S. Just make sure to add the nav menu item as "CPT Archive" and not a "Custom Link".
*
* @param array $menu_items - All Navigation Menu Items
* @param array $args - some arguments we do not use right now
*
* @return array $menu_items
*/
function my_highlight_cpt_archive_and_ancestors_in_menu( $menu_items, $args ) {
global $wp_query, $wp_rewrite;
$queried_object = $wp_query->get_queried_object();
$active_object = '';
$active_ancestor_item_ids = array();
$active_parent_item_ids = array();
$active_parent_object_ids = array();
$possible_taxonomy_ancestors = array();
// Highligh the current post's archive & prepare parents for highlighting
foreach ( (array) $menu_items as $key => $menu_item ) {
if (
( 'post_type_archive' == $menu_item->type && is_post_type_archive( array( $menu_item->object ) ) ) ||
( 'post_type_archive' == $menu_item->type && is_singular( array( $menu_item->object ) ) )
) {
$allow_highlight = true;
// This filter can be used to add some additional logic if needed. E.g. not highlight in some special cases
$allow_highlight = apply_filters( 'my_allow_menu_highlight', $allow_highlight, $menu_item, $queried_object, $args );
if ( $allow_highlight ) {
$menu_item->classes[] = 'current-menu-item';
$menu_items[ $key ]->current = true;
$_anc_id = (int) $menu_item->db_id;
while (
( $_anc_id = get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) &&
! in_array( $_anc_id, $active_ancestor_item_ids )
) {
$active_ancestor_item_ids[] = $_anc_id;
}
$active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
$active_parent_object_ids[] = (int) $menu_item->post_parent;
$active_object = $menu_item->object;
}
}
}
// Make sure the IDs are not present multiple times
$active_ancestor_item_ids = array_filter( array_unique( $active_ancestor_item_ids ) );
$active_parent_item_ids = array_filter( array_unique( $active_parent_item_ids ) );
$active_parent_object_ids = array_filter( array_unique( $active_parent_object_ids ) );
// set parent's class
foreach ( (array) $menu_items as $key => $parent_item ) {
$classes = (array) $parent_item->classes;
$menu_items[$key]->current_item_ancestor = false;
$menu_items[$key]->current_item_parent = false;
if (
isset( $parent_item->type ) &&
(
// ancestral post object
(
'post_type' == $parent_item->type &&
! empty( $queried_object->post_type ) &&
is_post_type_hierarchical( $queried_object->post_type ) &&
in_array( $parent_item->object_id, $queried_object->ancestors ) &&
$parent_item->object != $queried_object->ID
) ||
// ancestral term
(
'taxonomy' == $parent_item->type &&
isset( $possible_taxonomy_ancestors[ $parent_item->object ] ) &&
in_array( $parent_item->object_id, $possible_taxonomy_ancestors[ $parent_item->object ] ) &&
(
! isset( $queried_object->term_id ) ||
$parent_item->object_id != $queried_object->term_id
)
)
)
) {
$classes[] = empty( $queried_object->taxonomy ) ? 'current-' . $queried_object->post_type . '-ancestor' : 'current-' . $queried_object->taxonomy . '-ancestor';
}
if ( in_array( intval( $parent_item->db_id ), $active_ancestor_item_ids ) ) {
$classes[] = 'current-menu-ancestor';
$menu_items[$key]->current_item_ancestor = true;
}
if ( in_array( $parent_item->db_id, $active_parent_item_ids ) ) {
$classes[] = 'current-menu-parent';
$menu_items[$key]->current_item_parent = true;
}
if ( in_array( $parent_item->object_id, $active_parent_object_ids ) )
$classes[] = 'current-' . $active_object . '-parent';
if ( 'post_type' == $parent_item->type && 'page' == $parent_item->object ) {
// Back compat classes for pages to match wp_page_menu()
if ( in_array('current-menu-parent', $classes) )
$classes[] = 'current_page_parent';
if ( in_array('current-menu-ancestor', $classes) )
$classes[] = 'current_page_ancestor';
}
$menu_items[$key]->classes = array_unique( $classes );
}
return $menu_items;
}
add_filter( 'wp_nav_menu_objects', 'my_highlight_cpt_archive_and_ancestors_in_menu', 10, 2 );
@xxvii

This comment has been minimized.

Copy link

@xxvii xxvii commented Jun 22, 2019

Hi @VlooMan, any advice on how to highlight also the custom taxonomies registered for the CPT?

Currently I have a menu which contains the link to the CPT archive.
If I'm in the CPT archive -> CPT archive link highlighted
If I'm in a CPT taxonomy -> CPT archive link NOT highlighted
If I'm in a CPT single post -> CPT archive link highlighted

@xxvii

This comment has been minimized.

Copy link

@xxvii xxvii commented Jun 22, 2019

For the moment I managed to fix this adding a condition to the first "if", where I check:
if the menu item is relative to a CPT and
if the current page I'm visiting is a taxonomy and
if the CPT type of the menu item is among the object types associated with the taxonomy I'm visiting

I don't know if it's the best method but it seems to work...what do you think?

if (
	( 'post_type_archive' == $menu_item->type && is_post_type_archive( array( $menu_item->object ) ) ) ||
	( 'post_type_archive' == $menu_item->type && is_singular( array( $menu_item->object ) ) ) ||
	( 'post_type_archive' == $menu_item->type && is_tax($queried_object) && in_array($menu_item->object, get_taxonomy($queried_object->taxonomy)->object_type) )
)
@maljones

This comment has been minimized.

Copy link

@maljones maljones commented Nov 18, 2020

Super useful! I ended up adding some isset check around the if statements at the end to solve some notices that were showing when object_id wasn't defined. I'm using Max Mega Menu pro, so it might only be necessary in that use case.

if(isset($parent_item->db_id)){ // RK: Added a check for db_id to get rid of PHP notices
    if ( in_array(  intval( $parent_item->db_id ), $active_ancestor_item_ids ) ) {
        $classes[] = 'current-menu-ancestor';
        $menu_items[$key]->current_item_ancestor = true;
    }
			
    if ( in_array( $parent_item->db_id, $active_parent_item_ids ) ) {
        $classes[] = 'current-menu-parent';
        $menu_items[$key]->current_item_parent = true;
    }
}
if(isset($parent_item->object_id)){ // RK: Added a check for object_id to get rid of PHP notices
    if ( in_array( $parent_item->object_id, $active_parent_object_ids ) ) {
        $classes[] = 'current-' . $active_object . '-parent';
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment