Newer
Older

Dries Buytaert
committed
// $Id$
/**
* @file
* API for the Drupal 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.
*
* 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. Menu items may
* omit this attribute to use the value provided by an ancestor 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().
/**
* Internal menu flag -- menu item is the root of the menu tree.
*/
/**
* Internal menu flag -- menu item is visible in the menu tree.
*/
/**
* Internal menu flag -- menu item is visible in the breadcrumb.
*/
* 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);

Angie Byron
committed
/**
* Internal menu flag -- menu item is a local action.
*/
define('MENU_IS_LOCAL_ACTION', 0x0100);
* @{
* Menu item definitions provide one of these constants, which are shortcuts for
* combinations of the above flags.
*/
* 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.

Gábor Hojtsy
committed
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

Dries Buytaert
committed
* when the URL is accessed. They do not appear in menus or breadcrumbs.

Dries Buytaert
committed
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.

Gábor Hojtsy
committed
* 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.

Gábor Hojtsy
committed
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);

Angie Byron
committed
/**
* 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);

Angie Byron
committed

Angie Byron
committed
/**
* @name Menu context types
* @{
* Flags for use in the "context" attribute of menu router items.
*/

Dries Buytaert
committed
/**
* 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);

Angie Byron
committed
/**
* 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".
*/
/**
* Internal menu status code -- Menu item was found.
*/
/**
* Internal menu status code -- Menu item was not found.
*/
/**
* Internal menu status code -- Menu item access is denied.
*/
/**
* Internal menu status code -- Menu item inaccessible because site is offline.
*/

Dries Buytaert
committed
define('MENU_SITE_OFFLINE', 4);

Angie Byron
committed
/**
* Internal menu status code -- Everything is working fine.
*/
define('MENU_SITE_ONLINE', 5);
* @Name Menu tree parameters
* 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".
/**

Dries Buytaert
committed
* 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

Dries Buytaert
committed
*
* 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

Dries Buytaert
committed
* any argument matches that part. We limit ourselves to using binary
* numbers that correspond the patterns of wildcards of router items that

Dries Buytaert
committed
* actually exists. This list of 'masks' is built in menu_rebuild().

Dries Buytaert
committed
*
* @param $parts
* An array of path parts, for the above example
* array('node', '12345', 'edit').

Dries Buytaert
committed
*

Dries Buytaert
committed
* @return
* An array which contains the ancestors and placeholders. Placeholders
* simply contain as many '%s' as the ancestors.

Dries Buytaert
committed
*/
function menu_get_ancestors($parts) {
$number_parts = count($parts);

Dries Buytaert
committed
$ancestors = array();
$length = $number_parts - 1;
$end = (1 << $number_parts) - 1;
$masks = variable_get('menu_masks', array());
// 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;
}

Dries Buytaert
committed
$current = '';
for ($j = $length; $j >= 0; $j--) {
// Check the bit on the $j offset.

Dries Buytaert
committed
if ($i & (1 << $j)) {
// Bit one means the original value.

Dries Buytaert
committed
$current .= $parts[$length - $j];
}
else {
// Bit zero means means wildcard.

Dries Buytaert
committed
$current .= '%';
}
// Unless we are at offset 0, add a slash.

Dries Buytaert
committed
if ($j) {
$current .= '/';
}

Dries Buytaert
committed
$ancestors[] = $current;
}

Dries Buytaert
committed
return $ancestors;

Dries Buytaert
committed
* The menu system uses serialized arrays stored in the database for
* arguments. However, often these need to change according to the
* current path. This function unserializes such an array and does the
* necessary change.

Dries Buytaert
committed
* Integer values are mapped according to the $map parameter. For
* example, if unserialize($data) is array('view', 1) and $map is
* array('node', '12345') then 'view' will not be changed because
* it is not an integer, but 1 will as it is an integer. As $map[1]
* is '12345', 1 will be replaced with '12345'. So the result will
* be array('node_load', '12345').

Dries Buytaert
committed
*

Dries Buytaert
committed
* @param @data
* A serialized array.
* @param @map
* An array of potential replacements.

Dries Buytaert
committed
*

Dries Buytaert
committed
* @return

Dries Buytaert
committed
* The $data array unserialized and mapped.

Dries Buytaert
committed
*/

Dries Buytaert
committed
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;

Dries Buytaert
committed
}

Dries Buytaert
committed
else {
return array();

Dries Buytaert
committed
}
}

Dries Buytaert
committed
/**
* Replaces the statically cached item for a given path.

Dries Buytaert
committed
*

Dries Buytaert
committed
* @param $path
* The path.
* @param $router_item
* The router item. Usually you take a router entry from menu_get_item and
* set it back either modified or to a different path. This lets you modify the
* navigation block, the page title, the breadcrumb and the page help in one
* call.

Dries Buytaert
committed
*/
function menu_set_item($path, $router_item) {
menu_get_item($path, $router_item);
}
/**
* Get 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.

Dries Buytaert
committed
*
* @return
* The router item, an associate array corresponding to one row in the
* menu_router table. The value of key map holds the loaded objects. The
* value of key access is TRUE if the current user can access this page.

Dries Buytaert
committed
* The values for key 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) {

Dries Buytaert
committed
$router_items = &drupal_static(__FUNCTION__);

Dries Buytaert
committed
if (!isset($path)) {
$path = $_GET['q'];
}
if (isset($router_item)) {
$router_items[$path] = $router_item;
}

Dries Buytaert
committed
if (!isset($router_items[$path])) {
$original_map = arg(NULL, $path);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);

Dries Buytaert
committed
$ancestors = menu_get_ancestors($parts);

Angie Byron
committed

Dries Buytaert
committed
// Since there is no limit to the length of $path, use a hash to keep it
// short yet unique.
$cid = 'menu_item:' . hash('sha256', $path);

Angie Byron
committed
if ($cached = cache_get($cid, 'cache_menu')) {
$router_item = $cached->data;
}
else {
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
cache_set($cid, $router_item, 'cache_menu');
}

Dries Buytaert
committed
if ($router_item) {

Dries Buytaert
committed
$map = _menu_translate($router_item, $original_map);

Angie Byron
committed
$router_item['original_map'] = $original_map;
if ($map === FALSE) {

Dries Buytaert
committed
$router_items[$path] = FALSE;
return FALSE;

Dries Buytaert
committed
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']));

Dries Buytaert
committed
$router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));

Dries Buytaert
committed
$router_items[$path] = $router_item;

Dries Buytaert
committed
return $router_items[$path];

Dries Buytaert
committed
* Execute the page callback associated with the current path.

Dries Buytaert
committed
* @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) {

Angie Byron
committed
// 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) {

Dries Buytaert
committed
// 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();
}
if ($router_item = menu_get_item($path)) {
if ($router_item['access']) {

Dries Buytaert
committed
if ($router_item['include_file']) {
require_once DRUPAL_ROOT . '/' . $router_item['include_file'];

Dries Buytaert
committed
}
$page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
}
else {
$page_callback_result = MENU_ACCESS_DENIED;

Dries Buytaert
committed
}
}
else {

Dries Buytaert
committed
$page_callback_result = MENU_NOT_FOUND;

Dries Buytaert
committed
}

Dries Buytaert
committed
// 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;
}

Dries Buytaert
committed
}

Dries Buytaert
committed
* Loads objects into the map as defined in the $item['load_functions'].
*
* @param $item

Dries Buytaert
committed
* A menu router or menu link item
* @param $map
* An array of path arguments (ex: array('node', '5'))

Dries Buytaert
committed
*

Gábor Hojtsy
committed
* 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

Gábor Hojtsy
committed
* $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 ($load_functions_unserialized = unserialize($load_functions)) {
$load_functions = $load_functions_unserialized;
}
$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) {

Gábor Hojtsy
committed
if ($arg === '%index') {
// Pass on argument index to the load function, so multiple
// occurrences of the same placeholder can be identified.
$args[$i] = $index;
}

Gábor Hojtsy
committed
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) {

Dries Buytaert
committed
$item['access'] = FALSE;
$map = FALSE;
}
$map[$index] = $return;
}
}
$item['load_functions'] = $load_functions;
return TRUE;
}
/**
* Check access to a menu item using the access callback
*
* @param $item

Dries Buytaert
committed
* A menu router or menu link item
* @param $map
* An array of path arguments (ex: array('node', '5'))

Dries Buytaert
committed
* $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
*/
function _menu_check_access(&$item, $map) {
// 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.

Dries Buytaert
committed
if (is_numeric($callback)) {
$item['access'] = (bool) $callback;

Dries Buytaert
committed
$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') {

Dries Buytaert
committed
$item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
elseif (function_exists($callback)) {

Dries Buytaert
committed
$item['access'] = call_user_func_array($callback, $arguments);

Dries Buytaert
committed
/**

Gábor Hojtsy
committed
* Localize the router item title using t() or another callback.

Gábor Hojtsy
committed
* 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.

Dries Buytaert
committed
*

Gábor Hojtsy
committed
* @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

Gábor Hojtsy
committed
* TRUE.
* $item['description'] is translated using t().
* When doing link translation and the $item['options']['attributes']['title']

Gábor Hojtsy
committed
* (link title attribute) matches the description, it is translated as well.

Gábor Hojtsy
committed
*/
function _menu_item_localize(&$item, $map, $link_translate = FALSE) {

Dries Buytaert
committed
$callback = $item['title_callback'];
$item['localized_options'] = $item['options'];

Dries Buytaert
committed
// 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'])) {

Gábor Hojtsy
committed
// 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)) {

Gábor Hojtsy
committed
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;
}
}

Gábor Hojtsy
committed
elseif ($link_translate) {
$item['title'] = $item['link_title'];
}
// Translate description, see the motivation above.

Dries Buytaert
committed
if (!empty($item['description'])) {

Gábor Hojtsy
committed
$original_description = $item['description'];

Dries Buytaert
committed
$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'];

Gábor Hojtsy
committed
}
}
}
/**
* 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

Dries Buytaert
committed
* to the language required to generate the current page.

Dries Buytaert
committed
* @param $router_item
* A menu router item
* @param $map
* An array of path arguments (ex: array('node', '5'))
* @param $to_arg

Dries Buytaert
committed
* Execute $item['to_arg_functions'] or not. Use only if you want to render a
* path from the menu table, for example tabs.

Dries Buytaert
committed
*
* @return
* Returns the map with objects loaded as defined in the

Dries Buytaert
committed
* $item['load_functions']. $item['access'] becomes TRUE if the item is

Dries Buytaert
committed
* 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 existing node) then this function return FALSE.
*/

Dries Buytaert
committed
function _menu_translate(&$router_item, $map, $to_arg = FALSE) {

Angie Byron
committed
if ($to_arg && !empty($router_item['to_arg_functions'])) {

Dries Buytaert
committed
// 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;

Angie Byron
committed
if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) {
// An error occurred loading an object.

Dries Buytaert
committed
$router_item['access'] = FALSE;
return FALSE;
}
// Generate the link path for the page request or local tasks.

Dries Buytaert
committed
$link_map = explode('/', $router_item['path']);

Dries Buytaert
committed
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']);
}

Dries Buytaert
committed
for ($i = 0; $i < $router_item['number_parts']; $i++) {
if ($link_map[$i] == '%') {
$link_map[$i] = $path_map[$i];
}

Dries Buytaert
committed
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];
}

Dries Buytaert
committed
$router_item['href'] = implode('/', $link_map);

Dries Buytaert
committed
$router_item['tab_root_href'] = implode('/', $tab_root_map);
$router_item['tab_parent_href'] = implode('/', $tab_parent_map);

Gábor Hojtsy
committed
$router_item['options'] = array();

Dries Buytaert
committed
_menu_check_access($router_item, $map);

Dries Buytaert
committed
// For performance, don't localize an item the user can't access.
if ($router_item['access']) {
_menu_item_localize($router_item, $map);
}
return $map;
}
/**
* This function translates the path elements in the map using any to_arg
* helper function. These functions take an argument and return an object.
* See http://drupal.org/node/109153 for more information.
*
* An array of path arguments (ex: array('node', '5'))
* @param $to_arg_functions
* An array of helper function (ex: array(2 => 'menu_tail_to_arg'))
*/
function _menu_link_map_translate(&$map, $to_arg_functions) {

Angie Byron
committed
$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]);
}
}
}
function menu_tail_to_arg($arg, $map, $index) {
return implode('/', array_slice($map, $index));
}
/**
* This function is similar to _menu_translate() but does link-specific
* preparation such as always calling to_arg functions
*
* @param $item

Dries Buytaert
committed
* 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.

Dries Buytaert
committed
*
* @return
* Returns the map of path arguments with objects loaded as defined in the

Dries Buytaert
committed
* $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().

Dries Buytaert
committed
function _menu_link_translate(&$item, $translate = FALSE) {
if (!is_array($item['options'])) {
$item['options'] = unserialize($item['options']);
}

Dries Buytaert
committed
if ($item['external']) {
$item['access'] = 1;
$map = array();

Dries Buytaert
committed
$item['href'] = $item['link_path'];
$item['title'] = $item['link_title'];

Dries Buytaert
committed
$item['localized_options'] = $item['options'];
}
else {

Dries Buytaert
committed
// Complete the path of the menu link with elements from the current path,
// if it contains dynamic placeholders (%).

Dries Buytaert
committed
$map = explode('/', $item['link_path']);

Dries Buytaert
committed
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
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']);
}
}

Angie Byron
committed
}

Dries Buytaert
committed
$item['href'] = implode('/', $map);

Dries Buytaert
committed
// Skip links containing untranslated arguments.

Dries Buytaert
committed
if (strpos($item['href'], '%') !== FALSE) {
$item['access'] = FALSE;
return FALSE;
}

Dries Buytaert
committed
// menu_tree_check_access() may set this ahead of time for links to nodes.

Dries Buytaert
committed
if (!isset($item['access'])) {

Angie Byron
committed
if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {

Gábor Hojtsy
committed
// An error occurred loading an object.

Dries Buytaert
committed
$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);
}

Dries Buytaert
committed

Dries Buytaert
committed
// 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);
}

Dries Buytaert
committed
return $map;
/**
* Get a loaded object from a router item.
*

Dries Buytaert
committed
* 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

Dries Buytaert
committed
* 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];
}
}

Angie Byron
committed
* Renders a menu tree based on the current path.

Dries Buytaert
committed
*
* The tree is expanded based on the current path and dynamic paths are also

Angie Byron
committed
* changed according to the defined to_arg functions (for example the 'My

Dries Buytaert
committed
* account' link is changed from user/% to a link with the current user's uid).
*
* @param $menu_name
* The name of the menu.

Dries Buytaert
committed
*

Angie Byron
committed
* A structured array representing the specified menu on the current page, to
* be rendered by drupal_render().

Dries Buytaert
committed
function menu_tree($menu_name) {

Dries Buytaert
committed
$menu_output = &drupal_static(__FUNCTION__, array());
if (!isset($menu_output[$menu_name])) {

Dries Buytaert
committed
$tree = menu_tree_page_data($menu_name);
$menu_output[$menu_name] = menu_tree_output($tree);
}
return $menu_output[$menu_name];
}

Dries Buytaert
committed
* Returns a rendered menu tree.

Dries Buytaert
committed
* 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.

Dries Buytaert
committed
*

Dries Buytaert
committed
* A structured array to be rendered by drupal_render().
function menu_tree_output($tree) {

Dries Buytaert
committed
$build = array();

Gábor Hojtsy
committed
$items = array();

Dries Buytaert
committed
// Pull out just the menu links we are going to render so that we

Gábor Hojtsy
committed
// get an accurate count for the first/last classes.
foreach ($tree as $data) {

Dries Buytaert
committed
if ($data['link']['access'] && !$data['link']['hidden']) {

Gábor Hojtsy
committed
$items[] = $data;
}
}

Dries Buytaert
committed
$router_item = menu_get_item();

Gábor Hojtsy
committed
$num_items = count($items);
foreach ($items as $i => $data) {

Dries Buytaert
committed
$class = array();

Gábor Hojtsy
committed
if ($i == 0) {

Dries Buytaert
committed
$class[] = 'first';

Gábor Hojtsy
committed
}
if ($i == $num_items - 1) {

Dries Buytaert
committed
$class[] = 'last';

Gábor Hojtsy
committed
}

Dries Buytaert
committed
// 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']) {

Dries Buytaert
committed
$class[] = 'expanded';
}
elseif ($data['link']['has_children']) {
$class[] = 'collapsed';

Gábor Hojtsy
committed
}
else {

Dries Buytaert
committed
$class[] = 'leaf';

Dries Buytaert
committed
// Set a class if the link is in the active trail.
if ($data['link']['in_active_trail']) {
$class[] = 'active-trail';

Dries Buytaert
committed
$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';