webmaster@1: $end) { webmaster@1: // Only look at masks that are not longer than the path of interest. webmaster@1: continue; webmaster@1: } webmaster@1: elseif ($i < (1 << $length)) { webmaster@1: // We have exhausted the masks of a given length, so decrease the length. webmaster@1: --$length; webmaster@1: } webmaster@1: $current = ''; webmaster@1: for ($j = $length; $j >= 0; $j--) { webmaster@1: if ($i & (1 << $j)) { webmaster@1: $current .= $parts[$length - $j]; webmaster@1: } webmaster@1: else { webmaster@1: $current .= '%'; webmaster@1: } webmaster@1: if ($j) { webmaster@1: $current .= '/'; webmaster@1: } webmaster@1: } webmaster@1: $placeholders[] = "'%s'"; webmaster@1: $ancestors[] = $current; webmaster@1: } webmaster@1: return array($ancestors, $placeholders); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * The menu system uses serialized arrays stored in the database for webmaster@1: * arguments. However, often these need to change according to the webmaster@1: * current path. This function unserializes such an array and does the webmaster@1: * necessary change. webmaster@1: * webmaster@1: * Integer values are mapped according to the $map parameter. For webmaster@1: * example, if unserialize($data) is array('view', 1) and $map is webmaster@1: * array('node', '12345') then 'view' will not be changed because webmaster@1: * it is not an integer, but 1 will as it is an integer. As $map[1] webmaster@1: * is '12345', 1 will be replaced with '12345'. So the result will webmaster@1: * be array('node_load', '12345'). webmaster@1: * webmaster@1: * @param @data webmaster@1: * A serialized array. webmaster@1: * @param @map webmaster@1: * An array of potential replacements. webmaster@1: * @return webmaster@1: * The $data array unserialized and mapped. webmaster@1: */ webmaster@1: function menu_unserialize($data, $map) { webmaster@1: if ($data = unserialize($data)) { webmaster@1: foreach ($data as $k => $v) { webmaster@1: if (is_int($v)) { webmaster@1: $data[$k] = isset($map[$v]) ? $map[$v] : ''; webmaster@1: } webmaster@1: } webmaster@1: return $data; webmaster@1: } webmaster@1: else { webmaster@1: return array(); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: webmaster@1: webmaster@1: /** webmaster@1: * Replaces the statically cached item for a given path. webmaster@1: * webmaster@1: * @param $path webmaster@1: * The path. webmaster@1: * @param $router_item webmaster@1: * The router item. Usually you take a router entry from menu_get_item and webmaster@1: * set it back either modified or to a different path. This lets you modify the webmaster@1: * navigation block, the page title, the breadcrumb and the page help in one webmaster@1: * call. webmaster@1: */ webmaster@1: function menu_set_item($path, $router_item) { webmaster@1: menu_get_item($path, $router_item); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get a router item. webmaster@1: * webmaster@1: * @param $path webmaster@1: * The path, for example node/5. The function will find the corresponding webmaster@1: * node/% item and return that. webmaster@1: * @param $router_item webmaster@1: * Internal use only. webmaster@1: * @return webmaster@1: * The router item, an associate array corresponding to one row in the webmaster@1: * menu_router table. The value of key map holds the loaded objects. The webmaster@1: * value of key access is TRUE if the current user can access this page. webmaster@1: * The values for key title, page_arguments, access_arguments will be webmaster@1: * filled in based on the database values and the objects loaded. webmaster@1: */ webmaster@1: function menu_get_item($path = NULL, $router_item = NULL) { webmaster@1: static $router_items; webmaster@1: if (!isset($path)) { webmaster@1: $path = $_GET['q']; webmaster@1: } webmaster@1: if (isset($router_item)) { webmaster@1: $router_items[$path] = $router_item; webmaster@1: } webmaster@1: if (!isset($router_items[$path])) { webmaster@1: $original_map = arg(NULL, $path); webmaster@1: $parts = array_slice($original_map, 0, MENU_MAX_PARTS); webmaster@1: list($ancestors, $placeholders) = menu_get_ancestors($parts); webmaster@1: webmaster@1: if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) { webmaster@1: $map = _menu_translate($router_item, $original_map); webmaster@1: if ($map === FALSE) { webmaster@1: $router_items[$path] = FALSE; webmaster@1: return FALSE; webmaster@1: } webmaster@1: if ($router_item['access']) { webmaster@1: $router_item['map'] = $map; webmaster@1: $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts'])); webmaster@1: } webmaster@1: } webmaster@1: $router_items[$path] = $router_item; webmaster@1: } webmaster@1: return $router_items[$path]; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Execute the page callback associated with the current path webmaster@1: */ webmaster@1: function menu_execute_active_handler($path = NULL) { webmaster@1: if (_menu_site_is_offline()) { webmaster@1: return MENU_SITE_OFFLINE; webmaster@1: } webmaster@11: // Rebuild if we know it's needed, or if the menu masks are missing which webmaster@11: // occurs rarely, likely due to a race condition of multiple rebuilds. webmaster@11: if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) { webmaster@1: menu_rebuild(); webmaster@1: } webmaster@1: if ($router_item = menu_get_item($path)) { webmaster@1: if ($router_item['access']) { webmaster@1: if ($router_item['file']) { webmaster@1: require_once($router_item['file']); webmaster@1: } webmaster@1: return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); webmaster@1: } webmaster@1: else { webmaster@1: return MENU_ACCESS_DENIED; webmaster@1: } webmaster@1: } webmaster@1: return MENU_NOT_FOUND; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Loads objects into the map as defined in the $item['load_functions']. webmaster@1: * webmaster@1: * @param $item webmaster@1: * A menu router or menu link item webmaster@1: * @param $map webmaster@1: * An array of path arguments (ex: array('node', '5')) webmaster@1: * @return webmaster@1: * Returns TRUE for success, FALSE if an object cannot be loaded. webmaster@1: * Names of object loading functions are placed in $item['load_functions']. webmaster@11: * Loaded objects are placed in $map[]; keys are the same as keys in the webmaster@1: * $item['load_functions'] array. webmaster@1: * $item['access'] is set to FALSE if an object cannot be loaded. webmaster@1: */ webmaster@1: function _menu_load_objects(&$item, &$map) { webmaster@1: if ($load_functions = $item['load_functions']) { webmaster@1: // If someone calls this function twice, then unserialize will fail. webmaster@1: if ($load_functions_unserialized = unserialize($load_functions)) { webmaster@1: $load_functions = $load_functions_unserialized; webmaster@1: } webmaster@1: $path_map = $map; webmaster@1: foreach ($load_functions as $index => $function) { webmaster@1: if ($function) { webmaster@1: $value = isset($path_map[$index]) ? $path_map[$index] : ''; webmaster@1: if (is_array($function)) { webmaster@1: // Set up arguments for the load function. These were pulled from webmaster@1: // 'load arguments' in the hook_menu() entry, but they need webmaster@1: // some processing. In this case the $function is the key to the webmaster@1: // load_function array, and the value is the list of arguments. webmaster@1: list($function, $args) = each($function); webmaster@1: $load_functions[$index] = $function; webmaster@1: webmaster@1: // Some arguments are placeholders for dynamic items to process. webmaster@1: foreach ($args as $i => $arg) { webmaster@1: if ($arg === '%index') { webmaster@1: // Pass on argument index to the load function, so multiple webmaster@1: // occurances of the same placeholder can be identified. webmaster@1: $args[$i] = $index; webmaster@1: } webmaster@1: if ($arg === '%map') { webmaster@1: // Pass on menu map by reference. The accepting function must webmaster@1: // also declare this as a reference if it wants to modify webmaster@1: // the map. webmaster@1: $args[$i] = &$map; webmaster@1: } webmaster@1: if (is_int($arg)) { webmaster@1: $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : ''; webmaster@1: } webmaster@1: } webmaster@1: array_unshift($args, $value); webmaster@1: $return = call_user_func_array($function, $args); webmaster@1: } webmaster@1: else { webmaster@1: $return = $function($value); webmaster@1: } webmaster@1: // If callback returned an error or there is no callback, trigger 404. webmaster@1: if ($return === FALSE) { webmaster@1: $item['access'] = FALSE; webmaster@1: $map = FALSE; webmaster@1: return FALSE; webmaster@1: } webmaster@1: $map[$index] = $return; webmaster@1: } webmaster@1: } webmaster@1: $item['load_functions'] = $load_functions; webmaster@1: } webmaster@1: return TRUE; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Check access to a menu item using the access callback webmaster@1: * webmaster@1: * @param $item webmaster@1: * A menu router or menu link item webmaster@1: * @param $map webmaster@1: * An array of path arguments (ex: array('node', '5')) webmaster@1: * @return webmaster@1: * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. webmaster@1: */ webmaster@1: function _menu_check_access(&$item, $map) { webmaster@1: // Determine access callback, which will decide whether or not the current webmaster@1: // user has access to this path. webmaster@1: $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); webmaster@1: // Check for a TRUE or FALSE value. webmaster@1: if (is_numeric($callback)) { webmaster@1: $item['access'] = (bool)$callback; webmaster@1: } webmaster@1: else { webmaster@1: $arguments = menu_unserialize($item['access_arguments'], $map); webmaster@1: // As call_user_func_array is quite slow and user_access is a very common webmaster@1: // callback, it is worth making a special case for it. webmaster@1: if ($callback == 'user_access') { webmaster@1: $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]); webmaster@1: } webmaster@1: else { webmaster@1: $item['access'] = call_user_func_array($callback, $arguments); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Localize the router item title using t() or another callback. webmaster@1: * webmaster@1: * Translate the title and description to allow storage of English title webmaster@1: * strings in the database, yet display of them in the language required webmaster@1: * by the current user. webmaster@1: * webmaster@1: * @param $item webmaster@1: * A menu router item or a menu link item. webmaster@1: * @param $map webmaster@1: * The path as an array with objects already replaced. E.g., for path webmaster@1: * node/123 $map would be array('node', $node) where $node is the node webmaster@1: * object for node 123. webmaster@1: * @param $link_translate webmaster@1: * TRUE if we are translating a menu link item; FALSE if we are webmaster@1: * translating a menu router item. webmaster@1: * @return webmaster@1: * No return value. webmaster@1: * $item['title'] is localized according to $item['title_callback']. webmaster@11: * If an item's callback is check_plain(), $item['options']['html'] becomes webmaster@1: * TRUE. webmaster@1: * $item['description'] is translated using t(). webmaster@11: * When doing link translation and the $item['options']['attributes']['title'] webmaster@1: * (link title attribute) matches the description, it is translated as well. webmaster@1: */ webmaster@1: function _menu_item_localize(&$item, $map, $link_translate = FALSE) { webmaster@1: $callback = $item['title_callback']; webmaster@1: $item['localized_options'] = $item['options']; webmaster@1: // If we are not doing link translation or if the title matches the webmaster@1: // link title of its router item, localize it. webmaster@1: if (!$link_translate || (!empty($item['title']) && ($item['title'] == $item['link_title']))) { webmaster@1: // t() is a special case. Since it is used very close to all the time, webmaster@1: // we handle it directly instead of using indirect, slower methods. webmaster@1: if ($callback == 't') { webmaster@1: if (empty($item['title_arguments'])) { webmaster@1: $item['title'] = t($item['title']); webmaster@1: } webmaster@1: else { webmaster@1: $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map)); webmaster@1: } webmaster@1: } webmaster@1: elseif ($callback) { webmaster@1: if (empty($item['title_arguments'])) { webmaster@1: $item['title'] = $callback($item['title']); webmaster@1: } webmaster@1: else { webmaster@1: $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map)); webmaster@1: } webmaster@1: // Avoid calling check_plain again on l() function. webmaster@1: if ($callback == 'check_plain') { webmaster@1: $item['localized_options']['html'] = TRUE; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: elseif ($link_translate) { webmaster@1: $item['title'] = $item['link_title']; webmaster@1: } webmaster@1: webmaster@1: // Translate description, see the motivation above. webmaster@1: if (!empty($item['description'])) { webmaster@1: $original_description = $item['description']; webmaster@1: $item['description'] = t($item['description']); webmaster@7: if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) { webmaster@1: $item['localized_options']['attributes']['title'] = $item['description']; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Handles dynamic path translation and menu access control. webmaster@1: * webmaster@1: * When a user arrives on a page such as node/5, this function determines webmaster@1: * what "5" corresponds to, by inspecting the page's menu path definition, webmaster@1: * node/%node. This will call node_load(5) to load the corresponding node webmaster@1: * object. webmaster@1: * webmaster@1: * It also works in reverse, to allow the display of tabs and menu items which webmaster@1: * contain these dynamic arguments, translating node/%node to node/5. webmaster@1: * webmaster@1: * Translation of menu item titles and descriptions are done here to webmaster@1: * allow for storage of English strings in the database, and translation webmaster@1: * to the language required to generate the current page webmaster@1: * webmaster@1: * @param $router_item webmaster@1: * A menu router item webmaster@1: * @param $map webmaster@1: * An array of path arguments (ex: array('node', '5')) webmaster@1: * @param $to_arg webmaster@1: * Execute $item['to_arg_functions'] or not. Use only if you want to render a webmaster@1: * path from the menu table, for example tabs. webmaster@1: * @return webmaster@1: * Returns the map with objects loaded as defined in the webmaster@1: * $item['load_functions. $item['access'] becomes TRUE if the item is webmaster@1: * accessible, FALSE otherwise. $item['href'] is set according to the map. webmaster@1: * If an error occurs during calling the load_functions (like trying to load webmaster@1: * a non existing node) then this function return FALSE. webmaster@1: */ webmaster@1: function _menu_translate(&$router_item, $map, $to_arg = FALSE) { webmaster@1: $path_map = $map; webmaster@1: if (!_menu_load_objects($router_item, $map)) { webmaster@1: // An error occurred loading an object. webmaster@1: $router_item['access'] = FALSE; webmaster@1: return FALSE; webmaster@1: } webmaster@1: if ($to_arg) { webmaster@1: _menu_link_map_translate($path_map, $router_item['to_arg_functions']); webmaster@1: } webmaster@1: webmaster@1: // Generate the link path for the page request or local tasks. webmaster@1: $link_map = explode('/', $router_item['path']); webmaster@1: for ($i = 0; $i < $router_item['number_parts']; $i++) { webmaster@1: if ($link_map[$i] == '%') { webmaster@1: $link_map[$i] = $path_map[$i]; webmaster@1: } webmaster@1: } webmaster@1: $router_item['href'] = implode('/', $link_map); webmaster@1: $router_item['options'] = array(); webmaster@1: _menu_check_access($router_item, $map); webmaster@7: webmaster@7: // For performance, don't localize an item the user can't access. webmaster@7: if ($router_item['access']) { webmaster@7: _menu_item_localize($router_item, $map); webmaster@7: } webmaster@1: webmaster@1: return $map; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * This function translates the path elements in the map using any to_arg webmaster@1: * helper function. These functions take an argument and return an object. webmaster@1: * See http://drupal.org/node/109153 for more information. webmaster@1: * webmaster@1: * @param map webmaster@1: * An array of path arguments (ex: array('node', '5')) webmaster@1: * @param $to_arg_functions webmaster@1: * An array of helper function (ex: array(2 => 'menu_tail_to_arg')) webmaster@1: */ webmaster@1: function _menu_link_map_translate(&$map, $to_arg_functions) { webmaster@1: if ($to_arg_functions) { webmaster@1: $to_arg_functions = unserialize($to_arg_functions); webmaster@1: foreach ($to_arg_functions as $index => $function) { webmaster@1: // Translate place-holders into real values. webmaster@1: $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index); webmaster@1: if (!empty($map[$index]) || isset($arg)) { webmaster@1: $map[$index] = $arg; webmaster@1: } webmaster@1: else { webmaster@1: unset($map[$index]); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: function menu_tail_to_arg($arg, $map, $index) { webmaster@1: return implode('/', array_slice($map, $index)); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * This function is similar to _menu_translate() but does link-specific webmaster@3: * preparation such as always calling to_arg functions. webmaster@1: * webmaster@1: * @param $item webmaster@1: * A menu link webmaster@1: * @return webmaster@1: * Returns the map of path arguments with objects loaded as defined in the webmaster@3: * $item['load_functions']: webmaster@3: * - $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. webmaster@3: * - $item['href'] is generated from link_path, possibly by to_arg functions. webmaster@3: * - $item['title'] is generated from link_title, and may be localized. webmaster@3: * - $item['options'] is unserialized; it is also changed within the call webmaster@3: * here to $item['localized_options'] by _menu_item_localize(). webmaster@1: */ webmaster@1: function _menu_link_translate(&$item) { webmaster@1: $item['options'] = unserialize($item['options']); webmaster@1: if ($item['external']) { webmaster@1: $item['access'] = 1; webmaster@1: $map = array(); webmaster@1: $item['href'] = $item['link_path']; webmaster@1: $item['title'] = $item['link_title']; webmaster@1: $item['localized_options'] = $item['options']; webmaster@1: } webmaster@1: else { webmaster@1: $map = explode('/', $item['link_path']); webmaster@1: _menu_link_map_translate($map, $item['to_arg_functions']); webmaster@1: $item['href'] = implode('/', $map); webmaster@1: webmaster@1: // Note - skip callbacks without real values for their arguments. webmaster@1: if (strpos($item['href'], '%') !== FALSE) { webmaster@1: $item['access'] = FALSE; webmaster@1: return FALSE; webmaster@1: } webmaster@1: // menu_tree_check_access() may set this ahead of time for links to nodes. webmaster@1: if (!isset($item['access'])) { webmaster@1: if (!_menu_load_objects($item, $map)) { webmaster@1: // An error occurred loading an object. webmaster@1: $item['access'] = FALSE; webmaster@1: return FALSE; webmaster@1: } webmaster@1: _menu_check_access($item, $map); webmaster@1: } webmaster@7: // For performance, don't localize a link the user can't access. webmaster@7: if ($item['access']) { webmaster@7: _menu_item_localize($item, $map, TRUE); webmaster@7: } webmaster@1: } webmaster@11: webmaster@1: // Allow other customizations - e.g. adding a page-specific query string to the webmaster@1: // options array. For performance reasons we only invoke this hook if the link webmaster@1: // has the 'alter' flag set in the options array. webmaster@1: if (!empty($item['options']['alter'])) { webmaster@1: drupal_alter('translated_menu_link', $item, $map); webmaster@1: } webmaster@11: webmaster@1: return $map; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get a loaded object from a router item. webmaster@1: * webmaster@1: * menu_get_object() will provide you the current node on paths like node/5, webmaster@1: * node/5/revisions/48 etc. menu_get_object('user') will give you the user webmaster@1: * account on user/5 etc. Note - this function should never be called within a webmaster@1: * _to_arg function (like user_current_to_arg()) since this may result in an webmaster@1: * infinite recursion. webmaster@1: * webmaster@1: * @param $type webmaster@1: * Type of the object. These appear in hook_menu definitons as %type. Core webmaster@1: * provides aggregator_feed, aggregator_category, contact, filter_format, webmaster@1: * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the webmaster@1: * relevant {$type}_load function for more on each. Defaults to node. webmaster@1: * @param $position webmaster@1: * The expected position for $type object. For node/%node this is 1, for webmaster@1: * comment/reply/%node this is 2. Defaults to 1. webmaster@1: * @param $path webmaster@3: * See menu_get_item() for more on this. Defaults to the current path. webmaster@1: */ webmaster@1: function menu_get_object($type = 'node', $position = 1, $path = NULL) { webmaster@1: $router_item = menu_get_item($path); webmaster@1: if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type .'_load') { webmaster@1: return $router_item['map'][$position]; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Render a menu tree based on the current path. webmaster@1: * webmaster@1: * The tree is expanded based on the current path and dynamic paths are also webmaster@1: * changed according to the defined to_arg functions (for example the 'My account' webmaster@1: * link is changed from user/% to a link with the current user's uid). webmaster@1: * webmaster@1: * @param $menu_name webmaster@1: * The name of the menu. webmaster@1: * @return webmaster@1: * The rendered HTML of that menu on the current page. webmaster@1: */ webmaster@1: function menu_tree($menu_name = 'navigation') { webmaster@1: static $menu_output = array(); webmaster@1: webmaster@1: if (!isset($menu_output[$menu_name])) { webmaster@1: $tree = menu_tree_page_data($menu_name); webmaster@1: $menu_output[$menu_name] = menu_tree_output($tree); webmaster@1: } webmaster@1: return $menu_output[$menu_name]; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns a rendered menu tree. webmaster@1: * webmaster@1: * @param $tree webmaster@1: * A data structure representing the tree as returned from menu_tree_data. webmaster@1: * @return webmaster@1: * The rendered HTML of that data structure. webmaster@1: */ webmaster@1: function menu_tree_output($tree) { webmaster@1: $output = ''; webmaster@1: $items = array(); webmaster@1: webmaster@1: // Pull out just the menu items we are going to render so that we webmaster@1: // get an accurate count for the first/last classes. webmaster@1: foreach ($tree as $data) { webmaster@1: if (!$data['link']['hidden']) { webmaster@1: $items[] = $data; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: $num_items = count($items); webmaster@1: foreach ($items as $i => $data) { webmaster@1: $extra_class = NULL; webmaster@1: if ($i == 0) { webmaster@1: $extra_class = 'first'; webmaster@1: } webmaster@1: if ($i == $num_items - 1) { webmaster@1: $extra_class = 'last'; webmaster@1: } webmaster@1: $link = theme('menu_item_link', $data['link']); webmaster@1: if ($data['below']) { webmaster@1: $output .= theme('menu_item', $link, $data['link']['has_children'], menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class); webmaster@1: } webmaster@1: else { webmaster@1: $output .= theme('menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class); webmaster@1: } webmaster@1: } webmaster@1: return $output ? theme('menu_tree', $output) : ''; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the data structure representing a named menu tree. webmaster@1: * webmaster@1: * Since this can be the full tree including hidden items, the data returned webmaster@1: * may be used for generating an an admin interface or a select. webmaster@1: * webmaster@1: * @param $menu_name webmaster@1: * The named menu links to return webmaster@1: * @param $item webmaster@1: * A fully loaded menu link, or NULL. If a link is supplied, only the webmaster@1: * path to root will be included in the returned tree- as if this link webmaster@1: * represented the current page in a visible menu. webmaster@1: * @return webmaster@1: * An tree of menu links in an array, in the order they should be rendered. webmaster@1: */ webmaster@1: function menu_tree_all_data($menu_name = 'navigation', $item = NULL) { webmaster@1: static $tree = array(); webmaster@1: webmaster@1: // Use $mlid as a flag for whether the data being loaded is for the whole tree. webmaster@1: $mlid = isset($item['mlid']) ? $item['mlid'] : 0; webmaster@5: // Generate a cache ID (cid) specific for this $menu_name and $item. webmaster@5: $cid = 'links:'. $menu_name .':all-cid:'. $mlid; webmaster@1: webmaster@1: if (!isset($tree[$cid])) { webmaster@1: // If the static variable doesn't have the data, check {cache_menu}. webmaster@1: $cache = cache_get($cid, 'cache_menu'); webmaster@1: if ($cache && isset($cache->data)) { webmaster@5: // If the cache entry exists, it will just be the cid for the actual data. webmaster@5: // This avoids duplication of large amounts of data. webmaster@5: $cache = cache_get($cache->data, 'cache_menu'); webmaster@5: if ($cache && isset($cache->data)) { webmaster@5: $data = $cache->data; webmaster@5: } webmaster@1: } webmaster@5: // If the tree data was not in the cache, $data will be NULL. webmaster@5: if (!isset($data)) { webmaster@1: // Build and run the query, and build the tree. webmaster@1: if ($mlid) { webmaster@1: // The tree is for a single item, so we need to match the values in its webmaster@1: // p columns and 0 (the top level) with the plid values of other links. webmaster@1: $args = array(0); webmaster@1: for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { webmaster@1: $args[] = $item["p$i"]; webmaster@1: } webmaster@1: $args = array_unique($args); webmaster@1: $placeholders = implode(', ', array_fill(0, count($args), '%d')); webmaster@1: $where = ' AND ml.plid IN ('. $placeholders .')'; webmaster@1: $parents = $args; webmaster@1: $parents[] = $item['mlid']; webmaster@1: } webmaster@1: else { webmaster@1: // Get all links in this menu. webmaster@1: $where = ''; webmaster@1: $args = array(); webmaster@1: $parents = array(); webmaster@1: } webmaster@1: array_unshift($args, $menu_name); webmaster@1: // Select the links from the table, and recursively build the tree. We webmaster@1: // LEFT JOIN since there is no match in {menu_router} for an external webmaster@1: // link. webmaster@1: $data['tree'] = menu_tree_data(db_query(" webmaster@1: SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.* webmaster@1: FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path webmaster@1: WHERE ml.menu_name = '%s'". $where ." webmaster@1: ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents); webmaster@1: $data['node_links'] = array(); webmaster@1: menu_tree_collect_node_links($data['tree'], $data['node_links']); webmaster@5: // Cache the data, if it is not already in the cache. webmaster@5: $tree_cid = _menu_tree_cid($menu_name, $data); webmaster@5: if (!cache_get($tree_cid, 'cache_menu')) { webmaster@5: cache_set($tree_cid, $data, 'cache_menu'); webmaster@5: } webmaster@5: // Cache the cid of the (shared) data using the menu and item-specific cid. webmaster@5: cache_set($cid, $tree_cid, 'cache_menu'); webmaster@1: } webmaster@1: // Check access for the current user to each item in the tree. webmaster@1: menu_tree_check_access($data['tree'], $data['node_links']); webmaster@1: $tree[$cid] = $data['tree']; webmaster@1: } webmaster@1: webmaster@1: return $tree[$cid]; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the data structure representing a named menu tree, based on the current page. webmaster@1: * webmaster@1: * The tree order is maintained by storing each parent in an individual webmaster@1: * field, see http://drupal.org/node/141866 for more. webmaster@1: * webmaster@1: * @param $menu_name webmaster@1: * The named menu links to return webmaster@1: * @return webmaster@1: * An array of menu links, in the order they should be rendered. The array webmaster@1: * is a list of associative arrays -- these have two keys, link and below. webmaster@1: * link is a menu item, ready for theming as a link. Below represents the webmaster@1: * submenu below the link if there is one, and it is a subtree that has the webmaster@1: * same structure described for the top-level array. webmaster@1: */ webmaster@1: function menu_tree_page_data($menu_name = 'navigation') { webmaster@1: static $tree = array(); webmaster@1: webmaster@1: // Load the menu item corresponding to the current page. webmaster@1: if ($item = menu_get_item()) { webmaster@5: // Generate a cache ID (cid) specific for this page. webmaster@5: $cid = 'links:'. $menu_name .':page-cid:'. $item['href'] .':'. (int)$item['access']; webmaster@1: webmaster@1: if (!isset($tree[$cid])) { webmaster@1: // If the static variable doesn't have the data, check {cache_menu}. webmaster@1: $cache = cache_get($cid, 'cache_menu'); webmaster@1: if ($cache && isset($cache->data)) { webmaster@5: // If the cache entry exists, it will just be the cid for the actual data. webmaster@5: // This avoids duplication of large amounts of data. webmaster@5: $cache = cache_get($cache->data, 'cache_menu'); webmaster@5: if ($cache && isset($cache->data)) { webmaster@5: $data = $cache->data; webmaster@5: } webmaster@1: } webmaster@5: // If the tree data was not in the cache, $data will be NULL. webmaster@5: if (!isset($data)) { webmaster@1: // Build and run the query, and build the tree. webmaster@1: if ($item['access']) { webmaster@1: // Check whether a menu link exists that corresponds to the current path. webmaster@1: $args = array($menu_name, $item['href']); webmaster@1: $placeholders = "'%s'"; webmaster@1: if (drupal_is_front_page()) { webmaster@1: $args[] = ''; webmaster@1: $placeholders .= ", '%s'"; webmaster@1: } webmaster@1: $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path IN (". $placeholders .")", $args)); webmaster@1: webmaster@1: if (empty($parents)) { webmaster@1: // If no link exists, we may be on a local task that's not in the links. webmaster@1: // TODO: Handle the case like a local task on a specific node in the menu. webmaster@1: $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root'])); webmaster@1: } webmaster@1: // We always want all the top-level links with plid == 0. webmaster@1: $parents[] = '0'; webmaster@1: webmaster@1: // Use array_values() so that the indices are numeric for array_merge(). webmaster@1: $args = $parents = array_unique(array_values($parents)); webmaster@1: $placeholders = implode(', ', array_fill(0, count($args), '%d')); webmaster@1: $expanded = variable_get('menu_expanded', array()); webmaster@1: // Check whether the current menu has any links set to be expanded. webmaster@1: if (in_array($menu_name, $expanded)) { webmaster@1: // Collect all the links set to be expanded, and then add all of webmaster@1: // their children to the list as well. webmaster@1: do { webmaster@1: $result = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND expanded = 1 AND has_children = 1 AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args)); webmaster@1: $num_rows = FALSE; webmaster@1: while ($item = db_fetch_array($result)) { webmaster@1: $args[] = $item['mlid']; webmaster@1: $num_rows = TRUE; webmaster@1: } webmaster@1: $placeholders = implode(', ', array_fill(0, count($args), '%d')); webmaster@1: } while ($num_rows); webmaster@1: } webmaster@1: array_unshift($args, $menu_name); webmaster@1: } webmaster@1: else { webmaster@1: // Show only the top-level menu items when access is denied. webmaster@1: $args = array($menu_name, '0'); webmaster@1: $placeholders = '%d'; webmaster@1: $parents = array(); webmaster@1: } webmaster@1: // Select the links from the table, and recursively build the tree. We webmaster@1: // LEFT JOIN since there is no match in {menu_router} for an external webmaster@1: // link. webmaster@1: $data['tree'] = menu_tree_data(db_query(" webmaster@1: SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.* webmaster@1: FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path webmaster@1: WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .") webmaster@1: ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents); webmaster@1: $data['node_links'] = array(); webmaster@1: menu_tree_collect_node_links($data['tree'], $data['node_links']); webmaster@5: // Cache the data, if it is not already in the cache. webmaster@5: $tree_cid = _menu_tree_cid($menu_name, $data); webmaster@5: if (!cache_get($tree_cid, 'cache_menu')) { webmaster@5: cache_set($tree_cid, $data, 'cache_menu'); webmaster@5: } webmaster@5: // Cache the cid of the (shared) data using the page-specific cid. webmaster@5: cache_set($cid, $tree_cid, 'cache_menu'); webmaster@1: } webmaster@1: // Check access for the current user to each item in the tree. webmaster@1: menu_tree_check_access($data['tree'], $data['node_links']); webmaster@1: $tree[$cid] = $data['tree']; webmaster@1: } webmaster@1: return $tree[$cid]; webmaster@1: } webmaster@1: webmaster@1: return array(); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@5: * Helper function - compute the real cache ID for menu tree data. webmaster@5: */ webmaster@5: function _menu_tree_cid($menu_name, $data) { webmaster@5: return 'links:'. $menu_name .':tree-data:'. md5(serialize($data)); webmaster@5: } webmaster@5: webmaster@5: /** webmaster@1: * Recursive helper function - collect node links. webmaster@1: */ webmaster@1: function menu_tree_collect_node_links(&$tree, &$node_links) { webmaster@1: foreach ($tree as $key => $v) { webmaster@1: if ($tree[$key]['link']['router_path'] == 'node/%') { webmaster@1: $nid = substr($tree[$key]['link']['link_path'], 5); webmaster@1: if (is_numeric($nid)) { webmaster@1: $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link']; webmaster@1: $tree[$key]['link']['access'] = FALSE; webmaster@1: } webmaster@1: } webmaster@1: if ($tree[$key]['below']) { webmaster@1: menu_tree_collect_node_links($tree[$key]['below'], $node_links); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Check access and perform other dynamic operations for each link in the tree. webmaster@1: */ webmaster@1: function menu_tree_check_access(&$tree, $node_links = array()) { webmaster@1: webmaster@1: if ($node_links) { webmaster@1: // Use db_rewrite_sql to evaluate view access without loading each full node. webmaster@1: $nids = array_keys($node_links); webmaster@1: $placeholders = '%d'. str_repeat(', %d', count($nids) - 1); webmaster@1: $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.status = 1 AND n.nid IN (". $placeholders .")"), $nids); webmaster@1: while ($node = db_fetch_array($result)) { webmaster@1: $nid = $node['nid']; webmaster@1: foreach ($node_links[$nid] as $mlid => $link) { webmaster@1: $node_links[$nid][$mlid]['access'] = TRUE; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: _menu_tree_check_access($tree); webmaster@1: return; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Recursive helper function for menu_tree_check_access() webmaster@1: */ webmaster@1: function _menu_tree_check_access(&$tree) { webmaster@1: $new_tree = array(); webmaster@1: foreach ($tree as $key => $v) { webmaster@1: $item = &$tree[$key]['link']; webmaster@1: _menu_link_translate($item); webmaster@1: if ($item['access']) { webmaster@1: if ($tree[$key]['below']) { webmaster@1: _menu_tree_check_access($tree[$key]['below']); webmaster@1: } webmaster@1: // The weights are made a uniform 5 digits by adding 50000 as an offset. webmaster@1: // After _menu_link_translate(), $item['title'] has the localized link title. webmaster@1: // Adding the mlid to the end of the index insures that it is unique. webmaster@1: $new_tree[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $tree[$key]; webmaster@1: } webmaster@1: } webmaster@1: // Sort siblings in the tree based on the weights and localized titles. webmaster@1: ksort($new_tree); webmaster@1: $tree = $new_tree; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Build the data representing a menu tree. webmaster@1: * webmaster@1: * @param $result webmaster@1: * The database result. webmaster@1: * @param $parents webmaster@1: * An array of the plid values that represent the path from the current page webmaster@1: * to the root of the menu tree. webmaster@1: * @param $depth webmaster@1: * The depth of the current menu tree. webmaster@1: * @return webmaster@1: * See menu_tree_page_data for a description of the data structure. webmaster@1: */ webmaster@1: function menu_tree_data($result = NULL, $parents = array(), $depth = 1) { webmaster@1: list(, $tree) = _menu_tree_data($result, $parents, $depth); webmaster@1: return $tree; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Recursive helper function to build the data representing a menu tree. webmaster@1: * webmaster@1: * The function is a bit complex because the rendering of an item depends on webmaster@1: * the next menu item. So we are always rendering the element previously webmaster@1: * processed not the current one. webmaster@1: */ webmaster@1: function _menu_tree_data($result, $parents, $depth, $previous_element = '') { webmaster@1: $remnant = NULL; webmaster@1: $tree = array(); webmaster@1: while ($item = db_fetch_array($result)) { webmaster@1: // We need to determine if we're on the path to root so we can later build webmaster@1: // the correct active trail and breadcrumb. webmaster@1: $item['in_active_trail'] = in_array($item['mlid'], $parents); webmaster@1: // The current item is the first in a new submenu. webmaster@1: if ($item['depth'] > $depth) { webmaster@1: // _menu_tree returns an item and the menu tree structure. webmaster@1: list($item, $below) = _menu_tree_data($result, $parents, $item['depth'], $item); webmaster@1: if ($previous_element) { webmaster@1: $tree[$previous_element['mlid']] = array( webmaster@1: 'link' => $previous_element, webmaster@1: 'below' => $below, webmaster@1: ); webmaster@1: } webmaster@1: else { webmaster@1: $tree = $below; webmaster@1: } webmaster@1: // We need to fall back one level. webmaster@1: if (!isset($item) || $item['depth'] < $depth) { webmaster@1: return array($item, $tree); webmaster@1: } webmaster@1: // This will be the link to be output in the next iteration. webmaster@1: $previous_element = $item; webmaster@1: } webmaster@1: // We are at the same depth, so we use the previous element. webmaster@1: elseif ($item['depth'] == $depth) { webmaster@1: if ($previous_element) { webmaster@1: // Only the first time. webmaster@1: $tree[$previous_element['mlid']] = array( webmaster@1: 'link' => $previous_element, webmaster@1: 'below' => FALSE, webmaster@1: ); webmaster@1: } webmaster@1: // This will be the link to be output in the next iteration. webmaster@1: $previous_element = $item; webmaster@1: } webmaster@1: // The submenu ended with the previous item, so pass back the current item. webmaster@1: else { webmaster@1: $remnant = $item; webmaster@1: break; webmaster@1: } webmaster@1: } webmaster@1: if ($previous_element) { webmaster@1: // We have one more link dangling. webmaster@1: $tree[$previous_element['mlid']] = array( webmaster@1: 'link' => $previous_element, webmaster@1: 'below' => FALSE, webmaster@1: ); webmaster@1: } webmaster@1: return array($remnant, $tree); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generate the HTML output for a single menu link. webmaster@1: * webmaster@1: * @ingroup themeable webmaster@1: */ webmaster@1: function theme_menu_item_link($link) { webmaster@1: if (empty($link['localized_options'])) { webmaster@1: $link['localized_options'] = array(); webmaster@1: } webmaster@1: webmaster@1: return l($link['title'], $link['href'], $link['localized_options']); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generate the HTML output for a menu tree webmaster@1: * webmaster@1: * @ingroup themeable webmaster@1: */ webmaster@1: function theme_menu_tree($tree) { webmaster@1: return ''; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generate the HTML output for a menu item and submenu. webmaster@1: * webmaster@1: * @ingroup themeable webmaster@1: */ webmaster@1: function theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) { webmaster@1: $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf')); webmaster@1: if (!empty($extra_class)) { webmaster@1: $class .= ' '. $extra_class; webmaster@1: } webmaster@1: if ($in_active_trail) { webmaster@1: $class .= ' active-trail'; webmaster@1: } webmaster@1: return '
  • '. $link . $menu ."
  • \n"; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generate the HTML output for a single local task link. webmaster@1: * webmaster@1: * @ingroup themeable webmaster@1: */ webmaster@1: function theme_menu_local_task($link, $active = FALSE) { webmaster@1: return '
  • '. $link ."
  • \n"; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generates elements for the $arg array in the help hook. webmaster@1: */ webmaster@1: function drupal_help_arg($arg = array()) { webmaster@1: // Note - the number of empty elements should be > MENU_MAX_PARTS. webmaster@1: return $arg + array('', '', '', '', '', '', '', '', '', '', '', ''); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns the help associated with the active menu item. webmaster@1: */ webmaster@1: function menu_get_active_help() { webmaster@1: $output = ''; webmaster@1: $router_path = menu_tab_root_path(); webmaster@11: // We will always have a path unless we are on a 403 or 404. webmaster@11: if (!$router_path) { webmaster@11: return ''; webmaster@11: } webmaster@1: webmaster@1: $arg = drupal_help_arg(arg(NULL)); webmaster@1: $empty_arg = drupal_help_arg(); webmaster@1: webmaster@1: foreach (module_list() as $name) { webmaster@1: if (module_hook($name, 'help')) { webmaster@1: // Lookup help for this path. webmaster@1: if ($help = module_invoke($name, 'help', $router_path, $arg)) { webmaster@1: $output .= $help ."\n"; webmaster@1: } webmaster@1: // Add "more help" link on admin pages if the module provides a webmaster@1: // standalone help page. webmaster@1: if ($arg[0] == "admin" && module_exists('help') && module_invoke($name, 'help', 'admin/help#'. $arg[2], $empty_arg) && $help) { webmaster@1: $output .= theme("more_help_link", url('admin/help/'. $arg[2])); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: return $output; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Build a list of named menus. webmaster@1: */ webmaster@1: function menu_get_names($reset = FALSE) { webmaster@1: static $names; webmaster@1: webmaster@1: if ($reset || empty($names)) { webmaster@1: $names = array(); webmaster@1: $result = db_query("SELECT DISTINCT(menu_name) FROM {menu_links} ORDER BY menu_name"); webmaster@1: while ($name = db_fetch_array($result)) { webmaster@1: $names[] = $name['menu_name']; webmaster@1: } webmaster@1: } webmaster@1: return $names; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return an array containing the names of system-defined (default) menus. webmaster@1: */ webmaster@1: function menu_list_system_menus() { webmaster@1: return array('navigation', 'primary-links', 'secondary-links'); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return an array of links to be rendered as the Primary links. webmaster@1: */ webmaster@1: function menu_primary_links() { webmaster@1: return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links')); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return an array of links to be rendered as the Secondary links. webmaster@1: */ webmaster@1: function menu_secondary_links() { webmaster@1: webmaster@1: // If the secondary menu source is set as the primary menu, we display the webmaster@1: // second level of the primary menu. webmaster@1: if (variable_get('menu_secondary_links_source', 'secondary-links') == variable_get('menu_primary_links_source', 'primary-links')) { webmaster@1: return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'), 1); webmaster@1: } webmaster@1: else { webmaster@1: return menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 0); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return an array of links for a navigation menu. webmaster@1: * webmaster@1: * @param $menu_name webmaster@1: * The name of the menu. webmaster@1: * @param $level webmaster@1: * Optional, the depth of the menu to be returned. webmaster@1: * @return webmaster@1: * An array of links of the specified menu and level. webmaster@1: */ webmaster@1: function menu_navigation_links($menu_name, $level = 0) { webmaster@1: // Don't even bother querying the menu table if no menu is specified. webmaster@1: if (empty($menu_name)) { webmaster@1: return array(); webmaster@1: } webmaster@1: webmaster@1: // Get the menu hierarchy for the current page. webmaster@1: $tree = menu_tree_page_data($menu_name); webmaster@1: webmaster@1: // Go down the active trail until the right level is reached. webmaster@1: while ($level-- > 0 && $tree) { webmaster@1: // Loop through the current level's items until we find one that is in trail. webmaster@1: while ($item = array_shift($tree)) { webmaster@1: if ($item['link']['in_active_trail']) { webmaster@1: // If the item is in the active trail, we continue in the subtree. webmaster@1: $tree = empty($item['below']) ? array() : $item['below']; webmaster@1: break; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Create a single level of links. webmaster@1: $links = array(); webmaster@1: foreach ($tree as $item) { webmaster@1: if (!$item['link']['hidden']) { webmaster@1: $l = $item['link']['localized_options']; webmaster@1: $l['href'] = $item['link']['href']; webmaster@1: $l['title'] = $item['link']['title']; webmaster@7: if ($item['link']['in_active_trail']) { webmaster@7: if (empty($l['attributes']['class'])) { webmaster@7: $l['attributes']['class'] = 'active-trail'; webmaster@7: } webmaster@7: else { webmaster@7: $l['attributes']['class'] .= ' active-trail'; webmaster@7: } webmaster@7: } webmaster@1: // Keyed with unique menu id to generate classes from theme_links(). webmaster@1: $links['menu-'. $item['link']['mlid']] = $l; webmaster@1: } webmaster@1: } webmaster@1: return $links; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Collects the local tasks (tabs) for a given level. webmaster@1: * webmaster@1: * @param $level webmaster@1: * The level of tasks you ask for. Primary tasks are 0, secondary are 1. webmaster@1: * @param $return_root webmaster@1: * Whether to return the root path for the current page. webmaster@1: * @return webmaster@1: * Themed output corresponding to the tabs of the requested level, or webmaster@1: * router path if $return_root == TRUE. This router path corresponds to webmaster@1: * a parent tab, if the current page is a default local task. webmaster@1: */ webmaster@1: function menu_local_tasks($level = 0, $return_root = FALSE) { webmaster@1: static $tabs; webmaster@1: static $root_path; webmaster@1: webmaster@1: if (!isset($tabs)) { webmaster@1: $tabs = array(); webmaster@1: webmaster@1: $router_item = menu_get_item(); webmaster@1: if (!$router_item || !$router_item['access']) { webmaster@1: return ''; webmaster@1: } webmaster@1: // Get all tabs and the root page. webmaster@1: $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' ORDER BY weight, title", $router_item['tab_root']); webmaster@1: $map = arg(); webmaster@1: $children = array(); webmaster@1: $tasks = array(); webmaster@1: $root_path = $router_item['path']; webmaster@1: webmaster@1: while ($item = db_fetch_array($result)) { webmaster@1: _menu_translate($item, $map, TRUE); webmaster@1: if ($item['tab_parent']) { webmaster@1: // All tabs, but not the root page. webmaster@1: $children[$item['tab_parent']][$item['path']] = $item; webmaster@1: } webmaster@1: // Store the translated item for later use. webmaster@1: $tasks[$item['path']] = $item; webmaster@1: } webmaster@1: webmaster@1: // Find all tabs below the current path. webmaster@1: $path = $router_item['path']; webmaster@1: // Tab parenting may skip levels, so the number of parts in the path may not webmaster@1: // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). webmaster@1: $depth = 1001; webmaster@1: while (isset($children[$path])) { webmaster@1: $tabs_current = ''; webmaster@1: $next_path = ''; webmaster@1: $count = 0; webmaster@1: foreach ($children[$path] as $item) { webmaster@1: if ($item['access']) { webmaster@1: $count++; webmaster@1: // The default task is always active. webmaster@1: if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) { webmaster@1: // Find the first parent which is not a default local task. webmaster@1: for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']); webmaster@1: $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item); webmaster@1: $tabs_current .= theme('menu_local_task', $link, TRUE); webmaster@1: $next_path = $item['path']; webmaster@1: } webmaster@1: else { webmaster@1: $link = theme('menu_item_link', $item); webmaster@1: $tabs_current .= theme('menu_local_task', $link); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: $path = $next_path; webmaster@1: $tabs[$depth]['count'] = $count; webmaster@1: $tabs[$depth]['output'] = $tabs_current; webmaster@1: $depth++; webmaster@1: } webmaster@1: webmaster@1: // Find all tabs at the same level or above the current one. webmaster@1: $parent = $router_item['tab_parent']; webmaster@1: $path = $router_item['path']; webmaster@1: $current = $router_item; webmaster@1: $depth = 1000; webmaster@1: while (isset($children[$parent])) { webmaster@1: $tabs_current = ''; webmaster@1: $next_path = ''; webmaster@1: $next_parent = ''; webmaster@1: $count = 0; webmaster@1: foreach ($children[$parent] as $item) { webmaster@1: if ($item['access']) { webmaster@1: $count++; webmaster@1: if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) { webmaster@1: // Find the first parent which is not a default local task. webmaster@1: for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']); webmaster@1: $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item); webmaster@1: if ($item['path'] == $router_item['path']) { webmaster@1: $root_path = $tasks[$p]['path']; webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: $link = theme('menu_item_link', $item); webmaster@1: } webmaster@1: // We check for the active tab. webmaster@1: if ($item['path'] == $path) { webmaster@1: $tabs_current .= theme('menu_local_task', $link, TRUE); webmaster@1: $next_path = $item['tab_parent']; webmaster@1: if (isset($tasks[$next_path])) { webmaster@1: $next_parent = $tasks[$next_path]['tab_parent']; webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: $tabs_current .= theme('menu_local_task', $link); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: $path = $next_path; webmaster@1: $parent = $next_parent; webmaster@1: $tabs[$depth]['count'] = $count; webmaster@1: $tabs[$depth]['output'] = $tabs_current; webmaster@1: $depth--; webmaster@1: } webmaster@1: // Sort by depth. webmaster@1: ksort($tabs); webmaster@1: // Remove the depth, we are interested only in their relative placement. webmaster@1: $tabs = array_values($tabs); webmaster@1: } webmaster@1: webmaster@1: if ($return_root) { webmaster@1: return $root_path; webmaster@1: } webmaster@1: else { webmaster@1: // We do not display single tabs. webmaster@1: return (isset($tabs[$level]) && $tabs[$level]['count'] > 1) ? $tabs[$level]['output'] : ''; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns the rendered local tasks at the top level. webmaster@1: */ webmaster@1: function menu_primary_local_tasks() { webmaster@1: return menu_local_tasks(0); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns the rendered local tasks at the second level. webmaster@1: */ webmaster@1: function menu_secondary_local_tasks() { webmaster@1: return menu_local_tasks(1); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns the router path, or the path of the parent tab of a default local task. webmaster@1: */ webmaster@1: function menu_tab_root_path() { webmaster@1: return menu_local_tasks(0, TRUE); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns the rendered local tasks. The default implementation renders them as tabs. webmaster@1: * webmaster@1: * @ingroup themeable webmaster@1: */ webmaster@1: function theme_menu_local_tasks() { webmaster@1: $output = ''; webmaster@1: webmaster@1: if ($primary = menu_primary_local_tasks()) { webmaster@1: $output .= "\n"; webmaster@1: } webmaster@1: if ($secondary = menu_secondary_local_tasks()) { webmaster@1: $output .= "\n"; webmaster@1: } webmaster@1: webmaster@1: return $output; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Set (or get) the active menu for the current page - determines the active trail. webmaster@1: */ webmaster@1: function menu_set_active_menu_name($menu_name = NULL) { webmaster@1: static $active; webmaster@1: webmaster@1: if (isset($menu_name)) { webmaster@1: $active = $menu_name; webmaster@1: } webmaster@1: elseif (!isset($active)) { webmaster@1: $active = 'navigation'; webmaster@1: } webmaster@1: return $active; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the active menu for the current page - determines the active trail. webmaster@1: */ webmaster@1: function menu_get_active_menu_name() { webmaster@1: return menu_set_active_menu_name(); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Set the active path, which determines which page is loaded. webmaster@1: * webmaster@1: * @param $path webmaster@1: * A Drupal path - not a path alias. webmaster@1: * webmaster@1: * Note that this may not have the desired effect unless invoked very early webmaster@1: * in the page load, such as during hook_boot, or unless you call webmaster@1: * menu_execute_active_handler() to generate your page output. webmaster@1: */ webmaster@1: function menu_set_active_item($path) { webmaster@1: $_GET['q'] = $path; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Set (or get) the active trail for the current page - the path to root in the menu tree. webmaster@1: */ webmaster@1: function menu_set_active_trail($new_trail = NULL) { webmaster@1: static $trail; webmaster@1: webmaster@1: if (isset($new_trail)) { webmaster@1: $trail = $new_trail; webmaster@1: } webmaster@1: elseif (!isset($trail)) { webmaster@1: $trail = array(); webmaster@1: $trail[] = array('title' => t('Home'), 'href' => '', 'localized_options' => array(), 'type' => 0); webmaster@1: $item = menu_get_item(); webmaster@1: webmaster@1: // Check whether the current item is a local task (displayed as a tab). webmaster@1: if ($item['tab_parent']) { webmaster@1: // The title of a local task is used for the tab, never the page title. webmaster@1: // Thus, replace it with the item corresponding to the root path to get webmaster@1: // the relevant href and title. For example, the menu item corresponding webmaster@1: // to 'admin' is used when on the 'By module' tab at 'admin/by-module'. webmaster@1: $parts = explode('/', $item['tab_root']); webmaster@1: $args = arg(); webmaster@1: // Replace wildcards in the root path using the current path. webmaster@1: foreach ($parts as $index => $part) { webmaster@1: if ($part == '%') { webmaster@1: $parts[$index] = $args[$index]; webmaster@1: } webmaster@1: } webmaster@1: // Retrieve the menu item using the root path after wildcard replacement. webmaster@1: $root_item = menu_get_item(implode('/', $parts)); webmaster@1: if ($root_item && $root_item['access']) { webmaster@1: $item = $root_item; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: $tree = menu_tree_page_data(menu_get_active_menu_name()); webmaster@1: list($key, $curr) = each($tree); webmaster@1: webmaster@1: while ($curr) { webmaster@1: // Terminate the loop when we find the current path in the active trail. webmaster@1: if ($curr['link']['href'] == $item['href']) { webmaster@1: $trail[] = $curr['link']; webmaster@1: $curr = FALSE; webmaster@1: } webmaster@1: else { webmaster@7: // Add the link if it's in the active trail, then move to the link below. webmaster@7: if ($curr['link']['in_active_trail']) { webmaster@1: $trail[] = $curr['link']; webmaster@7: $tree = $curr['below'] ? $curr['below'] : array(); webmaster@1: } webmaster@1: list($key, $curr) = each($tree); webmaster@1: } webmaster@1: } webmaster@1: // Make sure the current page is in the trail (needed for the page title), webmaster@1: // but exclude tabs and the front page. webmaster@1: $last = count($trail) - 1; webmaster@1: if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) { webmaster@1: $trail[] = $item; webmaster@1: } webmaster@1: } webmaster@1: return $trail; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the active trail for the current page - the path to root in the menu tree. webmaster@1: */ webmaster@1: function menu_get_active_trail() { webmaster@1: return menu_set_active_trail(); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the breadcrumb for the current page, as determined by the active trail. webmaster@1: */ webmaster@1: function menu_get_active_breadcrumb() { webmaster@1: $breadcrumb = array(); webmaster@11: webmaster@1: // No breadcrumb for the front page. webmaster@1: if (drupal_is_front_page()) { webmaster@1: return $breadcrumb; webmaster@1: } webmaster@1: webmaster@1: $item = menu_get_item(); webmaster@1: if ($item && $item['access']) { webmaster@1: $active_trail = menu_get_active_trail(); webmaster@1: webmaster@1: foreach ($active_trail as $parent) { webmaster@1: $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']); webmaster@1: } webmaster@1: $end = end($active_trail); webmaster@1: webmaster@1: // Don't show a link to the current page in the breadcrumb trail. webmaster@1: if ($item['href'] == $end['href'] || ($item['type'] == MENU_DEFAULT_LOCAL_TASK && $end['href'] != '')) { webmaster@1: array_pop($breadcrumb); webmaster@1: } webmaster@1: } webmaster@1: return $breadcrumb; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the title of the current page, as determined by the active trail. webmaster@1: */ webmaster@1: function menu_get_active_title() { webmaster@1: $active_trail = menu_get_active_trail(); webmaster@1: webmaster@1: foreach (array_reverse($active_trail) as $item) { webmaster@1: if (!(bool)($item['type'] & MENU_IS_LOCAL_TASK)) { webmaster@1: return $item['title']; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get a menu link by its mlid, access checked and link translated for rendering. webmaster@1: * webmaster@1: * This function should never be called from within node_load() or any other webmaster@1: * function used as a menu object load function since an infinite recursion may webmaster@1: * occur. webmaster@1: * webmaster@1: * @param $mlid webmaster@1: * The mlid of the menu item. webmaster@1: * @return webmaster@1: * A menu link, with $item['access'] filled and link translated for webmaster@1: * rendering. webmaster@1: */ webmaster@1: function menu_link_load($mlid) { webmaster@1: if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) { webmaster@1: _menu_link_translate($item); webmaster@1: return $item; webmaster@1: } webmaster@1: return FALSE; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Clears the cached cached data for a single named menu. webmaster@1: */ webmaster@1: function menu_cache_clear($menu_name = 'navigation') { webmaster@1: static $cache_cleared = array(); webmaster@1: webmaster@1: if (empty($cache_cleared[$menu_name])) { webmaster@1: cache_clear_all('links:'. $menu_name .':', 'cache_menu', TRUE); webmaster@1: $cache_cleared[$menu_name] = 1; webmaster@1: } webmaster@1: elseif ($cache_cleared[$menu_name] == 1) { webmaster@1: register_shutdown_function('cache_clear_all', 'links:'. $menu_name .':', 'cache_menu', TRUE); webmaster@1: $cache_cleared[$menu_name] = 2; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Clears all cached menu data. This should be called any time broad changes webmaster@1: * might have been made to the router items or menu links. webmaster@1: */ webmaster@1: function menu_cache_clear_all() { webmaster@1: cache_clear_all('*', 'cache_menu', TRUE); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * (Re)populate the database tables used by various menu functions. webmaster@1: * webmaster@1: * This function will clear and populate the {menu_router} table, add entries webmaster@1: * to {menu_links} for new router items, then remove stale items from webmaster@1: * {menu_links}. If called from update.php or install.php, it will also webmaster@1: * schedule a call to itself on the first real page load from webmaster@1: * menu_execute_active_handler(), because the maintenance page environment webmaster@1: * is different and leaves stale data in the menu tables. webmaster@1: */ webmaster@1: function menu_rebuild() { webmaster@1: variable_del('menu_rebuild_needed'); webmaster@1: menu_cache_clear_all(); webmaster@1: $menu = menu_router_build(TRUE); webmaster@1: _menu_navigation_links_rebuild($menu); webmaster@1: // Clear the page and block caches. webmaster@1: _menu_clear_page_cache(); webmaster@1: if (defined('MAINTENANCE_MODE')) { webmaster@1: variable_set('menu_rebuild_needed', TRUE); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Collect, alter and store the menu definitions. webmaster@1: */ webmaster@1: function menu_router_build($reset = FALSE) { webmaster@1: static $menu; webmaster@1: webmaster@1: if (!isset($menu) || $reset) { webmaster@1: if (!$reset && ($cache = cache_get('router:', 'cache_menu')) && isset($cache->data)) { webmaster@1: $menu = $cache->data; webmaster@1: } webmaster@1: else { webmaster@1: // We need to manually call each module so that we can know which module webmaster@1: // a given item came from. webmaster@1: $callbacks = array(); webmaster@1: foreach (module_implements('menu') as $module) { webmaster@1: $router_items = call_user_func($module .'_menu'); webmaster@1: if (isset($router_items) && is_array($router_items)) { webmaster@1: foreach (array_keys($router_items) as $path) { webmaster@1: $router_items[$path]['module'] = $module; webmaster@1: } webmaster@1: $callbacks = array_merge($callbacks, $router_items); webmaster@1: } webmaster@1: } webmaster@1: // Alter the menu as defined in modules, keys are like user/%user. webmaster@1: drupal_alter('menu', $callbacks); webmaster@1: $menu = _menu_router_build($callbacks); webmaster@1: } webmaster@1: } webmaster@1: return $menu; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Builds a link from a router item. webmaster@1: */ webmaster@1: function _menu_link_build($item) { webmaster@1: if ($item['type'] == MENU_CALLBACK) { webmaster@1: $item['hidden'] = -1; webmaster@1: } webmaster@1: elseif ($item['type'] == MENU_SUGGESTED_ITEM) { webmaster@1: $item['hidden'] = 1; webmaster@1: } webmaster@1: // Note, we set this as 'system', so that we can be sure to distinguish all webmaster@1: // the menu links generated automatically from entries in {menu_router}. webmaster@1: $item['module'] = 'system'; webmaster@1: $item += array( webmaster@1: 'menu_name' => 'navigation', webmaster@1: 'link_title' => $item['title'], webmaster@1: 'link_path' => $item['path'], webmaster@1: 'hidden' => 0, webmaster@1: 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])), webmaster@1: ); webmaster@1: return $item; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function to build menu links for the items in the menu router. webmaster@1: */ webmaster@1: function _menu_navigation_links_rebuild($menu) { webmaster@1: // Add normal and suggested items as links. webmaster@1: $menu_links = array(); webmaster@1: foreach ($menu as $path => $item) { webmaster@1: if ($item['_visible']) { webmaster@1: $item = _menu_link_build($item); webmaster@1: $menu_links[$path] = $item; webmaster@1: $sort[$path] = $item['_number_parts']; webmaster@1: } webmaster@1: } webmaster@1: if ($menu_links) { webmaster@1: // Make sure no child comes before its parent. webmaster@1: array_multisort($sort, SORT_NUMERIC, $menu_links); webmaster@1: webmaster@1: foreach ($menu_links as $item) { webmaster@1: $existing_item = db_fetch_array(db_query("SELECT mlid, menu_name, plid, customized, has_children, updated FROM {menu_links} WHERE link_path = '%s' AND module = '%s'", $item['link_path'], 'system')); webmaster@1: if ($existing_item) { webmaster@1: $item['mlid'] = $existing_item['mlid']; webmaster@11: // A change in hook_menu may move the link to a different menu webmaster@11: if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) { webmaster@11: $item['menu_name'] = $existing_item['menu_name']; webmaster@11: $item['plid'] = $existing_item['plid']; webmaster@11: } webmaster@1: $item['has_children'] = $existing_item['has_children']; webmaster@1: $item['updated'] = $existing_item['updated']; webmaster@1: } webmaster@1: if (!$existing_item || !$existing_item['customized']) { webmaster@1: menu_link_save($item); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: $placeholders = db_placeholders($menu, 'varchar'); webmaster@1: $paths = array_keys($menu); webmaster@3: // Updated and customized items whose router paths are gone need new ones. webmaster@1: $result = db_query("SELECT ml.link_path, ml.mlid, ml.router_path, ml.updated FROM {menu_links} ml WHERE ml.updated = 1 OR (router_path NOT IN ($placeholders) AND external = 0 AND customized = 1)", $paths); webmaster@1: while ($item = db_fetch_array($result)) { webmaster@1: $router_path = _menu_find_router_path($menu, $item['link_path']); webmaster@1: if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) { webmaster@1: // If the router path and the link path matches, it's surely a working webmaster@1: // item, so we clear the updated flag. webmaster@1: $updated = $item['updated'] && $router_path != $item['link_path']; webmaster@1: db_query("UPDATE {menu_links} SET router_path = '%s', updated = %d WHERE mlid = %d", $router_path, $updated, $item['mlid']); webmaster@1: } webmaster@1: } webmaster@3: // Find any item whose router path does not exist any more. webmaster@1: $result = db_query("SELECT * FROM {menu_links} WHERE router_path NOT IN ($placeholders) AND external = 0 AND updated = 0 AND customized = 0 ORDER BY depth DESC", $paths); webmaster@1: // Remove all such items. Starting from those with the greatest depth will webmaster@1: // minimize the amount of re-parenting done by menu_link_delete(). webmaster@1: while ($item = db_fetch_array($result)) { webmaster@1: _menu_delete_item($item, TRUE); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Delete one or several menu links. webmaster@1: * webmaster@1: * @param $mlid webmaster@1: * A valid menu link mlid or NULL. If NULL, $path is used. webmaster@1: * @param $path webmaster@1: * The path to the menu items to be deleted. $mlid must be NULL. webmaster@1: */ webmaster@1: function menu_link_delete($mlid, $path = NULL) { webmaster@1: if (isset($mlid)) { webmaster@1: _menu_delete_item(db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $mlid))); webmaster@1: } webmaster@1: else { webmaster@1: $result = db_query("SELECT * FROM {menu_links} WHERE link_path = '%s'", $path); webmaster@1: while ($link = db_fetch_array($result)) { webmaster@1: _menu_delete_item($link); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function for menu_link_delete; deletes a single menu link. webmaster@1: * webmaster@1: * @param $item webmaster@1: * Item to be deleted. webmaster@1: * @param $force webmaster@1: * Forces deletion. Internal use only, setting to TRUE is discouraged. webmaster@1: */ webmaster@1: function _menu_delete_item($item, $force = FALSE) { webmaster@1: if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) { webmaster@1: // Children get re-attached to the item's parent. webmaster@1: if ($item['has_children']) { webmaster@1: $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = %d", $item['mlid']); webmaster@1: while ($m = db_fetch_array($result)) { webmaster@1: $child = menu_link_load($m['mlid']); webmaster@1: $child['plid'] = $item['plid']; webmaster@1: menu_link_save($child); webmaster@1: } webmaster@1: } webmaster@1: db_query('DELETE FROM {menu_links} WHERE mlid = %d', $item['mlid']); webmaster@1: webmaster@1: // Update the has_children status of the parent. webmaster@1: _menu_update_parental_status($item); webmaster@1: menu_cache_clear($item['menu_name']); webmaster@1: _menu_clear_page_cache(); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Save a menu link. webmaster@1: * webmaster@1: * @param $item webmaster@1: * An array representing a menu link item. The only mandatory keys are webmaster@3: * link_path and link_title. Possible keys are: webmaster@3: * - menu_name default is navigation webmaster@3: * - weight default is 0 webmaster@3: * - expanded whether the item is expanded. webmaster@3: * - options An array of options, @see l for more. webmaster@3: * - mlid Set to an existing value, or 0 or NULL to insert a new link. webmaster@3: * - plid The mlid of the parent. webmaster@3: * - router_path The path of the relevant router item. webmaster@11: * @return webmaster@11: * The mlid of the saved menu link, or FALSE if the menu link could not be webmaster@11: * saved. webmaster@1: */ webmaster@1: function menu_link_save(&$item) { webmaster@1: $menu = menu_router_build(); webmaster@1: webmaster@1: drupal_alter('menu_link', $item, $menu); webmaster@1: webmaster@1: // This is the easiest way to handle the unique internal path '', webmaster@1: // since a path marked as external does not need to match a router path. webmaster@1: $item['_external'] = menu_path_is_external($item['link_path']) || $item['link_path'] == ''; webmaster@1: // Load defaults. webmaster@1: $item += array( webmaster@1: 'menu_name' => 'navigation', webmaster@1: 'weight' => 0, webmaster@1: 'link_title' => '', webmaster@1: 'hidden' => 0, webmaster@1: 'has_children' => 0, webmaster@1: 'expanded' => 0, webmaster@1: 'options' => array(), webmaster@1: 'module' => 'menu', webmaster@1: 'customized' => 0, webmaster@1: 'updated' => 0, webmaster@1: ); webmaster@1: $existing_item = FALSE; webmaster@1: if (isset($item['mlid'])) { webmaster@1: $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid'])); webmaster@1: } webmaster@1: webmaster@1: if (isset($item['plid'])) { webmaster@1: $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid'])); webmaster@1: } webmaster@1: else { webmaster@1: // Find the parent - it must be unique. webmaster@1: $parent_path = $item['link_path']; webmaster@1: $where = "WHERE link_path = '%s'"; webmaster@1: // Only links derived from router items should have module == 'system', and webmaster@1: // we want to find the parent even if it's in a different menu. webmaster@1: if ($item['module'] == 'system') { webmaster@1: $where .= " AND module = '%s'"; webmaster@1: $arg2 = 'system'; webmaster@1: } webmaster@1: else { webmaster@1: // If not derived from a router item, we respect the specified menu name. webmaster@1: $where .= " AND menu_name = '%s'"; webmaster@1: $arg2 = $item['menu_name']; webmaster@1: } webmaster@1: do { webmaster@1: $parent = FALSE; webmaster@1: $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); webmaster@1: $result = db_query("SELECT COUNT(*) FROM {menu_links} ". $where, $parent_path, $arg2); webmaster@1: // Only valid if we get a unique result. webmaster@1: if (db_result($result) == 1) { webmaster@1: $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} ". $where, $parent_path, $arg2)); webmaster@1: } webmaster@1: } while ($parent === FALSE && $parent_path); webmaster@1: } webmaster@1: if ($parent !== FALSE) { webmaster@1: $item['menu_name'] = $parent['menu_name']; webmaster@1: } webmaster@1: $menu_name = $item['menu_name']; webmaster@1: // Menu callbacks need to be in the links table for breadcrumbs, but can't webmaster@1: // be parents if they are generated directly from a router item. webmaster@1: if (empty($parent['mlid']) || $parent['hidden'] < 0) { webmaster@1: $item['plid'] = 0; webmaster@1: } webmaster@1: else { webmaster@1: $item['plid'] = $parent['mlid']; webmaster@1: } webmaster@1: webmaster@1: if (!$existing_item) { webmaster@1: db_query("INSERT INTO {menu_links} ( webmaster@1: menu_name, plid, link_path, webmaster@1: hidden, external, has_children, webmaster@1: expanded, weight, webmaster@1: module, link_title, options, webmaster@1: customized, updated) VALUES ( webmaster@1: '%s', %d, '%s', webmaster@1: %d, %d, %d, webmaster@1: %d, %d, webmaster@1: '%s', '%s', '%s', %d, %d)", webmaster@1: $item['menu_name'], $item['plid'], $item['link_path'], webmaster@1: $item['hidden'], $item['_external'], $item['has_children'], webmaster@1: $item['expanded'], $item['weight'], webmaster@1: $item['module'], $item['link_title'], serialize($item['options']), webmaster@1: $item['customized'], $item['updated']); webmaster@1: $item['mlid'] = db_last_insert_id('menu_links', 'mlid'); webmaster@1: } webmaster@1: webmaster@1: if (!$item['plid']) { webmaster@1: $item['p1'] = $item['mlid']; webmaster@1: for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { webmaster@1: $item["p$i"] = 0; webmaster@1: } webmaster@1: $item['depth'] = 1; webmaster@1: } webmaster@1: else { webmaster@1: // Cannot add beyond the maximum depth. webmaster@1: if ($item['has_children'] && $existing_item) { webmaster@1: $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1; webmaster@1: } webmaster@1: else { webmaster@1: $limit = MENU_MAX_DEPTH - 1; webmaster@1: } webmaster@1: if ($parent['depth'] > $limit) { webmaster@1: return FALSE; webmaster@1: } webmaster@1: $item['depth'] = $parent['depth'] + 1; webmaster@1: _menu_link_parents_set($item, $parent); webmaster@1: } webmaster@1: // Need to check both plid and menu_name, since plid can be 0 in any menu. webmaster@1: if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) { webmaster@1: _menu_link_move_children($item, $existing_item); webmaster@1: } webmaster@1: // Find the callback. During the menu update we store empty paths to be webmaster@1: // fixed later, so we skip this. webmaster@1: if (!isset($_SESSION['system_update_6021']) && (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path']))) { webmaster@1: if ($item['_external']) { webmaster@1: $item['router_path'] = ''; webmaster@1: } webmaster@1: else { webmaster@1: // Find the router path which will serve this path. webmaster@1: $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS); webmaster@1: $item['router_path'] = _menu_find_router_path($menu, $item['link_path']); webmaster@1: } webmaster@1: } webmaster@1: db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s', webmaster@1: router_path = '%s', hidden = %d, external = %d, has_children = %d, webmaster@1: expanded = %d, weight = %d, depth = %d, webmaster@1: p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d, webmaster@1: module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d", webmaster@1: $item['menu_name'], $item['plid'], $item['link_path'], webmaster@1: $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'], webmaster@1: $item['expanded'], $item['weight'], $item['depth'], webmaster@1: $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'], webmaster@1: $item['module'], $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']); webmaster@1: // Check the has_children status of the parent. webmaster@1: _menu_update_parental_status($item); webmaster@1: menu_cache_clear($menu_name); webmaster@1: if ($existing_item && $menu_name != $existing_item['menu_name']) { webmaster@1: menu_cache_clear($existing_item['menu_name']); webmaster@1: } webmaster@1: webmaster@1: _menu_clear_page_cache(); webmaster@1: return $item['mlid']; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function to clear the page and block caches at most twice per page load. webmaster@1: */ webmaster@1: function _menu_clear_page_cache() { webmaster@1: static $cache_cleared = 0; webmaster@1: webmaster@1: // Clear the page and block caches, but at most twice, including at webmaster@1: // the end of the page load when there are multple links saved or deleted. webmaster@1: if (empty($cache_cleared)) { webmaster@1: cache_clear_all(); webmaster@1: // Keep track of which menus have expanded items. webmaster@1: _menu_set_expanded_menus(); webmaster@1: $cache_cleared = 1; webmaster@1: } webmaster@1: elseif ($cache_cleared == 1) { webmaster@1: register_shutdown_function('cache_clear_all'); webmaster@1: // Keep track of which menus have expanded items. webmaster@1: register_shutdown_function('_menu_set_expanded_menus'); webmaster@1: $cache_cleared = 2; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function to update a list of menus with expanded items webmaster@1: */ webmaster@1: function _menu_set_expanded_menus() { webmaster@1: $names = array(); webmaster@1: $result = db_query("SELECT menu_name FROM {menu_links} WHERE expanded != 0 GROUP BY menu_name"); webmaster@1: while ($n = db_fetch_array($result)) { webmaster@1: $names[] = $n['menu_name']; webmaster@1: } webmaster@1: variable_set('menu_expanded', $names); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Find the router path which will serve this path. webmaster@1: * webmaster@1: * @param $menu webmaster@1: * The full built menu. webmaster@1: * @param $link_path webmaster@1: * The path for we are looking up its router path. webmaster@1: * @return webmaster@1: * A path from $menu keys or empty if $link_path points to a nonexisting webmaster@1: * place. webmaster@1: */ webmaster@1: function _menu_find_router_path($menu, $link_path) { webmaster@1: $parts = explode('/', $link_path, MENU_MAX_PARTS); webmaster@1: $router_path = $link_path; webmaster@1: if (!isset($menu[$router_path])) { webmaster@1: list($ancestors) = menu_get_ancestors($parts); webmaster@1: $ancestors[] = ''; webmaster@1: foreach ($ancestors as $key => $router_path) { webmaster@1: if (isset($menu[$router_path])) { webmaster@1: break; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: return $router_path; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Insert, update or delete an uncustomized menu link related to a module. webmaster@1: * webmaster@1: * @param $module webmaster@1: * The name of the module. webmaster@1: * @param $op webmaster@1: * Operation to perform: insert, update or delete. webmaster@1: * @param $link_path webmaster@1: * The path this link points to. webmaster@1: * @param $link_title webmaster@1: * Title of the link to insert or new title to update the link to. webmaster@1: * Unused for delete. webmaster@1: * @return webmaster@1: * The insert op returns the mlid of the new item. Others op return NULL. webmaster@1: */ webmaster@1: function menu_link_maintain($module, $op, $link_path, $link_title) { webmaster@1: switch ($op) { webmaster@1: case 'insert': webmaster@1: $menu_link = array( webmaster@1: 'link_title' => $link_title, webmaster@1: 'link_path' => $link_path, webmaster@1: 'module' => $module, webmaster@1: ); webmaster@1: return menu_link_save($menu_link); webmaster@1: break; webmaster@1: case 'update': webmaster@1: db_query("UPDATE {menu_links} SET link_title = '%s' WHERE link_path = '%s' AND customized = 0 AND module = '%s'", $link_title, $link_path, $module); webmaster@11: $result = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = '%s' AND customized = 0 AND module = '%s'", $link_path, $module); webmaster@11: while ($item = db_fetch_array($result)) { webmaster@11: menu_cache_clear($item['menu_name']); webmaster@11: } webmaster@1: break; webmaster@1: case 'delete': webmaster@1: menu_link_delete(NULL, $link_path); webmaster@1: break; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Find the depth of an item's children relative to its depth. webmaster@1: * webmaster@1: * For example, if the item has a depth of 2, and the maximum of any child in webmaster@1: * the menu link tree is 5, the relative depth is 3. webmaster@1: * webmaster@1: * @param $item webmaster@1: * An array representing a menu link item. webmaster@1: * @return webmaster@1: * The relative depth, or zero. webmaster@1: * webmaster@1: */ webmaster@1: function menu_link_children_relative_depth($item) { webmaster@1: $i = 1; webmaster@1: $match = ''; webmaster@1: $args[] = $item['menu_name']; webmaster@1: $p = 'p1'; webmaster@1: while ($i <= MENU_MAX_DEPTH && $item[$p]) { webmaster@1: $match .= " AND $p = %d"; webmaster@1: $args[] = $item[$p]; webmaster@1: $p = 'p'. ++$i; webmaster@1: } webmaster@1: webmaster@1: $max_depth = db_result(db_query_range("SELECT depth FROM {menu_links} WHERE menu_name = '%s'". $match ." ORDER BY depth DESC", $args, 0, 1)); webmaster@1: webmaster@1: return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Update the children of a menu link that's being moved. webmaster@1: * webmaster@1: * The menu name, parents (p1 - p6), and depth are updated for all children of webmaster@1: * the link, and the has_children status of the previous parent is updated. webmaster@1: */ webmaster@1: function _menu_link_move_children($item, $existing_item) { webmaster@1: webmaster@1: $args[] = $item['menu_name']; webmaster@1: $set[] = "menu_name = '%s'"; webmaster@1: webmaster@1: $i = 1; webmaster@1: while ($i <= $item['depth']) { webmaster@1: $p = 'p'. $i++; webmaster@1: $set[] = "$p = %d"; webmaster@1: $args[] = $item[$p]; webmaster@1: } webmaster@1: $j = $existing_item['depth'] + 1; webmaster@1: while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { webmaster@1: $set[] = 'p'. $i++ .' = p'. $j++; webmaster@1: } webmaster@1: while ($i <= MENU_MAX_DEPTH) { webmaster@1: $set[] = 'p'. $i++ .' = 0'; webmaster@1: } webmaster@1: webmaster@1: $shift = $item['depth'] - $existing_item['depth']; webmaster@1: if ($shift < 0) { webmaster@1: $args[] = -$shift; webmaster@1: $set[] = 'depth = depth - %d'; webmaster@1: } webmaster@1: elseif ($shift > 0) { webmaster@1: // The order of $set must be reversed so the new values don't overwrite the webmaster@1: // old ones before they can be used because "Single-table UPDATE webmaster@1: // assignments are generally evaluated from left to right" webmaster@1: // see: http://dev.mysql.com/doc/refman/5.0/en/update.html webmaster@1: $set = array_reverse($set); webmaster@1: $args = array_reverse($args); webmaster@1: webmaster@1: $args[] = $shift; webmaster@1: $set[] = 'depth = depth + %d'; webmaster@1: } webmaster@1: $where[] = "menu_name = '%s'"; webmaster@1: $args[] = $existing_item['menu_name']; webmaster@1: $p = 'p1'; webmaster@1: for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p'. ++$i) { webmaster@1: $where[] = "$p = %d"; webmaster@1: $args[] = $existing_item[$p]; webmaster@1: } webmaster@1: webmaster@1: db_query("UPDATE {menu_links} SET ". implode(', ', $set) ." WHERE ". implode(' AND ', $where), $args); webmaster@1: // Check the has_children status of the parent, while excluding this item. webmaster@1: _menu_update_parental_status($existing_item, TRUE); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Check and update the has_children status for the parent of a link. webmaster@1: */ webmaster@1: function _menu_update_parental_status($item, $exclude = FALSE) { webmaster@1: // If plid == 0, there is nothing to update. webmaster@1: if ($item['plid']) { webmaster@1: // We may want to exclude the passed link as a possible child. webmaster@1: $where = $exclude ? " AND mlid != %d" : ''; webmaster@1: // Check if at least one visible child exists in the table. webmaster@1: $parent_has_children = (bool)db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0". $where, $item['menu_name'], $item['plid'], $item['mlid'], 0, 1)); webmaster@1: db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function that sets the p1..p9 values for a menu link being saved. webmaster@1: */ webmaster@1: function _menu_link_parents_set(&$item, $parent) { webmaster@1: $i = 1; webmaster@1: while ($i < $item['depth']) { webmaster@1: $p = 'p'. $i++; webmaster@1: $item[$p] = $parent[$p]; webmaster@1: } webmaster@1: $p = 'p'. $i++; webmaster@1: // The parent (p1 - p9) corresponding to the depth always equals the mlid. webmaster@1: $item[$p] = $item['mlid']; webmaster@1: while ($i <= MENU_MAX_DEPTH) { webmaster@1: $p = 'p'. $i++; webmaster@1: $item[$p] = 0; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Helper function to build the router table based on the data from hook_menu. webmaster@1: */ webmaster@1: function _menu_router_build($callbacks) { webmaster@1: // First pass: separate callbacks from paths, making paths ready for webmaster@1: // matching. Calculate fitness, and fill some default values. webmaster@1: $menu = array(); webmaster@1: foreach ($callbacks as $path => $item) { webmaster@1: $load_functions = array(); webmaster@1: $to_arg_functions = array(); webmaster@1: $fit = 0; webmaster@1: $move = FALSE; webmaster@1: webmaster@1: $parts = explode('/', $path, MENU_MAX_PARTS); webmaster@1: $number_parts = count($parts); webmaster@1: // We store the highest index of parts here to save some work in the fit webmaster@1: // calculation loop. webmaster@1: $slashes = $number_parts - 1; webmaster@1: // Extract load and to_arg functions. webmaster@1: foreach ($parts as $k => $part) { webmaster@1: $match = FALSE; webmaster@1: if (preg_match('/^%([a-z_]*)$/', $part, $matches)) { webmaster@1: if (empty($matches[1])) { webmaster@1: $match = TRUE; webmaster@1: $load_functions[$k] = NULL; webmaster@1: } webmaster@1: else { webmaster@1: if (function_exists($matches[1] .'_to_arg')) { webmaster@1: $to_arg_functions[$k] = $matches[1] .'_to_arg'; webmaster@1: $load_functions[$k] = NULL; webmaster@1: $match = TRUE; webmaster@1: } webmaster@1: if (function_exists($matches[1] .'_load')) { webmaster@1: $function = $matches[1] .'_load'; webmaster@1: // Create an array of arguments that will be passed to the _load webmaster@1: // function when this menu path is checked, if 'load arguments' webmaster@1: // exists. webmaster@1: $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function; webmaster@1: $match = TRUE; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: if ($match) { webmaster@1: $parts[$k] = '%'; webmaster@1: } webmaster@1: else { webmaster@1: $fit |= 1 << ($slashes - $k); webmaster@1: } webmaster@1: } webmaster@1: if ($fit) { webmaster@1: $move = TRUE; webmaster@1: } webmaster@1: else { webmaster@1: // If there is no %, it fits maximally. webmaster@1: $fit = (1 << $number_parts) - 1; webmaster@1: } webmaster@1: $masks[$fit] = 1; webmaster@1: $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions); webmaster@1: $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions); webmaster@1: $item += array( webmaster@1: 'title' => '', webmaster@1: 'weight' => 0, webmaster@1: 'type' => MENU_NORMAL_ITEM, webmaster@1: '_number_parts' => $number_parts, webmaster@1: '_parts' => $parts, webmaster@1: '_fit' => $fit, webmaster@1: ); webmaster@1: $item += array( webmaster@1: '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), webmaster@1: '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK), webmaster@1: ); webmaster@1: if ($move) { webmaster@1: $new_path = implode('/', $item['_parts']); webmaster@1: $menu[$new_path] = $item; webmaster@1: $sort[$new_path] = $number_parts; webmaster@1: } webmaster@1: else { webmaster@1: $menu[$path] = $item; webmaster@1: $sort[$path] = $number_parts; webmaster@1: } webmaster@1: } webmaster@1: array_multisort($sort, SORT_NUMERIC, $menu); webmaster@1: webmaster@7: if (!$menu) { webmaster@7: // We must have a serious error - there is no data to save. webmaster@7: watchdog('php', 'Menu router rebuild failed - some paths may not work correctly.', array(), WATCHDOG_ERROR); webmaster@7: return array(); webmaster@7: } webmaster@7: // Delete the existing router since we have some data to replace it. webmaster@7: db_query('DELETE FROM {menu_router}'); webmaster@1: // Apply inheritance rules. webmaster@1: foreach ($menu as $path => $v) { webmaster@1: $item = &$menu[$path]; webmaster@1: if (!$item['_tab']) { webmaster@1: // Non-tab items. webmaster@1: $item['tab_parent'] = ''; webmaster@1: $item['tab_root'] = $path; webmaster@1: } webmaster@1: for ($i = $item['_number_parts'] - 1; $i; $i--) { webmaster@1: $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); webmaster@1: if (isset($menu[$parent_path])) { webmaster@1: webmaster@1: $parent = $menu[$parent_path]; webmaster@1: webmaster@1: if (!isset($item['tab_parent'])) { webmaster@1: // Parent stores the parent of the path. webmaster@1: $item['tab_parent'] = $parent_path; webmaster@1: } webmaster@1: if (!isset($item['tab_root']) && !$parent['_tab']) { webmaster@1: $item['tab_root'] = $parent_path; webmaster@1: } webmaster@5: // If an access callback is not found for a default local task we use webmaster@5: // the callback from the parent, since we expect them to be identical. webmaster@5: // In all other cases, the access parameters must be specified. webmaster@5: if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) { webmaster@1: $item['access callback'] = $parent['access callback']; webmaster@1: if (!isset($item['access arguments']) && isset($parent['access arguments'])) { webmaster@1: $item['access arguments'] = $parent['access arguments']; webmaster@1: } webmaster@1: } webmaster@1: // Same for page callbacks. webmaster@1: if (!isset($item['page callback']) && isset($parent['page callback'])) { webmaster@1: $item['page callback'] = $parent['page callback']; webmaster@1: if (!isset($item['page arguments']) && isset($parent['page arguments'])) { webmaster@1: $item['page arguments'] = $parent['page arguments']; webmaster@1: } webmaster@1: if (!isset($item['file']) && isset($parent['file'])) { webmaster@1: $item['file'] = $parent['file']; webmaster@1: } webmaster@1: if (!isset($item['file path']) && isset($parent['file path'])) { webmaster@1: $item['file path'] = $parent['file path']; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: if (!isset($item['access callback']) && isset($item['access arguments'])) { webmaster@1: // Default callback. webmaster@1: $item['access callback'] = 'user_access'; webmaster@1: } webmaster@1: if (!isset($item['access callback']) || empty($item['page callback'])) { webmaster@1: $item['access callback'] = 0; webmaster@1: } webmaster@1: if (is_bool($item['access callback'])) { webmaster@1: $item['access callback'] = intval($item['access callback']); webmaster@1: } webmaster@1: webmaster@1: $item += array( webmaster@1: 'access arguments' => array(), webmaster@1: 'access callback' => '', webmaster@1: 'page arguments' => array(), webmaster@1: 'page callback' => '', webmaster@1: 'block callback' => '', webmaster@1: 'title arguments' => array(), webmaster@1: 'title callback' => 't', webmaster@1: 'description' => '', webmaster@1: 'position' => '', webmaster@1: 'tab_parent' => '', webmaster@1: 'tab_root' => $path, webmaster@1: 'path' => $path, webmaster@1: 'file' => '', webmaster@1: 'file path' => '', webmaster@1: 'include file' => '', webmaster@1: ); webmaster@1: webmaster@1: // Calculate out the file to be included for each callback, if any. webmaster@1: if ($item['file']) { webmaster@1: $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']); webmaster@1: $item['include file'] = $file_path .'/'. $item['file']; webmaster@1: } webmaster@1: webmaster@1: $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : ''; webmaster@1: db_query("INSERT INTO {menu_router} webmaster@1: (path, load_functions, to_arg_functions, access_callback, webmaster@1: access_arguments, page_callback, page_arguments, fit, webmaster@1: number_parts, tab_parent, tab_root, webmaster@1: title, title_callback, title_arguments, webmaster@1: type, block_callback, description, position, weight, file) webmaster@1: VALUES ('%s', '%s', '%s', '%s', webmaster@1: '%s', '%s', '%s', %d, webmaster@1: %d, '%s', '%s', webmaster@1: '%s', '%s', '%s', webmaster@1: %d, '%s', '%s', '%s', %d, '%s')", webmaster@1: $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'], webmaster@1: serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'], webmaster@1: $item['_number_parts'], $item['tab_parent'], $item['tab_root'], webmaster@1: $item['title'], $item['title callback'], $title_arguments, webmaster@1: $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']); webmaster@1: } webmaster@1: // Sort the masks so they are in order of descending fit, and store them. webmaster@1: $masks = array_keys($masks); webmaster@1: rsort($masks); webmaster@1: variable_set('menu_masks', $masks); webmaster@7: cache_set('router:', $menu, 'cache_menu'); webmaster@1: return $menu; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Returns TRUE if a path is external (e.g. http://example.com). webmaster@1: */ webmaster@1: function menu_path_is_external($path) { webmaster@1: $colonpos = strpos($path, ':'); webmaster@1: return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Checks whether the site is off-line for maintenance. webmaster@1: * webmaster@1: * This function will log the current user out and redirect to front page webmaster@1: * if the current user has no 'administer site configuration' permission. webmaster@1: * webmaster@1: * @return webmaster@1: * FALSE if the site is not off-line or its the login page or the user has webmaster@1: * 'administer site configuration' permission. webmaster@1: * TRUE for anonymous users not on the login page if the site is off-line. webmaster@1: */ webmaster@1: function _menu_site_is_offline() { webmaster@1: // Check if site is set to off-line mode. webmaster@1: if (variable_get('site_offline', 0)) { webmaster@1: // Check if the user has administration privileges. webmaster@1: if (user_access('administer site configuration')) { webmaster@1: // Ensure that the off-line message is displayed only once [allowing for webmaster@1: // page redirects], and specifically suppress its display on the site webmaster@1: // maintenance page. webmaster@1: if (drupal_get_normal_path($_GET['q']) != 'admin/settings/site-maintenance') { webmaster@1: drupal_set_message(t('Operating in off-line mode.'), 'status', FALSE); webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: // Anonymous users get a FALSE at the login prompt, TRUE otherwise. webmaster@1: if (user_is_anonymous()) { webmaster@1: return $_GET['q'] != 'user' && $_GET['q'] != 'user/login'; webmaster@1: } webmaster@1: // Logged in users are unprivileged here, so they are logged out. webmaster@1: require_once drupal_get_path('module', 'user') .'/user.pages.inc'; webmaster@1: user_logout(); webmaster@1: } webmaster@1: } webmaster@1: return FALSE; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Validates the path of a menu link being created or edited. webmaster@1: * webmaster@1: * @return webmaster@1: * TRUE if it is a valid path AND the current user has access permission, webmaster@1: * FALSE otherwise. webmaster@1: */ webmaster@1: function menu_valid_path($form_item) { webmaster@1: global $menu_admin; webmaster@1: $item = array(); webmaster@1: $path = $form_item['link_path']; webmaster@1: // We indicate that a menu administrator is running the menu access check. webmaster@1: $menu_admin = TRUE; webmaster@1: if ($path == '' || menu_path_is_external($path)) { webmaster@1: $item = array('access' => TRUE); webmaster@1: } webmaster@1: elseif (preg_match('/\/\%/', $path)) { webmaster@1: // Path is dynamic (ie 'user/%'), so check directly against menu_router table. webmaster@1: if ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path))) { webmaster@1: $item['link_path'] = $form_item['link_path']; webmaster@1: $item['link_title'] = $form_item['link_title']; webmaster@1: $item['external'] = FALSE; webmaster@1: $item['options'] = ''; webmaster@1: _menu_link_translate($item); webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: $item = menu_get_item($path); webmaster@1: } webmaster@1: $menu_admin = FALSE; webmaster@1: return $item && $item['access']; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * @} End of "defgroup menu". webmaster@1: */