How do I dynamically populate wp_nav_menu from a custom taxonomy?

I know this question has been asked in different forms. But as far as I can tell from searching Google and SE/WP it’s never been satisfactorily answered.

Let’s say I have a taxonomy clubs and a custom post type person.

On each taxonomy-club.php page I display all the people who are members of that club.

But I want to achieve a similar effect in my menus:

wp_nav_menu
|
|--Everyone
|  \
|  |- Mr Verlaine
|  |- Mr Lloyd
|  |- Mr Osterberg 
|  |- Mr Williamson    
|
|--Snooker Club
|  \ 
|  |- Mr Osterberg 
|  |- Mr Williamson
|
|--Golf Club
|  \
|  |- Mr Verlaine
|  |- Mr Lloyd

If possible, I would like my menus to have the current page highlighting functionality of wp_nav_menu().

I’ve experimented with this:

add_filter('wp_nav_menu_items','add_terms');

function add_terms($items) {
$items .= '<li>' . wp_list_categories( array(
    'taxonomy' => 'clubs', 
    'title_li' => 'Clubs') ) 
     . '</li>';
return $items;
}

but it doesn’t populate the submenus with the people’s names, and it doesn’t have that nav_menu css-class goodness.

Must I create a more complicated <li> by querying out the posts in each term of the taxonomy, paste that in, and that’s my lot? Or extend the built-in walker (will it give me the advantages of wp_nav_menu?)

Or something else…

Thanks!

1
1

I would go for custom nav menu walker…

//define the custom post type and custom taxonomy
define("MENU_CPT", "people");
define("MENU_CT", "club");

//custom function for selecting posts based on a term
function get_posts_by_term($term_id, $post_type=MENU_CPT, $taxonomy=MENU_CT) {
    $args = array(
        'posts_per_page' => -1,
        'post_type' => $post_type,
        'tax_query' => array(
            array(
                'taxonomy' => $taxonomy,
                'field' => 'id',
                'terms' => $term_id
            )
        )
    );                  
    return get_posts( $args );
}

//custom nav menu walker class
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
    /**
     * Display array of elements hierarchically.
     *
     * It is a generic function which does not assume any existing order of
     * elements. max_depth = -1 means flatly display every element. max_depth =
     * 0 means display all levels. max_depth > 0  specifies the number of
     * display levels.
     *
     * @since 2.1.0
     *
     * @param array $elements
     * @param int $max_depth
     * @return string
     */
    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output="";

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field ) 
            {
                $top_level_elements[] = $e;
                if ( $e->type=='taxonomy' && $e->object == MENU_CT ) {

                    $taxonomy_posts = get_posts_by_term($e->object_id);

                    foreach ( $taxonomy_posts as $tax_post ) {                      
                        $tax_post = wp_setup_nav_menu_item($tax_post);
                        $tax_post->post_type="nav_menu_item";
                        $tax_post->menu_item_parent = $e->$id_field;
                        $tax_post->object="custom";
                        $tax_post->type="custom";
                        $tax_post->ID = $e->$id_field.$tax_post->ID;
                        $children_elements[ $e->$id_field ][] = $tax_post; 
                        $children_elements_classes[] = $tax_post; 
                    }
                }
            }
            else
            {
                $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                {
                    $top_level_elements[] = $e;
                    if ( $e->type=='taxonomy' && $e->object == MENU_CT ) {

                        $taxonomy_posts = get_posts_by_term($e->object_id);

                        foreach ( $taxonomy_posts as $tax_post ) {                      
                            $tax_post = wp_setup_nav_menu_item($tax_post);
                            $tax_post->post_type="nav_menu_item";
                            $tax_post->menu_item_parent = $e->$id_field;
                            $tax_post->object="custom";
                            $tax_post->type="custom";
                            $tax_post->ID = $e->$id_field.$tax_post->ID;
                            $children_elements[ $e->$id_field ][] = $tax_post;
                            $children_elements_classes[] = $tax_post; 
                        }
                    }
                }
                else
                {
                    $children_elements[ $e->$parent_field ][] = $e;
                }
            }
        }

        //assing the classes to our dynamically populated posts
        if ( $children_elements_classes )
            _wp_menu_item_classes_by_context($children_elements_classes);

        foreach ( $top_level_elements as $e )
            $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }

         return $output;
    }
}

…you can now use it in wp_nav_menu function:

wp_nav_menu( array( 'theme_location' => 'primary', 'walker' => new Custom_Walker_Nav_Menu ) );

When you put the term from the defined taxonomy into your menu it will be automatically populated by the posts (people names) from defined custom post type for particular taxonomy term.

Leave a Comment