diff options
Diffstat (limited to 'includes/menu.inc')
-rw-r--r-- | includes/menu.inc | 390 |
1 files changed, 276 insertions, 114 deletions
diff --git a/includes/menu.inc b/includes/menu.inc index 1350d3580..9a7c7234d 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -10,6 +10,11 @@ define('MENU_SHOW', 0); define('MENU_HIDE', 1); define('MENU_HIDE_NOCHILD', 2); +define('MENU_NORMAL', 0); +define('MENU_MODIFIED', 1); +define('MENU_LOCKED', 2); +define('MENU_CUSTOM', 3); + /** @} */ /** @@ -20,39 +25,82 @@ define('MENU_HIDE_NOCHILD', 2); * @param $title The title of the menu item to show in the rendered menu. * @param $callback The function to call when this is the active menu item. * @param $weight Heavier menu items sink down the menu. - * @param $hidden - * - MENU_SHOW show the menu (default). - * - MENU_HIDE hide the menu item, but register a callback. - * - MENU_HIDE_NOCHILD hide the menu item when it has no children. + * @param $visibility + * - MENU_SHOW - Show the menu item (default). + * - MENU_HIDE - Hide the menu item, but register a callback. + * - MENU_HIDE_NOCHILD - Hide the menu item when it has no children. + * @param $status + * - MENU_NORMAL - The menu item can be moved (default). + * - MENU_MODIFIED - The administrator has moved or otherwise changed the menu item. + * - MENU_LOCKED - The administrator may not modify the item. + * - MENU_CUSTOM - The menu item was created by the administrator. */ -function menu($path, $title, $callback = NULL, $weight = 0, $hidden = MENU_SHOW) { - global $_list; +function menu($path, $title, $callback = NULL, $weight = 0, $visibility = MENU_SHOW, $status = MENU_NORMAL) { + global $_menu; // add the menu to the flat list of menu items: - $_list[$path] = array("title" => $title, "callback" => $callback, "weight" => $weight, "hidden" => $hidden); + $_menu['list'][$path] = array('title' => $title, 'callback' => $callback, 'weight' => $weight, 'visibility' => $visibility, 'status' => $status); +} + +/** + * Return the menu data structure. + * + * The returned structure contains much information that is useful only + * internally in the menu system. External modules are likely to need only + * the ['visible'] element of the returned array. All menu items that are + * accessible to the current user and not hidden will be present here, so + * modules and themes can use this structure to build their own representations + * of the menu. + * + * $menu['visible'] will contain an associative array, the keys of which + * are menu IDs. The values of this array are themselves associative arrays, + * with the following key-value pairs defined: + * - 'title' - The displayed title of the menu or menu item. It will already + * have been translated by the locale system. + * - 'path' - The Drupal path to the menu item. A link to a particular item + * can thus be constructed with l($item['title'], $item['path']). + * - '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; + + if (!isset($_menu['items'])) { + $cache = cache_get('menu:'. $user->uid); + if ($cache) { + $_menu = unserialize($cache->data); + } + else { + menu_build(); + cache_set('menu:'. $user->uid, serialize($_menu), 1); + } + } + return $_menu; } /** * Returns an array with the menu items that lead to the specified path. */ function menu_get_trail($path) { - global $_list; + $menu = menu_get_menu(); $trail = array(); - while ($path) { - if ($_list[$path]) { - array_unshift($trail, $path); - } - - $path = substr($path, 0, strrpos($path, "/")); + $mid = menu_get_active_item(); + while ($mid && $menu['items'][$mid]) { + array_unshift($trail, $mid); + $mid = $menu['items'][$mid]['pid']; } return $trail; } /** - * Returns the path of the active menu item. + * Returns the ID of the active menu item. * @ingroup menu */ function menu_get_active_item() { @@ -64,34 +112,34 @@ function menu_get_active_item() { * @ingroup menu */ function menu_set_active_item($path = NULL) { - global $_list; - static $stored_path; + static $stored_mid; + $menu = menu_get_menu(); - if (is_null($stored_path) || !empty($path)) { + if (is_null($stored_mid) || !empty($path)) { if (empty($path)) { - $path = $_GET["q"]; + $path = $_GET['q']; } else { $_GET['q'] = $path; } - while ($path && !$_list[$path]) { - $path = substr($path, 0, strrpos($path, "/")); + while ($path && !$menu['path index'][$path]) { + $path = substr($path, 0, strrpos($path, '/')); } - $stored_path = $path; + $stored_mid = $menu['path index'][$path]; } - return $stored_path; + return $stored_mid; } /** * Returns the title of the active menu item. */ function menu_get_active_title() { - global $_list; + $menu = menu_get_menu(); - if ($path = menu_get_active_item()) { - return ucfirst($_list[$path]["title"]); + if ($mid = menu_get_active_item()) { + return ucfirst($menu['items'][$mid]['title']); } } @@ -101,10 +149,10 @@ function menu_get_active_title() { function menu_get_active_help() { if (menu_active_handler_exists()) { - $path = $_GET["q"]; - $output = ""; + $path = $_GET['q']; + $output = ''; - $return = module_invoke_all("help", $path); + $return = module_invoke_all('help', $path); foreach ($return as $item) { if (!empty($item)) { $output .= $item ."\n"; @@ -118,168 +166,282 @@ function menu_get_active_help() { * Returns an array of rendered menu items in the active breadcrumb trail. */ function menu_get_active_breadcrumb() { + $menu = menu_get_menu(); - $links[] = l(t("Home"), ""); + $links[] = l(t('Home'), ''); - $trail = menu_get_trail($_GET["q"]); - foreach ($trail as $item) { - $links[] = _render_item($item); + $trail = menu_get_trail($_GET['q']); + foreach ($trail as $mid) { + // Don't show menu items without valid link targets. + if ($menu['items'][$mid]['path'] != '') { + $links[] = _menu_render_item($mid); + } } return $links; } - /** * Execute the handler associated with the active menu item. */ function menu_execute_active_handler() { - global $_list; + $menu = menu_get_menu(); - $path = menu_get_active_item(); + $path = $_GET['q']; + while ($path && (!$menu['path index'][$path] || !$menu['items'][$menu['path index'][$path]]['callback'])) { + $path = substr($path, 0, strrpos($path, '/')); + } + $mid = $menu['path index'][$path]; - if ($_list[$path]["callback"]) { - $arg = substr($_GET["q"], strlen($path) + 1); + if ($menu['items'][$mid]['callback']) { + $arg = substr($_GET['q'], strlen($menu['items'][$mid]['path']) + 1); if (isset($arg)) { - return call_user_func_array($_list[$path]["callback"], explode("/", $arg)); + return call_user_func_array($menu['items'][$mid]['callback'], explode('/', $arg)); } else { - return call_user_func($_list[$path]["callback"]); + return call_user_func($menu['items'][$mid]['callback']); } } } +/** + * Return true if a valid callback can be called from the current path. + */ function menu_active_handler_exists() { - global $_list; + $menu = menu_get_menu(); - $path = menu_get_active_item(); + $path = $_GET['q']; + while ($path && (!$menu['path index'][$path] || !$menu['items'][$menu['path index'][$path]]['callback'])) { + $path = substr($path, 0, strrpos($path, '/')); + } + $mid = $menu['path index'][$path]; - return function_exists($_list[$path]["callback"]); + return function_exists($menu['items'][$mid]['callback']); } /** * Returns true when the path is in the active trail. */ -function menu_in_active_trail($path) { +function menu_in_active_trail($mid) { static $trail; if (empty($trail)) { - $trail = menu_get_trail($_GET["q"]); + $trail = menu_get_trail($_GET['q']); } - return in_array($path, $trail); + return in_array($mid, $trail); } /** - * Returns true when the menu has visisble children. + * Returns a rendered menu tree. */ -function menu_has_visible_children($item) { - global $_list; +function menu_tree($pid = 1) { + static $trail; + $menu = menu_get_menu(); + $output = ''; - if ($_list[$item]['children']) { - foreach ($_list[$item]['children'] as $child) { - if ($_list[$child]['hidden'] == MENU_SHOW) { - return true; + if (empty($trail)) { + $trail = menu_get_trail($_GET['q']); + } + + if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) { + + foreach ($menu['visible'][$pid]['children'] as $mid) { + $style = (count($menu['visible'][$mid]['children']) ? (menu_in_active_trail($mid) ? 'expanded' : 'collapsed') : 'leaf'); + $output .= "<li class=\"$style\">"; + $output .= _menu_render_item($mid); + if (menu_in_active_trail($mid)) { + $output .= menu_tree($mid); } + $output .= "</li>\n"; + } + + if ($output != '') { + $output = "\n<ul>\n$output\n</ul>\n"; } } - return false; + return $output; } /** - * Returns a rendered menu tree. + * Build the menu by querying both modules and the database. */ -function menu_tree($parent = "", $hidden = 0) { - global $_list; - static $trail; - $output = ""; +function menu_build() { + global $_menu; + global $user; - if (empty($trail)) { - $trail = menu_get_trail($_GET["q"]); + // Start from a clean slate. + $_menu = array(); + + // Build a sequential list of all menu items. + module_invoke_all('link', 'system'); + + $_menu['path index'] = array(); + // Set up items array, including default "Navigation" menu. + $_menu['items'] = array(0 => array(), 1 => array('pid' => 0, 'title' => t('Navigation'), 'weight' => -50, 'visibility' => MENU_SHOW, 'status' => MENU_LOCKED)); + + // Menu items not in the DB get temporary negative IDs. + $temp_mid = -1; + + foreach ($_menu['list'] as $path => $data) { + $mid = $temp_mid; + $_menu['items'][$mid] = array('path' => $path, 'title' => $data['title'], 'callback' => $data['callback'], 'weight' => $data['weight'], 'visibility' => $data['visibility'], 'status' => $data['status']); + $_menu['path index'][$path] = $mid; + + $temp_mid--; } - if (isset($_list[$parent]) && $_list[$parent]["children"]) { - - usort($_list[$parent]["children"], "_menu_sort"); - foreach ($_list[$parent]["children"] as $item) { - /* - ** Don't render the menu when it is hidden, or when it has no call-back - ** nor children. The latter check avoids that useless links are being - ** rendered. - */ - $visible = menu_has_visible_children($item); - if (($_list[$item]["hidden"] == MENU_SHOW && $_list[$item]["callback"]) || - ($_list[$item]["hidden"] == MENU_SHOW && $visible) || - ($_list[$item]["hidden"] == MENU_HIDE_NOCHILD && $visible)) { - $style = ($visible ? (menu_in_active_trail($item) ? "expanded" : "collapsed") : "leaf"); - $output .= "<li class=\"$style\">"; - $output .= _render_item($item); - if (menu_in_active_trail($item)) { - $output .= menu_tree($item); + // Now fetch items from the DB, reassigning menu IDs as needed. + if (module_exist('menu')) { + $result = db_query('SELECT * FROM {menu}'); + while ($item = db_fetch_object($result)) { + // First, add any custom items added by the administrator. + if ($item->status == MENU_CUSTOM) { + $_menu['items'][$item->mid] = array('pid' => $item->pid, 'path' => $item->path, 'title' => $item->title, 'callback' => NULL, 'weight' => $item->weight, 'visibility' => MENU_SHOW, 'status' => MENU_CUSTOM); + $_menu['path index'][$item->path] = $item->mid; + } + // Don't display non-custom menu items if no module declared them. + else if ($old_mid = $_menu['path index'][$item->path]) { + $_menu['items'][$item->mid] = $_menu['items'][$old_mid]; + unset($_menu['items'][$old_mid]); + $_menu['path index'][$item->path] = $item->mid; + // If administrator has changed item position, reflect the change. + if ($item->status == MENU_MODIFIED) { + $_menu['items'][$item->mid]['title'] = $item->title; + $_menu['items'][$item->mid]['pid'] = $item->pid; + $_menu['items'][$item->mid]['weight'] = $item->weight; + $_menu['items'][$item->mid]['visibility'] = $item->visibility; + $_menu['items'][$item->mid]['status'] = $item->status; } - $output .= "</li>\n"; } - else if ($_list[$item]["hidden"] == MENU_HIDE && $_list[$item]["children"]) { - $output .= menu_tree($item, 1); + } + } + + // Establish parent-child relationships. + foreach ($_menu['items'] as $mid => $item) { + if (!isset($item['pid'])) { + // Parent's location has not been customized, so figure it out using the path. + $parent = $item['path']; + do { + $parent = substr($parent, 0, strrpos($parent, '/')); } + while ($parent && !$_menu['path index'][$parent]); + + $pid = $parent ? $_menu['path index'][$parent] : 1; + $_menu['items'][$mid]['pid'] = $pid; + } + else { + $pid = $item['pid']; } - if ($output != '' && $hidden != MENU_HIDE) { - $output = "\n<ul>\n$output\n</ul>\n"; + // Don't make root a child of itself. + if ($mid) { + if (isset ($_menu['items'][$pid])) { + $_menu['items'][$pid]['children'][] = $mid; + } + else { + // If parent is missing, it is a menu item that used to be defined + // but is no longer. Default to a root-level "Navigation" menu item. + $_menu['items'][1]['children'][] = $mid; + } } } - return $output; + // Prepare to display trees to the user as required. + menu_build_visible_tree(); } /** - * Query to module to build the menu. + * Find all visible items in the menu tree, for ease in displaying to user. + * + * Since this is only for display, we only need title, path, and children + * for each item. */ -function menu_build($type) { - /* - ** Build a sequential list of all menus items. - */ +function menu_build_visible_tree($pid = 0) { + global $_menu; - module_invoke_all("link", $type); + if (isset($_menu['items'][$pid])) { + $parent = $_menu['items'][$pid]; - /* - ** Tree-ify the sequential list of menu items by adding each - ** menu item to the 'children' array of their direct parent. - */ + $children = array(); + if ($parent['children']) { + usort($parent['children'], '_menu_sort'); + foreach ($parent['children'] as $mid) { + $children = array_merge($children, menu_build_visible_tree($mid)); + } + } + if (($parent['visibility'] == MENU_SHOW) || + ($parent['visibility'] == MENU_HIDE_NOCHILD && count($children) > 1)) { + $_menu['visible'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children); + return array($pid); + } + else { + return $children; + } + } - global $_list; + return array(); +} - foreach ($_list as $path => $data) { +/** + * Populate the database representation of the menu. + * + * This need only be called at the start of pages that modify the menu. + */ +function menu_rebuild() { + cache_clear_all(); + menu_build(); + $menu = menu_get_menu(); + + $new_items = array(); + foreach ($menu['items'] as $mid => $item) { + if ($mid < 0 && ($item->status != MENU_LOCKED)) { + $new_mid = db_next_id('menu_mid'); + if (isset($new_items[$item['pid']])) { + $new_pid = $new_items[$item['pid']]['mid']; + } + else { + $new_pid = $item['pid']; + } - /* - ** Find $path's direct parent: - */ - $parent = $path; - do { - $parent = substr($parent, 0, strrpos($parent, "/")); - } - while ($parent && !$_list[$parent]); + // Fix parent IDs for menu items already added. + if ($item['children']) { + foreach ($item['children'] as $child) { + if (isset($new_items[$child])) { + $new_items[$child]['pid'] = $new_mid; + } + } + } - if ($path) { - $_list[$parent]["children"][] = $path; + $new_items[$mid] = array('mid' => $new_mid, 'pid' => $new_pid, 'path' => $item['path'], 'title' => $item['title'], 'weight' => $item['weight'], 'visibility' => $item['visibility'], 'status' => $item['status']); } } + + foreach ($new_items as $item) { + db_query('INSERT INTO {menu} (mid, pid, path, title, weight, visibility, status) VALUES (%d, %d, \'%s\', \'%s\', %d, %d, %d)', $item['mid'], $item['pid'], $item['path'], $item['title'], $item['weight'], $item['visibility'], $item['status']); + } + + // Rebuild the menu to account for any changes. + menu_build(); } +/** + * Comparator routine for use in sorting menu items. + */ function _menu_sort($a, $b) { - global $_list; + $menu = menu_get_menu(); - $a = &$_list[$a]; - $b = &$_list[$b]; + $a = &$menu['items'][$a]; + $b = &$menu['items'][$b]; - return $a["weight"] < $b["weight"] ? -1 : ($a["weight"] > $b["weight"] ? 1 : ($a["title"] < $b["title"] ? -1 : 1)); + return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1)); } -function _render_item($path) { - global $_list; +function _menu_render_item($mid) { + $menu = menu_get_menu(); - return l($_list[$path]["title"], $path); + return l($menu['items'][$mid]['title'], $menu['items'][$mid]['path']); } |