Wordpress Dynamic Page Menu Navigation

After writing the function that creates a list of pages that are children of a given page in Wordpress I needed something more robust and automatic. To that end I created a plugin that will create a widget that contains a dynamically created menu of pages.

The widget figures out what page is currently being displayed and will climb the page tree until it finds the root page. Whilst climbing the page tree the plugin will keep the path to the currently selected page and when the tree is printed out the path will be open. It is best suited for sites that have a solid hieratic page structure, rather than a simple blogging site.

In terms of efficiency I have tested it with pages nested up to 25 levels deep with only a small decrease in page load. However, for the average small Wordpress site this plugin is perfect as pages will only be nested a few levels deep.

<?php
/**
 * Plugin Name: Page Menu Navigation
 * Plugin URI: http://www.hashbangcode.com/
 * Description: Adds an intelligent page navigation menu that is dependent on page hierarchy.
 * Version: 1.0
 * Author: Philip Norton
 * Author URI: http://www.hashbangcode.com/
 */

/**
 * Print out hirachical page structure.
 *
 * @global object $post The current post.
 */
function printPages()
{
    global $post;

    if ($post->post_type == 'page') {
        if ($post->post_parent > 0) {
            $root  = findPathInformation($post);
            $pages = traversePageTree($root['root'], $root['activepath'], $root['depth']);
        } else {
            $root = $post;
            $pages = traversePageTree($root, array($root->ID), 1);
        }
        echo printPageTree($pages);
    }
}

/**
 * From a single page find out how deep it is
 *
 * @param object $page The current page.
 *
 * @return array An array of information about the page and the path.
 */
function findPathInformation($page)
{
    // Go up the tree and see what is in the path.
    $reverse_tree = climbPageTree($page);
    // Flattern the tree to get the current active path.
    $activePath = flattenTree($reverse_tree);

    // Make sure current page is in the active path.
    $activePath[] = $page->ID;

    $root = $reverse_tree[0];

    // Set to 2 as if we are in this code we are in the level just below root.
    $depth = 2;

    // Recursivley loop through the pages and find the root page and the depth.
    while (is_array($root->post_parent)) {
       ++$depth;
       $root = $root->post_parent[0];
    }

    return array('root' => $root, 'depth' => $depth, 'activepath' => $activePath);
}

/**
 * Flatten the tree into a single array.
 *
 * @param array $tree A multi dimensional array of pages.
 *
 * @return array A single dimensional array of pages.
 */
function flattenTree($tree)
{
    $flat = array();

    while (is_array($tree[0]->post_parent)) {
       $flat[] = $tree[0]->ID;
       $tree = $tree[0]->post_parent;
    }

    $flat[] = $tree[0]->ID;

    return $flat;
}

/**
 * Find out if the current page is in the active path.
 *
 * @param integer $id The ID of the current page.
 * @param array $activePath An array of ID's of the pages in the current path.
 *
 * @return boolean True if the page is in current path, otherwise false.
 */
function inActivePath($id, $activePath)
{
    if (in_array($id, $activePath)) {
        return true;
    } else {
        return false;
    }
}

/**
 * Starting with the current page go up level after level until the root page
 * reached. This function will run one SQL query for every level.
 *
 * @global wpdb $wpdb The current Wordpress database connection object.
 *
 * @param object $page The current page.
 *
 * @return <type>
 */
function climbPageTree($page)
{
    global $wpdb;
    $parent = $wpdb->get_results("SELECT ID, post_title, post_parent FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'page' AND ID = " . $page->post_parent . " ORDER BY menu_order, post_title", OBJECT);

    if (count($parent) > 0) {
        foreach ($parent as $item => $par) {
            if ($par->post_parent != 0) {
                $parent_parent = climbPageTree($par);
                if ($parent_parent !== false) {
                    $parent[$item]->post_parent = $parent_parent;
                }
            } else {
                // Reached top of tree
                return $parent;
            }
        }
    }

    return $parent;
}

/**
 * Traverse the page structure and create a tree of the pages.
 *
 * @global wpdb $wpdb The current Wordpress database connection object.
 *
 * @param object $page The page to start the tree traversal from, usually root.
 * @param array $activePath An array of page ID's in the active path.
 * @param integer $maxdepth The maximum depth to follow the traversal
 * @param integer $depth The current depth of the traversal.
 *
 * @return array A tree of pages.
 */
function traversePageTree($page, $activePath = array(), $maxdepth = 10, $depth = 0)
{
    if ($depth >= $maxdepth) {
        // We have reached the maximum depth, stop traversal.
        return array();
    }

    // Get Wordpress db object.
    global $wpdb;

    $children = $wpdb->get_results("SELECT ID, post_title, post_parent FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'page' AND post_parent = " . $page->ID . " ORDER BY menu_order, post_title", OBJECT);
    if (count($children) > 0) {
        foreach ($children as $item => $child) {
            if (inActivePath($child->ID, $activePath)) {
                // Current page is in active path, find the children.
                $children[$item]->children = traversePageTree($child, $activePath, $maxdepth, $depth + 1);
            }
        }
    }

    return $children;
}

/**
 * Print out the page tree as created by the traversePageTree() function.
 *
 * @see traversePageTree()
 *
 * @param array $pages A tree of pages.
 */
function printPageTree($pages)
{
    $class = '';
    $output = '';
    $output .= "\n<ul>\n";
    foreach ($pages as $page) {
        if (is_page($page->ID) === true) {
            $class = ' class="on"';
        }
        $output .=  "<li" . $class . "><a href=\"" . get_page_link($page->ID) . "\" title=\"" . $page->post_title . "\">" . $page->post_title . "</a>";
        $class = '';
        if (isset($page->children) && count($page->children) > 0) {
            $output .= printPageTree($page->children);
        }
        $output .=  "</li>\n";
    }
    $output .=  "</ul>\n";
    return $output;
}

/**
 * Widget function
 *
 * @param array $args
 */
function pageMenuNavigationWidget($args)
{
    extract($args);

    echo '<div id="subNav">';
    echo printPages();
    echo '</div>';
}

register_sidebar_widget(__('Page Menu Navigation'), 'pageMenuNavigationWidget');
$wp_registered_widgets[sanitize_title('Page Menu Navigation')]['description'] = 'Creates a navigation menu.';

If you like this plugin and find it useful then let me know and I will add it to the Wordpress plugin centre. I'm sure there are some improvements that could be made to the plugin, if you think of any then leave a comment and let me know.

Update 01/11/2010

I have had another look at generating a dynamic menu using the Walker_Page class. The good thing about using the Walker_Page method is that it can be used to override the default page widget that comes with WordPress.

Comments

I have been scouring the Internet to find something that would work like this. I've gotten close in some code but just couldn't get it to work. UGGG Great great work!!!! I can't believe I'm the first to find this. Thank you thank you thank you!!!!!!!!

Permalink

Nice work but i can't help thinking this would be easier:

<?php
		  if($post->post_parent)
		  $children =
			wp_list_pages("title_li=&child_of=".$post->post_parent."&echo=0");
		  else
		  $children = wp_list_pages("title_li=&child_of=".$post->ID."&echo=0");
		  if ($children) { ?>

<ul>
<?php echo $children; ?>
</ul>

Or am i missing something?

Permalink
Maybe it's good to have a look at navigo, a rather old plugin, that still works on current WP-Versions as it seems to be rather simple (but powerfull), even though no widget (but would work in a text-widget via
Permalink
The article is really awesome, and I got lots of valuable information from the article, it’s really very helpful for the visitors.
Permalink

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
1 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.