WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a custom walker that extends the Walker class. My code uses the Walker_Page class as it's base as it does pretty much everything else I want but I need to modify it a bit so that it behaves a little differently.

Essentially, I want to prevent the walker from creating new levels of navigation for menu items that are not related to the current page -- that is I only want it to walk deeper levels when the current page is either the current page, or an ancestor of it.

The key to this seems to be the $current_page (current_object_id) argument that is passed to the start_el method. If this were also passed to the start_lvl method, I think this would be easy to code, but start_el seems to be the only method that gets it.

Any help/direction would be much appreciated.

I would paste my code here, but it's basically the same as Walker_Page

share|improve this question

You may find this article I wrote over at WPtuts helpful.

The following example I've adapted from that article. It lists all the top leve links, but only explores ancestors of the 'current' menu item. Hopefully the logic is clear with the comments:

class WPSE73358_Ancestors_Only_Walker extends Walker_Nav_Menu {

    // Only follow down one branch
    function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

        // Check if element as a 'current element' class
        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
        $current_class = array_intersect( $current_element_markers, $element->classes );

        // If element has a 'current' class, it is an ancestor of the current element
        $ancestor_of_current = !empty($current_class);

        // If this is not the top level nor the current, or ancestor of the current menu item - stop here.
       if ( 0 != $depth &&  !$ancestor_of_current)

        parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );

Although it extends the Walker_Nav_Menu it is written so that it can extend pretty much any of the Walker classes (including the base class). You may find that the classes need to be changed, or - for instance with posts, it may be more appropriate to use get_post_ancestors() instead.

share|improve this answer
Thanks for your help. I actually read that article recently! It will require some tweaking like you said as the elements of a Walker_Page don't have a classes property. I thought of using get_post_ancestors but I was concerned that may be adding extra db queries on every iteration. I suppose that info is all probably cached though? – Evan Mattson Nov 21 '12 at 14:34
Yes it is, using it is fine. You will need to determine which page ID to apply get_post_ancestors() to though - I assume the current page? – Stephen Harris Nov 21 '12 at 15:23
Yeah, I got it, thanks! – Evan Mattson Nov 21 '12 at 16:29
..I almost got it. Will post my solution below when I get it figured out. Need to work out the logic so that it still shows posts that are children of the current page, not just ancestors. – Evan Mattson Nov 21 '12 at 21:45
up vote 0 down vote accepted

I accidentally left out an important detail in my question that made it a little more complex to solve. I also wanted to show sub navigation of the current page if it had children.

I did this by comparing the element's ancestors to the current page's ancestors (as well as the current page itself), if there wasn't any match, the iteration was skipped.

Here is my solution (Thanks Stephen!)

class WPSE73358_Walker extends Walker_Page {

    function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

        if ( 0 < $depth ) {

            $qoID = get_queried_object_id();

            // seperated for readability
            $current_ancestors = get_post_ancestors( $qoID );
            $element_ancestors = get_post_ancestors( $element->ID );

            $check = $current_ancestors;
            unset( $check[ count($check)-1 ] ); // remove the last ancestor - all pages will share this in common
            $check[] = $qoID; // include current page id

            $against = $element_ancestors;
            unset( $against[ count($against)-1 ] );
            // element id is unnecessary because we're checking against it's ancestry

            // check to see if the current page & current element have anything in common
            $results = array_intersect( $check, $against );

            // if current page & element have something in common, array will not be empty, and we can continue.
            // if not, move on to the next iteration.
            if ( empty( $results ) )

        parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.