2 // $Id: menu.inc 144 2007-03-28 07:52:20Z thierry $
6 * API for the Drupal menu system.
10 * @defgroup menu Menu system
12 * Define the navigation menus, and route page requests to code based on URLs.
14 * The Drupal menu system drives both the navigation system from a user
15 * perspective and the callback system that Drupal uses to respond to URLs
16 * passed from the browser. For this reason, a good understanding of the
17 * menu system is fundamental to the creation of complex modules.
19 * Drupal's menu system follows a simple hierarchy defined by paths.
20 * Implementations of hook_menu() define menu items and assign them to
21 * paths (which should be unique). The menu system aggregates these items
22 * and determines the menu hierarchy from the paths. For example, if the
23 * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
24 * would form the structure:
31 * Note that the number of elements in the path does not necessarily
32 * determine the depth of the menu item in the tree.
34 * When responding to a page request, the menu system looks to see if the
35 * path requested by the browser is registered as a menu item with a
36 * callback. If not, the system searches up the menu tree for the most
37 * complete match with a callback it can find. If the path a/b/i is
38 * requested in the tree above, the callback for a/b would be used.
40 * The found callback function is called with any arguments specified
41 * in the "callback arguments" attribute of its menu item. The
42 * attribute must be an array. After these arguments, any remaining
43 * components of the path are appended as further arguments. In this
44 * way, the callback for a/b above could respond to a request for
45 * a/b/i differently than a request for a/b/j.
47 * For an illustration of this process, see page_example.module.
49 * Access to the callback functions is also protected by the menu system.
50 * The "access" attribute of each menu item is checked as the search for a
51 * callback proceeds. If this attribute is TRUE, then access is granted; if
52 * FALSE, then access is denied. The first found "access" attribute
53 * determines the accessibility of the target. Menu items may omit this
54 * attribute to use the value provided by an ancestor item.
56 * In the default Drupal interface, you will notice many links rendered as
57 * tabs. These are known in the menu system as "local tasks", and they are
58 * rendered as tabs by default, though other presentations are possible.
59 * Local tasks function just as other menu items in most respects. It is
60 * convention that the names of these tasks should be short verbs if
61 * possible. In addition, a "default" local task should be provided for
62 * each set. When visiting a local task's parent menu item, the default
63 * local task will be rendered as if it is selected; this provides for a
64 * normal tab user experience. This default task is special in that it
65 * links not to its provided path, but to its parent item's path instead.
66 * The default task's path is only used to place it appropriately in the
73 * Flags for use in the "type" attribute of menu items.
76 define('MENU_IS_ROOT', 0x0001);
77 define('MENU_VISIBLE_IN_TREE', 0x0002);
78 define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
79 define('MENU_VISIBLE_IF_HAS_CHILDREN', 0x0008);
80 define('MENU_MODIFIABLE_BY_ADMIN', 0x0010);
81 define('MENU_MODIFIED_BY_ADMIN', 0x0020);
82 define('MENU_CREATED_BY_ADMIN', 0x0040);
83 define('MENU_IS_LOCAL_TASK', 0x0080);
84 define('MENU_EXPANDED', 0x0100);
85 define('MENU_LINKS_TO_PARENT', 0x0200);
88 * @} End of "Menu flags".
92 * @name Menu item types
94 * Menu item definitions provide one of these constants, which are shortcuts for
95 * combinations of the above flags.
99 * Normal menu items show up in the menu tree and can be moved/hidden by
100 * the administrator. Use this for most menu items. It is the default value if
101 * no menu item type is specified.
103 define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
106 * Item groupings are used for pages like "node/add" that simply list
107 * subpages to visit. They are distinguished from other pages in that they will
108 * disappear from the menu if no subpages exist.
110 define('MENU_ITEM_GROUPING', MENU_VISIBLE_IF_HAS_CHILDREN | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
113 * Callbacks simply register a path so that the correct function is fired
114 * when the URL is accessed. They are not shown in the menu.
116 define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB);
119 * Dynamic menu items change frequently, and so should not be stored in the
120 * database for administrative customization.
122 define('MENU_DYNAMIC_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
125 * Modules may "suggest" menu items that the administrator may enable. They act
126 * just as callbacks do until enabled, at which time they act like normal items.
128 define('MENU_SUGGESTED_ITEM', MENU_MODIFIABLE_BY_ADMIN | MENU_VISIBLE_IN_BREADCRUMB);
131 * Local tasks are rendered as tabs by default. Use this for menu items that
132 * describe actions to be performed on their parent item. An example is the path
133 * "node/52/edit", which performs the "edit" task on "node/52".
135 define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK);
138 * Every set of local tasks should provide one "default" task, that links to the
139 * same path as its parent when clicked.
141 define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT);
144 * Custom items are those defined by the administrator. Reserved for internal
145 * use; do not return from hook_menu() implementations.
147 define('MENU_CUSTOM_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);
150 * Custom menus are those defined by the administrator. Reserved for internal
151 * use; do not return from hook_menu() implementations.
153 define('MENU_CUSTOM_MENU', MENU_IS_ROOT | MENU_VISIBLE_IN_TREE | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);
156 * @} End of "Menu item types".
160 * @name Menu status codes
162 * Status codes for menu callbacks.
165 define('MENU_FOUND', 1);
166 define('MENU_NOT_FOUND', 2);
167 define('MENU_ACCESS_DENIED', 3);
168 define('MENU_SITE_OFFLINE', 4);
171 * @} End of "Menu status codes".
175 * Return the menu data structure.
177 * The returned structure contains much information that is useful only
178 * internally in the menu system. External modules are likely to need only
179 * the ['visible'] element of the returned array. All menu items that are
180 * accessible to the current user and not hidden will be present here, so
181 * modules and themes can use this structure to build their own representations
184 * $menu['visible'] will contain an associative array, the keys of which
185 * are menu IDs. The values of this array are themselves associative arrays,
186 * with the following key-value pairs defined:
187 * - 'title' - The displayed title of the menu or menu item. It will already
188 * have been translated by the locale system.
189 * - 'description' - The description (link title attribute) of the menu item.
190 * It will already have been translated by the locale system.
191 * - 'path' - The Drupal path to the menu item. A link to a particular item
192 * can thus be constructed with
193 * l($item['title'], $item['path'], array('title' => $item['description'])).
194 * - 'children' - A linear list of the menu ID's of this item's children.
196 * Menu ID 0 is the "root" of the menu. The children of this item are the
197 * menus themselves (they will have no associated path). Menu ID 1 will
198 * always be one of these children; it is the default "Navigation" menu.
200 function menu_get_menu() {
205 if (!isset($_menu['items'])) {
206 // _menu_build() may indirectly call this function, so prevent infinite loops.
207 $_menu['items'] = array();
209 $cid = "menu:$user->uid:$locale";
210 if ($cached = cache_get($cid)) {
211 $_menu = unserialize($cached->data);
215 // Cache the menu structure for this user, to expire after one day.
216 cache_set($cid, serialize($_menu), time() + (60 * 60 * 24));
219 // Make sure items that cannot be cached are added.
220 _menu_append_contextual_items();
222 // Reset the cached $menu in menu_get_item().
223 menu_get_item(NULL, NULL, TRUE);
230 * Return the local task tree.
232 * Unlike the rest of the menu structure, the local task tree cannot be cached
233 * nor determined too early in the page request, because the user's current
234 * location may be changed by a menu_set_location() call, and the tasks shown
235 * (just as the breadcrumb trail) need to reflect the changed location.
237 function menu_get_local_tasks() {
240 // Don't cache the local task tree, as it varies by location and tasks are
241 // allowed to be dynamically determined.
242 if (!isset($_menu['local tasks'])) {
243 // _menu_build_local_tasks() may indirectly call this function, so prevent
245 $_menu['local tasks'] = array();
246 $pid = menu_get_active_nontask_item();
247 if (!_menu_build_local_tasks($pid)) {
248 // If the build returned FALSE, the tasks need not be displayed.
249 $_menu['local tasks'][$pid]['children'] = array();
253 return $_menu['local tasks'];
257 * Retrieves the menu item specified by $mid, or by $path if $mid is not given.
260 * The menu ID of the menu item to retrieve.
262 * The internal path of the menu item to retrieve. Defaults to NULL. Only
263 * used if $mid is not set.
265 * Optional flag that resets the static variable cache of the menu tree, if
266 * set to TRUE. Default is FALSE.
269 * The menu item found in the site menu, or an empty array if none could be
272 function menu_get_item($mid, $path = NULL, $reset = FALSE) {
275 if (!isset($menu) || $reset) {
276 $menu = menu_get_menu();
280 return $menu['items'][$mid];
284 return $menu['items'][$menu['path index'][$path]];
291 * Retrieves the menu ID and title of all root menus.
294 * Array containing all menus (but not menu items), in the form mid => title.
296 function menu_get_root_menus() {
297 $menu = menu_get_menu();
298 $root_menus = array();
300 foreach ($menu['items'][0]['children'] as $mid) {
301 $root_menus[$mid] = $menu['items'][$mid]['title'];
308 * Change the current menu location of the user.
310 * Frequently, modules may want to make a page or node act as if it were
311 * in the menu tree somewhere, even though it was not registered in a
312 * hook_menu() implementation. If the administrator has rearranged the menu,
313 * the newly set location should respect this in the breadcrumb trail and
314 * expanded/collapsed status of menu items in the tree. This function
315 * allows this behavior.
318 * An array specifying a complete or partial breadcrumb trail for the
319 * new location, in the same format as the return value of hook_menu().
320 * The last element of this array should be the new location itself.
322 * This function will set the new breadcrumb trail to the passed-in value,
323 * but if any elements of this trail are visible in the site tree, the
324 * trail will be "spliced in" to the existing site navigation at that point.
326 function menu_set_location($location) {
328 $temp_id = min(array_keys($_menu['items'])) - 1;
331 // Don't allow this function to change the actual current path, just the
332 // position in the menu tree.
333 $location[count($location) - 1]['path'] = $_GET['q'];
335 foreach (array_reverse($location) as $item) {
336 if (isset($_menu['path index'][$item['path']])) {
337 $mid = $_menu['path index'][$item['path']];
338 if (isset($_menu['visible'][$mid])) {
339 // Splice in the breadcrumb at this location.
341 $_menu['items'][$prev_id]['pid'] = $mid;
347 // A hidden item; show it, but only temporarily.
348 $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
350 $_menu['items'][$prev_id]['pid'] = $mid;
356 $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
358 $_menu['items'][$prev_id]['pid'] = $temp_id;
360 $_menu['items'][$temp_id] = $item;
361 $_menu['path index'][$item['path']] = $temp_id;
369 // Didn't find a home, so attach this to the main navigation menu.
370 $_menu['items'][$prev_id]['pid'] = 1;
373 $final_item = array_pop($location);
374 menu_set_active_item($final_item['path']);
378 * Execute the handler associated with the active menu item.
380 * This is called early in the page request. The active menu item is at
381 * this point determined exclusively by the URL. The handler that is called
382 * here may, as a side effect, change the active menu item so that later
383 * menu functions (that display the menus and breadcrumbs, for example)
384 * act as if the user were in a different location on the site.
386 function menu_execute_active_handler() {
387 if (_menu_site_is_offline()) {
388 return MENU_SITE_OFFLINE;
391 $menu = menu_get_menu();
393 // Determine the menu item containing the callback.
395 while ($path && !isset($menu['callbacks'][$path])) {
396 $path = substr($path, 0, strrpos($path, '/'));
399 if (!isset($menu['callbacks'][$path])) {
400 return MENU_NOT_FOUND;
403 if (!function_exists($menu['callbacks'][$path]['callback'])) {
404 return MENU_NOT_FOUND;
407 if (!_menu_item_is_accessible(menu_get_active_item())) {
408 return MENU_ACCESS_DENIED;
411 // We found one, and are allowed to execute it.
412 $arguments = isset($menu['callbacks'][$path]['callback arguments']) ? $menu['callbacks'][$path]['callback arguments'] : array();
413 $arg = substr($_GET['q'], strlen($path) + 1);
415 $arguments = array_merge($arguments, explode('/', $arg));
418 return call_user_func_array($menu['callbacks'][$path]['callback'], $arguments);
422 * Returns the ID of the active menu item.
424 function menu_get_active_item() {
425 return menu_set_active_item();
429 * Sets the path of the active menu item.
431 function menu_set_active_item($path = NULL) {
434 if (!isset($stored_mid) || isset($path)) {
435 $menu = menu_get_menu();
443 while ($path && !isset($menu['path index'][$path])) {
444 $path = substr($path, 0, strrpos($path, '/'));
446 $stored_mid = isset($menu['path index'][$path]) ? $menu['path index'][$path] : 0;
448 // Search for default local tasks to activate instead of this item.
452 if (isset($menu['items'][$stored_mid]['children'])) {
453 foreach ($menu['items'][$stored_mid]['children'] as $cid) {
454 if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) {
462 // Reset the cached $menu in menu_get_item().
463 menu_get_item(NULL, NULL, TRUE);
470 * Returns the ID of the current menu item or, if the current item is a
471 * local task, the menu item to which this task is attached.
473 function menu_get_active_nontask_item() {
474 $mid = menu_get_active_item();
476 // Find the first non-task item:
478 $item = menu_get_item($mid);
480 if (!($item['type'] & MENU_IS_LOCAL_TASK)) {
489 * Returns the title of the active menu item.
491 function menu_get_active_title() {
492 if ($mid = menu_get_active_nontask_item()) {
493 $item = menu_get_item($mid);
494 return $item['title'];
499 * Returns the help associated with the active menu item.
501 function menu_get_active_help() {
505 if (!_menu_item_is_accessible(menu_get_active_item())) {
506 // Don't return help text for areas the user cannot access.
510 foreach (module_list() as $name) {
511 if (module_hook($name, 'help')) {
512 if ($temp = module_invoke($name, 'help', $path)) {
513 $output .= $temp . "\n";
515 if (module_hook('help', 'page')) {
516 if (substr($path, 0, 6) == "admin/") {
517 if (module_invoke($name, 'help', 'admin/help#' . substr($path, 6))) {
518 $output .= theme("more_help_link", url('admin/help/' . substr($path, 6)));
528 * Returns an array of rendered menu items in the active breadcrumb trail.
530 function menu_get_active_breadcrumb() {
532 // No breadcrumb for the front page.
533 if (drupal_is_front_page()) {
537 $links[] = l(t('Home'), variable_get('site_frontpage', 'node'));
539 $trail = _menu_get_active_trail();
540 foreach ($trail as $mid) {
541 $item = menu_get_item($mid);
542 if ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB) {
543 $links[] = menu_item_link($mid);
547 // The last item in the trail is the page title; don't display it here.
554 * Returns true when the menu item is in the active trail.
556 function menu_in_active_trail($mid) {
557 $trail = _menu_get_active_trail();
559 return in_array($mid, $trail);
563 * Returns true when the menu item is in the active trail within a
564 * specific subsection of the menu tree.
567 * The menu item being considered.
569 * The root of the subsection of the menu tree in which to look.
571 function menu_in_active_trail_in_submenu($mid, $pid) {
572 $trail = _menu_get_active_trail_in_submenu($pid);
578 return in_array($mid, $trail);
582 * Populate the database representation of the menu.
584 * This need only be called at the start of pages that modify the menu.
586 function menu_rebuild() {
587 // Clear the page cache, so that changed menus are reflected for anonymous users.
589 // Also clear the menu cache.
590 cache_clear_all('menu:', TRUE);
594 if (module_exist('menu')) {
595 $menu = menu_get_menu();
597 // Fill a queue of new menu items which are modifiable.
598 $new_items = array();
599 foreach ($menu['items'] as $mid => $item) {
600 if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) {
601 $new_items[$mid] = $item;
606 // Save the new items updating the pids in each iteration
607 while (($c = count($new_items)) && ($c != $old_count)) {
608 $old_count = count($new_items);
609 foreach($new_items as $mid => $item) {
610 // If the item has a valid parent, save it
611 if ($item['pid'] >= 0) {
612 // The new menu ID gets passed back by reference as $item['mid']
613 menu_save_item($item);
614 // Fix parent IDs for the children of the menu item just saved
615 if ($item['children']) {
616 foreach ($item['children'] as $child) {
617 if (isset($new_items[$child])) {
618 $new_items[$child]['pid'] = $item['mid'];
623 unset($new_items[$mid]);
627 // Rebuild the menu to account for the changes.
631 // Reset the cached $menu in menu_get_item().
632 menu_get_item(NULL, NULL, TRUE);
637 * Generate the HTML for a menu tree.
640 * The parent id of the menu.
644 function theme_menu_tree($pid = 1) {
645 if ($tree = menu_tree($pid)) {
646 return "\n<ul class=\"menu\">\n". $tree ."\n</ul>\n";
651 * Returns a rendered menu tree.
654 * The parent id of the menu.
656 function menu_tree($pid = 1) {
657 $menu = menu_get_menu();
660 if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
661 foreach ($menu['visible'][$pid]['children'] as $mid) {
662 $type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL;
663 $children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL;
664 $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0);
672 * Generate the HTML output for a single menu item.
675 * The menu id of the item.
677 * A string containing any rendered child items of this menu.
679 * A boolean indicating whether this menu item is a leaf.
683 function theme_menu_item($mid, $children = '', $leaf = TRUE) {
684 return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid) . $children ."</li>\n";
688 * Generate the HTML representing a given menu item ID.
691 * The menu item to render.
693 * The menu item which should be used to find the correct path.
697 function theme_menu_item_link($item, $link_item) {
698 return l($item['title'], $link_item['path'], isset($item['description']) ? array('title' => $item['description']) : array());
702 * Returns the rendered link to a menu item.
705 * The menu item id to render.
707 function menu_item_link($mid) {
708 $item = menu_get_item($mid);
711 while ($link_item['type'] & MENU_LINKS_TO_PARENT) {
712 $link_item = menu_get_item($link_item['pid']);
715 return theme('menu_item_link', $item, $link_item);
719 * Returns the rendered local tasks. The default implementation renders
724 function theme_menu_local_tasks() {
727 if ($primary = menu_primary_local_tasks()) {
728 $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
730 if ($secondary = menu_secondary_local_tasks()) {
731 $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
738 * Returns the rendered HTML of the primary local tasks.
740 function menu_primary_local_tasks() {
741 $local_tasks = menu_get_local_tasks();
742 $pid = menu_get_active_nontask_item();
745 if (count($local_tasks[$pid]['children'])) {
746 foreach ($local_tasks[$pid]['children'] as $mid) {
747 $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE);
755 * Returns the rendered HTML of the secondary local tasks.
757 function menu_secondary_local_tasks() {
758 $local_tasks = menu_get_local_tasks();
759 $pid = menu_get_active_nontask_item();
762 if (count($local_tasks[$pid]['children'])) {
763 foreach ($local_tasks[$pid]['children'] as $mid) {
764 if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) {
765 foreach ($local_tasks[$mid]['children'] as $cid) {
766 $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE);
776 * Generate the HTML representing a given menu item ID as a tab.
779 * The menu ID to render.
781 * Whether this tab or a subtab is the active menu item.
783 * Whether this tab is a primary tab or a subtab.
787 function theme_menu_local_task($mid, $active, $primary) {
789 return '<li class="active">'. menu_item_link($mid) ."</li>\n";
792 return '<li>'. menu_item_link($mid) ."</li>\n";
797 * Returns an array containing the primary links.
798 * Can optionally descend from the root of the Primary links menu towards the
799 * current node for a specified number of levels and return that submenu.
800 * Used to generate a primary/secondary menu from different levels of one menu.
802 * @param $start_level
803 * This optional parameter can be used to retrieve a context-sensitive array
804 * of links at $start_level levels deep into the Primary links menu.
805 * The default is to return the top-level links.
807 * The parent menu ID from which to search for children. Defaults to the
808 * menu_primary_menu setting.
809 * @return An array containing the themed links as the values. The keys of
810 * the array contain some extra encoded information about the results.
811 * The format of the key is {level}-{num}{-active}.
812 * level is the depth within the menu tree of this list.
813 * num is the number within this array, used only to make the key unique.
814 * -active is appended if this element is in the active trail.
816 function menu_primary_links($start_level = 1, $pid = 0) {
817 if (!module_exist('menu')) {
821 $pid = variable_get('menu_primary_menu', 0);
827 if ($start_level < 1) {
831 if ($start_level > 1) {
832 $trail = _menu_get_active_trail_in_submenu($pid);
837 $pid = $trail[$start_level - 1];
841 $menu = menu_get_menu();
843 if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) {
845 foreach ($menu['visible'][$pid]['children'] as $cid) {
846 $index = "menu-$start_level-$count";
847 if (menu_in_active_trail_in_submenu($cid, $pid)) {
850 $links[$index] = menu_item_link($cid);
855 // Special case - provide link to admin/menu if primary links is empty.
856 if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0)) {
857 $links['1-1'] = l(t('edit primary links'),'admin/menu');
864 * Returns an array containing the secondary links.
865 * Secondary links can be either a second level of the Primary links
866 * menu or generated from their own menu.
868 function menu_secondary_links() {
869 $msm = variable_get('menu_secondary_menu', 0);
874 if ($msm == variable_get('menu_primary_menu', 0)) {
875 return menu_primary_links(2, $msm);
878 return menu_primary_links(1, $msm);
882 * Returns the themed HTML for primary and secondary links.
883 * Note that this function is overridden by most core themes because
884 * those themes display links in "link | link" format, not from a list.
885 * Also note that by default links rendered with this function are
886 * displayed with the same CSS as is used for the local tasks.
887 * If a theme wishes to render links from a ul it is expected that
888 * the theme will provide suitable CSS.
891 * An array containing links to render.
893 * A string containing the themed links.
897 function theme_menu_links($links) {
898 if (!count($links)) {
901 $level_tmp = explode('-', key($links));
902 $level = $level_tmp[0];
903 $output = "<ul class=\"links-$level\">\n";
904 foreach ($links as $index => $link) {
906 if (stristr($index, 'active')) {
907 $output .= ' class="active"';
909 $output .= ">$link</li>\n";
917 * @} End of "defgroup menu".
921 * Returns an array with the menu items that lead to the current menu item.
923 function _menu_get_active_trail() {
926 if (!isset($trail)) {
929 $mid = menu_get_active_item();
931 // Follow the parents up the chain to get the trail.
932 while ($mid && ($item = menu_get_item($mid))) {
933 array_unshift($trail, $mid);
941 * Find the active trail through a specific subsection of the menu tree.
944 * The root item from which the active trail must descend.
946 function _menu_get_active_trail_in_submenu($pid) {
949 if (!isset($trails)) {
950 // Find all menu items which point to the current node and for each
951 // follow the parents up the chain to build an active trail.
953 $menu = menu_get_menu();
956 while ($path && !$count) {
957 foreach ($menu['items'] as $key => $item) {
958 if (isset($item['path']) && $item['path'] == $path) {
959 $trails[$count] = array();
961 while ($mid && $menu['items'][$mid]) {
962 array_unshift($trails[$count], $mid);
963 $mid = $menu['items'][$mid]['pid'];
968 $path = substr($path, 0, strrpos($path, '/'));
973 foreach ($trails as $trail) {
974 $count_trail = count($trail);
975 for ($i = 0; $i < $count_trail; $i++) {
976 if ($trail[$i] == $pid) {
977 // Return a trail from $pid down to the current page inclusive.
978 for ( ; $i < $count_trail; $i++) {
979 $subtrail[] = $trail[$i];
991 * Comparator routine for use in sorting menu items.
993 function _menu_sort($a, $b) {
994 $menu = menu_get_menu();
996 $a = &$menu['items'][$a];
997 $b = &$menu['items'][$b];
999 if ($a['weight'] < $b['weight']) {
1002 elseif ($a['weight'] > $b['weight']) {
1005 elseif (isset($a['title']) && isset($b['title'])) {
1006 return strnatcasecmp($a['title'], $b['title']);
1014 * Build the menu by querying both modules and the database.
1016 function _menu_build() {
1020 // Start from a clean slate.
1023 $_menu['path index'] = array();
1024 // Set up items array, including default "Navigation" menu.
1025 $_menu['items'] = array(
1026 0 => array('path' => '', 'title' => '', 'type' => MENU_IS_ROOT),
1027 1 => array('pid' => 0, 'path' => '', 'title' => t('Navigation'), 'weight' => -50, 'access' => TRUE, 'type' => MENU_IS_ROOT | MENU_VISIBLE_IN_TREE)
1029 $_menu['callbacks'] = array();
1031 // Build a sequential list of all menu items.
1032 $menu_item_list = module_invoke_all('menu', TRUE);
1034 // Menu items not in the DB get temporary negative IDs.
1037 foreach ($menu_item_list as $item) {
1038 if (!isset($item['path'])) {
1041 if (!isset($item['type'])) {
1042 $item['type'] = MENU_NORMAL_ITEM;
1044 if (!isset($item['weight'])) {
1045 $item['weight'] = 0;
1048 if (isset($_menu['path index'][$item['path']])) {
1049 // Newer menu items overwrite older ones.
1050 unset($_menu['items'][$_menu['path index'][$item['path']]]);
1052 if (isset($item['callback'])) {
1053 $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']);
1054 if (isset($item['callback arguments'])) {
1055 $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments'];
1058 unset($item['callback']);
1059 unset($item['callback arguments']);
1060 $_menu['items'][$mid] = $item;
1061 $_menu['path index'][$item['path']] = $mid;
1066 // Now fetch items from the DB, reassigning menu IDs as needed.
1067 if (module_exist('menu')) {
1068 $result = db_query(db_rewrite_sql('SELECT m.mid, m.* FROM {menu} m ORDER BY m.mid ASC', 'm', 'mid'));
1069 while ($item = db_fetch_object($result)) {
1070 // Handle URL aliases if entered in menu administration.
1071 if (!isset($_menu['path index'][$item->path])) {
1072 $item->path = drupal_get_normal_path($item->path);
1074 if (isset($_menu['path index'][$item->path])) {
1075 // The path is already declared.
1076 $old_mid = $_menu['path index'][$item->path];
1078 // It had a temporary ID, so use a permanent one.
1079 $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
1080 unset($_menu['items'][$old_mid]);
1081 $_menu['path index'][$item->path] = $item->mid;
1084 // It has a permanent ID. Only replace with non-custom menu items.
1085 if ($item->type & MENU_CREATED_BY_ADMIN) {
1086 $_menu['items'][$item->mid] = array('path' => $item->path);
1089 // Leave the old item around as a shortcut to this one.
1090 $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
1091 $_menu['path index'][$item->path] = $item->mid;
1096 // The path was not declared, so this is a custom item or an orphaned one.
1097 if ($item->type & MENU_CREATED_BY_ADMIN) {
1098 $_menu['items'][$item->mid] = array('path' => $item->path);
1099 if (!empty($item->path)) {
1100 $_menu['path index'][$item->path] = $item->mid;
1105 // If the administrator has changed the item, reflect the change.
1106 if ($item->type & MENU_MODIFIED_BY_ADMIN) {
1107 $_menu['items'][$item->mid]['title'] = $item->title;
1108 $_menu['items'][$item->mid]['description'] = $item->description;
1109 $_menu['items'][$item->mid]['pid'] = $item->pid;
1110 $_menu['items'][$item->mid]['weight'] = $item->weight;
1111 $_menu['items'][$item->mid]['type'] = $item->type;
1116 // Associate parent and child menu items.
1117 _menu_find_parents($_menu['items']);
1119 // Prepare to display trees to the user as required.
1120 _menu_build_visible_tree();
1124 * Determine whether the given menu item is accessible to the current user.
1126 * Use this instead of just checking the "access" property of a menu item
1127 * to properly handle items with fall-through semantics.
1129 function _menu_item_is_accessible($mid) {
1130 $menu = menu_get_menu();
1132 // Follow the path up to find the first "access" attribute.
1133 $path = isset($menu['items'][$mid]['path']) ? $menu['items'][$mid]['path'] : NULL;
1134 while ($path && (!isset($menu['path index'][$path]) || !isset($menu['items'][$menu['path index'][$path]]['access']))) {
1135 $path = substr($path, 0, strrpos($path, '/'));
1138 // Items without any access attribute up the chain are denied, unless they
1139 // were created by the admin. They most likely point to non-Drupal directories
1140 // or to an external URL and should be allowed.
1141 return $menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN;
1143 return $menu['items'][$menu['path index'][$path]]['access'];
1147 * Find all visible items in the menu tree, for ease in displaying to user.
1149 * Since this is only for display, we only need title, path, and children
1152 function _menu_build_visible_tree($pid = 0) {
1155 if (isset($_menu['items'][$pid])) {
1156 $parent = $_menu['items'][$pid];
1158 $children = array();
1159 if (isset($parent['children'])) {
1160 usort($parent['children'], '_menu_sort');
1161 foreach ($parent['children'] as $mid) {
1162 $children = array_merge($children, _menu_build_visible_tree($mid));
1165 $visible = ($parent['type'] & MENU_VISIBLE_IN_TREE) ||
1166 ($parent['type'] & MENU_VISIBLE_IF_HAS_CHILDREN && count($children) > 0);
1167 $allowed = _menu_item_is_accessible($pid);
1169 if (($parent['type'] & MENU_IS_ROOT) || ($visible && $allowed)) {
1170 $_menu['visible'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children, 'type' => $parent['type']);
1171 foreach ($children as $mid) {
1172 $_menu['visible'][$mid]['pid'] = $pid;
1185 * Account for menu items that are only defined at certain paths, so will not
1188 * We don't support the full range of menu item options for these menu items. We
1189 * don't support MENU_VISIBLE_IF_HAS_CHILDREN, and we require parent items to be
1190 * declared before their children.
1192 function _menu_append_contextual_items() {
1195 // Build a sequential list of all menu items.
1196 $menu_item_list = module_invoke_all('menu', FALSE);
1198 // Menu items not in the DB get temporary negative IDs.
1199 $temp_mid = min(array_keys($_menu['items'])) - 1;
1200 $new_items = array();
1202 foreach ($menu_item_list as $item) {
1203 if (isset($item['callback'])) {
1204 $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']);
1205 if (isset($item['callback arguments'])) {
1206 $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments'];
1209 unset($item['callback']);
1210 unset($item['callback arguments']);
1211 if (!isset($_menu['path index'][$item['path']])) {
1212 if (!isset($item['path'])) {
1215 if (!isset($item['type'])) {
1216 $item['type'] = MENU_NORMAL_ITEM;
1218 if (!isset($item['weight'])) {
1219 $item['weight'] = 0;
1221 $_menu['items'][$temp_mid] = $item;
1222 $_menu['path index'][$item['path']] = $temp_mid;
1223 $new_items[$temp_mid] = $item;
1227 $mid = $_menu['path index'][$item['path']];
1228 if ($_menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN) {
1229 $_menu['items'][$mid]['access'] = $item['access'];
1230 if (isset($_menu['items'][$mid]['callback'])) {
1231 $_menu['items'][$mid]['callback'] = $item['callback'];
1233 if (isset($_menu['items'][$mid]['callback arguments'])) {
1234 $_menu['items'][$mid]['callback arguments'] = $item['callback arguments'];
1240 // Establish parent-child relationships.
1241 _menu_find_parents($new_items);
1243 // Add new items to the visible tree if necessary.
1244 foreach ($new_items as $mid => $item) {
1245 $item = $_menu['items'][$mid];
1246 if (($item['type'] & MENU_VISIBLE_IN_TREE) && _menu_item_is_accessible($mid)) {
1247 $pid = $item['pid'];
1248 while ($pid && !isset($_menu['visible'][$pid])) {
1249 $pid = $_menu['items'][$pid]['pid'];
1251 $_menu['visible'][$mid] = array('title' => $item['title'], 'path' => $item['path'], 'pid' => $pid);
1252 $_menu['visible'][$pid]['children'][] = $mid;
1253 usort($_menu['visible'][$pid]['children'], '_menu_sort');
1259 * Establish parent-child relationships.
1261 function _menu_find_parents(&$items) {
1264 foreach ($items as $mid => $item) {
1265 if (!isset($item['pid'])) {
1266 // Parent's location has not been customized, so figure it out using the path.
1267 $parent = $item['path'];
1270 $parent = substr($parent, 0, strrpos($parent, '/'));
1272 while ($parent && !isset($_menu['path index'][$parent]));
1275 $pid = $parent ? $_menu['path index'][$parent] : 1;
1276 $_menu['items'][$mid]['pid'] = $pid;
1279 $pid = $item['pid'];
1282 // Don't make root a child of itself.
1284 if (isset ($_menu['items'][$pid])) {
1285 $_menu['items'][$pid]['children'][] = $mid;
1288 // If parent is missing, it is a menu item that used to be defined
1289 // but is no longer. Default to a root-level "Navigation" menu item.
1290 $_menu['items'][1]['children'][] = $mid;
1297 * Find all the items in the current local task tree.
1299 * Since this is only for display, we only need title, path, and children
1302 * At the close of this function, $_menu['local tasks'] is populated with the
1303 * menu items in the local task tree.
1306 * TRUE if the local task tree is forked. It does not need to be displayed
1309 function _menu_build_local_tasks($pid) {
1314 if (isset($_menu['items'][$pid])) {
1315 $parent = $_menu['items'][$pid];
1317 $children = array();
1318 if (isset($parent['children'])) {
1319 foreach ($parent['children'] as $mid) {
1320 if (($_menu['items'][$mid]['type'] & MENU_IS_LOCAL_TASK) && _menu_item_is_accessible($mid)) {
1322 // Beware short-circuiting || operator!
1323 $forked = _menu_build_local_tasks($mid) || $forked;
1327 usort($children, '_menu_sort');
1328 $forked = $forked || count($children) > 1;
1330 $_menu['local tasks'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children);
1331 foreach ($children as $mid) {
1332 $_menu['local tasks'][$mid]['pid'] = $pid;
1340 * Returns TRUE if the site is off-line for maintenance.
1342 function _menu_site_is_offline() {
1343 // Check if site is set to off-line mode
1344 if (variable_get('site_offline', 0)) {
1345 // Check if the user has administration privileges
1346 if (!user_access('administer site configuration')) {
1347 // Check if this is an attempt to login
1348 if (drupal_get_normal_path($_GET['q']) != 'user') {
1353 $offline_message = t('Operating in off-line mode.');
1354 $messages = drupal_set_message();
1355 // Ensure that the off-line message is displayed only once [allowing for page redirects].
1356 if (!isset($messages) || !isset($messages['status']) || !in_array($offline_message, $messages['status'])) {
1357 drupal_set_message($offline_message);