/includes/menu.inc
Pascal | 3891 lines | 2254 code | 123 blank | 1514 comment | 260 complexity | 9ce121f9042e16e941b5078dc51480c9 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * @file
- * API for the Drupal menu system.
- */
- /**
- * @defgroup menu Menu system
- * @{
- * Define the navigation menus, and route page requests to code based on URLs.
- *
- * The Drupal menu system drives both the navigation system from a user
- * perspective and the callback system that Drupal uses to respond to URLs
- * passed from the browser. For this reason, a good understanding of the
- * menu system is fundamental to the creation of complex modules. As a note,
- * this is related to, but separate from menu.module, which allows menus
- * (which in this context are hierarchical lists of links) to be customized from
- * the Drupal administrative interface.
- *
- * Drupal's menu system follows a simple hierarchy defined by paths.
- * Implementations of hook_menu() define menu items and assign them to
- * paths (which should be unique). The menu system aggregates these items
- * and determines the menu hierarchy from the paths. For example, if the
- * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
- * would form the structure:
- * - a
- * - a/b
- * - a/b/c/d
- * - a/b/h
- * - e
- * - f/g
- * Note that the number of elements in the path does not necessarily
- * determine the depth of the menu item in the tree.
- *
- * When responding to a page request, the menu system looks to see if the
- * path requested by the browser is registered as a menu item with a
- * callback. If not, the system searches up the menu tree for the most
- * complete match with a callback it can find. If the path a/b/i is
- * requested in the tree above, the callback for a/b would be used.
- *
- * The found callback function is called with any arguments specified
- * in the "page arguments" attribute of its menu item. The
- * attribute must be an array. After these arguments, any remaining
- * components of the path are appended as further arguments. In this
- * way, the callback for a/b above could respond to a request for
- * a/b/i differently than a request for a/b/j.
- *
- * For an illustration of this process, see page_example.module.
- *
- * Access to the callback functions is also protected by the menu system.
- * The "access callback" with an optional "access arguments" of each menu
- * item is called before the page callback proceeds. If this returns TRUE,
- * then access is granted; if FALSE, then access is denied. Default local task
- * menu items (see next paragraph) may omit this attribute to use the value
- * provided by the parent item.
- *
- * In the default Drupal interface, you will notice many links rendered as
- * tabs. These are known in the menu system as "local tasks", and they are
- * rendered as tabs by default, though other presentations are possible.
- * Local tasks function just as other menu items in most respects. It is
- * convention that the names of these tasks should be short verbs if
- * possible. In addition, a "default" local task should be provided for
- * each set. When visiting a local task's parent menu item, the default
- * local task will be rendered as if it is selected; this provides for a
- * normal tab user experience. This default task is special in that it
- * links not to its provided path, but to its parent item's path instead.
- * The default task's path is only used to place it appropriately in the
- * menu hierarchy.
- *
- * Everything described so far is stored in the menu_router table. The
- * menu_links table holds the visible menu links. By default these are
- * derived from the same hook_menu definitions, however you are free to
- * add more with menu_link_save().
- */
- /**
- * @defgroup menu_flags Menu flags
- * @{
- * Flags for use in the "type" attribute of menu items.
- */
- /**
- * Internal menu flag -- menu item is the root of the menu tree.
- */
- define('MENU_IS_ROOT', 0x0001);
- /**
- * Internal menu flag -- menu item is visible in the menu tree.
- */
- define('MENU_VISIBLE_IN_TREE', 0x0002);
- /**
- * Internal menu flag -- menu item is visible in the breadcrumb.
- */
- define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
- /**
- * Internal menu flag -- menu item links back to its parent.
- */
- define('MENU_LINKS_TO_PARENT', 0x0008);
- /**
- * Internal menu flag -- menu item can be modified by administrator.
- */
- define('MENU_MODIFIED_BY_ADMIN', 0x0020);
- /**
- * Internal menu flag -- menu item was created by administrator.
- */
- define('MENU_CREATED_BY_ADMIN', 0x0040);
- /**
- * Internal menu flag -- menu item is a local task.
- */
- define('MENU_IS_LOCAL_TASK', 0x0080);
- /**
- * Internal menu flag -- menu item is a local action.
- */
- define('MENU_IS_LOCAL_ACTION', 0x0100);
- /**
- * @} End of "Menu flags".
- */
- /**
- * @defgroup menu_item_types Menu item types
- * @{
- * Definitions for various menu item types.
- *
- * Menu item definitions provide one of these constants, which are shortcuts for
- * combinations of @link menu_flags Menu flags @endlink.
- */
- /**
- * Menu type -- A "normal" menu item that's shown in menu and breadcrumbs.
- *
- * Normal menu items show up in the menu tree and can be moved/hidden by
- * the administrator. Use this for most menu items. It is the default value if
- * no menu item type is specified.
- */
- define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
- /**
- * Menu type -- A hidden, internal callback, typically used for API calls.
- *
- * Callbacks simply register a path so that the correct function is fired
- * when the URL is accessed. They do not appear in menus or breadcrumbs.
- */
- define('MENU_CALLBACK', 0x0000);
- /**
- * Menu type -- A normal menu item, hidden until enabled by an administrator.
- *
- * Modules may "suggest" menu items that the administrator may enable. They act
- * just as callbacks do until enabled, at which time they act like normal items.
- * Note for the value: 0x0010 was a flag which is no longer used, but this way
- * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate.
- */
- define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010);
- /**
- * Menu type -- A task specific to the parent item, usually rendered as a tab.
- *
- * Local tasks are menu items that describe actions to be performed on their
- * parent item. An example is the path "node/52/edit", which performs the
- * "edit" task on "node/52".
- */
- define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB);
- /**
- * Menu type -- The "default" local task, which is initially active.
- *
- * Every set of local tasks should provide one "default" task, that links to the
- * same path as its parent when clicked.
- */
- define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB);
- /**
- * Menu type -- An action specific to the parent, usually rendered as a link.
- *
- * Local actions are menu items that describe actions on the parent item such
- * as adding a new user, taxonomy term, etc.
- */
- define('MENU_LOCAL_ACTION', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
- /**
- * @} End of "Menu item types".
- */
- /**
- * @defgroup menu_context_types Menu context types
- * @{
- * Flags for use in the "context" attribute of menu router items.
- */
- /**
- * Internal menu flag: Invisible local task.
- *
- * This flag may be used for local tasks like "Delete", so custom modules and
- * themes can alter the default context and expose the task by altering menu.
- */
- define('MENU_CONTEXT_NONE', 0x0000);
- /**
- * Internal menu flag: Local task should be displayed in page context.
- */
- define('MENU_CONTEXT_PAGE', 0x0001);
- /**
- * Internal menu flag: Local task should be displayed inline.
- */
- define('MENU_CONTEXT_INLINE', 0x0002);
- /**
- * @} End of "Menu context types".
- */
- /**
- * @defgroup menu_status_codes Menu status codes
- * @{
- * Status codes for menu callbacks.
- */
- /**
- * Internal menu status code -- Menu item was found.
- */
- define('MENU_FOUND', 1);
- /**
- * Internal menu status code -- Menu item was not found.
- */
- define('MENU_NOT_FOUND', 2);
- /**
- * Internal menu status code -- Menu item access is denied.
- */
- define('MENU_ACCESS_DENIED', 3);
- /**
- * Internal menu status code -- Menu item inaccessible because site is offline.
- */
- define('MENU_SITE_OFFLINE', 4);
- /**
- * Internal menu status code -- Everything is working fine.
- */
- define('MENU_SITE_ONLINE', 5);
- /**
- * @} End of "Menu status codes".
- */
- /**
- * @defgroup menu_tree_parameters Menu tree parameters
- * @{
- * Parameters for a menu tree.
- */
- /**
- * The maximum number of path elements for a menu callback
- */
- define('MENU_MAX_PARTS', 9);
- /**
- * The maximum depth of a menu links tree - matches the number of p columns.
- */
- define('MENU_MAX_DEPTH', 9);
- /**
- * @} End of "Menu tree parameters".
- */
- /**
- * Reserved key to identify the most specific menu link for a given path.
- *
- * The value of this constant is a hash of the constant name. We use the hash
- * so that the reserved key is over 32 characters in length and will not
- * collide with allowed menu names:
- * @code
- * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
- * @endcode
- *
- * @see menu_link_get_preferred()
- */
- define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91');
- /**
- * Returns the ancestors (and relevant placeholders) for any given path.
- *
- * For example, the ancestors of node/12345/edit are:
- * - node/12345/edit
- * - node/12345/%
- * - node/%/edit
- * - node/%/%
- * - node/12345
- * - node/%
- * - node
- *
- * To generate these, we will use binary numbers. Each bit represents a
- * part of the path. If the bit is 1, then it represents the original
- * value while 0 means wildcard. If the path is node/12/edit/foo
- * then the 1011 bitstring represents node/%/edit/foo where % means that
- * any argument matches that part. We limit ourselves to using binary
- * numbers that correspond the patterns of wildcards of router items that
- * actually exists. This list of 'masks' is built in menu_rebuild().
- *
- * @param $parts
- * An array of path parts; for the above example,
- * array('node', '12345', 'edit').
- *
- * @return
- * An array which contains the ancestors and placeholders. Placeholders
- * simply contain as many '%s' as the ancestors.
- */
- function menu_get_ancestors($parts) {
- $number_parts = count($parts);
- $ancestors = array();
- $length = $number_parts - 1;
- $end = (1 << $number_parts) - 1;
- $masks = variable_get('menu_masks');
- // If the optimized menu_masks array is not available use brute force to get
- // the correct $ancestors and $placeholders returned. Do not use this as the
- // default value of the menu_masks variable to avoid building such a big
- // array.
- if (!$masks) {
- $masks = range(511, 1);
- }
- // Only examine patterns that actually exist as router items (the masks).
- foreach ($masks as $i) {
- if ($i > $end) {
- // Only look at masks that are not longer than the path of interest.
- continue;
- }
- elseif ($i < (1 << $length)) {
- // We have exhausted the masks of a given length, so decrease the length.
- --$length;
- }
- $current = '';
- for ($j = $length; $j >= 0; $j--) {
- // Check the bit on the $j offset.
- if ($i & (1 << $j)) {
- // Bit one means the original value.
- $current .= $parts[$length - $j];
- }
- else {
- // Bit zero means means wildcard.
- $current .= '%';
- }
- // Unless we are at offset 0, add a slash.
- if ($j) {
- $current .= '/';
- }
- }
- $ancestors[] = $current;
- }
- return $ancestors;
- }
- /**
- * Unserializes menu data, using a map to replace path elements.
- *
- * The menu system stores various path-related information (such as the 'page
- * arguments' and 'access arguments' components of a menu item) in the database
- * using serialized arrays, where integer values in the arrays represent
- * arguments to be replaced by values from the path. This function first
- * unserializes such menu information arrays, and then does the path
- * replacement.
- *
- * The path replacement acts on each integer-valued element of the unserialized
- * menu data array ($data) using a map array ($map, which is typically an array
- * of path arguments) as a list of replacements. For instance, if there is an
- * element of $data whose value is the number 2, then it is replaced in $data
- * with $map[2]; non-integer values in $data are left alone.
- *
- * As an example, an unserialized $data array with elements ('node_load', 1)
- * represents instructions for calling the node_load() function. Specifically,
- * this instruction says to use the path component at index 1 as the input
- * parameter to node_load(). If the path is 'node/123', then $map will be the
- * array ('node', 123), and the returned array from this function will have
- * elements ('node_load', 123), since $map[1] is 123. This return value will
- * indicate specifically that node_load(123) is to be called to load the node
- * whose ID is 123 for this menu item.
- *
- * @param $data
- * A serialized array of menu data, as read from the database.
- * @param $map
- * A path argument array, used to replace integer values in $data; an integer
- * value N in $data will be replaced by value $map[N]. Typically, the $map
- * array is generated from a call to the arg() function.
- *
- * @return
- * The unserialized $data array, with path arguments replaced.
- */
- function menu_unserialize($data, $map) {
- if ($data = unserialize($data)) {
- foreach ($data as $k => $v) {
- if (is_int($v)) {
- $data[$k] = isset($map[$v]) ? $map[$v] : '';
- }
- }
- return $data;
- }
- else {
- return array();
- }
- }
- /**
- * Replaces the statically cached item for a given path.
- *
- * @param $path
- * The path.
- * @param $router_item
- * The router item. Usually a router entry from menu_get_item() is either
- * modified or set to a different path. This allows the navigation block,
- * the page title, the breadcrumb, and the page help to be modified in one
- * call.
- */
- function menu_set_item($path, $router_item) {
- menu_get_item($path, $router_item);
- }
- /**
- * Gets a router item.
- *
- * @param $path
- * The path; for example, 'node/5'. The function will find the corresponding
- * node/% item and return that.
- * @param $router_item
- * Internal use only.
- *
- * @return
- * The router item or, if an error occurs in _menu_translate(), FALSE. A
- * router item is an associative array corresponding to one row in the
- * menu_router table. The value corresponding to the key 'map' holds the
- * loaded objects. The value corresponding to the key 'access' is TRUE if the
- * current user can access this page. The values corresponding to the keys
- * 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will
- * be filled in based on the database values and the objects loaded.
- */
- function menu_get_item($path = NULL, $router_item = NULL) {
- $router_items = &drupal_static(__FUNCTION__);
- if (!isset($path)) {
- $path = $_GET['q'];
- }
- if (isset($router_item)) {
- $router_items[$path] = $router_item;
- }
- if (!isset($router_items[$path])) {
- // Rebuild if we know it's needed, or if the menu masks are missing which
- // occurs rarely, likely due to a race condition of multiple rebuilds.
- if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
- menu_rebuild();
- }
- $original_map = arg(NULL, $path);
- $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
- $ancestors = menu_get_ancestors($parts);
- $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
- if ($router_item) {
- // Allow modules to alter the router item before it is translated and
- // checked for access.
- drupal_alter('menu_get_item', $router_item, $path, $original_map);
- $map = _menu_translate($router_item, $original_map);
- $router_item['original_map'] = $original_map;
- if ($map === FALSE) {
- $router_items[$path] = FALSE;
- return FALSE;
- }
- if ($router_item['access']) {
- $router_item['map'] = $map;
- $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
- $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
- }
- }
- $router_items[$path] = $router_item;
- }
- return $router_items[$path];
- }
- /**
- * Execute the page callback associated with the current path.
- *
- * @param $path
- * The drupal path whose handler is to be be executed. If set to NULL, then
- * the current path is used.
- * @param $deliver
- * (optional) A boolean to indicate whether the content should be sent to the
- * browser using the appropriate delivery callback (TRUE) or whether to return
- * the result to the caller (FALSE).
- */
- function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
- // Check if site is offline.
- $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
- // Allow other modules to change the site status but not the path because that
- // would not change the global variable. hook_url_inbound_alter() can be used
- // to change the path. Code later will not use the $read_only_path variable.
- $read_only_path = !empty($path) ? $path : $_GET['q'];
- drupal_alter('menu_site_status', $page_callback_result, $read_only_path);
- // Only continue if the site status is not set.
- if ($page_callback_result == MENU_SITE_ONLINE) {
- if ($router_item = menu_get_item($path)) {
- if ($router_item['access']) {
- if ($router_item['include_file']) {
- require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
- }
- $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
- }
- else {
- $page_callback_result = MENU_ACCESS_DENIED;
- }
- }
- else {
- $page_callback_result = MENU_NOT_FOUND;
- }
- }
- // Deliver the result of the page callback to the browser, or if requested,
- // return it raw, so calling code can do more processing.
- if ($deliver) {
- $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;
- drupal_deliver_page($page_callback_result, $default_delivery_callback);
- }
- else {
- return $page_callback_result;
- }
- }
- /**
- * Loads objects into the map as defined in the $item['load_functions'].
- *
- * @param $item
- * A menu router or menu link item
- * @param $map
- * An array of path arguments; for example, array('node', '5').
- *
- * @return
- * Returns TRUE for success, FALSE if an object cannot be loaded.
- * Names of object loading functions are placed in $item['load_functions'].
- * Loaded objects are placed in $map[]; keys are the same as keys in the
- * $item['load_functions'] array.
- * $item['access'] is set to FALSE if an object cannot be loaded.
- */
- function _menu_load_objects(&$item, &$map) {
- if ($load_functions = $item['load_functions']) {
- // If someone calls this function twice, then unserialize will fail.
- if (!is_array($load_functions)) {
- $load_functions = unserialize($load_functions);
- }
- $path_map = $map;
- foreach ($load_functions as $index => $function) {
- if ($function) {
- $value = isset($path_map[$index]) ? $path_map[$index] : '';
- if (is_array($function)) {
- // Set up arguments for the load function. These were pulled from
- // 'load arguments' in the hook_menu() entry, but they need
- // some processing. In this case the $function is the key to the
- // load_function array, and the value is the list of arguments.
- list($function, $args) = each($function);
- $load_functions[$index] = $function;
- // Some arguments are placeholders for dynamic items to process.
- foreach ($args as $i => $arg) {
- if ($arg === '%index') {
- // Pass on argument index to the load function, so multiple
- // occurrences of the same placeholder can be identified.
- $args[$i] = $index;
- }
- if ($arg === '%map') {
- // Pass on menu map by reference. The accepting function must
- // also declare this as a reference if it wants to modify
- // the map.
- $args[$i] = &$map;
- }
- if (is_int($arg)) {
- $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : '';
- }
- }
- array_unshift($args, $value);
- $return = call_user_func_array($function, $args);
- }
- else {
- $return = $function($value);
- }
- // If callback returned an error or there is no callback, trigger 404.
- if ($return === FALSE) {
- $item['access'] = FALSE;
- $map = FALSE;
- return FALSE;
- }
- $map[$index] = $return;
- }
- }
- $item['load_functions'] = $load_functions;
- }
- return TRUE;
- }
- /**
- * Checks access to a menu item using the access callback.
- *
- * @param $item
- * A menu router or menu link item
- * @param $map
- * An array of path arguments; for example, array('node', '5').
- *
- * @return
- * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
- */
- function _menu_check_access(&$item, $map) {
- $item['access'] = FALSE;
- // Determine access callback, which will decide whether or not the current
- // user has access to this path.
- $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
- // Check for a TRUE or FALSE value.
- if (is_numeric($callback)) {
- $item['access'] = (bool) $callback;
- }
- else {
- $arguments = menu_unserialize($item['access_arguments'], $map);
- // As call_user_func_array is quite slow and user_access is a very common
- // callback, it is worth making a special case for it.
- if ($callback == 'user_access') {
- $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
- }
- elseif (function_exists($callback)) {
- $item['access'] = call_user_func_array($callback, $arguments);
- }
- }
- }
- /**
- * Localizes the router item title using t() or another callback.
- *
- * Translate the title and description to allow storage of English title
- * strings in the database, yet display of them in the language required
- * by the current user.
- *
- * @param $item
- * A menu router item or a menu link item.
- * @param $map
- * The path as an array with objects already replaced. E.g., for path
- * node/123 $map would be array('node', $node) where $node is the node
- * object for node 123.
- * @param $link_translate
- * TRUE if we are translating a menu link item; FALSE if we are
- * translating a menu router item.
- *
- * @return
- * No return value.
- * $item['title'] is localized according to $item['title_callback'].
- * If an item's callback is check_plain(), $item['options']['html'] becomes
- * TRUE.
- * $item['description'] is translated using t().
- * When doing link translation and the $item['options']['attributes']['title']
- * (link title attribute) matches the description, it is translated as well.
- */
- function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
- $callback = $item['title_callback'];
- $item['localized_options'] = $item['options'];
- // All 'class' attributes are assumed to be an array during rendering, but
- // links stored in the database may use an old string value.
- // @todo In order to remove this code we need to implement a database update
- // including unserializing all existing link options and running this code
- // on them, as well as adding validation to menu_link_save().
- if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
- $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
- }
- // If we are translating the title of a menu link, and its title is the same
- // as the corresponding router item, then we can use the title information
- // from the router. If it's customized, then we need to use the link title
- // itself; can't localize.
- // If we are translating a router item (tabs, page, breadcrumb), then we
- // can always use the information from the router item.
- if (!$link_translate || ($item['title'] == $item['link_title'])) {
- // t() is a special case. Since it is used very close to all the time,
- // we handle it directly instead of using indirect, slower methods.
- if ($callback == 't') {
- if (empty($item['title_arguments'])) {
- $item['title'] = t($item['title']);
- }
- else {
- $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
- }
- }
- elseif ($callback && function_exists($callback)) {
- if (empty($item['title_arguments'])) {
- $item['title'] = $callback($item['title']);
- }
- else {
- $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
- }
- // Avoid calling check_plain again on l() function.
- if ($callback == 'check_plain') {
- $item['localized_options']['html'] = TRUE;
- }
- }
- }
- elseif ($link_translate) {
- $item['title'] = $item['link_title'];
- }
- // Translate description, see the motivation above.
- if (!empty($item['description'])) {
- $original_description = $item['description'];
- $item['description'] = t($item['description']);
- if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
- $item['localized_options']['attributes']['title'] = $item['description'];
- }
- }
- }
- /**
- * Handles dynamic path translation and menu access control.
- *
- * When a user arrives on a page such as node/5, this function determines
- * what "5" corresponds to, by inspecting the page's menu path definition,
- * node/%node. This will call node_load(5) to load the corresponding node
- * object.
- *
- * It also works in reverse, to allow the display of tabs and menu items which
- * contain these dynamic arguments, translating node/%node to node/5.
- *
- * Translation of menu item titles and descriptions are done here to
- * allow for storage of English strings in the database, and translation
- * to the language required to generate the current page.
- *
- * @param $router_item
- * A menu router item
- * @param $map
- * An array of path arguments; for example, array('node', '5').
- * @param $to_arg
- * Execute $item['to_arg_functions'] or not. Use only if you want to render a
- * path from the menu table, for example tabs.
- *
- * @return
- * Returns the map with objects loaded as defined in the
- * $item['load_functions']. $item['access'] becomes TRUE if the item is
- * accessible, FALSE otherwise. $item['href'] is set according to the map.
- * If an error occurs during calling the load_functions (like trying to load
- * a non-existent node) then this function returns FALSE.
- */
- function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
- if ($to_arg && !empty($router_item['to_arg_functions'])) {
- // Fill in missing path elements, such as the current uid.
- _menu_link_map_translate($map, $router_item['to_arg_functions']);
- }
- // The $path_map saves the pieces of the path as strings, while elements in
- // $map may be replaced with loaded objects.
- $path_map = $map;
- if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) {
- // An error occurred loading an object.
- $router_item['access'] = FALSE;
- return FALSE;
- }
- // Generate the link path for the page request or local tasks.
- $link_map = explode('/', $router_item['path']);
- if (isset($router_item['tab_root'])) {
- $tab_root_map = explode('/', $router_item['tab_root']);
- }
- if (isset($router_item['tab_parent'])) {
- $tab_parent_map = explode('/', $router_item['tab_parent']);
- }
- for ($i = 0; $i < $router_item['number_parts']; $i++) {
- if ($link_map[$i] == '%') {
- $link_map[$i] = $path_map[$i];
- }
- if (isset($tab_root_map[$i]) && $tab_root_map[$i] == '%') {
- $tab_root_map[$i] = $path_map[$i];
- }
- if (isset($tab_parent_map[$i]) && $tab_parent_map[$i] == '%') {
- $tab_parent_map[$i] = $path_map[$i];
- }
- }
- $router_item['href'] = implode('/', $link_map);
- $router_item['tab_root_href'] = implode('/', $tab_root_map);
- $router_item['tab_parent_href'] = implode('/', $tab_parent_map);
- $router_item['options'] = array();
- _menu_check_access($router_item, $map);
- // For performance, don't localize an item the user can't access.
- if ($router_item['access']) {
- _menu_item_localize($router_item, $map);
- }
- return $map;
- }
- /**
- * Translates the path elements in the map using any to_arg helper function.
- *
- * @param $map
- * An array of path arguments; for example, array('node', '5').
- * @param $to_arg_functions
- * An array of helper functions; for example, array(2 => 'menu_tail_to_arg').
- *
- * @see hook_menu()
- */
- function _menu_link_map_translate(&$map, $to_arg_functions) {
- $to_arg_functions = unserialize($to_arg_functions);
- foreach ($to_arg_functions as $index => $function) {
- // Translate place-holders into real values.
- $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index);
- if (!empty($map[$index]) || isset($arg)) {
- $map[$index] = $arg;
- }
- else {
- unset($map[$index]);
- }
- }
- }
- /**
- * Returns a string containing the path relative to the current index.
- */
- function menu_tail_to_arg($arg, $map, $index) {
- return implode('/', array_slice($map, $index));
- }
- /**
- * Loads the path as one string relative to the current index.
- *
- * To use this load function, you must specify the load arguments
- * in the router item as:
- * @code
- * $item['load arguments'] = array('%map', '%index');
- * @endcode
- *
- * @see search_menu().
- */
- function menu_tail_load($arg, &$map, $index) {
- $arg = implode('/', array_slice($map, $index));
- $map = array_slice($map, 0, $index);
- return $arg;
- }
- /**
- * Provides menu link access control, translation, and argument handling.
- *
- * This function is similar to _menu_translate(), but it also does
- * link-specific preparation (such as always calling to_arg() functions).
- *
- * @param $item
- * A menu link.
- * @param $translate
- * (optional) Whether to try to translate a link containing dynamic path
- * argument placeholders (%) based on the menu router item of the current
- * path. Defaults to FALSE. Internally used for breadcrumbs.
- *
- * @return
- * Returns the map of path arguments with objects loaded as defined in the
- * $item['load_functions'].
- * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
- * $item['href'] is generated from link_path, possibly by to_arg functions.
- * $item['title'] is generated from link_title, and may be localized.
- * $item['options'] is unserialized; it is also changed within the call here
- * to $item['localized_options'] by _menu_item_localize().
- */
- function _menu_link_translate(&$item, $translate = FALSE) {
- if (!is_array($item['options'])) {
- $item['options'] = unserialize($item['options']);
- }
- if ($item['external']) {
- $item['access'] = 1;
- $map = array();
- $item['href'] = $item['link_path'];
- $item['title'] = $item['link_title'];
- $item['localized_options'] = $item['options'];
- }
- else {
- // Complete the path of the menu link with elements from the current path,
- // if it contains dynamic placeholders (%).
- $map = explode('/', $item['link_path']);
- if (strpos($item['link_path'], '%') !== FALSE) {
- // Invoke registered to_arg callbacks.
- if (!empty($item['to_arg_functions'])) {
- _menu_link_map_translate($map, $item['to_arg_functions']);
- }
- // Or try to derive the path argument map from the current router item,
- // if this $item's path is within the router item's path. This means
- // that if we are on the current path 'foo/%/bar/%/baz', then
- // menu_get_item() will have translated the menu router item for the
- // current path, and we can take over the argument map for a link like
- // 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
- // @see _menu_tree_check_access()
- // @see menu_get_active_breadcrumb()
- elseif ($translate && ($current_router_item = menu_get_item())) {
- // If $translate is TRUE, then this link is in the active trail.
- // Only translate paths within the current path.
- if (strpos($current_router_item['path'], $item['link_path']) === 0) {
- $count = count($map);
- $map = array_slice($current_router_item['original_map'], 0, $count);
- $item['original_map'] = $map;
- if (isset($current_router_item['map'])) {
- $item['map'] = array_slice($current_router_item['map'], 0, $count);
- }
- // Reset access to check it (for the first time).
- unset($item['access']);
- }
- }
- }
- $item['href'] = implode('/', $map);
- // Skip links containing untranslated arguments.
- if (strpos($item['href'], '%') !== FALSE) {
- $item['access'] = FALSE;
- return FALSE;
- }
- // menu_tree_check_access() may set this ahead of time for links to nodes.
- if (!isset($item['access'])) {
- if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
- // An error occurred loading an object.
- $item['access'] = FALSE;
- return FALSE;
- }
- _menu_check_access($item, $map);
- }
- // For performance, don't localize a link the user can't access.
- if ($item['access']) {
- _menu_item_localize($item, $map, TRUE);
- }
- }
- // Allow other customizations - e.g. adding a page-specific query string to the
- // options array. For performance reasons we only invoke this hook if the link
- // has the 'alter' flag set in the options array.
- if (!empty($item['options']['alter'])) {
- drupal_alter('translated_menu_link', $item, $map);
- }
- return $map;
- }
- /**
- * Gets a loaded object from a router item.
- *
- * menu_get_object() provides access to objects loaded by the current router
- * item. For example, on the page node/%node, the router loads the %node object,
- * and calling menu_get_object() will return that. Normally, it is necessary to
- * specify the type of object referenced, however node is the default.
- * The following example tests to see whether the node being displayed is of the
- * "story" content type:
- * @code
- * $node = menu_get_object();
- * $story = $node->type == 'story';
- * @endcode
- *
- * @param $type
- * Type of the object. These appear in hook_menu definitions as %type. Core
- * provides aggregator_feed, aggregator_category, contact, filter_format,
- * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
- * relevant {$type}_load function for more on each. Defaults to node.
- * @param $position
- * The position of the object in the path, where the first path segment is 0.
- * For node/%node, the position of %node is 1, but for comment/reply/%node,
- * it's 2. Defaults to 1.
- * @param $path
- * See menu_get_item() for more on this. Defaults to the current path.
- */
- function menu_get_object($type = 'node', $position = 1, $path = NULL) {
- $router_item = menu_get_item($path);
- if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') {
- return $router_item['map'][$position];
- }
- }
- /**
- * Renders a menu tree based on the current path.
- *
- * The tree is expanded based on the current path and dynamic paths are also
- * changed according to the defined to_arg functions (for example the 'My
- * account' link is changed from user/% to a link with the current user's uid).
- *
- * @param $menu_name
- * The name of the menu.
- *
- * @return
- * A structured array representing the specified menu on the current page, to
- * be rendered by drupal_render().
- */
- function menu_tree($menu_name) {
- $menu_output = &drupal_static(__FUNCTION__, array());
- if (!isset($menu_output[$menu_name])) {
- $tree = menu_tree_page_data($menu_name);
- $menu_output[$menu_name] = menu_tree_output($tree);
- }
- return $menu_output[$menu_name];
- }
- /**
- * Returns an output structure for rendering a menu tree.
- *
- * The menu item's LI element is given one of the following classes:
- * - expanded: The menu item is showing its submenu.
- * - collapsed: The menu item has a submenu which is not shown.
- * - leaf: The menu item has no submenu.
- *
- * @param $tree
- * A data structure representing the tree as returned from menu_tree_data.
- *
- * @return
- * A structured array to be rendered by drupal_render().
- */
- function menu_tree_output($tree) {
- $build = array();
- $items = array();
- // Pull out just the menu links we are going to render so that we
- // get an accurate count for the first/last classes.
- foreach ($tree as $data) {
- if ($data['link']['access'] && !$data['link']['hidden']) {
- $items[] = $data;
- }
- }
- $router_item = menu_get_item();
- $num_items = count($items);
- foreach ($items as $i => $data) {
- $class = array();
- if ($i == 0) {
- $class[] = 'first';
- }
- if ($i == $num_items - 1) {
- $class[] = 'last';
- }
- // Set a class for the <li>-tag. Since $data['below'] may contain local
- // tasks, only set 'expanded' class if the link also has children within
- // the current menu.
- if ($data['link']['has_children'] && $data['below']) {
- $class[] = 'expanded';
- }
- elseif ($data['link']['has_children']) {
- $class[] = 'collapsed';
- }
- else {
- $class[] = 'leaf';
- }
- // Set a class if the link is in the active trail.
- if ($data['link']['in_active_trail']) {
- $class[] = 'active-trail';
- $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
- }
- // Normally, l() compares the href of every link with $_GET['q'] and sets
- // the active class accordingly. But local tasks do not appear in menu
- // trees, so if the current path is a local task, and this link is its
- // tab root, then we have to set the class manually.
- if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) {
- $data['link']['localized_options']['attributes']['class'][] = 'active';
- }
- // Allow menu-specific theme overrides.
- $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
- $element['#attributes']['class'] = $class;
- $element['#title'] = $data['link']['title'];
- $element['#href'] = $data['link']['href'];
- $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
- $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
- $element['#original_link'] = $data['link'];
- // Index using the link's unique mlid.
- $build[$data['link']['mlid']] = $element;
- }
- if ($build) {
- // Make sure drupal_render() does not re-order the links.
- $build['#sorted'] = TRUE;
- // Add the theme wrapper for outer markup.
- // Allow menu-specific theme overrides.
- $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
- }
- return $build;
- }
- /**
- * Gets the data structure representing a named menu tree.
- *
- * Since this can be the full tree including hidden items, the data returned
- * may be used for generating an an admin interface or a select.
- *
- * @param $menu_name
- * The named menu links to return
- * @param $link
- * A fully loaded menu link, or NULL. If a link is supplied, only the
- * path to root will be included in the returned tree - as if this link
- * represented the current page in a visible menu.
- * @param $max_depth
- * Optional maximum depth of links to retrieve. Typically useful if only one
- * or two levels of a sub tree are needed in conjunction with a non-NULL
- * $link, in which case $max_depth should be greater than $link['depth'].
- *
- * @return
- * An tree of menu links in an array, in the order they should be rendered.
- */
- function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
- $tree = &drupal_static(__FUNCTION__, array());
- // Use $mlid as a flag for whether the data being loaded is for the whole tree.
- $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
- // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
- $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth;
- if (!isset($tree[$cid])) {
- // If the static variable doesn't have the data, check {cache_menu}.
- $cache = cache_get($cid, 'cache_menu');
- if ($cache && isset($cache->data)) {
- // If the cache entry exists, it contains the parameters for
- // menu_build_tree().
- $tree_parameters = $cache->data;
- }
- // If the tree data was not in the cache, build $tree_parameters.
- if (!isset($tree_parameters)) {
- $tree_parameters = array(
- 'min_depth' => 1,
- 'max_depth' => $max_depth,
- );
- if ($mlid) {
- // The tree is for a single item, so we need to match the values in its
- // p columns and 0 (the top level) with the plid values of other links.
- $parents = array(0);
- for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
- if (!empty($link["p$i"])) {
- $parents[] = $link["p$i"];
- }
- }
- $tree_parameters['expanded'] = $parents;
- $tree_parameters['active_trail'] = $parents;
- $tree_parameters['active_trail'][] = $mlid;
- }
- // Cache the tree building parameters using the page-specific cid.
- cache_set($cid, $tree_parameters, 'cache_menu');
- }
- // Build the tree using the parameters; the resulting tree will be cached
- // by _menu_build_tree()).
- $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
- }
- return $tree[$cid];
- }
- /**
- * Sets the path for determining the active trail of the specified menu tree.
- *
- * This path will also affect the breadcrumbs under some circumstances.
- * Breadcrumbs are built using the preferred link returned by
- * menu_link_get_preferred(). If the preferred link is inside one of the menus
- * specified in calls to menu_tree_set_path(), the preferred link will be
- * overridden by the corresponding path returned by menu_tree_get_path().
- *
- * Setting this path does not affect the main content; for that use
- * menu_set_active_item() instead.
- *
- * @param $menu_name
- * The name of the affected menu tree.
- * @param $path
- * The path to use when finding the active trail.
- */
- function menu_tree_set_path($menu_name, $path = NULL) {
- $paths = &drupal_static(__FUNCTION__);
- if (isset($path)) {
- $paths[$menu_name] = $path;
- }
- return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
- }
- /**
- * Gets the path for determining the active trail of the specified menu tree.
- *
- * @param $menu_name
- * The menu name of the requested tree.
- *
- * @return
- * A string containing the path. If no path has been specified with
- * menu_tree_set_path(), NULL is returned.
- */
- function menu_tree_get_path($menu_name) {
- return menu_tree_set_path($menu_name);
- }
- /**
- * Gets the data structure for a named menu tree, based on the current page.
- *
- * The tree order is maintained by storing each parent in an individual
- * field, see http://drupal.org/node/141866 for more.
- *
- * @param $menu_name
- * The named menu links to return.
- * @param $max_depth
- * (optional) The maximum depth of links to retrieve.
- * @param $only_active_trail
- * (optional) Whether to only return the links in the active trail (TRUE)
- * instead of all links on every level of the menu link tree (FALSE). Defaults
- * to FALSE. Internally used for breadcrumbs only.
- *
- * @return
- * An array of menu links, in the order they should be rendered. The array
- * is a list of associative arrays -- these have two keys, link and below.
- * link is a menu item, ready for theming as a link. Below represents the
- * submenu below the link if there is one, and it is a subtree that has the
- * same structure described for the top-level array.
- */
- function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
- $tree = &drupal_static(__FUNCTION__, array());
- // Check if the active trail has been overridden for this menu tree.
- $active_path = menu_tree_get_path($menu_name);
- // Load the menu item corresponding to the current page.
- if ($item = menu_get_item($active_path)) {
- if (isset($max_depth)) {
- $max_depth = min($max_depth, MENU_MAX_DEPTH);
- }
- // Generate a cache ID (cid) specific for this page.
- $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth;
- // If we are asked for the active trail only, and $menu_name has not been
- // built and cached for this page yet, then this likely means that it
- // won't be built anymore, as this function is invoked from
- // template_process_page(). So in order to not build a giant menu tree
- // that needs to be checked for access on all levels, we simply check
- // whether we have the menu already in cache, or otherwise, build a minimum
- // tree containing the breadcrumb/active trail only.
- // @see menu_set_active_trail()
- if (!isset($tree[$cid]) && $only_active_trail) {
- $cid .= ':trail';
- }
- if (!isset($tree[$cid])) {
- // If the static variable doesn't have the data, check {cache_menu}.
- $cache = cache_get($cid, 'cache_menu');
- if ($cache && isset($cache->data)) {
- // If the cache entry exists, it contains the parameters for
- // menu_build_tree().
- $tree_parameters = $cache->data;
- }
- // If the tree data was not in the cache, build $tree_parameters.
- if (!isset($tree_parameters)) {
- $tree_parameters = array(
- 'min_depth' => 1,
- 'max_depth' => $max_depth,
- );
- // Parent mlids; used both as key and value to ensure uniqueness.
- // We always want all the top-level links with plid == 0.
- $active_trail = array(0 => 0);
- // If the item for the current page is accessible, build the tree
- // parameters accordingly.
- if ($item['access']) {
- // Find a menu link corresponding to the current path. If $active_path
- // is NULL, let menu_link_get_preferred() determine the path.
- if ($active_link = menu_link_get_preferred($active_path, $menu_name)) {
- // The active link may only be taken into account to build the
- // active trail, if it resides in the requested menu. Otherwise,
- // we'd needlessly re-run _menu_build_tree() queries for every menu
- // on every page.
- if ($active_link['menu_name'] == $menu_name) {
- // Use all the coordinates, except the last one because there
- // can be no child beyond the last column.
- for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
- if ($active_link['p' . $i]) {
- $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
- }
- }
- // If we are asked to build links for the active trail only, skip
- // the entire 'expanded' handling.
- if ($only_active_trail) {
- $tree_parameters['only_active_trail'] = TRUE;
- }
- }
- }
- $parents = $active_trail;
- $expanded = variable_get('menu_expanded', array());
- // Check whether the current menu has any links set to be expanded.
- if (!$only_active_trail && in_array($menu_name, $expanded)) {
- // Collect all the links set to be expanded, and then add all of
- // their children to the list as well.
- do {
- $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
- ->fields('menu_links', array('mlid'))
- ->condition('menu_name', $menu_name)
- ->condition('expanded', 1)
- ->condition('has_children', 1)
- ->condition('plid', $parents, 'IN')
- ->condition('mlid', $parents, 'NOT IN')
- ->execute();
- $num_rows = FALSE;
- foreach ($result as $item) {
- $parents[$item['mlid']] = $item['mlid'];
- $num_rows = TRUE;
- }
- } while ($num_rows);
- }
- $tree_parameters['expanded'] = $parents;
- $tree_parameters['active_trail'] = $active_trail;
- }
- // If access is denied, we only show top-level links in menus.
- else {
- $tree_parameters['expanded'] = $active_trail;
- $tree_parameters['active_trail'] = $active_trail;
- }
- // Cache the tree building parameters using the page-specific cid.
- cache_set($cid, $tree_parameters, 'cache_menu');
- }
- // Build the tree using the parameters; the resulting tree will be cached
- // by _menu_build_tree().
- $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
- }
- return $tree[$cid];
- }
- return array();
- }
- /**
- * Builds a menu tree, translates links, and checks access.
- *
- * @param $menu_name
- * The name of the menu.
- * @param $parameters
- * (optional) An associative array of build parameters. Possible keys:
- * - expanded: An array of parent link ids to return only menu links that are
- * children of one of the plids in this list. If empty, the whole menu tree
- * is built, unless 'only_active_trail' is TRUE.
- * - active_trail: An array…
Large files files are truncated, but you can click here to view the full file