Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wordpress custom post type hierarchy and menu highlighting (current_page_parent)

I have created a custom post type of 'portfolio' and page with a template that retrieves all posts matching that custom post type.

The problem is when I drill down into the actual post, the post seems to sit under 'blog' in the main menu highlighting (displays current_page_parent as a class)

The permalink url is correct: www.site.com/portfolio/post-slug

But the menu thinks the parent is 'blog'.

This is obviously a hierarchical issue but I don't know what to do to fix it.

like image 339
Craig Avatar asked Jul 17 '10 01:07

Craig


5 Answers

It appears this is an issue with the core Wordpress code; the code that generates the menu classes adds current_page_parent to your Blog page everywhere except when viewing static page templates.

(This has been discussed in passing at http://core.trac.wordpress.org/ticket/13543).

You can however get around this with some custom code using the page_css_class filter. For example, add something along these lines to functions.php (not 100% tested):

function my_page_css_class($css_class, $page) {
    if (get_post_type()=='portfolio' || is_page(57)) {
        if ($page->ID == get_option('page_for_posts')) {
            foreach ($css_class as $k=>$v) {
                if ($v=='current_page_parent') unset($css_class[$k]);
            }
        }
        if ($page->ID==57) {
            $css_class[]='current_page_parent';
        }
    }
    return $css_class;
}
add_filter('page_css_class','my_page_css_class',10,2);

Replacing 57 with the ID of your portfolios page, of course. That removes current_page_parent when printing the blog page and adds current_page_parent to your portfolios page, when either viewing a single portfolio or viewing the portfolios page itself.

like image 106
user11977 Avatar answered Nov 06 '22 16:11

user11977


As explained at https://core.trac.wordpress.org/ticket/16382, .current_page_parent matches "anything that isn't a page" for the sake of backwards compatibility (bear in mind that this was considered backwards 10 years ago...) so themes nowadays really shouldn't still be using it.

So the simplest solution, and the most efficient (since unlike previous answers it doesn't require running extra code on every page load), is to modify your theme's CSS to replace use of the .current_page_parent class selector with .current-menu-parent, which does the right thing. (NB underscores vs hyphens.)

If you are using a third-party theme and don't want to modify it directly, then you can overwrite its properties in your own stylesheet. For example, if your theme has:

.current_page_parent > a {
    border-bottom: 4px solid blue;
}

then in your child theme's stylesheet you would do this to cancel out its effects, and apply them to the correct class:

.current_page_parent > a {
    border-bottom: transparent !important;  /* Cancel out incorrect styling */
}
.current-menu-parent > a {
    border-bottom: 4px solid blue;   /* Add styling correctly */
}

This is just an example - the correct way will depend on how your theme is styling these links.

like image 32
Ben23 Avatar answered Sep 28 '22 03:09

Ben23


Here is my optimized/extended version of previously suggested solutions, which is pretty much fully automated. No more extra CSS or menu attributes needed.

This version dynamically gets a list of custom post types and if the current post type is a custom post type, then it removes the 'current_page_parent' class from all menu items.

Furthermore it checks each menu item to see if it's for a page with a page template like "page-{custom_post_type_slug}.php", and if so, it'll add the 'current_page_parent' class.

The filter priority is 1, as some themes, replace the current_page_parent/etc. classes with a class like 'active' (eg. 'roots' does this), so this filter needs to execute first.

Lastly, it makes use of 3 static variables since this function is repeatedly called and these (obviously) remain the same through all calls.

function theme_current_type_nav_class($css_class, $item) {
    static $custom_post_types, $post_type, $filter_func;

    if (empty($custom_post_types))
        $custom_post_types = get_post_types(array('_builtin' => false));

    if (empty($post_type))
        $post_type = get_post_type();

    if ('page' == $item->object && in_array($post_type, $custom_post_types)) {
        $css_class = array_filter($css_class, function($el) {
            return $el !== "current_page_parent";
        });

        $template = get_page_template_slug($item->object_id);
        if (!empty($template) && preg_match("/^page(-[^-]+)*-$post_type/", $template) === 1)
            array_push($css_class, 'current_page_parent');

    }

    return $css_class;
}
add_filter('nav_menu_css_class', 'theme_current_type_nav_class', 1, 2);

PS. Just to point out one shortcoming in all non-CSS solutions I've seen so far, including my own: Something not taken into account is highlighting the menu item parent/ancestor of an item linking to a page which displays posts of the current custom post type. Consider a custom post type "product" and a menu like:

Home  Company  News  Contact
      |
      \--About Us
      \--Products

"Products" is a page with a template "page-product.php" and shows an overview of posts of type 'product'. It is highlighted due to posted solution. However 'Company' as its parent/ancestor should also be highlighted, but isn't. Something to keep in mind.

like image 7
Tanuki Avatar answered Nov 06 '22 17:11

Tanuki


WP ticket: http://core.trac.wordpress.org/ticket/16382

function fix_blog_menu_css_class( $classes, $item ) {
    if ( is_tax( 'my-cat-tax' ) || is_singular( 'my-post-type' ) || is_post_type_archive( 'my-post-type' ) ) {
        if ( $item->object_id == get_option('page_for_posts') ) {
            $key = array_search( 'current_page_parent', $classes );
            if ( false !== $key )
                unset( $classes[ $key ] );
        }
    }

    return $classes;
}
add_filter( 'nav_menu_css_class', 'fix_blog_menu_css_class', 10, 2 );
like image 3
meloniq Avatar answered Nov 06 '22 16:11

meloniq


I did some more looking around on this and found another way of doing this.

add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2);
function current_type_nav_class($css_class, $item)
{
    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);
        });
    }

    $post_type = get_query_var('post_type');
    if ($item->attr_title !== '' && $item->attr_title === $post_type) {     
        array_push($css_class, 'current_page_parent');
    };

    return $css_class;
}

I got some help form this post and then modified it to also remove the "current_page_parent" class from the blog page. https://wordpress.stackexchange.com/questions/3014/highlighting-wp-nav-menu-ancestor-class-w-o-children-in-nav-structure/3034#3034

Cordially Vayu

like image 1
Vayu Robins Avatar answered Nov 06 '22 16:11

Vayu Robins