0

The goal: use wordpress's wp_nav_menu to generate a menu where child pages were dynamically added while keeping the correct submenu classes.

Before:

  1. Installations
  2. Habits

After:

  1. Installations
    • Roof
    • Garden
  2. Habits

Basically, I have a wp_nav_menu set up to show some pages (i.e. "Installations" & "Habits"). Either of those pages could have several child pages associated with them and I wanted my navigation to dynamically add those child pages into the menu, without using wp_list_pages. In my case, the nav was pulling from a custom post type ("actions"), but this can be customized.

4
  • "This isn't a question..."-- then it should be split into a question and an answer.
    – s_ha_dum
    Jun 25, 2013 at 16:52
  • Hm, good point. That makes sense, I'll do that. Thanks.
    – danbrellis
    Jun 25, 2013 at 16:58
  • I just tried to split it, but it says I have to wait 7 hours to answer my own question. I'll fix it tomorrow.
    – danbrellis
    Jun 25, 2013 at 17:03
  • Must be your rep. Come back later.
    – s_ha_dum
    Jun 25, 2013 at 17:07

2 Answers 2

0

My solution uses the Walker_Nav_Menu and a custom class and is mostly hacked together from this post: How do I dynamically populate wp_nav_menu from a custom taxonomy?

Solution:

//define the custom post type
//could use "page" or "post" as well.
define("MENU_CPT", "action");

//custom function for selecting posts based on a page parent (ne' term_id)
function hgs_get_children_by_id($parent_id, $post_type=MENU_CPT) {
    $args = array(
        'posts_per_page' => -1,
        'post_type' => $post_type,
        'post_parent' => $parent_id,
        'orderby' => 'menu_order title',
        'order'  => 'DESC'
    );                  
    return get_posts( $args );
}

//custom nav menu walker class for Take Action Dropdown
class HGS_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 ) 
            {
            //var_dump($e); //for troubleshooting
                $top_level_elements[] = $e;
                if ( $e->type=='post_type' && $e->object == MENU_CPT ) {

                    $child_posts = hgs_get_children_by_id($e->object_id);

                    foreach ( $child_posts as $child ) {                      
                        $child = wp_setup_nav_menu_item($child);
                        $child->post_type = 'nav_menu_item';
                        $child->menu_item_parent = $e->$id_field;
                        $child->object = 'custom';
                        $child->type = 'custom';
                        $child->ID = $e->$id_field.$child->ID;
                        $children_elements[ $e->$id_field ][] = $child; 
                        $children_elements_classes[] = $child; 
                    }
                }
            }
            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=='post_type' && $e->object == MENU_CPT ) {

                        $child_posts = hgs_get_children_by_id($e->object_id);

                        foreach ( $child_posts as $child ) {                      
                            $child = wp_setup_nav_menu_item($child);
                            $child->post_type = 'nav_menu_item';
                            $child->menu_item_parent = $e->$id_field;
                            $child->object = 'custom';
                            $child->type = 'custom';
                            $child->ID = $e->$id_field.$child->ID;
                            $children_elements[ $e->$id_field ][] = $child;
                            $children_elements_classes[] = $child; 
                        }
                    }
                }
                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;
    }
}

We implement the menu like so:

<?php wp_nav_menu( array(
                        'theme_location'  => 'nav-right',
                        'menu'            => 'Right Navigation Column',
                        'container'       => 'div',
                        'container_class' => 'nav-inner',
                        'container_id'    => '',
                        'menu_class'      => 'menu',
                        'menu_id'         => '',
                        'echo'            => true,
                        'items_wrap'      => '<h2>Take Action</h2><ul class="%2$s">%3$s</ul>',
                        **'walker'        => new HGS_Walker_Nav_Menu**
                    ) ); ?>

So I hope this helps someone. It is certainly very customizable if you are using various post types in a single menu as well by running a condition on $e->type and $e->object.

If anyone has more insight or some thoughts on how to improve this, please share.

0

If your goal is to actually AUTO add the pages to nav menues, use a class like below.

You might wanna add some stuff when update pages or delete as well, but that was not the question, only add.

class EntexAutoSubmenu {

    /**
     * Constructor
     */
    function __construct(){
        add_action('publish_page', array($this, 'on_publish_page'));
    }

    /**
     * When publishing a new child page, add it to the appropriate custom menu.
     */
    function on_publish_page($post_id){

        // Theme supports custom menus?
        if (!current_theme_supports('menus')) return;

        // Published page has parent?
        $post = get_post($post_id);
        if (!$post->post_parent) return;

        $all_menus = get_terms('nav_menu', array('hide_empty' => true));

        // Loop through the menus to find page parent
        foreach ($all_menus as $menu) {

            $menu_parent = NULL;
            $menu_items = wp_get_nav_menu_items($menu->term_id, array('post_status' => 'publish,draft'));

            if (!is_array($menu_items)) continue;

            foreach ($menu_items as $menu_item){
                // Item already in menu?
                if ($menu_item->object_id == $post->ID) continue 2;
                if ($menu_item->object_id == $post->post_parent) $menu_parent = $menu_item;
            }

            // Add new item
            if ($menu_parent) {
                wp_update_nav_menu_item($menu->term_id, 0, array(
                    'menu-item-object-id' => $post->ID,
                    'menu-item-object' => $post->post_type,
                    'menu-item-parent-id' => $menu_parent->ID,
                    'menu-item-type' => 'post_type',
                    'menu-item-status' => 'publish'
                ));
            }
        }
    }
}

$auto_submenu = new EntexAutoSubmenu();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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