Newer
Older
/**
* @file
* API for the Drupal menu system.
*/

Angie Byron
committed
use Drupal\Component\Utility\NestedArray;

Angie Byron
committed
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\Language;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\Template\Attribute;
use Drupal\menu_link\Entity\MenuLink;

Angie Byron
committed
use Drupal\menu_link\MenuLinkStorageController;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;

Dries Buytaert
committed
use Symfony\Component\Routing\Route;

Angie Byron
committed
* @defgroup menu Menu and routing system
* Define the navigation menus, and route page requests to code based on URLs.

Angie Byron
committed
* The Drupal routing system defines how Drupal responds to URLs passed to the
* browser. The menu system, which depends on the routing system, is used for
* navigation. The Menu module allows menus to be created in the user interface
* as hierarchical lists of links.
*
* @section registering_paths Registering router paths
* To register a path, you need to add lines similar to this in a
* module.routing.yml file:
* @code
* block.admin_display:
* path: '/admin/structure/block'
* defaults:
* _content: '\Drupal\block\Controller\BlockListController::listing'
* requirements:
* _permission: 'administer blocks'
* @endcode
* @todo Add more information here, especially about controllers and what all
* the stuff in the routing.yml file means.
*
* @section Defining menu links
* Once you have a route defined, you can use hook_menu() to define links
* for your module's paths in the main Navigation menu or other menus. See
* the hook_menu() documentation for more details.
*
* @todo The rest of this topic has not been reviewed or updated for Drupal 8.x
* and is not correct!
* @todo It is quite likely that hook_menu() will be replaced with a different
* hook, configuration system, or plugin system before the 8.0 release.
*
* 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
/**
* Internal menu flag -- menu item is the root of the menu tree.
*/
const MENU_IS_ROOT = 0x0001;
/**
* Internal menu flag -- menu item is visible in the menu tree.
*/
const MENU_VISIBLE_IN_TREE = 0x0002;
/**
* Internal menu flag -- menu item is visible in the breadcrumb.
*/
const MENU_VISIBLE_IN_BREADCRUMB = 0x0004;
* Internal menu flag -- menu item links back to its parent.
const MENU_LINKS_TO_PARENT = 0x0008;
/**
* Internal menu flag -- menu item can be modified by administrator.
*/
const MENU_MODIFIED_BY_ADMIN = 0x0020;
/**
* Internal menu flag -- menu item was created by administrator.
*/
const MENU_CREATED_BY_ADMIN = 0x0040;
/**
* Internal menu flag -- menu item is a local task.
*/
const MENU_IS_LOCAL_TASK = 0x0080;
* @} End of "defgroup 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 menus.
* 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
* when the URL is accessed. They do not appear in menus.
const 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);

Alex Pott
committed
/**
* Menu type -- A task specific to the parent, which is never rendered.
*
* Sibling local tasks are not rendered themselves, but affect the active

Alex Pott
committed
* trail and need their sibling tasks rendered as tabs.
*/

catch
committed
define('MENU_SIBLING_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB);

Alex Pott
committed
* @} End of "defgroup menu_item_types".

Angie Byron
committed
/**
* @defgroup menu_context_types Menu context types

Angie Byron
committed
* @{
* 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.
*/
const MENU_CONTEXT_NONE = 0x0000;

Dries Buytaert
committed

Angie Byron
committed
/**
* Internal menu flag: Local task should be displayed in page context.
*/
const MENU_CONTEXT_PAGE = 0x0001;

Angie Byron
committed
/**
* @} End of "defgroup menu_context_types".

Angie Byron
committed
*/
* @defgroup menu_status_codes Menu status codes
/**
* Internal menu status code -- Menu item was not found.
*/
const MENU_NOT_FOUND = 404;
/**
* Internal menu status code -- Menu item access is denied.
*/
const MENU_ACCESS_DENIED = 403;
/**
* Internal menu status code -- Menu item inaccessible because site is offline.
*/
const MENU_SITE_OFFLINE = 4;

Angie Byron
committed
/**
* Internal menu status code -- Everything is working fine.
*/
const MENU_SITE_ONLINE = 5;

Angie Byron
committed
* @} End of "defgroup 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
const MENU_MAX_PARTS = 9;
* The maximum depth of a menu links tree - matches the number of p columns.

Angie Byron
committed
*
* @todo Move this constant to MenuLinkStorageController along with all the tree
* functionality.
const MENU_MAX_DEPTH = 9;
* @} End of "defgroup 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()
*/
const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91';
/**

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
* actually exists. This list of 'masks' is built in menu_router_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 = \Drupal::state()->get('menu.masks');
// If the optimized menu.masks array is not available use brute force to get

catch
committed
// 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

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

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
* Unserializes menu data, using a map to replace path elements.

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

Dries Buytaert
committed
*

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

Dries Buytaert
committed
*

Dries Buytaert
committed
* @return

Dries Buytaert
committed
* The unserialized $data array, with path arguments replaced.

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

Jennifer Hodgdon
committed
* 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 active trail, and the page help to be modified in one
* call.

Dries Buytaert
committed
*/
function menu_set_item($path, $router_item) {
menu_get_item($path, $router_item);
}
/**

Jennifer Hodgdon
committed
* Gets a router item.
*
* @param $path

Jennifer Hodgdon
committed
* 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

Jennifer Hodgdon
committed
* 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', and 'access_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)) {

Dries Buytaert
committed
$path = current_path();

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

Dries Buytaert
committed
if (!isset($router_items[$path])) {
\Drupal::service('router.builder')->rebuildIfNeeded();
$original_map = arg(NULL, $path);

Angie Byron
committed
$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();

Dries Buytaert
committed
if ($router_item) {

Angie Byron
committed
// Allow modules to alter the router item before it is translated and
// checked for access.
\Drupal::moduleHandler()->alter('menu_get_item', $router_item, $path, $original_map);

Angie Byron
committed

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_items[$path] = $router_item;

Dries Buytaert
committed
return $router_items[$path];

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

Jennifer Hodgdon
committed
* An array of path arguments; for example, 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.

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

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.

Dries Buytaert
committed
if (empty($return)) {

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

Dries Buytaert
committed
* A menu router or menu link item
* @param $map

Jennifer Hodgdon
committed
* An array of path arguments; for example, 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]);

Dries Buytaert
committed
else {

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

Dries Buytaert
committed
/**

Jennifer Hodgdon
committed
* Localizes 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.

catch
committed
* $item['description'] is computed using $item['description_callback'] if
* specified; otherwise it 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) {

catch
committed
$title_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 || !isset($item['link_title']) || ($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.

catch
committed
if ($title_callback == 't') {

Gábor Hojtsy
committed
if (empty($item['title_arguments'])) {
$item['title'] = t($item['title']);
}
else {
$item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
}
}

catch
committed
elseif ($title_callback) {

Gábor Hojtsy
committed
if (empty($item['title_arguments'])) {

catch
committed
$item['title'] = $title_callback($item['title']);

Gábor Hojtsy
committed
}
else {

catch
committed
$item['title'] = call_user_func_array($title_callback, menu_unserialize($item['title_arguments'], $map));

Gábor Hojtsy
committed
}
// Avoid calling check_plain again on l() function.

catch
committed
if ($title_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'];

catch
committed
}
if (!empty($item['description_arguments']) || !empty($item['description'])) {
$description_callback = $item['description_callback'];
// If the description callback is t(), call it directly.
if ($description_callback == 't') {
if (empty($item['description_arguments'])) {
$item['description'] = t($item['description']);
}
else {
$item['description'] = t($item['description'], menu_unserialize($item['description_arguments'], $map));
}

Gábor Hojtsy
committed
}

catch
committed
elseif ($description_callback) {
// If there are no arguments, call the description callback directly.
if (empty($item['description_arguments'])) {
$item['description'] = $description_callback($item['description']);
}
// Otherwise, use call_user_func_array() to pass the arguments.
else {
$item['description'] = call_user_func_array($description_callback, menu_unserialize($item['description_arguments'], $map));
}
}
}
// If the title and description are the same, use the translated description
// as a localized title.
if ($link_translate && isset($original_description) && 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

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

Dries Buytaert
committed
* @param $router_item
* A menu router item
* @param $map

Jennifer Hodgdon
committed
* An array of path arguments; for example, 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

Jennifer Hodgdon
committed
* a non-existent node) then this function returns 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;
}
// Avoid notices until we remove this function.
// @see https://drupal.org/node/2107533
$tab_root_map = array();
$tab_parent_map = array();
// 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();

Angie Byron
committed
if (!empty($router_item['route_name'])) {

Alex Pott
committed
// Route-provided menu items do not have menu loaders, so replace the map
// with the link map.
$map = $link_map;
$route_provider = \Drupal::getContainer()->get('router.route_provider');

Angie Byron
committed
$route = $route_provider->getRouteByName($router_item['route_name']);

Alex Pott
committed
$router_item['access'] = menu_item_route_access($route, $router_item['href'], $map);

Angie Byron
committed
}
else {
// @todo: Remove once all routes are converted.
_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;
}
/**

Jennifer Hodgdon
committed
* Translates the path elements in the map using any to_arg helper function.

Jennifer Hodgdon
committed
* An array of path arguments; for example, array('node', '5').
* @param $to_arg_functions

Jennifer Hodgdon
committed
* An array of helper functions; for example, array(2 => 'menu_tail_to_arg').

Jennifer Hodgdon
committed
*
* @see hook_menu()
*/
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]);
}
}
}

Angie Byron
committed
/**

Jennifer Hodgdon
committed
* Returns a string containing the path relative to the current index.

Angie Byron
committed
*/
function menu_tail_to_arg($arg, $map, $index) {
return implode('/', array_slice($map, $index));
}

Angie Byron
committed
/**

Jennifer Hodgdon
committed
* Loads the path as one string relative to the current index.

Angie Byron
committed
*
* 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 unserializing, access control, and argument handling.

Jennifer Hodgdon
committed
*
* This function is similar to _menu_translate(), but it also does
* link-specific preparation (such as always calling to_arg() functions).
* @param array $item
* The passed in item has the following keys:
* - access: (optional) Becomes TRUE if the item is accessible, FALSE
* otherwise. If the key is not set, the access manager is used to
* determine the access.
* - options: (required) Is unserialized and copied to $item['localized_options'].
* - link_title: (required) The title of the menu link.
* - route_name: (required) The route name of the menu link.
* - route_parameters: (required) The unserialized route parameters of the menu link.
* The passed in item is changed by the following keys:
* - href: The actual path to the link. This path is generated from the
* link_path of the menu link entity.
* - title: The title of the link. This title is generated from the
* link_title of the menu link entity.
*/
function _menu_link_translate(&$item) {

Dries Buytaert
committed
if (!is_array($item['options'])) {
$item['options'] = (array) unserialize($item['options']);

Dries Buytaert
committed
}
$item['localized_options'] = $item['options'];
$item['title'] = $item['link_title'];
if ($item['external'] || empty($item['route_name'])) {

Dries Buytaert
committed
$item['access'] = 1;
$item['href'] = $item['link_path'];
$item['route_parameters'] = array();
// Set to NULL so that drupal_pre_render_link() is certain to skip it.
$item['route_name'] = NULL;
}
else {
$item['href'] = NULL;
if (!is_array($item['route_parameters'])) {
$item['route_parameters'] = (array) unserialize($item['route_parameters']);

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

Dries Buytaert
committed
if (!isset($item['access'])) {
$item['access'] = \Drupal::getContainer()->get('access_manager')->checkNamedRoute($item['route_name'], $item['route_parameters'], \Drupal::currentUser());
// For performance, don't localize a link the user can't access.
if ($item['access']) {
_menu_item_localize($item, array(), 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::moduleHandler()->alter('translated_menu_link', $item, $map);

Dries Buytaert
committed
}

Dries Buytaert
committed
/**
* Checks access to a menu item by mocking a request for a path.
*
* @param \Symfony\Component\Routing\Route $route
* Router for the given menu item.
* @param string $href
* Menu path as returned by $item['href'] of menu_get_item().

Alex Pott
committed
* @param array $map

Jennifer Hodgdon
committed
* An array of path arguments; for example, array('node', '5').
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object, used to find the current route.

Dries Buytaert
committed
*
* @return bool
* TRUE if the user has access or FALSE if the user should be presented
* with access denied.

Dries Buytaert
committed
*/
function menu_item_route_access(Route $route, $href, &$map, Request $request = NULL) {
if (!isset($request)) {
$request = RequestHelper::duplicate(\Drupal::request(), '/' . $href);
$request->attributes->set('_system_path', $href);
}

Dries Buytaert
committed
// Attempt to match this path to provide a fully built request to the
// access checker.
try {
$request->attributes->add(\Drupal::service('router')->matchRequest($request));

Dries Buytaert
committed
}
catch (ParamNotConvertedException $e) {

Dries Buytaert
committed
return FALSE;
}

Alex Pott
committed
// Populate the map with any matching values from the request.
$path_bits = explode('/', trim($route->getPath(), '/'));
foreach ($map as $index => $map_item) {
$matches = array();
// Search for placeholders wrapped by curly braces. For example, a path
// 'foo/{bar}/baz' would return 'bar'.
if (isset($path_bits[$index]) && preg_match('/{(?<placeholder>.*)}/', $path_bits[$index], $matches)) {
// If that placeholder is present on the request attributes, replace the
// placeholder in the map with the value.
if ($request->attributes->has($matches['placeholder'])) {
$map[$index] = $request->attributes->get($matches['placeholder']);
}
}
}
return \Drupal::service('access_manager')->check($route, $request, \Drupal::currentUser());

Dries Buytaert
committed
}

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();