$item['description'])). * - 'children' - A linear list of the menu ID's of this item's children. * * Menu ID 0 is the "root" of the menu. The children of this item are the * menus themselves (they will have no associated path). Menu ID 1 will * always be one of these children; it is the default "Navigation" menu. */ function menu_get_menu() { global $_menu; global $user; global $locale; if (!isset($_menu['items'])) { // _menu_build() may indirectly call this function, so prevent infinite loops. $_menu['items'] = array(); $cid = "menu:$user->uid:$locale"; if ($cached = cache_get($cid)) { $_menu = unserialize($cached->data); } else { _menu_build(); // Cache the menu structure for this user, to expire after one day. cache_set($cid, serialize($_menu), time() + (60 * 60 * 24)); } // Make sure items that cannot be cached are added. _menu_append_contextual_items(); // Reset the cached $menu in menu_get_item(). menu_get_item(NULL, NULL, TRUE); } return $_menu; } /** * Return the local task tree. * * Unlike the rest of the menu structure, the local task tree cannot be cached * nor determined too early in the page request, because the user's current * location may be changed by a menu_set_location() call, and the tasks shown * (just as the breadcrumb trail) need to reflect the changed location. */ function menu_get_local_tasks() { global $_menu; // Don't cache the local task tree, as it varies by location and tasks are // allowed to be dynamically determined. if (!isset($_menu['local tasks'])) { // _menu_build_local_tasks() may indirectly call this function, so prevent // infinite loops. $_menu['local tasks'] = array(); $pid = menu_get_active_nontask_item(); if (!_menu_build_local_tasks($pid)) { // If the build returned FALSE, the tasks need not be displayed. $_menu['local tasks'][$pid]['children'] = array(); } } return $_menu['local tasks']; } /** * Retrieves the menu item specified by $mid, or by $path if $mid is not given. * * @param $mid * The menu ID of the menu item to retrieve. * @param $path * The internal path of the menu item to retrieve. Defaults to NULL. Only * used if no item can be found matching $mid. * @param $reset * Optional flag that resets the static variable cache of the menu tree, if * set to TRUE. Default is FALSE. * * @return * The menu item found in the site menu, or an empty array if none could be * found. */ function menu_get_item($mid, $path = NULL, $reset = FALSE) { static $menu; if (!isset($menu) || $reset) { $menu = menu_get_menu(); } if (isset($mid)) { return $menu['items'][$mid]; } if (isset($path)) { return $menu['items'][$menu['path index'][$path]]; } return array(); } /** * Retrieves the menu ID and title of all root menus. * * @return * Array containing all menus (but not menu items), in the form mid => title. */ function menu_get_root_menus() { $menu = menu_get_menu(); $root_menus = array(); foreach ($menu['items'][0]['children'] as $mid) { $root_menus[$mid] = $menu['items'][$mid]['title']; } return $root_menus; } /** * Change the current menu location of the user. * * Frequently, modules may want to make a page or node act as if it were * in the menu tree somewhere, even though it was not registered in a * hook_menu() implementation. If the administrator has rearranged the menu, * the newly set location should respect this in the breadcrumb trail and * expanded/collapsed status of menu items in the tree. This function * allows this behavior. * * @param $location * An array specifying a complete or partial breadcrumb trail for the * new location, in the same format as the return value of hook_menu(). * The last element of this array should be the new location itself. * * This function will set the new breadcrumb trail to the passed-in value, * but if any elements of this trail are visible in the site tree, the * trail will be "spliced in" to the existing site navigation at that point. */ function menu_set_location($location) { global $_menu; $temp_id = min(array_keys($_menu['items'])) - 1; $prev_id = 0; // Don't allow this function to change the actual current path, just the // position in the menu tree. $location[count($location) - 1]['path'] = $_GET['q']; foreach (array_reverse($location) as $item) { if (isset($_menu['path index'][$item['path']])) { $mid = $_menu['path index'][$item['path']]; if (isset ($_menu['visible'][$mid])) { // Splice in the breadcrumb at this location. if ($prev_id) { $_menu['items'][$prev_id]['pid'] = $mid; } $prev_id = 0; break; } else { // A hidden item; show it, but only temporarily. $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB; if ($prev_id) { $_menu['items'][$prev_id]['pid'] = $mid; } $prev_id = $mid; } } else { $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB; if ($prev_id) { $_menu['items'][$prev_id]['pid'] = $temp_id; } $_menu['items'][$temp_id] = $item; $_menu['path index'][$item['path']] = $temp_id; $prev_id = $temp_id; $temp_id--; } } if ($prev_id) { // Didn't find a home, so attach this to the main navigation menu. $_menu['items'][$prev_id]['pid'] = 1; } $final_item = array_pop($location); menu_set_active_item($final_item['path']); } /** * Execute the handler associated with the active menu item. * * This is called early in the page request. The active menu item is at * this point determined exclusively by the URL. The handler that is called * here may, as a side effect, change the active menu item so that later * menu functions (that display the menus and breadcrumbs, for example) * act as if the user were in a different location on the site. */ function menu_execute_active_handler() { if (_menu_site_is_offline()) { return MENU_SITE_OFFLINE; } $menu = menu_get_menu(); // Determine the menu item containing the callback. $path = $_GET['q']; while ($path && !isset($menu['callbacks'][$path])) { $path = substr($path, 0, strrpos($path, '/')); } if (!isset($menu['callbacks'][$path])) { return MENU_NOT_FOUND; } if (!function_exists($menu['callbacks'][$path]['callback'])) { return MENU_NOT_FOUND; } if (!_menu_item_is_accessible(menu_get_active_item())) { return MENU_ACCESS_DENIED; } // We found one, and are allowed to execute it. $arguments = isset($menu['callbacks'][$path]['callback arguments']) ? $menu['callbacks'][$path]['callback arguments'] : array(); $arg = substr($_GET['q'], strlen($path) + 1); if (strlen($arg)) { $arguments = array_merge($arguments, explode('/', $arg)); } return call_user_func_array($menu['callbacks'][$path]['callback'], $arguments); } /** * Returns the ID of the active menu item. */ function menu_get_active_item() { return menu_set_active_item(); } /** * Sets the path of the active menu item. */ function menu_set_active_item($path = NULL) { static $stored_mid; $menu = menu_get_menu(); if (!isset($stored_mid) || isset($path)) { if (!isset($path)) { $path = $_GET['q']; } else { $_GET['q'] = $path; } while ($path && !isset($menu['path index'][$path])) { $path = substr($path, 0, strrpos($path, '/')); } $stored_mid = isset($menu['path index'][$path]) ? $menu['path index'][$path] : 0; // Search for default local tasks to activate instead of this item. $continue = TRUE; while ($continue) { $continue = FALSE; if (isset($menu['items'][$stored_mid]['children'])) { foreach ($menu['items'][$stored_mid]['children'] as $cid) { if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) { $stored_mid = $cid; $continue = TRUE; } } } } // Reset the cached $menu in menu_get_item(). menu_get_item(NULL, NULL, TRUE); } return $stored_mid; } /** * Returns the ID of the current menu item or, if the current item is a * local task, the menu item to which this task is attached. */ function menu_get_active_nontask_item() { $mid = menu_get_active_item(); // Find the first non-task item: while ($mid) { $item = menu_get_item($mid); if (!($item['type'] & MENU_IS_LOCAL_TASK)) { return $mid; } $mid = $item['pid']; } } /** * Returns the title of the active menu item. */ function menu_get_active_title() { if ($mid = menu_get_active_nontask_item()) { $item = menu_get_item($mid); return $item['title']; } } /** * Returns the help associated with the active menu item. */ function menu_get_active_help() { $path = $_GET['q']; $output = ''; if (!_menu_item_is_accessible(menu_get_active_item())) { // Don't return help text for areas the user cannot access. return; } foreach (module_list() as $name) { if (module_hook($name, 'help')) { if ($temp = module_invoke($name, 'help', $path)) { $output .= $temp . "\n"; } if (module_hook('help', 'page')) { if (substr($path, 0, 6) == "admin/") { if (module_invoke($name, 'help', 'admin/help#' . substr($path, 6))) { $output .= theme("more_help_link", url('admin/help/' . substr($path, 6))); } } } } } return $output; } /** * Returns an array of rendered menu items in the active breadcrumb trail. */ function menu_get_active_breadcrumb() { $links[] = l(t('Home'), ''); $trail = _menu_get_active_trail(); foreach ($trail as $mid) { $item = menu_get_item($mid); if ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB) { $links[] = menu_item_link($mid); } } // The last item in the trail is the page title; don't display it here. array_pop($links); return $links; } /** * Returns true when the menu item is in the active trail. */ function menu_in_active_trail($mid) { $trail = _menu_get_active_trail(); return in_array($mid, $trail); } /** * Returns true when the menu item is in the active trail within a * specific subsection of the menu tree. * * @param $mid * The menu item being considered. * @param $pid * The root of the subsection of the menu tree in which to look. */ function menu_in_active_trail_in_submenu($mid, $pid) { $trail = _menu_get_active_trail_in_submenu($pid); if (!$trail) { return FALSE; } return in_array($mid, $trail); } /** * Populate the database representation of the menu. * * This need only be called at the start of pages that modify the menu. */ function menu_rebuild() { // Clear the page cache, so that changed menus are reflected for anonymous users. cache_clear_all(); // Also clear the menu cache. cache_clear_all('menu:', TRUE); _menu_build(); if (module_exist('menu')) { $menu = menu_get_menu(); // Fill a queue of new menu items which are modifiable. $new_items = array(); foreach ($menu['items'] as $mid => $item) { if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) { $new_items[$mid] = $item; } } $old_count = -1; // Save the new items updating the pids in each iteration while (($c = count($new_items)) && ($c != $old_count)) { $old_count = count($new_items); foreach($new_items as $mid => $item) { // If the item has a valid parent, save it if ($item['pid'] >= 0) { // The new menu ID gets passed back by reference as $item['mid'] menu_save_item($item); // Fix parent IDs for the children of the menu item just saved if ($item['children']) { foreach ($item['children'] as $child) { if (isset($new_items[$child])) { $new_items[$child]['pid'] = $item['mid']; } } } // remove the item unset($new_items[$mid]); } } } // Rebuild the menu to account for the changes. _menu_build(); } // Reset the cached $menu in menu_get_item(). menu_get_item(NULL, NULL, TRUE); } /** * Generate the HTML for a menu tree. * * @param $pid * The parent id of the menu. * * @ingroup themeable */ function theme_menu_tree($pid = 1) { if ($tree = menu_tree($pid)) { return "\n\n"; } } /** * Returns a rendered menu tree. * * @param $pid * The parent id of the menu. */ function menu_tree($pid = 1) { $menu = menu_get_menu(); $output = ''; if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) { foreach ($menu['visible'][$pid]['children'] as $mid) { $type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL; $children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL; $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0); } } return $output; } /** * Generate the HTML output for a single menu item. * * @param $mid * The menu id of the item. * @param $children * A string containing any rendered child items of this menu. * @param $leaf * A boolean indicating whether this menu item is a leaf. * * @ingroup themeable */ function theme_menu_item($mid, $children = '', $leaf = TRUE) { return '
  • '. menu_item_link($mid) . $children ."
  • \n"; } /** * Generate the HTML representing a given menu item ID. * * @param $item * The menu item to render. * @param $link_item * The menu item which should be used to find the correct path. * * @ingroup themeable */ function theme_menu_item_link($item, $link_item) { return l($item['title'], $link_item['path'], isset($item['description']) ? array('title' => $item['description']) : array()); } /** * Returns the rendered link to a menu item. * * @param $mid * The menu item id to render. */ function menu_item_link($mid) { $item = menu_get_item($mid); $link_item = $item; while ($link_item['type'] & MENU_LINKS_TO_PARENT) { $link_item = menu_get_item($link_item['pid']); } return theme('menu_item_link', $item, $link_item); } /** * Returns the rendered local tasks. The default implementation renders * them as tabs. * * @ingroup themeable */ function theme_menu_local_tasks() { $output = ''; if ($primary = menu_primary_local_tasks()) { $output .= "\n"; } if ($secondary = menu_secondary_local_tasks()) { $output .= "\n"; } return $output; } /** * Returns the rendered HTML of the primary local tasks. */ function menu_primary_local_tasks() { $local_tasks = menu_get_local_tasks(); $pid = menu_get_active_nontask_item(); $output = ''; if (count($local_tasks[$pid]['children'])) { foreach ($local_tasks[$pid]['children'] as $mid) { $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE); } } return $output; } /** * Returns the rendered HTML of the secondary local tasks. */ function menu_secondary_local_tasks() { $local_tasks = menu_get_local_tasks(); $pid = menu_get_active_nontask_item(); $output = ''; if (count($local_tasks[$pid]['children'])) { foreach ($local_tasks[$pid]['children'] as $mid) { if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) { foreach ($local_tasks[$mid]['children'] as $cid) { $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE); } } } } return $output; } /** * Generate the HTML representing a given menu item ID as a tab. * * @param $mid * The menu ID to render. * @param $active * Whether this tab or a subtab is the active menu item. * @param $primary * Whether this tab is a primary tab or a subtab. * * @ingroup themeable */ function theme_menu_local_task($mid, $active, $primary) { if ($active) { return '
  • '. menu_item_link($mid) ."
  • \n"; } else { return '
  • '. menu_item_link($mid) ."
  • \n"; } } /** * Returns an array containing the primary links. * Can optionally descend from the root of the Primary links menu towards the * current node for a specified number of levels and return that submenu. * Used to generate a primary/secondary menu from different levels of one menu. * * @param $start_level * This optional parameter can be used to retrieve a context-sensitive array * of links at $start_level levels deep into the Primary links menu. * The default is to return the top-level links. * @param $pid * The parent menu ID from which to search for children. Defaults to the * menu_primary_menu setting. * @return An array containing the themed links as the values. The keys of * the array contain some extra encoded information about the results. * The format of the key is {level}-{num}{-active}. * level is the depth within the menu tree of this list. * num is the number within this array, used only to make the key unique. * -active is appended if this element is in the active trail. */ function menu_primary_links($start_level = 1, $pid = 0) { if (!module_exist('menu')) { return NULL; } if (!$pid) { $pid = variable_get('menu_primary_menu', 0); } if (!$pid) { return NULL; } if ($start_level < 1) { $start_level = 1; } if ($start_level > 1) { $trail = _menu_get_active_trail_in_submenu($pid); if (!$trail) { return NULL; } else { $pid = $trail[$start_level - 1]; } } $menu = menu_get_menu(); if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) { $count = 1; foreach ($menu['visible'][$pid]['children'] as $cid) { $index = "$start_level-$count"; if (menu_in_active_trail_in_submenu($cid, $pid)) { $index .= "-active"; } $links[$index] = menu_item_link($cid); $count++; } } // Special case - provide link to admin/menu if primary links is empty. if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0)) { $links['1-1'] = l(t('edit primary links'),'admin/menu'); } return $links; } /** * Returns an array containing the secondary links. * Secondary links can be either a second level of the Primary links * menu or generated from their own menu. */ function menu_secondary_links() { $msm = variable_get('menu_secondary_menu', 0); if ($msm == 0) { return NULL; } if ($msm == variable_get('menu_primary_menu', 0)) { return menu_primary_links(2, $msm); } return menu_primary_links(1, $msm); } /** * Returns the themed HTML for primary and secondary links. * Note that this function is overridden by most core themes because * those themes display links in "link | link" format, not from a list. * Also note that by default links rendered with this function are * displayed with the same CSS as is used for the local tasks. * If a theme wishes to render links from a ul it is expected that * the theme will provide suitable CSS. * * @param $links * An array containing links to render. * @return * A string containing the themed links. * * @ingroup themeable */ function theme_menu_links($links) { if (!count($links)) { return ''; } $level_tmp = explode('-', key($links)); $level = $level_tmp[0]; $output = "