Wordpress Dynamic Page Menu Navigation

Wednesday, May 19, 2010 - 10:25

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?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.

Category: 
philipnorton42's picture

Philip Norton

Phil is the founder and administrator of #! code and is an IT professional working in the North West of the UK.
Google+ | Twitter

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!!!!!!!! And thank God for you!
Nice work but i can't help thinking this would be easier: 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) { ?>
Or am i missing something?
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
The article is really awesome, and I got lots of valuable information from the article, it’s really very helpful for the visitors.

Add new comment