2002-12-24 15:40:32 +00:00
< ? php
2003-12-27 21:29:20 +00:00
2004-07-22 16:06:54 +00:00
/**
* @ file
* API for the Drupal menu system .
*/
2013-02-08 23:55:25 +00:00
use Drupal\Component\Utility\NestedArray ;
2012-10-03 21:06:11 +00:00
use Drupal\Core\Cache\CacheBackendInterface ;
2013-05-25 20:12:45 +00:00
use Drupal\Core\Language\Language ;
2012-09-04 13:32:47 +00:00
use Drupal\Core\Template\Attribute ;
2013-02-08 23:55:25 +00:00
use Drupal\menu_link\Plugin\Core\Entity\MenuLink ;
use Drupal\menu_link\MenuLinkStorageController ;
2013-05-07 16:16:03 +00:00
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException ;
2013-05-31 18:19:40 +00:00
use Symfony\Component\Routing\Route ;
2012-09-04 13:32:47 +00:00
2003-12-17 22:15:35 +00:00
/**
* @ defgroup menu Menu system
* @ {
2004-09-09 05:51:08 +00:00
* Define the navigation menus , and route page requests to code based on URLs .
2004-07-10 18:10:36 +00:00
*
* 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
2011-03-28 00:13:59 +00:00
* 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 .
2004-07-10 18:10:36 +00:00
*
* 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 .
*
2005-11-24 22:04:10 +00:00
* The found callback function is called with any arguments specified
2007-05-16 13:45:17 +00:00
* in the " page arguments " attribute of its menu item . The
2005-11-24 22:04:10 +00:00
* 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 .
2004-07-10 18:10:36 +00:00
*
* For an illustration of this process , see page_example . module .
*
* Access to the callback functions is also protected by the menu system .
2007-05-16 13:45:17 +00:00
* The " access callback " with an optional " access arguments " of each menu
* item is called before the page callback proceeds . If this returns TRUE ,
2010-12-08 06:48:39 +00:00
* 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 .
2004-07-10 18:10:36 +00:00
*
* 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 .
2007-05-16 13:45:17 +00:00
*
* Everything described so far is stored in the menu_router table . The
* menu_links table holds the visible menu links . By default these are
2007-07-04 21:33:55 +00:00
* derived from the same hook_menu definitions , however you are free to
2007-05-16 13:45:17 +00:00
* add more with menu_link_save () .
2003-12-17 22:15:35 +00:00
*/
2004-06-18 15:04:37 +00:00
/**
2010-10-08 05:07:53 +00:00
* @ defgroup menu_flags Menu flags
2004-07-10 15:51:48 +00:00
* @ {
2004-06-18 15:04:37 +00:00
* Flags for use in the " type " attribute of menu items .
*/
2004-09-09 05:51:08 +00:00
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is the root of the menu tree .
*/
2011-11-29 09:56:53 +00:00
const MENU_IS_ROOT = 0x0001 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is visible in the menu tree .
*/
2011-11-29 09:56:53 +00:00
const MENU_VISIBLE_IN_TREE = 0x0002 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is visible in the breadcrumb .
*/
2011-11-29 09:56:53 +00:00
const MENU_VISIBLE_IN_BREADCRUMB = 0x0004 ;
2008-05-26 17:12:55 +00:00
/**
2010-01-25 10:38:35 +00:00
* Internal menu flag -- menu item links back to its parent .
2008-05-26 17:12:55 +00:00
*/
2011-11-29 09:56:53 +00:00
const MENU_LINKS_TO_PARENT = 0x0008 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item can be modified by administrator .
*/
2011-11-29 09:56:53 +00:00
const MENU_MODIFIED_BY_ADMIN = 0x0020 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item was created by administrator .
*/
2011-11-29 09:56:53 +00:00
const MENU_CREATED_BY_ADMIN = 0x0040 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is a local task .
*/
2011-11-29 09:56:53 +00:00
const MENU_IS_LOCAL_TASK = 0x0080 ;
2004-09-09 05:51:08 +00:00
2009-08-22 19:58:28 +00:00
/**
* Internal menu flag -- menu item is a local action .
*/
2011-11-29 09:56:53 +00:00
const MENU_IS_LOCAL_ACTION = 0x0100 ;
2009-08-22 19:58:28 +00:00
2004-07-10 15:51:48 +00:00
/**
2012-05-17 12:58:49 +00:00
* @ } End of " defgroup menu_flags " .
2004-07-10 15:51:48 +00:00
*/
/**
2010-10-08 05:07:53 +00:00
* @ defgroup menu_item_types Menu item types
2004-07-10 15:51:48 +00:00
* @ {
2011-04-12 20:47:04 +00:00
* Definitions for various menu item types .
2011-01-03 18:03:54 +00:00
*
2004-07-10 15:51:48 +00:00
* Menu item definitions provide one of these constants , which are shortcuts for
2011-01-03 18:03:54 +00:00
* combinations of @ link menu_flags Menu flags @ endlink .
2004-07-10 15:51:48 +00:00
*/
2003-12-17 22:15:35 +00:00
2004-06-18 15:04:37 +00:00
/**
2008-05-26 17:12:55 +00:00
* Menu type -- A " normal " menu item that ' s shown in menu and breadcrumbs .
*
2004-06-18 15:04:37 +00:00
* Normal menu items show up in the menu tree and can be moved / hidden by
2004-07-10 18:10:36 +00:00
* the administrator . Use this for most menu items . It is the default value if
* no menu item type is specified .
2004-06-18 15:04:37 +00:00
*/
2007-11-24 20:59:32 +00:00
define ( 'MENU_NORMAL_ITEM' , MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB );
2004-04-15 20:49:42 +00:00
2004-06-18 15:04:37 +00:00
/**
2008-05-26 17:12:55 +00:00
* Menu type -- A hidden , internal callback , typically used for API calls .
*
2004-06-18 15:04:37 +00:00
* Callbacks simply register a path so that the correct function is fired
2010-09-24 00:37:45 +00:00
* when the URL is accessed . They do not appear in menus or breadcrumbs .
2004-06-18 15:04:37 +00:00
*/
2011-11-29 09:56:53 +00:00
const MENU_CALLBACK = 0x0000 ;
2002-12-24 15:40:32 +00:00
2004-06-18 15:04:37 +00:00
/**
2008-05-26 17:12:55 +00:00
* Menu type -- A normal menu item , hidden until enabled by an administrator .
*
2004-07-10 18:10:36 +00:00
* 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 .
2007-11-24 20:59:32 +00:00
* 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 .
2004-06-18 15:04:37 +00:00
*/
2007-11-24 20:59:32 +00:00
define ( 'MENU_SUGGESTED_ITEM' , MENU_VISIBLE_IN_BREADCRUMB | 0x0010 );
2004-06-18 15:04:37 +00:00
/**
2008-05-26 17:12:55 +00:00
* 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 " .
2004-06-18 15:04:37 +00:00
*/
2009-12-14 20:23:01 +00:00
define ( 'MENU_LOCAL_TASK' , MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB );
2004-06-18 15:04:37 +00:00
2004-07-10 15:51:48 +00:00
/**
2008-05-26 17:12:55 +00:00
* Menu type -- The " default " local task , which is initially active .
*
2004-07-10 15:51:48 +00:00
* Every set of local tasks should provide one " default " task , that links to the
* same path as its parent when clicked .
*/
2009-12-14 20:23:01 +00:00
define ( 'MENU_DEFAULT_LOCAL_TASK' , MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB );
2004-07-10 15:51:48 +00:00
2009-08-22 19:58:28 +00:00
/**
* 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 .
*/
2009-12-14 20:23:01 +00:00
define ( 'MENU_LOCAL_ACTION' , MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB );
2009-08-22 19:58:28 +00:00
2013-05-28 01:20:55 +00:00
/**
* Menu type -- A task specific to the parent , which is never rendered .
*
* Sibling local tasks are not rendered themselves , but affect the breadcrumb
* trail and need their sibling tasks rendered as tabs .
*/
define ( 'MENU_SIBLING_LOCAL_TASK' , MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB );
2004-06-18 15:04:37 +00:00
/**
2012-05-17 12:58:49 +00:00
* @ } End of " defgroup menu_item_types " .
2004-07-10 15:51:48 +00:00
*/
2009-10-17 05:50:29 +00:00
/**
2010-10-08 05:07:53 +00:00
* @ defgroup menu_context_types Menu context types
2009-10-17 05:50:29 +00:00
* @ {
* Flags for use in the " context " attribute of menu router items .
*/
2009-11-11 08:28:50 +00:00
/**
* 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 .
*/
2011-11-29 09:56:53 +00:00
const MENU_CONTEXT_NONE = 0x0000 ;
2009-11-11 08:28:50 +00:00
2009-10-17 05:50:29 +00:00
/**
* Internal menu flag : Local task should be displayed in page context .
*/
2011-11-29 09:56:53 +00:00
const MENU_CONTEXT_PAGE = 0x0001 ;
2009-10-17 05:50:29 +00:00
/**
* Internal menu flag : Local task should be displayed inline .
*/
2011-11-29 09:56:53 +00:00
const MENU_CONTEXT_INLINE = 0x0002 ;
2009-10-17 05:50:29 +00:00
/**
2012-05-17 12:58:49 +00:00
* @ } End of " defgroup menu_context_types " .
2009-10-17 05:50:29 +00:00
*/
2004-07-10 15:51:48 +00:00
/**
2010-10-08 05:07:53 +00:00
* @ defgroup menu_status_codes Menu status codes
2004-07-10 15:51:48 +00:00
* @ {
2004-06-18 15:04:37 +00:00
* Status codes for menu callbacks .
*/
2004-09-09 05:51:08 +00:00
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item was not found .
*/
2011-12-23 03:13:43 +00:00
const MENU_NOT_FOUND = 404 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item access is denied .
*/
2011-12-23 03:13:43 +00:00
const MENU_ACCESS_DENIED = 403 ;
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item inaccessible because site is offline .
*/
2011-11-29 09:56:53 +00:00
const MENU_SITE_OFFLINE = 4 ;
2004-09-09 05:51:08 +00:00
2010-07-07 08:05:01 +00:00
/**
* Internal menu status code -- Everything is working fine .
*/
2011-11-29 09:56:53 +00:00
const MENU_SITE_ONLINE = 5 ;
2010-07-07 08:05:01 +00:00
2004-07-10 15:51:48 +00:00
/**
2012-05-17 12:58:49 +00:00
* @ } End of " defgroup menu_status_codes " .
2004-07-10 15:51:48 +00:00
*/
2004-04-15 20:49:42 +00:00
2007-04-06 04:39:51 +00:00
/**
2010-10-08 05:07:53 +00:00
* @ defgroup menu_tree_parameters Menu tree parameters
2007-04-06 04:39:51 +00:00
* @ {
2011-01-03 18:03:54 +00:00
* Parameters for a menu tree .
2007-04-06 04:39:51 +00:00
*/
2007-05-16 13:45:17 +00:00
/**
* The maximum number of path elements for a menu callback
2007-04-06 04:39:51 +00:00
*/
2011-11-29 09:56:53 +00:00
const MENU_MAX_PARTS = 9 ;
2007-05-16 13:45:17 +00:00
2007-04-06 04:39:51 +00:00
/**
2007-05-16 13:45:17 +00:00
* The maximum depth of a menu links tree - matches the number of p columns .
2013-02-08 23:55:25 +00:00
*
* @ todo Move this constant to MenuLinkStorageController along with all the tree
* functionality .
2007-04-06 04:39:51 +00:00
*/
2011-11-29 09:56:53 +00:00
const MENU_MAX_DEPTH = 9 ;
2007-05-16 13:45:17 +00:00
2007-04-06 04:39:51 +00:00
/**
2012-05-17 12:58:49 +00:00
* @ } End of " defgroup menu_tree_parameters " .
2007-02-11 09:30:51 +00:00
*/
2011-12-06 12:18:12 +00:00
/**
* 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' ) = 1 cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
* @ endcode
*
* @ see menu_link_get_preferred ()
*/
const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91' ;
2004-04-15 20:49:42 +00:00
/**
2007-01-24 14:48:36 +00:00
* Returns the ancestors ( and relevant placeholders ) for any given path .
*
* For example , the ancestors of node / 12345 / edit are :
2008-03-19 07:38:29 +00:00
* - node / 12345 / edit
* - node / 12345 /%
* - node /%/ edit
* - node /%/%
* - node / 12345
* - node /%
* - node
2007-01-24 14:48:36 +00:00
*
* 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
2008-12-20 18:24:41 +00:00
* any argument matches that part . We limit ourselves to using binary
2007-08-11 14:06:15 +00:00
* numbers that correspond the patterns of wildcards of router items that
2012-05-03 15:09:39 +00:00
* actually exists . This list of 'masks' is built in menu_router_rebuild () .
2007-01-24 14:48:36 +00:00
*
* @ param $parts
* An array of path parts , for the above example
* array ( 'node' , '12345' , 'edit' ) .
2010-03-26 17:14:46 +00:00
*
2007-01-24 14:48:36 +00:00
* @ return
* An array which contains the ancestors and placeholders . Placeholders
2007-02-11 09:30:51 +00:00
* simply contain as many '%s' as the ancestors .
2007-01-24 14:48:36 +00:00
*/
function menu_get_ancestors ( $parts ) {
2007-08-11 14:06:15 +00:00
$number_parts = count ( $parts );
2007-01-24 14:48:36 +00:00
$ancestors = array ();
2007-08-11 14:06:15 +00:00
$length = $number_parts - 1 ;
$end = ( 1 << $number_parts ) - 1 ;
2013-06-05 14:24:40 +00:00
$masks = Drupal :: state () -> get ( 'menu.masks' );
2012-10-14 04:25:56 +00:00
// If the optimized menu.masks array is not available use brute force to get
2012-04-13 02:51:07 +00:00
// the correct $ancestors and $placeholders returned. Do not use this as the
2012-10-14 04:25:56 +00:00
// default value of the menu.masks variable to avoid building such a big
2012-04-13 02:51:07 +00:00
// array.
if ( ! $masks ) {
$masks = range ( 511 , 1 );
}
2007-08-11 14:06:15 +00:00
// 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 ;
}
2007-01-24 14:48:36 +00:00
$current = '' ;
for ( $j = $length ; $j >= 0 ; $j -- ) {
2010-08-20 01:42:52 +00:00
// Check the bit on the $j offset.
2007-01-24 14:48:36 +00:00
if ( $i & ( 1 << $j )) {
2010-08-20 01:42:52 +00:00
// Bit one means the original value.
2007-01-24 14:48:36 +00:00
$current .= $parts [ $length - $j ];
}
else {
2010-08-20 01:42:52 +00:00
// Bit zero means means wildcard.
2007-01-24 14:48:36 +00:00
$current .= '%' ;
}
2010-08-20 01:42:52 +00:00
// Unless we are at offset 0, add a slash.
2007-01-24 14:48:36 +00:00
if ( $j ) {
$current .= '/' ;
}
2004-09-16 07:17:56 +00:00
}
2007-01-24 14:48:36 +00:00
$ancestors [] = $current ;
2004-04-15 20:49:42 +00:00
}
2008-12-03 14:38:59 +00:00
return $ancestors ;
2004-07-06 07:33:59 +00:00
}
/**
2010-12-17 01:08:15 +00:00
* Unserializes menu data , using a map to replace path elements .
2004-07-06 07:33:59 +00:00
*
2010-12-17 01:08:15 +00:00
* 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 .
2006-01-19 08:58:00 +00:00
*
2010-12-17 01:08:15 +00:00
* 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 .
2010-03-26 17:14:46 +00:00
*
2006-01-19 08:58:00 +00:00
* @ return
2010-12-17 01:08:15 +00:00
* The unserialized $data array , with path arguments replaced .
2006-01-19 08:58:00 +00:00
*/
2007-01-24 14:48:36 +00:00
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 ;
2006-01-19 08:58:00 +00:00
}
2007-01-24 14:48:36 +00:00
else {
return array ();
2006-01-19 08:58:00 +00:00
}
}
2007-10-25 08:24:43 +00:00
2006-01-19 08:58:00 +00:00
/**
2007-10-25 08:24:43 +00:00
* Replaces the statically cached item for a given path .
2006-01-19 08:58:00 +00:00
*
2007-01-24 14:48:36 +00:00
* @ param $path
2007-10-25 08:24:43 +00:00
* The path .
* @ param $router_item
2012-08-31 15:56:36 +00:00
* 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
2007-10-25 08:24:43 +00:00
* call .
2006-01-19 08:58:00 +00:00
*/
2007-10-25 08:24:43 +00:00
function menu_set_item ( $path , $router_item ) {
menu_get_item ( $path , $router_item );
}
/**
2012-08-31 15:56:36 +00:00
* Gets a router item .
2007-10-25 08:24:43 +00:00
*
* @ param $path
2013-06-28 18:39:33 +00:00
* The path ; for example , 'node/5' . The function will find the corresponding
2007-10-25 08:24:43 +00:00
* node /% item and return that .
* @ param $router_item
* Internal use only .
2010-03-26 17:14:46 +00:00
*
2007-10-25 08:24:43 +00:00
* @ return
2012-10-10 17:05:45 +00:00
* 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 .
2007-10-25 08:24:43 +00:00
*/
function menu_get_item ( $path = NULL , $router_item = NULL ) {
2009-04-25 15:19:12 +00:00
$router_items = & drupal_static ( __FUNCTION__ );
2007-01-24 14:48:36 +00:00
if ( ! isset ( $path )) {
2012-04-29 15:16:27 +00:00
$path = current_path ();
2007-01-24 14:48:36 +00:00
}
2007-10-25 08:24:43 +00:00
if ( isset ( $router_item )) {
$router_items [ $path ] = $router_item ;
}
2007-05-27 20:31:13 +00:00
if ( ! isset ( $router_items [ $path ])) {
2011-07-18 08:04:37 +00:00
// 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.
2013-06-05 14:24:40 +00:00
if ( Drupal :: state () -> get ( 'menu_rebuild_needed' ) || ! Drupal :: state () -> get ( 'menu.masks' )) {
2012-05-03 15:09:39 +00:00
menu_router_rebuild ();
2011-07-18 08:04:37 +00:00
}
2007-03-12 13:01:10 +00:00
$original_map = arg ( NULL , $path );
2010-01-04 04:47:24 +00:00
2012-01-31 07:12:52 +00:00
$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 ();
2008-12-03 14:38:59 +00:00
if ( $router_item ) {
2010-11-15 08:29:03 +00:00
// 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 );
2007-05-27 20:31:13 +00:00
$map = _menu_translate ( $router_item , $original_map );
2009-08-24 01:49:41 +00:00
$router_item [ 'original_map' ] = $original_map ;
2007-05-16 13:45:17 +00:00
if ( $map === FALSE ) {
2007-05-27 20:31:13 +00:00
$router_items [ $path ] = FALSE ;
2007-05-16 13:45:17 +00:00
return FALSE ;
2007-03-12 13:01:10 +00:00
}
2007-05-27 20:31:13 +00:00
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' ]));
2009-09-30 13:09:30 +00:00
$router_item [ 'theme_arguments' ] = array_merge ( menu_unserialize ( $router_item [ 'theme_arguments' ], $map ), array_slice ( $map , $router_item [ 'number_parts' ]));
2004-06-18 15:04:37 +00:00
}
}
2007-05-27 20:31:13 +00:00
$router_items [ $path ] = $router_item ;
2004-06-18 15:04:37 +00:00
}
2007-05-27 20:31:13 +00:00
return $router_items [ $path ];
2004-06-18 15:04:37 +00:00
}
2007-02-11 09:30:51 +00:00
/**
2007-05-27 20:31:13 +00:00
* Loads objects into the map as defined in the $item [ 'load_functions' ] .
2007-04-30 17:03:29 +00:00
*
2007-02-11 09:30:51 +00:00
* @ param $item
2007-05-27 20:31:13 +00:00
* A menu router or menu link item
2007-02-11 09:30:51 +00:00
* @ param $map
2013-06-28 18:39:33 +00:00
* An array of path arguments ; for example , array ( 'node' , '5' ) .
2010-03-26 17:14:46 +00:00
*
2007-02-11 09:30:51 +00:00
* @ return
2008-01-29 11:01:32 +00:00
* Returns TRUE for success , FALSE if an object cannot be loaded .
* Names of object loading functions are placed in $item [ 'load_functions' ] .
2008-02-06 19:38:28 +00:00
* Loaded objects are placed in $map []; keys are the same as keys in the
2008-01-29 11:01:32 +00:00
* $item [ 'load_functions' ] array .
* $item [ 'access' ] is set to FALSE if an object cannot be loaded .
2007-05-16 13:45:17 +00:00
*/
2007-12-20 09:20:41 +00:00
function _menu_load_objects ( & $item , & $map ) {
if ( $load_functions = $item [ 'load_functions' ]) {
// If someone calls this function twice, then unserialize will fail.
2010-11-21 08:53:18 +00:00
if ( ! is_array ( $load_functions )) {
$load_functions = unserialize ( $load_functions );
2007-12-20 09:20:41 +00:00
}
2007-05-16 13:45:17 +00:00
$path_map = $map ;
foreach ( $load_functions as $index => $function ) {
if ( $function ) {
2007-10-17 14:46:34 +00:00
$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 );
2007-12-20 09:20:41 +00:00
$load_functions [ $index ] = $function ;
2007-10-17 14:46:34 +00:00
// Some arguments are placeholders for dynamic items to process.
foreach ( $args as $i => $arg ) {
2007-12-27 12:23:59 +00:00
if ( $arg === '%index' ) {
2007-10-17 14:46:34 +00:00
// Pass on argument index to the load function, so multiple
2008-12-30 16:43:20 +00:00
// occurrences of the same placeholder can be identified.
2007-10-17 14:46:34 +00:00
$args [ $i ] = $index ;
}
2007-12-27 12:23:59 +00:00
if ( $arg === '%map' ) {
2007-10-17 14:46:34 +00:00
// 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 ;
}
2007-12-20 09:20:41 +00:00
if ( is_int ( $arg )) {
$args [ $i ] = isset ( $path_map [ $arg ]) ? $path_map [ $arg ] : '' ;
}
2007-10-17 14:46:34 +00:00
}
array_unshift ( $args , $value );
$return = call_user_func_array ( $function , $args );
}
else {
$return = $function ( $value );
}
2007-02-11 09:30:51 +00:00
// If callback returned an error or there is no callback, trigger 404.
2013-07-01 00:09:20 +00:00
if ( empty ( $return )) {
2007-05-27 20:31:13 +00:00
$item [ 'access' ] = FALSE ;
2007-05-16 13:45:17 +00:00
$map = FALSE ;
2007-04-06 04:39:51 +00:00
return FALSE ;
2007-02-11 09:30:51 +00:00
}
$map [ $index ] = $return ;
}
}
2007-12-20 09:20:41 +00:00
$item [ 'load_functions' ] = $load_functions ;
2007-02-11 09:30:51 +00:00
}
2007-05-16 13:45:17 +00:00
return TRUE ;
}
/**
2012-10-05 18:12:56 +00:00
* Checks access to a menu item using the access callback .
2007-05-16 13:45:17 +00:00
*
* @ param $item
2007-05-27 20:31:13 +00:00
* A menu router or menu link item
2007-05-16 13:45:17 +00:00
* @ param $map
2013-06-28 18:39:33 +00:00
* An array of path arguments ; for example , array ( 'node' , '5' ) .
2010-07-16 02:54:09 +00:00
*
2007-05-16 13:45:17 +00:00
* @ return
2007-05-27 20:31:13 +00:00
* $item [ 'access' ] becomes TRUE if the item is accessible , FALSE otherwise .
2007-05-16 13:45:17 +00:00
*/
function _menu_check_access ( & $item , $map ) {
2007-07-04 21:33:55 +00:00
// Determine access callback, which will decide whether or not the current
// user has access to this path.
2007-08-29 20:46:18 +00:00
$callback = empty ( $item [ 'access_callback' ]) ? 0 : trim ( $item [ 'access_callback' ]);
2007-02-11 09:30:51 +00:00
// Check for a TRUE or FALSE value.
2007-01-24 14:48:36 +00:00
if ( is_numeric ( $callback )) {
2010-05-06 05:59:31 +00:00
$item [ 'access' ] = ( bool ) $callback ;
2004-06-18 15:04:37 +00:00
}
2007-03-08 19:03:48 +00:00
else {
2007-05-27 20:31:13 +00:00
$arguments = menu_unserialize ( $item [ 'access_arguments' ], $map );
2007-03-08 19:03:48 +00:00
// 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' ) {
2007-05-27 20:31:13 +00:00
$item [ 'access' ] = ( count ( $arguments ) == 1 ) ? user_access ( $arguments [ 0 ]) : user_access ( $arguments [ 0 ], $arguments [ 1 ]);
2007-03-08 19:03:48 +00:00
}
2011-12-15 17:33:38 +00:00
else {
2007-05-27 20:31:13 +00:00
$item [ 'access' ] = call_user_func_array ( $callback , $arguments );
2007-03-08 19:03:48 +00:00
}
2004-06-18 15:04:37 +00:00
}
2007-05-16 13:45:17 +00:00
}
2007-04-30 17:03:29 +00:00
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Localizes the router item title using t () or another callback .
2008-01-28 19:09:19 +00:00
*
2008-01-10 20:04:19 +00:00
* 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 .
2010-03-26 17:14:46 +00:00
*
2008-01-29 11:01:32 +00:00
* @ return
* No return value .
* $item [ 'title' ] is localized according to $item [ 'title_callback' ] .
2008-02-06 19:38:28 +00:00
* If an item 's callback is check_plain(), $item[' options '][' html ' ] becomes
2008-01-29 11:01:32 +00:00
* TRUE .
2012-07-18 10:03:02 +00:00
* $item [ 'description' ] is computed using $item [ 'description_callback' ] if
* specified ; otherwise it is translated using t () .
2008-02-06 19:38:28 +00:00
* When doing link translation and the $item [ 'options' ][ 'attributes' ][ 'title' ]
2008-01-29 11:01:32 +00:00
* ( link title attribute ) matches the description , it is translated as well .
2008-01-10 20:04:19 +00:00
*/
function _menu_item_localize ( & $item , $map , $link_translate = FALSE ) {
2012-07-18 10:03:02 +00:00
$title_callback = $item [ 'title_callback' ];
2008-02-04 12:07:23 +00:00
$item [ 'localized_options' ] = $item [ 'options' ];
2009-09-18 10:54:20 +00:00
// 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' ]);
}
2009-03-14 02:28:05 +00:00
// 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' ])) {
2008-01-10 20:04:19 +00:00
// 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.
2012-07-18 10:03:02 +00:00
if ( $title_callback == 't' ) {
2008-01-10 20:04:19 +00:00
if ( empty ( $item [ 'title_arguments' ])) {
$item [ 'title' ] = t ( $item [ 'title' ]);
}
else {
$item [ 'title' ] = t ( $item [ 'title' ], menu_unserialize ( $item [ 'title_arguments' ], $map ));
}
2007-04-30 17:03:29 +00:00
}
2012-07-18 10:03:02 +00:00
elseif ( $title_callback ) {
2008-01-10 20:04:19 +00:00
if ( empty ( $item [ 'title_arguments' ])) {
2012-07-18 10:03:02 +00:00
$item [ 'title' ] = $title_callback ( $item [ 'title' ]);
2008-01-10 20:04:19 +00:00
}
else {
2012-07-18 10:03:02 +00:00
$item [ 'title' ] = call_user_func_array ( $title_callback , menu_unserialize ( $item [ 'title_arguments' ], $map ));
2008-01-10 20:04:19 +00:00
}
2008-01-22 17:19:28 +00:00
// Avoid calling check_plain again on l() function.
2012-07-18 10:03:02 +00:00
if ( $title_callback == 'check_plain' ) {
2008-02-04 12:07:23 +00:00
$item [ 'localized_options' ][ 'html' ] = TRUE ;
2008-01-22 17:19:28 +00:00
}
2007-04-30 17:03:29 +00:00
}
}
2008-01-10 20:04:19 +00:00
elseif ( $link_translate ) {
$item [ 'title' ] = $item [ 'link_title' ];
2007-04-30 17:03:29 +00:00
}
// Translate description, see the motivation above.
2007-05-27 20:31:13 +00:00
if ( ! empty ( $item [ 'description' ])) {
2008-01-10 20:04:19 +00:00
$original_description = $item [ 'description' ];
2012-07-18 10:03:02 +00:00
}
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 ));
}
2008-01-10 20:04:19 +00:00
}
2012-07-18 10:03:02 +00:00
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' ];
2007-04-30 17:03:29 +00:00
}
2007-05-16 13:45:17 +00:00
}
/**
* 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
2009-02-09 16:27:35 +00:00
* to the language required to generate the current page .
2007-05-16 13:45:17 +00:00
*
2007-05-27 20:31:13 +00:00
* @ param $router_item
* A menu router item
2007-05-16 13:45:17 +00:00
* @ param $map
2013-06-28 18:39:33 +00:00
* An array of path arguments ; for example , array ( 'node' , '5' ) .
2007-05-16 13:45:17 +00:00
* @ param $to_arg
2007-05-27 20:31:13 +00:00
* Execute $item [ 'to_arg_functions' ] or not . Use only if you want to render a
2007-05-16 13:45:17 +00:00
* path from the menu table , for example tabs .
2010-03-26 17:14:46 +00:00
*
2007-05-16 13:45:17 +00:00
* @ return
* Returns the map with objects loaded as defined in the
2009-02-09 16:27:35 +00:00
* $item [ 'load_functions' ] . $item [ 'access' ] becomes TRUE if the item is
2007-05-27 20:31:13 +00:00
* accessible , FALSE otherwise . $item [ 'href' ] is set according to the map .
2007-05-16 13:45:17 +00:00
* If an error occurs during calling the load_functions ( like trying to load
2012-10-10 17:05:45 +00:00
* a non - existent node ) then this function returns FALSE .
2007-05-16 13:45:17 +00:00
*/
2007-05-27 20:31:13 +00:00
function _menu_translate ( & $router_item , $map , $to_arg = FALSE ) {
2009-10-17 05:50:29 +00:00
if ( $to_arg && ! empty ( $router_item [ 'to_arg_functions' ])) {
2009-02-09 16:27:35 +00:00
// 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.
2007-05-16 13:45:17 +00:00
$path_map = $map ;
2009-10-17 05:50:29 +00:00
if ( ! empty ( $router_item [ 'load_functions' ]) && ! _menu_load_objects ( $router_item , $map )) {
2007-05-16 13:45:17 +00:00
// An error occurred loading an object.
2007-05-27 20:31:13 +00:00
$router_item [ 'access' ] = FALSE ;
2007-05-16 13:45:17 +00:00
return FALSE ;
}
// Generate the link path for the page request or local tasks.
2007-05-27 20:31:13 +00:00
$link_map = explode ( '/' , $router_item [ 'path' ]);
2010-09-24 00:37:45 +00:00
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' ]);
}
2007-05-27 20:31:13 +00:00
for ( $i = 0 ; $i < $router_item [ 'number_parts' ]; $i ++ ) {
2007-05-16 13:45:17 +00:00
if ( $link_map [ $i ] == '%' ) {
$link_map [ $i ] = $path_map [ $i ];
}
2010-09-24 00:37:45 +00:00
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 ];
}
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
$router_item [ 'href' ] = implode ( '/' , $link_map );
2010-09-24 00:37:45 +00:00
$router_item [ 'tab_root_href' ] = implode ( '/' , $tab_root_map );
$router_item [ 'tab_parent_href' ] = implode ( '/' , $tab_parent_map );
2007-09-07 20:31:02 +00:00
$router_item [ 'options' ] = array ();
2013-05-02 04:09:48 +00:00
if ( ! empty ( $router_item [ 'route_name' ])) {
2013-06-25 15:21:53 +00:00
// Route-provided menu items do not have menu loaders, so replace the map
// with the link map.
$map = $link_map ;
2013-05-02 04:09:48 +00:00
$route_provider = Drupal :: getContainer () -> get ( 'router.route_provider' );
$route = $route_provider -> getRouteByName ( $router_item [ 'route_name' ]);
2013-06-25 15:21:53 +00:00
$router_item [ 'access' ] = menu_item_route_access ( $route , $router_item [ 'href' ], $map );
2013-05-02 04:09:48 +00:00
}
else {
// @todo: Remove once all routes are converted.
_menu_check_access ( $router_item , $map );
}
2008-06-24 17:01:33 +00:00
2008-06-12 20:49:39 +00:00
// For performance, don't localize an item the user can't access.
if ( $router_item [ 'access' ]) {
_menu_item_localize ( $router_item , $map );
}
2007-05-16 13:45:17 +00:00
return $map ;
}
/**
2012-08-31 15:56:36 +00:00
* Translates the path elements in the map using any to_arg helper function .
2007-05-16 13:45:17 +00:00
*
2009-07-20 01:28:16 +00:00
* @ param $map
2013-06-28 18:39:33 +00:00
* An array of path arguments ; for example , array ( 'node' , '5' ) .
2007-05-16 13:45:17 +00:00
* @ param $to_arg_functions
2013-06-28 18:39:33 +00:00
* An array of helper functions ; for example , array ( 2 => 'menu_tail_to_arg' ) .
2012-08-31 15:56:36 +00:00
*
* @ see hook_menu ()
2007-05-16 13:45:17 +00:00
*/
function _menu_link_map_translate ( & $map , $to_arg_functions ) {
2009-10-17 05:50:29 +00:00
$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 ]);
2007-05-16 13:45:17 +00:00
}
}
}
2010-11-23 06:00:27 +00:00
/**
2012-08-31 15:56:36 +00:00
* Returns a string containing the path relative to the current index .
2010-11-23 06:00:27 +00:00
*/
2007-08-25 10:29:18 +00:00
function menu_tail_to_arg ( $arg , $map , $index ) {
return implode ( '/' , array_slice ( $map , $index ));
}
2010-11-23 06:00:27 +00:00
/**
2012-08-31 15:56:36 +00:00
* Loads the path as one string relative to the current index .
2010-11-23 06:00:27 +00:00
*
* 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 ;
}
2007-05-16 13:45:17 +00:00
/**
2012-08-31 15:56:36 +00:00
* 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 ) .
2007-05-16 13:45:17 +00:00
*
* @ param $item
2010-09-24 00:37:45 +00:00
* 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 .
2010-03-26 17:14:46 +00:00
*
2007-05-16 13:45:17 +00:00
* @ return
* Returns the map of path arguments with objects loaded as defined in the
2007-05-27 20:31:13 +00:00
* $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 .
2008-02-06 19:38:28 +00:00
* $item [ 'options' ] is unserialized ; it is also changed within the call here
2008-02-04 12:07:23 +00:00
* to $item [ 'localized_options' ] by _menu_item_localize () .
2007-05-16 13:45:17 +00:00
*/
2010-09-24 00:37:45 +00:00
function _menu_link_translate ( & $item , $translate = FALSE ) {
if ( ! is_array ( $item [ 'options' ])) {
$item [ 'options' ] = unserialize ( $item [ 'options' ]);
}
2007-05-27 20:31:13 +00:00
if ( $item [ 'external' ]) {
$item [ 'access' ] = 1 ;
2007-05-16 13:45:17 +00:00
$map = array ();
2007-05-27 20:31:13 +00:00
$item [ 'href' ] = $item [ 'link_path' ];
$item [ 'title' ] = $item [ 'link_title' ];
2008-02-10 19:49:37 +00:00
$item [ 'localized_options' ] = $item [ 'options' ];
2007-05-16 13:45:17 +00:00
}
else {
2010-09-24 00:37:45 +00:00
// Complete the path of the menu link with elements from the current path,
// if it contains dynamic placeholders (%).
2007-05-27 20:31:13 +00:00
$map = explode ( '/' , $item [ 'link_path' ]);
2010-09-24 00:37:45 +00:00
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' ]);
}
}
2009-10-17 05:50:29 +00:00
}
2007-05-27 20:31:13 +00:00
$item [ 'href' ] = implode ( '/' , $map );
2007-05-16 13:45:17 +00:00
2010-09-24 00:37:45 +00:00
// Skip links containing untranslated arguments.
2007-05-27 20:31:13 +00:00
if ( strpos ( $item [ 'href' ], '%' ) !== FALSE ) {
$item [ 'access' ] = FALSE ;
2007-05-16 13:45:17 +00:00
return FALSE ;
}
2007-07-04 15:49:44 +00:00
// menu_tree_check_access() may set this ahead of time for links to nodes.
2007-05-27 20:31:13 +00:00
if ( ! isset ( $item [ 'access' ])) {
2013-03-09 05:46:28 +00:00
if ( $route = $item -> getRoute ()) {
2013-06-25 15:21:53 +00:00
$item [ 'access' ] = menu_item_route_access ( $route , $item [ 'href' ], $map );
2013-03-09 05:46:28 +00:00
}
elseif ( ! empty ( $item [ 'load_functions' ]) && ! _menu_load_objects ( $item , $map )) {
2007-10-21 18:59:02 +00:00
// An error occurred loading an object.
2007-05-27 20:31:13 +00:00
$item [ 'access' ] = FALSE ;
return FALSE ;
}
2013-03-12 10:47:42 +00:00
// Apply the access check defined in hook_menu() if there is not route
// defined.
else {
_menu_check_access ( $item , $map );
}
2007-05-16 13:45:17 +00:00
}
2008-06-12 20:49:39 +00:00
// For performance, don't localize a link the user can't access.
if ( $item [ 'access' ]) {
_menu_item_localize ( $item , $map , TRUE );
}
2007-05-16 13:45:17 +00:00
}
2008-02-06 19:52:54 +00:00
2008-02-06 19:41:24 +00:00
// 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 );
}
2008-02-06 19:52:54 +00:00
2007-03-12 13:01:10 +00:00
return $map ;
2003-09-26 10:04:09 +00:00
}
2013-05-31 18:19:40 +00:00
/**
* 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 () .
2013-06-25 15:21:53 +00:00
* @ param array $map
2013-06-28 18:39:33 +00:00
* An array of path arguments ; for example , array ( 'node' , '5' ) .
2013-05-31 18:19:40 +00:00
*
* @ return bool
* TRUE if the user has access or FALSE if the user should be presented
* with access denied .
*/
2013-06-25 15:21:53 +00:00
function menu_item_route_access ( Route $route , $href , & $map ) {
2013-05-31 18:19:40 +00:00
$request = Request :: create ( '/' . $href );
$request -> attributes -> set ( 'system_path' , $href );
// Attempt to match this path to provide a fully built request to the
// access checker.
try {
$request -> attributes -> add ( Drupal :: service ( 'router.dynamic' ) -> matchRequest ( $request ));
}
catch ( NotFoundHttpException $e ) {
return FALSE ;
}
2013-06-25 15:21:53 +00:00
// 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 );
2013-05-31 18:19:40 +00:00
}
2007-12-20 09:20:41 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets a loaded object from a router item .
2007-12-20 09:20:41 +00:00
*
2009-01-04 20:04:32 +00:00
* 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
2007-12-20 09:20:41 +00:00
*
* @ param $type
2008-12-30 16:43:20 +00:00
* Type of the object . These appear in hook_menu definitions as % type . Core
2007-12-20 09:20:41 +00:00
* 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
2009-01-04 20:04:32 +00:00
* 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.
2007-12-20 09:20:41 +00:00
* @ param $path
2008-03-19 07:38:29 +00:00
* See menu_get_item () for more on this . Defaults to the current path .
2007-12-20 09:20:41 +00:00
*/
function menu_get_object ( $type = 'node' , $position = 1 , $path = NULL ) {
$router_item = menu_get_item ( $path );
2008-04-14 17:48:46 +00:00
if ( isset ( $router_item [ 'load_functions' ][ $position ]) && ! empty ( $router_item [ 'map' ][ $position ]) && $router_item [ 'load_functions' ][ $position ] == $type . '_load' ) {
2007-12-20 09:20:41 +00:00
return $router_item [ 'map' ][ $position ];
}
}
2007-05-16 13:45:17 +00:00
/**
2010-08-05 07:56:50 +00:00
* Renders a menu tree based on the current path .
2007-07-16 12:51:03 +00:00
*
* The tree is expanded based on the current path and dynamic paths are also
2010-04-22 10:09:27 +00:00
* changed according to the defined to_arg functions ( for example the ' My
2010-03-26 17:14:46 +00:00
* account ' link is changed from user/% to a link with the current user' s uid ) .
2007-05-16 13:45:17 +00:00
*
* @ param $menu_name
* The name of the menu .
2010-03-26 17:14:46 +00:00
*
2007-05-16 13:45:17 +00:00
* @ return
2010-08-05 07:56:50 +00:00
* A structured array representing the specified menu on the current page , to
* be rendered by drupal_render () .
2007-05-16 13:45:17 +00:00
*/
2009-03-20 19:18:11 +00:00
function menu_tree ( $menu_name ) {
2009-04-25 15:19:12 +00:00
$menu_output = & drupal_static ( __FUNCTION__ , array ());
2007-05-16 13:45:17 +00:00
if ( ! isset ( $menu_output [ $menu_name ])) {
2007-05-27 20:31:13 +00:00
$tree = menu_tree_page_data ( $menu_name );
2007-05-16 13:45:17 +00:00
$menu_output [ $menu_name ] = menu_tree_output ( $tree );
}
return $menu_output [ $menu_name ];
}
2003-09-28 11:08:17 +00:00
/**
2007-01-24 14:48:36 +00:00
* Returns a rendered menu tree .
2007-05-16 13:45:17 +00:00
*
2009-09-18 10:54:20 +00:00
* 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 .
*
2007-05-16 13:45:17 +00:00
* @ param $tree
* A data structure representing the tree as returned from menu_tree_data .
2010-03-26 17:14:46 +00:00
*
2007-05-16 13:45:17 +00:00
* @ return
2009-09-18 10:54:20 +00:00
* A structured array to be rendered by drupal_render () .
2003-09-28 11:08:17 +00:00
*/
2007-05-16 13:45:17 +00:00
function menu_tree_output ( $tree ) {
2009-09-18 10:54:20 +00:00
$build = array ();
2007-10-08 15:01:05 +00:00
$items = array ();
2007-05-16 13:45:17 +00:00
2009-09-18 10:54:20 +00:00
// Pull out just the menu links we are going to render so that we
2007-10-08 14:15:09 +00:00
// get an accurate count for the first/last classes.
2007-05-16 13:45:17 +00:00
foreach ( $tree as $data ) {
2010-09-24 00:37:45 +00:00
if ( $data [ 'link' ][ 'access' ] && ! $data [ 'link' ][ 'hidden' ]) {
2007-10-08 14:15:09 +00:00
$items [] = $data ;
}
}
2007-10-12 14:10:18 +00:00
2010-09-24 00:37:45 +00:00
$router_item = menu_get_item ();
2007-10-08 14:15:09 +00:00
$num_items = count ( $items );
foreach ( $items as $i => $data ) {
2009-09-18 10:54:20 +00:00
$class = array ();
2007-10-08 14:15:09 +00:00
if ( $i == 0 ) {
2009-09-18 10:54:20 +00:00
$class [] = 'first' ;
2007-10-08 14:15:09 +00:00
}
if ( $i == $num_items - 1 ) {
2009-09-18 10:54:20 +00:00
$class [] = 'last' ;
2007-10-08 14:15:09 +00:00
}
2010-07-19 21:57:49 +00:00
// 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' ]) {
2009-09-18 10:54:20 +00:00
$class [] = 'expanded' ;
}
elseif ( $data [ 'link' ][ 'has_children' ]) {
$class [] = 'collapsed' ;
2007-10-08 14:15:09 +00:00
}
else {
2009-09-18 10:54:20 +00:00
$class [] = 'leaf' ;
2007-04-15 14:38:16 +00:00
}
2009-09-18 10:54:20 +00:00
// Set a class if the link is in the active trail.
if ( $data [ 'link' ][ 'in_active_trail' ]) {
$class [] = 'active-trail' ;
2010-09-24 00:37:45 +00:00
$data [ 'link' ][ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'active-trail' ;
}
2012-04-29 15:16:27 +00:00
// Normally, l() compares the href of every link with the current path and
// sets the active class accordingly. But local tasks do not appear in menu
2010-09-24 00:37:45 +00:00
// 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.
2012-04-29 15:16:27 +00:00
if ( $data [ 'link' ][ 'href' ] == $router_item [ 'tab_root_href' ] && $data [ 'link' ][ 'href' ] != current_path ()) {
2010-09-24 00:37:45 +00:00
$data [ 'link' ][ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'active' ;
2009-09-18 10:54:20 +00:00
}
2010-01-08 07:30:34 +00:00
// Allow menu-specific theme overrides.
2011-05-18 00:12:11 +00:00
$element [ '#theme' ] = 'menu_link__' . strtr ( $data [ 'link' ][ 'menu_name' ], '-' , '_' );
2009-09-18 10:54:20 +00:00
$element [ '#attributes' ][ 'class' ] = $class ;
$element [ '#title' ] = $data [ 'link' ][ 'title' ];
$element [ '#href' ] = $data [ 'link' ][ 'href' ];
2009-12-08 07:09:43 +00:00
$element [ '#localized_options' ] = ! empty ( $data [ 'link' ][ 'localized_options' ]) ? $data [ 'link' ][ 'localized_options' ] : array ();
2009-09-18 10:54:20 +00:00
$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 ;
2007-05-16 13:45:17 +00:00
}
2009-09-18 10:54:20 +00:00
if ( $build ) {
// Make sure drupal_render() does not re-order the links.
$build [ '#sorted' ] = TRUE ;
// Add the theme wrapper for outer markup.
2010-01-08 07:30:34 +00:00
// Allow menu-specific theme overrides.
2010-09-01 02:59:04 +00:00
$build [ '#theme_wrappers' ][] = 'menu_tree__' . strtr ( $data [ 'link' ][ 'menu_name' ], '-' , '_' );
2009-09-18 10:54:20 +00:00
}
return $build ;
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the data structure representing a named menu tree .
2007-07-16 12:51:03 +00:00
*
* 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 .
2007-05-27 20:31:13 +00:00
*
* @ param $menu_name
* The named menu links to return
2009-08-24 01:49:41 +00:00
* @ param $link
2008-12-20 18:24:41 +00:00
* A fully loaded menu link , or NULL . If a link is supplied , only the
2009-08-24 01:49:41 +00:00
* path to root will be included in the returned tree - as if this link
2007-05-27 20:31:13 +00:00
* represented the current page in a visible menu .
2009-08-24 01:49:41 +00:00
* @ 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' ] .
*
2007-05-27 20:31:13 +00:00
* @ return
* An tree of menu links in an array , in the order they should be rendered .
*/
2009-08-24 01:49:41 +00:00
function menu_tree_all_data ( $menu_name , $link = NULL , $max_depth = NULL ) {
2009-04-25 15:19:12 +00:00
$tree = & drupal_static ( __FUNCTION__ , array ());
2013-05-25 20:12:45 +00:00
$language_interface = language ( Language :: TYPE_INTERFACE );
2012-04-18 18:30:50 +00:00
2007-07-04 15:49:44 +00:00
// Use $mlid as a flag for whether the data being loaded is for the whole tree.
2009-08-24 01:49:41 +00:00
$mlid = isset ( $link [ 'mlid' ]) ? $link [ 'mlid' ] : 0 ;
2009-10-16 19:06:25 +00:00
// Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
2013-06-29 10:56:53 +00:00
$cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface -> id . ':' . ( int ) $max_depth ;
2007-05-27 20:31:13 +00:00
if ( ! isset ( $tree [ $cid ])) {
2007-07-04 15:49:44 +00:00
// If the static variable doesn't have the data, check {cache_menu}.
2011-09-11 16:14:18 +00:00
$cache = cache ( 'menu' ) -> get ( $cid );
2007-05-27 20:31:13 +00:00
if ( $cache && isset ( $cache -> data )) {
2010-07-02 02:22:26 +00:00
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache -> data ;
2007-05-27 20:31:13 +00:00
}
2010-07-02 02:22:26 +00:00
// 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 ,
);
2007-05-27 20:31:13 +00:00
if ( $mlid ) {
2007-07-04 15:49:44 +00:00
// 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.
2010-07-02 02:22:26 +00:00
$parents = array ( 0 );
2007-08-11 14:06:15 +00:00
for ( $i = 1 ; $i < MENU_MAX_DEPTH ; $i ++ ) {
2010-07-02 02:22:26 +00:00
if ( ! empty ( $link [ " p $i " ])) {
$parents [] = $link [ " p $i " ];
}
2007-08-11 14:06:15 +00:00
}
2010-07-02 02:22:26 +00:00
$tree_parameters [ 'expanded' ] = $parents ;
$tree_parameters [ 'active_trail' ] = $parents ;
$tree_parameters [ 'active_trail' ][] = $mlid ;
2008-03-14 08:51:37 +00:00
}
2010-07-02 02:22:26 +00:00
// Cache the tree building parameters using the page-specific cid.
2012-10-03 21:06:11 +00:00
cache ( 'menu' ) -> set ( $cid , $tree_parameters , CacheBackendInterface :: CACHE_PERMANENT , array ( 'menu' => $menu_name ));
2007-05-27 20:31:13 +00:00
}
2010-07-02 02:22:26 +00:00
// 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 );
2007-05-27 20:31:13 +00:00
}
return $tree [ $cid ];
}
2011-09-27 16:33:35 +00:00
/**
2012-08-31 15:56:36 +00:00
* Sets the path for determining the active trail of the specified menu tree .
2011-09-27 16:33:35 +00:00
*
* 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 ;
}
/**
2012-08-31 15:56:36 +00:00
* Gets the path for determining the active trail of the specified menu tree .
2011-09-27 16:33:35 +00:00
*
* @ 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 );
}
2007-05-16 13:45:17 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the data structure for a named menu tree , based on the current page .
2007-07-16 12:51:03 +00:00
*
* The tree order is maintained by storing each parent in an individual
2007-05-16 13:45:17 +00:00
* field , see http :// drupal . org / node / 141866 for more .
*
* @ param $menu_name
2010-09-24 00:37:45 +00:00
* The named menu links to return .
2009-08-24 01:49:41 +00:00
* @ param $max_depth
2010-09-24 00:37:45 +00:00
* ( 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 .
2009-08-24 01:49:41 +00:00
*
2007-05-16 13:45:17 +00:00
* @ 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
2007-07-04 21:33:55 +00:00
* submenu below the link if there is one , and it is a subtree that has the
* same structure described for the top - level array .
2007-05-16 13:45:17 +00:00
*/
2010-09-24 00:37:45 +00:00
function menu_tree_page_data ( $menu_name , $max_depth = NULL , $only_active_trail = FALSE ) {
2009-04-25 15:19:12 +00:00
$tree = & drupal_static ( __FUNCTION__ , array ());
2007-05-16 13:45:17 +00:00
2013-05-25 20:12:45 +00:00
$language_interface = language ( Language :: TYPE_INTERFACE );
2012-04-18 18:30:50 +00:00
2011-09-27 16:33:35 +00:00
// Check if the active trail has been overridden for this menu tree.
$active_path = menu_tree_get_path ( $menu_name );
2007-07-04 15:49:44 +00:00
// Load the menu item corresponding to the current page.
2011-09-27 16:33:35 +00:00
if ( $item = menu_get_item ( $active_path )) {
2009-08-24 01:49:41 +00:00
if ( isset ( $max_depth )) {
$max_depth = min ( $max_depth , MENU_MAX_DEPTH );
}
2008-03-14 08:51:37 +00:00
// Generate a cache ID (cid) specific for this page.
2013-06-29 10:56:53 +00:00
$cid = 'links:' . $menu_name . ':page:' . $item [ 'href' ] . ':' . $language_interface -> id . ':' . ( int ) $item [ 'access' ] . ':' . ( int ) $max_depth ;
2010-09-24 00:37:45 +00:00
// 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
2013-07-19 18:15:13 +00:00
// template_preprocess_page(). So in order to not build a giant menu tree
2010-09-24 00:37:45 +00:00
// 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' ;
}
2007-05-16 13:45:17 +00:00
2007-05-27 20:31:13 +00:00
if ( ! isset ( $tree [ $cid ])) {
2007-07-04 15:49:44 +00:00
// If the static variable doesn't have the data, check {cache_menu}.
2011-09-11 16:14:18 +00:00
$cache = cache ( 'menu' ) -> get ( $cid );
2007-05-27 20:31:13 +00:00
if ( $cache && isset ( $cache -> data )) {
2010-07-02 02:22:26 +00:00
// If the cache entry exists, it contains the parameters for
// menu_build_tree().
$tree_parameters = $cache -> data ;
2007-05-16 13:45:17 +00:00
}
2010-07-02 02:22:26 +00:00
// 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 ,
);
2010-09-24 00:37:45 +00:00
// 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 );
2010-07-02 02:22:26 +00:00
// If the item for the current page is accessible, build the tree
// parameters accordingly.
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2011-09-27 16:33:35 +00:00
// Find a menu link corresponding to the current path. If $active_path
// is NULL, let menu_link_get_preferred() determine the path.
2011-12-06 12:18:12 +00:00
if ( $active_link = menu_link_get_preferred ( $active_path , $menu_name )) {
2010-09-24 00:37:45 +00:00
// 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 ;
}
}
2007-05-27 20:31:13 +00:00
}
2010-09-24 00:37:45 +00:00
$parents = $active_trail ;
2007-05-27 20:31:13 +00:00
2013-06-05 14:24:40 +00:00
$expanded = Drupal :: state () -> get ( 'menu_expanded' );
2007-07-04 15:49:44 +00:00
// Check whether the current menu has any links set to be expanded.
2012-11-02 17:23:40 +00:00
if ( ! $only_active_trail && $expanded && in_array ( $menu_name , $expanded )) {
2007-07-04 21:33:55 +00:00
// Collect all the links set to be expanded, and then add all of
// their children to the list as well.
2007-05-27 20:31:13 +00:00
do {
2013-04-11 12:55:05 +00:00
$query = Drupal :: entityQuery ( 'menu_link' )
2008-12-03 14:38:59 +00:00
-> condition ( 'menu_name' , $menu_name )
-> condition ( 'expanded' , 1 )
-> condition ( 'has_children' , 1 )
2010-07-02 02:22:26 +00:00
-> condition ( 'plid' , $parents , 'IN' )
2013-02-08 23:55:25 +00:00
-> condition ( 'mlid' , $parents , 'NOT IN' );
$result = $query -> execute ();
$parents += $result ;
} while ( ! empty ( $result ));
2007-05-27 20:31:13 +00:00
}
2010-07-02 02:22:26 +00:00
$tree_parameters [ 'expanded' ] = $parents ;
2010-09-24 00:37:45 +00:00
$tree_parameters [ 'active_trail' ] = $active_trail ;
2007-05-27 20:31:13 +00:00
}
2010-09-24 00:37:45 +00:00
// If access is denied, we only show top-level links in menus.
2007-05-27 20:31:13 +00:00
else {
2010-09-24 00:37:45 +00:00
$tree_parameters [ 'expanded' ] = $active_trail ;
$tree_parameters [ 'active_trail' ] = $active_trail ;
2008-03-14 08:51:37 +00:00
}
2010-07-02 02:22:26 +00:00
// Cache the tree building parameters using the page-specific cid.
2012-10-03 21:06:11 +00:00
cache ( 'menu' ) -> set ( $cid , $tree_parameters , CacheBackendInterface :: CACHE_PERMANENT , array ( 'menu' => $menu_name ));
2007-05-16 13:45:17 +00:00
}
2010-07-02 02:22:26 +00:00
// 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 );
2007-04-15 14:38:16 +00:00
}
2007-05-27 20:31:13 +00:00
return $tree [ $cid ];
2007-02-11 09:30:51 +00:00
}
2007-05-27 20:31:13 +00:00
return array ();
2003-12-16 21:06:34 +00:00
}
2008-03-14 08:51:37 +00:00
/**
2012-08-31 15:56:36 +00:00
* Builds a menu tree , translates links , and checks access .
2010-07-02 02:22:26 +00:00
*
* @ 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
2010-09-24 00:37:45 +00:00
* is built , unless 'only_active_trail' is TRUE .
2010-07-02 02:22:26 +00:00
* - active_trail : An array of mlids , representing the coordinates of the
* currently active menu link .
2010-09-24 00:37:45 +00:00
* - only_active_trail : Whether to only return links that are in the active
* trail . This option is ignored , if 'expanded' is non - empty . Internally
* used for breadcrumbs .
2010-07-02 02:22:26 +00:00
* - min_depth : The minimum depth of menu links in the resulting tree .
2012-08-31 15:56:36 +00:00
* Defaults to 1 , which is the default to build a whole tree for a menu
* ( excluding menu container itself ) .
2010-07-02 02:22:26 +00:00
* - max_depth : The maximum depth of menu links in the resulting tree .
2011-12-14 03:17:05 +00:00
* - conditions : An associative array of custom database select query
* condition key / value pairs ; see _menu_build_tree () for the actual query .
2010-07-02 02:22:26 +00:00
*
* @ return
* A fully built menu tree .
2008-03-14 08:51:37 +00:00
*/
2010-07-02 02:22:26 +00:00
function menu_build_tree ( $menu_name , array $parameters = array ()) {
// Build the menu tree.
$data = _menu_build_tree ( $menu_name , $parameters );
// Check access for the current user to each item in the tree.
menu_tree_check_access ( $data [ 'tree' ], $data [ 'node_links' ]);
return $data [ 'tree' ];
}
/**
2012-08-31 15:56:36 +00:00
* Builds a menu tree .
2010-07-02 02:22:26 +00:00
*
* This function may be used build the data for a menu tree only , for example
* to further massage the data manually before further processing happens .
* menu_tree_check_access () needs to be invoked afterwards .
*
* @ see menu_build_tree ()
*/
function _menu_build_tree ( $menu_name , array $parameters = array ()) {
// Static cache of already built menu trees.
$trees = & drupal_static ( __FUNCTION__ , array ());
2013-05-25 20:12:45 +00:00
$language_interface = language ( Language :: TYPE_INTERFACE );
2012-04-18 18:30:50 +00:00
2010-07-02 02:22:26 +00:00
// Build the cache id; sort parents to prevent duplicate storage and remove
// default parameter values.
if ( isset ( $parameters [ 'expanded' ])) {
sort ( $parameters [ 'expanded' ]);
}
2013-06-29 10:56:53 +00:00
$tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface -> id . ':' . hash ( 'sha256' , serialize ( $parameters ));
2010-07-02 02:22:26 +00:00
// If we do not have this tree in the static cache, check {cache_menu}.
if ( ! isset ( $trees [ $tree_cid ])) {
2011-09-11 16:14:18 +00:00
$cache = cache ( 'menu' ) -> get ( $tree_cid );
2010-07-02 02:22:26 +00:00
if ( $cache && isset ( $cache -> data )) {
$trees [ $tree_cid ] = $cache -> data ;
}
}
if ( ! isset ( $trees [ $tree_cid ])) {
2013-04-11 12:55:05 +00:00
$query = Drupal :: entityQuery ( 'menu_link' );
2010-07-02 02:22:26 +00:00
for ( $i = 1 ; $i <= MENU_MAX_DEPTH ; $i ++ ) {
2013-02-08 23:55:25 +00:00
$query -> sort ( 'p' . $i , 'ASC' );
2010-07-02 02:22:26 +00:00
}
2013-02-08 23:55:25 +00:00
$query -> condition ( 'menu_name' , $menu_name );
2010-07-02 02:22:26 +00:00
if ( ! empty ( $parameters [ 'expanded' ])) {
2013-02-08 23:55:25 +00:00
$query -> condition ( 'plid' , $parameters [ 'expanded' ], 'IN' );
2010-07-02 02:22:26 +00:00
}
2010-09-24 00:37:45 +00:00
elseif ( ! empty ( $parameters [ 'only_active_trail' ])) {
2013-02-08 23:55:25 +00:00
$query -> condition ( 'mlid' , $parameters [ 'active_trail' ], 'IN' );
2010-09-24 00:37:45 +00:00
}
2010-07-02 02:22:26 +00:00
$min_depth = ( isset ( $parameters [ 'min_depth' ]) ? $parameters [ 'min_depth' ] : 1 );
if ( $min_depth != 1 ) {
2013-02-08 23:55:25 +00:00
$query -> condition ( 'depth' , $min_depth , '>=' );
2010-07-02 02:22:26 +00:00
}
if ( isset ( $parameters [ 'max_depth' ])) {
2013-02-08 23:55:25 +00:00
$query -> condition ( 'depth' , $parameters [ 'max_depth' ], '<=' );
2010-07-02 02:22:26 +00:00
}
2011-12-14 03:17:05 +00:00
// Add custom query conditions, if any were passed.
if ( isset ( $parameters [ 'conditions' ])) {
foreach ( $parameters [ 'conditions' ] as $column => $value ) {
$query -> condition ( $column , $value );
}
}
2010-07-02 02:22:26 +00:00
// Build an ordered array of links using the query result object.
$links = array ();
2013-02-08 23:55:25 +00:00
if ( $result = $query -> execute ()) {
$links = menu_link_load_multiple ( $result );
2010-07-02 02:22:26 +00:00
}
2010-09-24 00:37:45 +00:00
$active_trail = ( isset ( $parameters [ 'active_trail' ]) ? $parameters [ 'active_trail' ] : array ());
$data [ 'tree' ] = menu_tree_data ( $links , $active_trail , $min_depth );
2010-07-02 02:22:26 +00:00
$data [ 'node_links' ] = array ();
menu_tree_collect_node_links ( $data [ 'tree' ], $data [ 'node_links' ]);
// Cache the data, if it is not already in the cache.
2012-10-03 21:06:11 +00:00
cache ( 'menu' ) -> set ( $tree_cid , $data , CacheBackendInterface :: CACHE_PERMANENT , array ( 'menu' => $menu_name ));
2010-07-02 02:22:26 +00:00
$trees [ $tree_cid ] = $data ;
}
return $trees [ $tree_cid ];
2008-03-14 08:51:37 +00:00
}
2007-07-16 12:51:03 +00:00
/**
2012-08-31 15:56:36 +00:00
* Collects node links from a given menu tree recursively .
2009-03-21 17:58:14 +00:00
*
* @ param $tree
* The menu tree you wish to collect node links from .
* @ param $node_links
* An array in which to store the collected node links .
2007-07-16 12:51:03 +00:00
*/
function menu_tree_collect_node_links ( & $tree , & $node_links ) {
foreach ( $tree as $key => $v ) {
if ( $tree [ $key ][ 'link' ][ 'router_path' ] == 'node/%' ) {
$nid = substr ( $tree [ $key ][ 'link' ][ 'link_path' ], 5 );
if ( is_numeric ( $nid )) {
2007-10-01 09:53:03 +00:00
$node_links [ $nid ][ $tree [ $key ][ 'link' ][ 'mlid' ]] = & $tree [ $key ][ 'link' ];
2007-07-16 12:51:03 +00:00
$tree [ $key ][ 'link' ][ 'access' ] = FALSE ;
}
}
if ( $tree [ $key ][ 'below' ]) {
2007-07-17 06:13:34 +00:00
menu_tree_collect_node_links ( $tree [ $key ][ 'below' ], $node_links );
2007-07-16 12:51:03 +00:00
}
}
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Checks access and performs dynamic operations for each link in the tree .
2009-03-21 17:58:14 +00:00
*
* @ param $tree
* The menu tree you wish to operate on .
* @ param $node_links
* A collection of node link references generated from $tree by
* menu_tree_collect_node_links () .
2007-07-04 15:49:44 +00:00
*/
2007-08-11 14:06:15 +00:00
function menu_tree_check_access ( & $tree , $node_links = array ()) {
2007-07-16 12:51:03 +00:00
if ( $node_links ) {
$nids = array_keys ( $node_links );
2013-05-26 20:18:10 +00:00
$select = db_select ( 'node_field_data' , 'n' );
2010-06-11 10:39:10 +00:00
$select -> addField ( 'n' , 'nid' );
2013-05-26 20:18:10 +00:00
// @todo This should be actually filtering on the desired node status field
// language and just fall back to the default language.
2010-06-16 05:07:16 +00:00
$select -> condition ( 'n.status' , 1 );
2013-05-26 20:18:10 +00:00
2010-06-11 10:39:10 +00:00
$select -> condition ( 'n.nid' , $nids , 'IN' );
2008-12-03 14:38:59 +00:00
$select -> addTag ( 'node_access' );
$nids = $select -> execute () -> fetchCol ();
foreach ( $nids as $nid ) {
2007-10-01 09:53:03 +00:00
foreach ( $node_links [ $nid ] as $mlid => $link ) {
$node_links [ $nid ][ $mlid ][ 'access' ] = TRUE ;
}
2007-07-16 12:51:03 +00:00
}
}
2007-08-11 14:06:15 +00:00
_menu_tree_check_access ( $tree );
2007-07-04 15:49:44 +00:00
}
/**
2012-08-31 15:56:36 +00:00
* Sorts the menu tree and recursively checks access for each item .
2007-07-04 15:49:44 +00:00
*/
2007-08-11 14:06:15 +00:00
function _menu_tree_check_access ( & $tree ) {
2007-07-16 12:51:03 +00:00
$new_tree = array ();
2007-05-27 20:31:13 +00:00
foreach ( $tree as $key => $v ) {
$item = & $tree [ $key ][ 'link' ];
2007-08-11 14:06:15 +00:00
_menu_link_translate ( $item );
2010-09-24 00:37:45 +00:00
if ( $item [ 'access' ] || ( $item [ 'in_active_trail' ] && strpos ( $item [ 'href' ], '%' ) !== FALSE )) {
2007-07-16 12:51:03 +00:00
if ( $tree [ $key ][ 'below' ]) {
2007-08-11 14:06:15 +00:00
_menu_tree_check_access ( $tree [ $key ][ 'below' ]);
2007-07-16 12:51:03 +00:00
}
// The weights are made a uniform 5 digits by adding 50000 as an offset.
// After _menu_link_translate(), $item['title'] has the localized link title.
// Adding the mlid to the end of the index insures that it is unique.
2008-04-14 17:48:46 +00:00
$new_tree [( 50000 + $item [ 'weight' ]) . ' ' . $item [ 'title' ] . ' ' . $item [ 'mlid' ]] = $tree [ $key ];
2007-05-27 20:31:13 +00:00
}
}
2007-07-16 12:51:03 +00:00
// Sort siblings in the tree based on the weights and localized titles.
ksort ( $new_tree );
$tree = $new_tree ;
2007-05-27 20:31:13 +00:00
}
2007-05-16 13:45:17 +00:00
2007-03-12 13:01:10 +00:00
/**
2012-08-31 15:56:36 +00:00
* Sorts and returns the built data representing a menu tree .
2007-03-12 13:01:10 +00:00
*
2009-08-24 01:49:41 +00:00
* @ param $links
2011-09-28 15:12:21 +00:00
* A flat array of menu links that are part of the menu . Each array element
* is an associative array of information about the menu link , containing the
* fields from the { menu_links } table , and optionally additional information
* from the { menu_router } table , if the menu item appears in both tables .
* This array must be ordered depth - first . See _menu_build_tree () for a sample
* query .
2007-05-16 13:45:17 +00:00
* @ param $parents
2011-09-28 15:12:21 +00:00
* An array of the menu link ID values that are in the path from the current
* page to the root of the menu tree .
2007-03-12 13:01:10 +00:00
* @ param $depth
2011-09-28 15:12:21 +00:00
* The minimum depth to include in the returned menu tree .
2010-03-26 17:14:46 +00:00
*
2007-03-12 13:01:10 +00:00
* @ return
2011-09-28 15:12:21 +00:00
* An array of menu links in the form of a tree . Each item in the tree is an
* associative array containing :
* - link : The menu link item from $links , with additional element
* 'in_active_trail' ( TRUE if the link ID was in $parents ) .
* - below : An array containing the sub - tree of this item , where each element
* is a tree item array with 'link' and 'below' elements . This array will be
* empty if the menu item has no items in its sub - tree having a depth
* greater than or equal to $depth .
2007-07-04 15:49:44 +00:00
*/
2009-08-24 01:49:41 +00:00
function menu_tree_data ( array $links , array $parents = array (), $depth = 1 ) {
// Reverse the array so we can use the more efficient array_pop() function.
$links = array_reverse ( $links );
return _menu_tree_data ( $links , $parents , $depth );
2007-07-04 15:49:44 +00:00
}
/**
2012-08-31 15:56:36 +00:00
* Builds the data representing a menu tree .
2007-07-04 15:49:44 +00:00
*
2009-08-24 01:49:41 +00:00
* The function is a bit complex because the rendering of a link depends on
* the next menu link .
2007-03-12 13:01:10 +00:00
*/
2009-08-24 01:49:41 +00:00
function _menu_tree_data ( & $links , $parents , $depth ) {
2007-05-16 13:45:17 +00:00
$tree = array ();
2009-09-11 01:28:34 +00:00
while ( $item = array_pop ( $links )) {
2007-05-16 13:45:17 +00:00
// We need to determine if we're on the path to root so we can later build
// the correct active trail and breadcrumb.
2007-05-27 20:31:13 +00:00
$item [ 'in_active_trail' ] = in_array ( $item [ 'mlid' ], $parents );
2009-08-24 01:49:41 +00:00
// Add the current link to the tree.
$tree [ $item [ 'mlid' ]] = array (
'link' => $item ,
'below' => array (),
);
2009-09-11 01:28:34 +00:00
// Look ahead to the next link, but leave it on the array so it's available
// to other recursive function calls if we return or build a sub-tree.
$next = end ( $links );
2009-08-24 01:49:41 +00:00
// Check whether the next link is the first in a new sub-tree.
if ( $next && $next [ 'depth' ] > $depth ) {
// Recursively call _menu_tree_data to build the sub-tree.
$tree [ $item [ 'mlid' ]][ 'below' ] = _menu_tree_data ( $links , $parents , $next [ 'depth' ]);
2009-09-11 01:28:34 +00:00
// Fetch next link after filling the sub-tree.
$next = end ( $links );
2003-02-20 22:44:51 +00:00
}
2009-09-11 01:28:34 +00:00
// Determine if we should exit the loop and return.
if ( ! $next || $next [ 'depth' ] < $depth ) {
break ;
2004-07-10 15:51:48 +00:00
}
2002-12-24 15:40:32 +00:00
}
2009-08-24 01:49:41 +00:00
return $tree ;
2002-12-24 15:40:32 +00:00
}
2004-06-18 15:04:37 +00:00
/**
2012-10-05 18:12:56 +00:00
* Implements template_preprocess_HOOK () for theme_menu_tree () .
2004-06-18 15:04:37 +00:00
*/
2009-09-18 10:54:20 +00:00
function template_preprocess_menu_tree ( & $variables ) {
$variables [ 'tree' ] = $variables [ 'tree' ][ '#children' ];
2004-06-18 15:04:37 +00:00
}
2003-09-28 11:08:17 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a wrapper for a menu sub - tree .
2007-12-06 09:58:34 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
2010-04-13 15:23:03 +00:00
* - tree : An HTML string containing the tree ' s items .
2009-10-09 01:00:08 +00:00
*
2010-04-13 15:23:03 +00:00
* @ see template_preprocess_menu_tree ()
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2003-12-08 06:32:19 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_menu_tree ( $variables ) {
return '<ul class="menu">' . $variables [ 'tree' ] . '</ul>' ;
2007-03-12 13:01:10 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a menu link and submenu .
2007-12-06 09:58:34 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : Structured array data for a menu link .
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2009-09-18 10:54:20 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_menu_link ( array $variables ) {
$element = $variables [ 'element' ];
2009-09-18 10:54:20 +00:00
$sub_menu = '' ;
if ( $element [ '#below' ]) {
$sub_menu = drupal_render ( $element [ '#below' ]);
2007-05-27 20:31:13 +00:00
}
2009-09-18 10:54:20 +00:00
$output = l ( $element [ '#title' ], $element [ '#href' ], $element [ '#localized_options' ]);
2012-09-04 13:32:47 +00:00
return '<li' . new Attribute ( $element [ '#attributes' ]) . '>' . $output . $sub_menu . " </li> \n " ;
2007-02-11 09:30:51 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a single local task link .
2007-12-06 09:58:34 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
2010-04-13 15:23:03 +00:00
* - element : A render element containing :
* - #link: A menu link array with 'title', 'href', and 'localized_options'
* keys .
* - #active: A boolean indicating whether the local task is active.
2009-09-18 10:54:20 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2007-07-04 15:49:44 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_menu_local_task ( $variables ) {
2009-10-11 06:05:53 +00:00
$link = $variables [ 'element' ][ '#link' ];
2013-02-18 17:03:05 +00:00
$link += array (
'localized_options' => array (),
);
2009-11-08 12:30:35 +00:00
$link_text = $link [ 'title' ];
if ( ! empty ( $variables [ 'element' ][ '#active' ])) {
// Add text to indicate active tab for non-visual users.
2013-06-17 19:58:27 +00:00
$active = '<span class="visually-hidden">' . t ( '(active tab)' ) . '</span>' ;
2009-11-08 12:30:35 +00:00
// If the link does not contain HTML already, check_plain() it now.
// After we set 'html'=TRUE the link will not be sanitized by l().
if ( empty ( $link [ 'localized_options' ][ 'html' ])) {
$link [ 'title' ] = check_plain ( $link [ 'title' ]);
}
$link [ 'localized_options' ][ 'html' ] = TRUE ;
2010-05-04 20:29:57 +00:00
$link_text = t ( '!local-task-title!active' , array ( '!local-task-title' => $link [ 'title' ], '!active' => $active ));
2009-11-08 12:30:35 +00:00
}
2012-11-03 00:48:16 +00:00
return '<li' . ( ! empty ( $variables [ 'element' ][ '#active' ]) ? ' class="active"' : '' ) . '>' . l ( $link_text , $link [ 'href' ], $link [ 'localized_options' ]) . '</li>' ;
2003-09-28 10:51:40 +00:00
}
2002-12-24 15:40:32 +00:00
2009-08-22 19:58:28 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a single local action link .
2009-08-22 19:58:28 +00:00
*
2009-10-11 19:39:30 +00:00
* @ param $variables
2009-10-09 01:00:08 +00:00
* An associative array containing :
2010-04-13 15:23:03 +00:00
* - element : A render element containing :
* - #link: A menu link array with 'title', 'href', and 'localized_options'
* keys .
2009-09-18 10:54:20 +00:00
*
2009-08-22 19:58:28 +00:00
* @ ingroup themeable
*/
2009-10-09 01:00:08 +00:00
function theme_menu_local_action ( $variables ) {
2009-10-11 06:05:53 +00:00
$link = $variables [ 'element' ][ '#link' ];
2012-11-19 11:43:55 +00:00
$link += array (
'href' => '' ,
'localized_options' => array (),
);
$link [ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'button' ;
2013-01-02 12:00:25 +00:00
$link [ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'button-action' ;
2009-12-03 20:21:50 +00:00
$output = '<li>' ;
2012-11-19 11:43:55 +00:00
$output .= l ( $link [ 'title' ], $link [ 'href' ], $link [ 'localized_options' ]);
$output .= " </li> " ;
2009-12-03 20:21:50 +00:00
return $output ;
2009-08-22 19:58:28 +00:00
}
2007-06-30 19:46:58 +00:00
/**
* Generates elements for the $arg array in the help hook .
*/
function drupal_help_arg ( $arg = array ()) {
// Note - the number of empty elements should be > MENU_MAX_PARTS.
return $arg + array ( '' , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' );
}
2003-09-28 11:08:17 +00:00
/**
2003-12-08 06:32:19 +00:00
* Returns the help associated with the active menu item .
*/
2003-09-28 10:51:40 +00:00
function menu_get_active_help () {
2004-06-18 15:04:37 +00:00
$output = '' ;
2007-06-30 19:46:58 +00:00
$router_path = menu_tab_root_path ();
2008-09-20 08:07:34 +00:00
// We will always have a path unless we are on a 403 or 404.
if ( ! $router_path ) {
return '' ;
}
2002-12-24 15:40:32 +00:00
2007-06-30 19:46:58 +00:00
$arg = drupal_help_arg ( arg ( NULL ));
2003-12-18 21:13:17 +00:00
2013-07-27 14:30:10 +00:00
foreach ( Drupal :: moduleHandler () -> getImplementations ( 'help' ) as $module ) {
2008-11-15 11:45:04 +00:00
$function = $module . '_help' ;
// Lookup help for this path.
if ( $help = $function ( $router_path , $arg )) {
$output .= $help . " \n " ;
2003-10-09 19:24:09 +00:00
}
2002-12-24 15:40:32 +00:00
}
2004-06-18 15:04:37 +00:00
return $output ;
2003-09-28 10:51:40 +00:00
}
2009-09-30 13:09:30 +00:00
/**
* Gets the custom theme for the current page , if there is one .
*
* @ param $initialize
* This parameter should only be used internally ; it is set to TRUE in order
2010-01-30 03:38:22 +00:00
* to force the custom theme to be initialized for the current page request .
*
2009-09-30 13:09:30 +00:00
* @ return
* The machine - readable name of the custom theme , if there is one .
*
* @ see menu_set_custom_theme ()
*/
function menu_get_custom_theme ( $initialize = FALSE ) {
$custom_theme = & drupal_static ( __FUNCTION__ );
// Skip this if the site is offline or being installed or updated, since the
// menu system may not be correctly initialized then.
if ( $initialize && ! _menu_site_is_offline ( TRUE ) && ( ! defined ( 'MAINTENANCE_MODE' ) || ( MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install' ))) {
2010-01-30 03:38:22 +00:00
// First allow modules to dynamically set a custom theme for the current
// page. Since we can only have one, the last module to return a valid
// theme takes precedence.
$custom_themes = array_filter ( module_invoke_all ( 'custom_theme' ), 'drupal_theme_access' );
if ( ! empty ( $custom_themes )) {
$custom_theme = array_pop ( $custom_themes );
}
2010-11-27 20:25:44 +00:00
// If there is a theme callback function for the current page, execute it.
// If this returns a valid theme, it will override any theme that was set
// by a hook_custom_theme() implementation above.
$router_item = menu_get_item ();
2011-12-15 17:33:38 +00:00
if ( ! empty ( $router_item [ 'access' ]) && ! empty ( $router_item [ 'theme_callback' ])) {
2010-11-27 20:25:44 +00:00
$theme_name = call_user_func_array ( $router_item [ 'theme_callback' ], $router_item [ 'theme_arguments' ]);
if ( drupal_theme_access ( $theme_name )) {
$custom_theme = $theme_name ;
2010-01-30 03:38:22 +00:00
}
2009-09-30 13:09:30 +00:00
}
}
return $custom_theme ;
}
/**
* Sets a custom theme for the current page , if there is one .
*/
function menu_set_custom_theme () {
menu_get_custom_theme ( TRUE );
}
2007-07-25 14:44:03 +00:00
/**
2012-08-31 15:56:36 +00:00
* Returns an array containing the names of system - defined ( default ) menus .
2007-07-25 14:44:03 +00:00
*/
function menu_list_system_menus () {
2009-10-09 08:02:25 +00:00
return array (
2012-10-25 15:53:18 +00:00
'tools' => 'Tools' ,
'admin' => 'Administration' ,
'account' => 'User account menu' ,
'main' => 'Main navigation' ,
2012-11-20 11:36:51 +00:00
'footer' => 'Footer menu' ,
2009-10-09 08:02:25 +00:00
);
2007-07-25 14:44:03 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Returns an array of links to be rendered as the Main menu .
2007-07-04 15:49:44 +00:00
*/
2008-06-25 09:12:25 +00:00
function menu_main_menu () {
2012-08-03 17:09:51 +00:00
$config = config ( 'menu.settings' );
$menu_enabled = module_exists ( 'menu' );
// When menu module is not enabled, we need a hardcoded default value.
2012-10-25 15:53:18 +00:00
$main_links_source = $menu_enabled ? $config -> get ( 'main_links' ) : 'main' ;
2012-08-03 17:09:51 +00:00
return menu_navigation_links ( $main_links_source );
2004-06-18 15:04:37 +00:00
}
2007-08-23 16:41:19 +00:00
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Returns an array of links to be rendered as the Secondary links .
2007-07-04 15:49:44 +00:00
*/
2008-06-25 09:12:25 +00:00
function menu_secondary_menu () {
2012-08-03 17:09:51 +00:00
$config = config ( 'menu.settings' );
$menu_enabled = module_exists ( 'menu' );
// When menu module is not enabled, we need a hardcoded default value.
2012-10-25 15:53:18 +00:00
$main_links_source = $menu_enabled ? $config -> get ( 'main_links' ) : 'main' ;
$secondary_links_source = $menu_enabled ? $config -> get ( 'secondary_links' ) : 'account' ;
2007-08-20 18:26:41 +00:00
2007-08-23 16:41:19 +00:00
// If the secondary menu source is set as the primary menu, we display the
2007-08-20 18:26:41 +00:00
// second level of the primary menu.
2012-08-03 17:09:51 +00:00
if ( $secondary_links_source == $main_links_source ) {
return menu_navigation_links ( $main_links_source , 1 );
2007-08-20 18:26:41 +00:00
}
else {
2012-08-03 17:09:51 +00:00
return menu_navigation_links ( $secondary_links_source , 0 );
2007-08-20 18:26:41 +00:00
}
}
/**
2012-08-31 15:56:36 +00:00
* Returns an array of links for a navigation menu .
2007-08-20 18:26:41 +00:00
*
* @ param $menu_name
* The name of the menu .
* @ param $level
* Optional , the depth of the menu to be returned .
2010-03-26 17:14:46 +00:00
*
2007-08-20 18:26:41 +00:00
* @ return
* An array of links of the specified menu and level .
*/
function menu_navigation_links ( $menu_name , $level = 0 ) {
// Don't even bother querying the menu table if no menu is specified.
2007-09-04 21:10:45 +00:00
if ( empty ( $menu_name )) {
2007-08-20 18:26:41 +00:00
return array ();
}
// Get the menu hierarchy for the current page.
2009-08-24 01:49:41 +00:00
$tree = menu_tree_page_data ( $menu_name , $level + 1 );
2007-08-20 18:26:41 +00:00
// Go down the active trail until the right level is reached.
while ( $level -- > 0 && $tree ) {
// Loop through the current level's items until we find one that is in trail.
while ( $item = array_shift ( $tree )) {
if ( $item [ 'link' ][ 'in_active_trail' ]) {
// If the item is in the active trail, we continue in the subtree.
$tree = empty ( $item [ 'below' ]) ? array () : $item [ 'below' ];
break ;
}
}
}
// Create a single level of links.
2010-09-24 00:37:45 +00:00
$router_item = menu_get_item ();
2007-05-27 20:31:13 +00:00
$links = array ();
foreach ( $tree as $item ) {
2007-08-11 14:06:15 +00:00
if ( ! $item [ 'link' ][ 'hidden' ]) {
2008-10-13 04:46:31 +00:00
$class = '' ;
2008-02-04 12:07:23 +00:00
$l = $item [ 'link' ][ 'localized_options' ];
2007-08-11 14:06:15 +00:00
$l [ 'href' ] = $item [ 'link' ][ 'href' ];
$l [ 'title' ] = $item [ 'link' ][ 'title' ];
2008-05-05 20:55:13 +00:00
if ( $item [ 'link' ][ 'in_active_trail' ]) {
2008-10-13 04:46:31 +00:00
$class = ' active-trail' ;
2010-09-24 00:37:45 +00:00
$l [ 'attributes' ][ 'class' ][] = 'active-trail' ;
}
2012-04-29 15:16:27 +00:00
// Normally, l() compares the href of every link with the current path 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 ( $item [ 'link' ][ 'href' ] == $router_item [ 'tab_root_href' ] && $item [ 'link' ][ 'href' ] != current_path ()) {
2010-09-24 00:37:45 +00:00
$l [ 'attributes' ][ 'class' ][] = 'active' ;
2008-05-05 20:55:13 +00:00
}
2008-10-13 04:46:31 +00:00
// Keyed with the unique mlid to generate classes in theme_links().
$links [ 'menu-' . $item [ 'link' ][ 'mlid' ] . $class ] = $l ;
2007-08-11 14:06:15 +00:00
}
2007-05-27 20:31:13 +00:00
}
return $links ;
2002-12-24 15:40:32 +00:00
}
2007-02-11 09:30:51 +00:00
/**
2009-08-22 19:58:28 +00:00
* Collects the local tasks ( tabs ), action links , and the root path .
2007-02-11 09:30:51 +00:00
*
* @ param $level
2007-05-16 13:45:17 +00:00
* The level of tasks you ask for . Primary tasks are 0 , secondary are 1.
2010-03-26 17:14:46 +00:00
*
2007-02-11 09:30:51 +00:00
* @ return
2009-08-22 19:58:28 +00:00
* An array containing
2013-02-18 17:03:05 +00:00
* - tabs : Local tasks for the requested level .
* - actions : Action links for the requested level .
2009-08-22 19:58:28 +00:00
* - root_path : The router path for the current page . If the current page is
* a default local task , then this corresponds to the parent tab .
2013-02-18 17:03:05 +00:00
*
* @ see hook_menu_local_tasks ()
* @ see hook_menu_local_tasks_alter ()
2009-08-22 19:58:28 +00:00
*/
function menu_local_tasks ( $level = 0 ) {
$data = & drupal_static ( __FUNCTION__ );
$root_path = & drupal_static ( __FUNCTION__ . ':root_path' , '' );
$empty = array (
2013-02-18 17:03:05 +00:00
'tabs' => array (),
'actions' => array (),
2009-08-22 19:58:28 +00:00
'root_path' => & $root_path ,
);
if ( ! isset ( $data )) {
$data = array ();
// Set defaults in case there are no actions or tabs.
$actions = $empty [ 'actions' ];
2007-08-11 14:06:15 +00:00
$tabs = array ();
2007-02-11 09:30:51 +00:00
$router_item = menu_get_item ();
2010-05-05 06:31:26 +00:00
// If this router item points to its parent, start from the parents to
// compute tabs and actions.
if ( $router_item && ( $router_item [ 'type' ] & MENU_LINKS_TO_PARENT )) {
2010-09-24 00:37:45 +00:00
$router_item = menu_get_item ( $router_item [ 'tab_parent_href' ]);
2010-05-05 06:31:26 +00:00
}
// If we failed to fetch a router item or the current user doesn't have
// access to it, don't bother computing the tabs.
2007-05-27 20:31:13 +00:00
if ( ! $router_item || ! $router_item [ 'access' ]) {
2009-08-22 19:58:28 +00:00
return $empty ;
2007-04-15 14:38:16 +00:00
}
2013-07-11 17:39:42 +00:00
// @todo remove all code using {menu_router} and anything using MENU_*
// constants when all local actions and local tasks are converted to
// plugins. The remaining code should just invoke those managers plus do the
// invocations of hook_menu_local_tasks() and hook_menu_local_tasks_alter().
2010-05-05 06:31:26 +00:00
2010-08-20 01:42:52 +00:00
// Get all tabs (also known as local tasks) and the root page.
2008-12-03 14:38:59 +00:00
$result = db_select ( 'menu_router' , NULL , array ( 'fetch' => PDO :: FETCH_ASSOC ))
-> fields ( 'menu_router' )
-> condition ( 'tab_root' , $router_item [ 'tab_root' ])
2009-10-17 05:50:29 +00:00
-> condition ( 'context' , MENU_CONTEXT_INLINE , '<>' )
2008-12-03 14:38:59 +00:00
-> orderBy ( 'weight' )
-> orderBy ( 'title' )
-> execute ();
2009-10-17 05:50:29 +00:00
$map = $router_item [ 'original_map' ];
2007-05-16 13:45:17 +00:00
$children = array ();
2007-06-17 14:55:39 +00:00
$tasks = array ();
2007-06-30 19:46:58 +00:00
$root_path = $router_item [ 'path' ];
2007-05-16 13:45:17 +00:00
2008-12-03 14:38:59 +00:00
foreach ( $result as $item ) {
2007-06-17 14:55:39 +00:00
_menu_translate ( $item , $map , TRUE );
if ( $item [ 'tab_parent' ]) {
2007-07-04 21:33:55 +00:00
// All tabs, but not the root page.
2007-06-17 14:55:39 +00:00
$children [ $item [ 'tab_parent' ]][ $item [ 'path' ]] = $item ;
}
// Store the translated item for later use.
$tasks [ $item [ 'path' ]] = $item ;
2007-05-16 13:45:17 +00:00
}
2010-05-05 06:31:26 +00:00
2007-06-17 14:55:39 +00:00
// Find all tabs below the current path.
2010-05-05 06:31:26 +00:00
$path = $router_item [ 'path' ];
2007-10-01 09:49:14 +00:00
// Tab parenting may skip levels, so the number of parts in the path may not
// equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
$depth = 1001 ;
2013-02-18 17:03:05 +00:00
$actions = array ();
2007-05-16 13:45:17 +00:00
while ( isset ( $children [ $path ])) {
2009-10-11 06:05:53 +00:00
$tabs_current = array ();
$actions_current = array ();
2007-05-16 13:45:17 +00:00
$next_path = '' ;
2009-08-22 19:58:28 +00:00
$tab_count = 0 ;
$action_count = 0 ;
2007-05-16 13:45:17 +00:00
foreach ( $children [ $path ] as $item ) {
2010-08-20 01:42:52 +00:00
// Local tasks can be normal items too, so bitmask with
// MENU_IS_LOCAL_TASK before checking.
if ( ! ( $item [ 'type' ] & MENU_IS_LOCAL_TASK )) {
// This item is not a tab, skip it.
continue ;
}
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2009-09-18 10:54:20 +00:00
$link = $item ;
2010-08-20 01:42:52 +00:00
// The default task is always active. As tabs can be normal items
// too, so bitmask with MENU_LINKS_TO_PARENT before checking.
if (( $item [ 'type' ] & MENU_LINKS_TO_PARENT ) == MENU_LINKS_TO_PARENT ) {
2009-08-22 19:58:28 +00:00
// Find the first parent which is not a default local task or action.
2010-08-20 01:42:52 +00:00
for ( $p = $item [ 'tab_parent' ]; ( $tasks [ $p ][ 'type' ] & MENU_LINKS_TO_PARENT ) == MENU_LINKS_TO_PARENT ; $p = $tasks [ $p ][ 'tab_parent' ]);
2009-09-18 10:54:20 +00:00
// Use the path of the parent instead.
$link [ 'href' ] = $tasks [ $p ][ 'href' ];
2010-09-24 00:37:45 +00:00
// Mark the link as active, if the current path happens to be the
// path of the default local task itself (i.e., instead of its
// tab_parent_href or tab_root_href). Normally, links for default
// local tasks link to their parent, but the path of default local
// tasks can still be accessed directly, in which case this link
// would not be marked as active, since l() only compares the href
2012-04-29 15:16:27 +00:00
// with current_path().
if ( $link [ 'href' ] != current_path ()) {
2010-09-24 00:37:45 +00:00
$link [ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'active' ;
}
2013-02-18 17:03:05 +00:00
$tabs_current [ $link [ 'href' ]] = array (
2009-10-11 06:05:53 +00:00
'#theme' => 'menu_local_task' ,
'#link' => $link ,
'#active' => TRUE ,
2013-02-18 17:03:05 +00:00
'#weight' => isset ( $link [ 'weight' ]) ? $link [ 'weight' ] : NULL ,
2009-10-11 06:05:53 +00:00
);
2007-05-27 20:31:13 +00:00
$next_path = $item [ 'path' ];
2009-08-22 19:58:28 +00:00
$tab_count ++ ;
2007-05-16 13:45:17 +00:00
}
else {
2010-08-20 01:42:52 +00:00
// Actions can be normal items too, so bitmask with
// MENU_IS_LOCAL_ACTION before checking.
if (( $item [ 'type' ] & MENU_IS_LOCAL_ACTION ) == MENU_IS_LOCAL_ACTION ) {
// The item is an action, display it as such.
2013-02-18 17:03:05 +00:00
$actions_current [ $link [ 'href' ]] = array (
2010-08-20 01:42:52 +00:00
'#theme' => 'menu_local_action' ,
2009-10-11 06:05:53 +00:00
'#link' => $link ,
2013-02-18 17:03:05 +00:00
'#weight' => isset ( $link [ 'weight' ]) ? $link [ 'weight' ] : NULL ,
2009-10-11 06:05:53 +00:00
);
2010-08-20 01:42:52 +00:00
$action_count ++ ;
2009-08-22 19:58:28 +00:00
}
else {
2010-08-20 01:42:52 +00:00
// Otherwise, it's a normal tab.
2013-02-18 17:03:05 +00:00
$tabs_current [ $link [ 'href' ]] = array (
2010-08-20 01:42:52 +00:00
'#theme' => 'menu_local_task' ,
2009-10-11 06:05:53 +00:00
'#link' => $link ,
2013-02-18 17:03:05 +00:00
'#weight' => isset ( $link [ 'weight' ]) ? $link [ 'weight' ] : NULL ,
2009-10-11 06:05:53 +00:00
);
2010-08-20 01:42:52 +00:00
$tab_count ++ ;
2009-08-22 19:58:28 +00:00
}
2007-05-16 13:45:17 +00:00
}
}
2007-01-31 21:26:56 +00:00
}
2007-05-16 13:45:17 +00:00
$path = $next_path ;
2013-02-18 17:03:05 +00:00
$tabs [ $depth ] = $tabs_current ;
$actions = array_merge ( $actions , $actions_current );
2007-10-01 09:49:14 +00:00
$depth ++ ;
2007-05-16 13:45:17 +00:00
}
2009-08-22 19:58:28 +00:00
$data [ 'actions' ] = $actions ;
2007-07-04 15:49:44 +00:00
// Find all tabs at the same level or above the current one.
2007-05-27 20:31:13 +00:00
$parent = $router_item [ 'tab_parent' ];
$path = $router_item [ 'path' ];
2007-05-16 13:45:17 +00:00
$current = $router_item ;
2007-10-01 09:49:14 +00:00
$depth = 1000 ;
2007-05-16 13:45:17 +00:00
while ( isset ( $children [ $parent ])) {
2009-10-11 06:05:53 +00:00
$tabs_current = array ();
2007-05-16 13:45:17 +00:00
$next_path = '' ;
$next_parent = '' ;
2007-06-22 06:12:09 +00:00
$count = 0 ;
2007-05-16 13:45:17 +00:00
foreach ( $children [ $parent ] as $item ) {
2010-08-20 01:42:52 +00:00
// Skip local actions.
2010-01-29 12:26:10 +00:00
if ( $item [ 'type' ] & MENU_IS_LOCAL_ACTION ) {
2009-08-22 19:58:28 +00:00
continue ;
}
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2007-06-22 06:12:09 +00:00
$count ++ ;
2009-09-18 10:54:20 +00:00
$link = $item ;
2010-08-20 01:42:52 +00:00
// Local tasks can be normal items too, so bitmask with
// MENU_LINKS_TO_PARENT before checking.
if (( $item [ 'type' ] & MENU_LINKS_TO_PARENT ) == MENU_LINKS_TO_PARENT ) {
2007-06-17 14:55:39 +00:00
// Find the first parent which is not a default local task.
2010-08-20 01:42:52 +00:00
for ( $p = $item [ 'tab_parent' ]; ( $tasks [ $p ][ 'type' ] & MENU_LINKS_TO_PARENT ) == MENU_LINKS_TO_PARENT ; $p = $tasks [ $p ][ 'tab_parent' ]);
2009-09-18 10:54:20 +00:00
// Use the path of the parent instead.
$link [ 'href' ] = $tasks [ $p ][ 'href' ];
2007-06-30 19:46:58 +00:00
if ( $item [ 'path' ] == $router_item [ 'path' ]) {
$root_path = $tasks [ $p ][ 'path' ];
}
2007-06-17 14:55:39 +00:00
}
2007-02-11 09:30:51 +00:00
// We check for the active tab.
2007-05-27 20:31:13 +00:00
if ( $item [ 'path' ] == $path ) {
2010-09-24 00:37:45 +00:00
// Mark the link as active, if the current path is a (second-level)
// local task of a default local task. Since this default local task
// links to its parent, l() will not mark it as active, as it only
2012-04-29 15:16:27 +00:00
// compares the link's href to current_path().
if ( $link [ 'href' ] != current_path ()) {
2010-09-24 00:37:45 +00:00
$link [ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'active' ;
}
2013-02-18 17:03:05 +00:00
$tabs_current [ $link [ 'href' ]] = array (
2009-10-11 06:05:53 +00:00
'#theme' => 'menu_local_task' ,
'#link' => $link ,
'#active' => TRUE ,
2013-02-18 17:03:05 +00:00
'#weight' => isset ( $link [ 'weight' ]) ? $link [ 'weight' ] : NULL ,
2009-10-11 06:05:53 +00:00
);
2007-05-27 20:31:13 +00:00
$next_path = $item [ 'tab_parent' ];
2007-06-17 14:55:39 +00:00
if ( isset ( $tasks [ $next_path ])) {
$next_parent = $tasks [ $next_path ][ 'tab_parent' ];
2007-05-16 13:45:17 +00:00
}
2007-02-11 09:30:51 +00:00
}
else {
2013-02-18 17:03:05 +00:00
$tabs_current [ $link [ 'href' ]] = array (
2009-10-11 06:05:53 +00:00
'#theme' => 'menu_local_task' ,
'#link' => $link ,
2013-02-18 17:03:05 +00:00
'#weight' => isset ( $link [ 'weight' ]) ? $link [ 'weight' ] : NULL ,
2009-10-11 06:05:53 +00:00
);
2007-02-11 09:30:51 +00:00
}
}
2007-01-31 21:26:56 +00:00
}
2007-05-16 13:45:17 +00:00
$path = $next_path ;
$parent = $next_parent ;
2013-02-18 17:03:05 +00:00
$tabs [ $depth ] = $tabs_current ;
2007-10-01 09:49:14 +00:00
$depth -- ;
2007-05-16 13:45:17 +00:00
}
2007-06-17 14:55:39 +00:00
// Sort by depth.
2007-02-11 09:30:51 +00:00
ksort ( $tabs );
// Remove the depth, we are interested only in their relative placement.
$tabs = array_values ( $tabs );
2009-08-22 19:58:28 +00:00
$data [ 'tabs' ] = $tabs ;
2013-07-11 17:39:42 +00:00
// Look for route-based tabs.
$route_name = Drupal :: request () -> attributes -> get ( '_route' );
if ( ! empty ( $route_name )) {
$manager = Drupal :: service ( 'plugin.manager.menu.local_task' );
$local_tasks = $manager -> getTasksBuild ( $route_name );
foreach ( $local_tasks as $level => $items ) {
$data [ 'tabs' ][ $level ] = empty ( $data [ 'tabs' ][ $level ]) ? $items : array_merge ( $data [ 'tabs' ][ $level ], $items );
}
}
2009-10-11 06:05:53 +00:00
2013-02-18 17:03:05 +00:00
// Allow modules to dynamically add further tasks.
2013-06-05 01:58:17 +00:00
$module_handler = Drupal :: moduleHandler ();
2013-02-18 17:03:05 +00:00
foreach ( $module_handler -> getImplementations ( 'menu_local_tasks' ) as $module ) {
$function = $module . '_menu_local_tasks' ;
$function ( $data , $router_item , $root_path );
}
// Allow modules to alter local tasks.
$module_handler -> alter ( 'menu_local_tasks' , $data , $router_item , $root_path );
2007-01-31 21:26:56 +00:00
}
2007-06-30 19:46:58 +00:00
2009-08-22 19:58:28 +00:00
if ( isset ( $data [ 'tabs' ][ $level ])) {
return array (
'tabs' => $data [ 'tabs' ][ $level ],
'actions' => $data [ 'actions' ],
'root_path' => $root_path ,
);
2007-06-30 19:46:58 +00:00
}
2013-02-18 17:03:05 +00:00
elseif ( ! empty ( $data [ 'actions' ])) {
2009-12-03 20:21:50 +00:00
return array ( 'actions' => $data [ 'actions' ]) + $empty ;
}
2009-08-22 19:58:28 +00:00
return $empty ;
2007-02-11 09:30:51 +00:00
}
2009-10-17 05:50:29 +00:00
/**
2012-08-31 15:56:36 +00:00
* Retrieves contextual links for a path based on registered local tasks .
2009-10-17 05:50:29 +00:00
*
* This leverages the menu system to retrieve the first layer of registered
2009-10-17 11:39:15 +00:00
* local tasks for a given system path . All local tasks of the tab type
* MENU_CONTEXT_INLINE are taken into account .
2009-10-17 05:50:29 +00:00
*
* For example , when considering the following registered local tasks :
* - node /% node / view ( default local task ) with no 'context' defined
* - node /% node / edit with context : MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE
* - node /% node / revisions with context : MENU_CONTEXT_PAGE
* - node /% node / report - as - spam with context : MENU_CONTEXT_INLINE
*
* If the path " node/123 " is passed to this function , then it will return the
* links for 'edit' and 'report-as-spam' .
*
2009-11-07 13:35:21 +00:00
* @ param $module
* The name of the implementing module . This is used to prefix the key for
* each contextual link , which is transformed into a CSS class during
* rendering by theme_links () . For example , if $module is 'block' and the
* retrieved local task path argument is 'edit' , then the resulting CSS class
* will be 'block-edit' .
2009-11-10 22:08:43 +00:00
* @ param $parent_path
2009-11-07 13:35:21 +00:00
* The static menu router path of the object to retrieve local tasks for , for
* example 'node' or 'admin/structure/block/manage' .
* @ param $args
2009-11-10 22:08:43 +00:00
* A list of dynamic path arguments to append to $parent_path to form the
2013-06-28 18:39:33 +00:00
* fully - qualified menu router path ; for example , array ( 123 ) for a certain
2012-10-25 15:53:18 +00:00
* node or array ( 'system' , 'tools' ) for a certain block .
2009-10-17 05:50:29 +00:00
*
* @ return
2011-04-12 20:54:16 +00:00
* A list of menu router items that are local tasks for the passed - in path .
2009-10-17 05:50:29 +00:00
*
2009-12-06 01:00:27 +00:00
* @ see contextual_links_preprocess ()
2012-08-31 15:56:36 +00:00
* @ see hook_menu ()
2009-10-17 05:50:29 +00:00
*/
2009-11-07 13:35:21 +00:00
function menu_contextual_links ( $module , $parent_path , $args ) {
2009-10-17 05:50:29 +00:00
static $path_empty = array ();
$links = array ();
// Performance: In case a previous invocation for the same parent path did not
// return any links, we immediately return here.
2012-02-06 22:25:01 +00:00
if ( isset ( $path_empty [ $parent_path ]) && strpos ( $parent_path , '%' ) !== FALSE ) {
2009-10-17 05:50:29 +00:00
return $links ;
}
// Construct the item-specific parent path.
$path = $parent_path . '/' . implode ( '/' , $args );
// Get the router item for the given parent link path.
$router_item = menu_get_item ( $path );
if ( ! $router_item || ! $router_item [ 'access' ]) {
$path_empty [ $parent_path ] = TRUE ;
return $links ;
}
$data = & drupal_static ( __FUNCTION__ , array ());
$root_path = $router_item [ 'path' ];
// Performance: For a single, normalized path (such as 'node/%') we only query
// available tasks once per request.
if ( ! isset ( $data [ $root_path ])) {
// Get all contextual links that are direct children of the router item and
// not of the tab type 'view'.
$data [ $root_path ] = db_select ( 'menu_router' , 'm' )
-> fields ( 'm' )
-> condition ( 'tab_parent' , $router_item [ 'tab_root' ])
2009-11-11 08:28:50 +00:00
-> condition ( 'context' , MENU_CONTEXT_NONE , '<>' )
2009-10-17 05:50:29 +00:00
-> condition ( 'context' , MENU_CONTEXT_PAGE , '<>' )
-> orderBy ( 'weight' )
-> orderBy ( 'title' )
-> execute ()
-> fetchAllAssoc ( 'path' , PDO :: FETCH_ASSOC );
}
$parent_length = drupal_strlen ( $root_path ) + 1 ;
$map = $router_item [ 'original_map' ];
foreach ( $data [ $root_path ] as $item ) {
// Extract the actual "task" string from the path argument.
$key = drupal_substr ( $item [ 'path' ], $parent_length );
// Denormalize and translate the contextual link.
_menu_translate ( $item , $map , TRUE );
if ( ! $item [ 'access' ]) {
continue ;
}
2013-06-03 19:52:29 +00:00
// If this item is a default local task, rewrite the href to link to its
// parent item.
if ( $item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK ) {
$item [ 'href' ] = $item [ 'tab_parent_href' ];
}
2009-11-07 13:35:21 +00:00
// All contextual links are keyed by the actual "task" path argument,
// prefixed with the name of the implementing module.
$links [ $module . '-' . $key ] = $item ;
2009-10-17 05:50:29 +00:00
}
// Allow modules to alter contextual links.
drupal_alter ( 'menu_contextual_links' , $links , $router_item , $root_path );
// Performance: If the current user does not have access to any links for this
// router path and no other module added further links, we assign FALSE here
// to skip the entire process the next time the same router path is requested.
if ( empty ( $links )) {
$path_empty [ $parent_path ] = TRUE ;
}
return $links ;
}
2007-07-04 15:49:44 +00:00
/**
* Returns the rendered local tasks at the top level .
*/
2007-02-11 09:30:51 +00:00
function menu_primary_local_tasks () {
2009-08-22 19:58:28 +00:00
$links = menu_local_tasks ( 0 );
// Do not display single tabs.
2013-02-18 17:03:05 +00:00
return count ( element_get_visible_children ( $links [ 'tabs' ])) > 1 ? $links [ 'tabs' ] : '' ;
2004-04-15 20:49:42 +00:00
}
2003-09-28 10:51:40 +00:00
2007-07-04 15:49:44 +00:00
/**
* Returns the rendered local tasks at the second level .
*/
2007-01-24 14:48:36 +00:00
function menu_secondary_local_tasks () {
2009-08-22 19:58:28 +00:00
$links = menu_local_tasks ( 1 );
// Do not display single tabs.
2013-02-18 17:03:05 +00:00
return count ( element_get_visible_children ( $links [ 'tabs' ])) > 1 ? $links [ 'tabs' ] : '' ;
2009-08-22 19:58:28 +00:00
}
/**
* Returns the rendered local actions at the current level .
*/
2013-05-28 01:20:55 +00:00
function menu_get_local_actions () {
2009-08-22 19:58:28 +00:00
$links = menu_local_tasks ();
2013-05-28 01:20:55 +00:00
$router_item = menu_get_item ();
2013-07-11 16:40:33 +00:00
$manager = Drupal :: service ( 'plugin.manager.menu.local_action' );
$local_actions = $manager -> getActionsForRoute ( $router_item [ 'route_name' ]);
foreach ( $local_actions as $plugin ) {
$route_path = $manager -> getPath ( $plugin );
$action_router_item = menu_get_item ( $route_path );
$links [ 'actions' ][ $route_path ] = array (
'#theme' => 'menu_local_action' ,
'#link' => array (
'title' => $manager -> getTitle ( $plugin ),
'href' => $route_path ,
),
'#access' => $action_router_item [ 'access' ],
);
2013-05-28 01:20:55 +00:00
}
2013-02-18 17:03:05 +00:00
return $links [ 'actions' ];
2004-09-16 07:17:56 +00:00
}
2007-05-27 20:31:13 +00:00
/**
2012-08-31 15:56:36 +00:00
* Returns the router path , or the path for a default local task ' s parent .
2007-06-30 19:46:58 +00:00
*/
function menu_tab_root_path () {
2009-08-22 19:58:28 +00:00
$links = menu_local_tasks ();
return $links [ 'root_path' ];
2007-06-30 19:46:58 +00:00
}
/**
2010-11-20 04:03:51 +00:00
* Returns a renderable element for the primary and secondary tabs .
*/
function menu_local_tabs () {
2013-02-18 17:03:05 +00:00
$build = array (
2010-11-20 04:03:51 +00:00
'#theme' => 'menu_local_tasks' ,
'#primary' => menu_primary_local_tasks (),
'#secondary' => menu_secondary_local_tasks (),
);
2013-02-18 17:03:05 +00:00
return ! empty ( $build [ '#primary' ]) || ! empty ( $build [ '#secondary' ]) ? $build : array ();
2010-11-20 04:03:51 +00:00
}
/**
* Returns HTML for primary and secondary local tasks .
2007-05-27 20:31:13 +00:00
*
2012-10-05 18:12:56 +00:00
* @ param $variables
* An associative array containing :
* - primary : ( optional ) An array of local tasks ( tabs ) .
* - secondary : ( optional ) An array of local tasks ( tabs ) .
*
2007-05-27 20:31:13 +00:00
* @ ingroup themeable
2012-10-05 18:12:56 +00:00
* @ see menu_local_tasks ()
2007-05-27 20:31:13 +00:00
*/
2010-11-20 04:03:51 +00:00
function theme_menu_local_tasks ( & $variables ) {
$output = '' ;
2007-05-27 20:31:13 +00:00
2011-08-26 09:52:08 +00:00
if ( ! empty ( $variables [ 'primary' ])) {
2013-06-17 19:58:27 +00:00
$variables [ 'primary' ][ '#prefix' ] = '<h2 class="visually-hidden">' . t ( 'Primary tabs' ) . '</h2>' ;
2011-08-26 09:52:08 +00:00
$variables [ 'primary' ][ '#prefix' ] .= '<ul class="tabs primary">' ;
$variables [ 'primary' ][ '#suffix' ] = '</ul>' ;
$output .= drupal_render ( $variables [ 'primary' ]);
}
if ( ! empty ( $variables [ 'secondary' ])) {
2013-06-17 19:58:27 +00:00
$variables [ 'secondary' ][ '#prefix' ] = '<h2 class="visually-hidden">' . t ( 'Secondary tabs' ) . '</h2>' ;
2011-08-26 09:52:08 +00:00
$variables [ 'secondary' ][ '#prefix' ] .= '<ul class="tabs secondary">' ;
$variables [ 'secondary' ][ '#suffix' ] = '</ul>' ;
$output .= drupal_render ( $variables [ 'secondary' ]);
2007-07-05 08:48:58 +00:00
}
2007-05-27 20:31:13 +00:00
2007-07-05 08:48:58 +00:00
return $output ;
2007-05-27 20:31:13 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Sets ( or gets ) the active menu for the current page .
*
* The active menu for the page determines the active trail .
2011-12-06 12:18:12 +00:00
*
* @ return
* An array of menu machine names , in order of preference . The
2012-11-02 17:23:40 +00:00
* 'system.menu.active_menus_default' config item may be used to assert a menu
* order different from the order of creation , or to prevent a particular menu
* from being used at all in the active trail .
* E . g . , $conf [ 'system.menu' ][ 'active_menus_default' ] = array ( 'tools' ,
* 'main' ) .
2007-07-04 15:49:44 +00:00
*/
2009-03-20 19:18:11 +00:00
function menu_set_active_menu_names ( $menu_names = NULL ) {
2009-04-25 15:19:12 +00:00
$active = & drupal_static ( __FUNCTION__ );
2007-05-16 13:45:17 +00:00
2009-03-20 19:18:11 +00:00
if ( isset ( $menu_names ) && is_array ( $menu_names )) {
$active = $menu_names ;
2007-05-16 13:45:17 +00:00
}
elseif ( ! isset ( $active )) {
2012-11-02 17:23:40 +00:00
$config = config ( 'system.menu' );
$active = $config -> get ( 'active_menus_default' ) ? : array_keys ( menu_list_system_menus ());
2007-05-16 13:45:17 +00:00
}
return $active ;
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the active menu for the current page .
2007-07-04 15:49:44 +00:00
*/
2009-03-20 19:18:11 +00:00
function menu_get_active_menu_names () {
return menu_set_active_menu_names ();
2007-05-16 13:45:17 +00:00
}
2007-09-06 12:47:20 +00:00
/**
2012-08-31 15:56:36 +00:00
* Sets the active path , which determines which page is loaded .
2007-09-06 12:47:20 +00:00
*
* Note that this may not have the desired effect unless invoked very early
2013-02-06 11:50:46 +00:00
* in the page load or unless you do a subrequest to generate your page output .
2011-09-27 16:33:35 +00:00
*
* @ param $path
* A Drupal path - not a path alias .
2007-09-06 12:47:20 +00:00
*/
function menu_set_active_item ( $path ) {
2012-03-27 06:53:33 +00:00
// Since the active item has changed, the active menu trail may also be out
// of date.
drupal_static_reset ( 'menu_set_active_trail' );
2012-04-29 15:16:27 +00:00
// @todo Refactor to use the Symfony Request object.
_current_path ( $path );
2004-09-16 07:17:56 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Sets the active trail ( path to the menu tree root ) of the current page .
2011-09-27 16:33:35 +00:00
*
* Any trail set by this function will only be used for functionality that calls
* menu_get_active_trail () . Drupal core only uses trails set here for
* breadcrumbs and the page title and not for menu trees or page content .
* Additionally , breadcrumbs set by drupal_set_breadcrumb () will override any
* trail set here .
*
* To affect the trail used by menu trees , use menu_tree_set_path () . To affect
* the page content , use menu_set_active_item () instead .
2009-10-01 19:07:12 +00:00
*
* @ param $new_trail
2011-09-27 16:33:35 +00:00
* Menu trail to set ; the value is saved in a static variable and can be
* retrieved by menu_get_active_trail () . The format of this array should be
* the same as the return value of menu_get_active_trail () .
2010-03-26 17:14:46 +00:00
*
2009-10-01 19:07:12 +00:00
* @ return
2011-09-27 16:33:35 +00:00
* The active trail . See menu_get_active_trail () for details .
2007-07-04 15:49:44 +00:00
*/
2007-05-16 13:45:17 +00:00
function menu_set_active_trail ( $new_trail = NULL ) {
2009-04-25 15:19:12 +00:00
$trail = & drupal_static ( __FUNCTION__ );
2007-05-16 13:45:17 +00:00
if ( isset ( $new_trail )) {
$trail = $new_trail ;
}
elseif ( ! isset ( $trail )) {
$trail = array ();
2010-09-24 00:37:45 +00:00
$trail [] = array (
'title' => t ( 'Home' ),
'href' => '<front>' ,
'link_path' => '' ,
'localized_options' => array (),
'type' => 0 ,
);
// Try to retrieve a menu link corresponding to the current path. If more
// than one exists, the link from the most preferred menu is returned.
$preferred_link = menu_link_get_preferred ();
$current_item = menu_get_item ();
// There is a link for the current path.
if ( $preferred_link ) {
// Pass TRUE for $only_active_trail to make menu_tree_page_data() build
// a stripped down menu tree containing the active trail only, in case
// the given menu has not been built in this request yet.
$tree = menu_tree_page_data ( $preferred_link [ 'menu_name' ], NULL , TRUE );
list ( $key , $curr ) = each ( $tree );
2007-05-16 13:45:17 +00:00
}
2010-09-24 00:37:45 +00:00
// There is no link for the current path.
else {
$preferred_link = $current_item ;
$curr = FALSE ;
2009-03-20 19:18:11 +00:00
}
2007-05-16 13:45:17 +00:00
while ( $curr ) {
2010-09-24 00:37:45 +00:00
$link = $curr [ 'link' ];
if ( $link [ 'in_active_trail' ]) {
// Add the link to the trail, unless it links to its parent.
if ( ! ( $link [ 'type' ] & MENU_LINKS_TO_PARENT )) {
// The menu tree for the active trail may contain additional links
// that have not been translated yet, since they contain dynamic
// argument placeholders (%). Such links are not contained in regular
// menu trees, and have only been loaded for the additional
// translation that happens here, so as to be able to display them in
// the breadcumb for the current page.
// @see _menu_tree_check_access()
// @see _menu_link_translate()
if ( strpos ( $link [ 'href' ], '%' ) !== FALSE ) {
_menu_link_translate ( $link , TRUE );
}
if ( $link [ 'access' ]) {
$trail [] = $link ;
}
2007-05-16 13:45:17 +00:00
}
2010-09-24 00:37:45 +00:00
$tree = $curr [ 'below' ] ? $curr [ 'below' ] : array ();
2007-05-16 13:45:17 +00:00
}
2010-09-24 00:37:45 +00:00
list ( $key , $curr ) = each ( $tree );
2007-05-16 13:45:17 +00:00
}
2011-04-21 02:29:38 +00:00
// Make sure the current page is in the trail to build the page title, by
// appending either the preferred link or the menu router item for the
// current page. Exclude it if we are on the front page.
2010-09-24 00:37:45 +00:00
$last = end ( $trail );
2012-03-27 06:53:33 +00:00
if ( $preferred_link && $last [ 'href' ] != $preferred_link [ 'href' ] && ! drupal_is_front_page ()) {
2010-09-24 00:37:45 +00:00
$trail [] = $preferred_link ;
2007-09-07 20:31:02 +00:00
}
2007-05-16 13:45:17 +00:00
}
return $trail ;
}
2010-09-24 00:37:45 +00:00
/**
2012-08-31 15:56:36 +00:00
* Looks up the preferred menu link for a given system path .
2010-09-24 00:37:45 +00:00
*
* @ param $path
2013-06-28 18:39:33 +00:00
* The path ; for example , 'node/5' . The function will find the corresponding
2010-09-24 00:37:45 +00:00
* menu link ( 'node/5' if it exists , or fallback to 'node/%' ) .
2011-12-06 12:18:12 +00:00
* @ param $selected_menu
* The name of a menu used to restrict the search for a preferred menu link .
* If not specified , all the menus returned by menu_get_active_menu_names ()
* will be used .
2010-09-24 00:37:45 +00:00
*
* @ return
2011-12-06 12:18:12 +00:00
* A fully translated menu link , or FALSE if no matching menu link was
2010-09-24 00:37:45 +00:00
* found . The most specific menu link ( 'node/5' preferred over 'node/%' ) in
* the most preferred menu ( as defined by menu_get_active_menu_names ()) is
* returned .
*/
2011-12-06 12:18:12 +00:00
function menu_link_get_preferred ( $path = NULL , $selected_menu = NULL ) {
2010-09-24 00:37:45 +00:00
$preferred_links = & drupal_static ( __FUNCTION__ );
if ( ! isset ( $path )) {
2012-04-29 15:16:27 +00:00
$path = current_path ();
2010-09-24 00:37:45 +00:00
}
2011-12-06 12:18:12 +00:00
if ( empty ( $selected_menu )) {
// Use an illegal menu name as the key for the preferred menu link.
$selected_menu = MENU_PREFERRED_LINK ;
}
2010-09-24 00:37:45 +00:00
2011-12-06 12:18:12 +00:00
if ( ! isset ( $preferred_links [ $path ])) {
2010-09-24 00:37:45 +00:00
// Look for the correct menu link by building a list of candidate paths,
// which are ordered by priority (translated hrefs are preferred over
// untranslated paths). Afterwards, the most relevant path is picked from
// the menus, ordered by menu preference.
$item = menu_get_item ( $path );
$path_candidates = array ();
// 1. The current item href.
$path_candidates [ $item [ 'href' ]] = $item [ 'href' ];
// 2. The tab root href of the current item (if any).
if ( $item [ 'tab_parent' ] && ( $tab_root = menu_get_item ( $item [ 'tab_root_href' ]))) {
$path_candidates [ $tab_root [ 'href' ]] = $tab_root [ 'href' ];
}
// 3. The current item path (with wildcards).
$path_candidates [ $item [ 'path' ]] = $item [ 'path' ];
// 4. The tab root path of the current item (if any).
if ( ! empty ( $tab_root )) {
$path_candidates [ $tab_root [ 'path' ]] = $tab_root [ 'path' ];
}
// Retrieve a list of menu names, ordered by preference.
$menu_names = menu_get_active_menu_names ();
2011-12-06 12:18:12 +00:00
// Put the selected menu at the front of the list.
array_unshift ( $menu_names , $selected_menu );
2010-09-24 00:37:45 +00:00
2013-02-08 23:55:25 +00:00
$menu_links = entity_load_multiple_by_properties ( 'menu_link' , array ( 'link_path' => $path_candidates ));
2010-09-24 00:37:45 +00:00
// Sort candidates by link path and menu name.
$candidates = array ();
2013-02-08 23:55:25 +00:00
foreach ( $menu_links as $candidate ) {
2010-09-24 00:37:45 +00:00
$candidates [ $candidate [ 'link_path' ]][ $candidate [ 'menu_name' ]] = $candidate ;
2011-12-06 12:18:12 +00:00
// Add any menus not already in the menu name search list.
if ( ! in_array ( $candidate [ 'menu_name' ], $menu_names )) {
$menu_names [] = $candidate [ 'menu_name' ];
}
2010-09-24 00:37:45 +00:00
}
2011-12-06 12:18:12 +00:00
// Store the most specific link for each menu. Also save the most specific
// link of the most preferred menu in $preferred_link.
2010-09-24 00:37:45 +00:00
foreach ( $path_candidates as $link_path ) {
2011-12-06 12:18:12 +00:00
if ( isset ( $candidates [ $link_path ])) {
foreach ( $menu_names as $menu_name ) {
if ( empty ( $preferred_links [ $path ][ $menu_name ]) && isset ( $candidates [ $link_path ][ $menu_name ])) {
$candidate_item = $candidates [ $link_path ][ $menu_name ];
$map = explode ( '/' , $path );
_menu_translate ( $candidate_item , $map );
if ( $candidate_item [ 'access' ]) {
$preferred_links [ $path ][ $menu_name ] = $candidate_item ;
if ( empty ( $preferred_links [ $path ][ MENU_PREFERRED_LINK ])) {
// Store the most specific link.
$preferred_links [ $path ][ MENU_PREFERRED_LINK ] = $candidate_item ;
}
}
}
2010-09-24 00:37:45 +00:00
}
}
}
}
2011-12-06 12:18:12 +00:00
return isset ( $preferred_links [ $path ][ $selected_menu ]) ? $preferred_links [ $path ][ $selected_menu ] : FALSE ;
2010-09-24 00:37:45 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2009-10-01 19:07:12 +00:00
* Gets the active trail ( path to root menu root ) of the current page .
*
2011-09-27 16:33:35 +00:00
* If a trail is supplied to menu_set_active_trail (), that value is returned . If
* a trail is not supplied to menu_set_active_trail (), the path to the current
* page is calculated and returned . The calculated trail is also saved as a
* static value for use by subsequent calls to menu_get_active_trail () .
*
* @ return
* Path to menu root of the current page , as an array of menu link items ,
* starting with the site ' s home page . Each link item is an associative array
* with the following components :
* - title : Title of the item .
* - href : Drupal path of the item .
* - localized_options : Options for passing into the l () function .
* - type : A menu type constant , such as MENU_DEFAULT_LOCAL_TASK , or 0 to
* indicate it ' s not really in the menu ( used for the home page item ) .
2007-07-04 15:49:44 +00:00
*/
2007-05-16 13:45:17 +00:00
function menu_get_active_trail () {
return menu_set_active_trail ();
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the breadcrumb for the current page , as determined by the active trail .
2010-09-24 00:37:45 +00:00
*
* @ see menu_set_active_trail ()
2007-07-04 15:49:44 +00:00
*/
2007-01-24 14:48:36 +00:00
function menu_get_active_breadcrumb () {
2007-05-16 13:45:17 +00:00
$breadcrumb = array ();
2008-02-06 19:38:28 +00:00
2008-01-27 17:43:23 +00:00
// No breadcrumb for the front page.
if ( drupal_is_front_page ()) {
return $breadcrumb ;
}
2007-03-12 13:01:10 +00:00
$item = menu_get_item ();
2010-09-24 00:37:45 +00:00
if ( ! empty ( $item [ 'access' ])) {
2007-05-16 13:45:17 +00:00
$active_trail = menu_get_active_trail ();
2010-09-24 00:37:45 +00:00
// Allow modules to alter the breadcrumb, if possible, as that is much
// faster than rebuilding an entirely new active trail.
drupal_alter ( 'menu_breadcrumb' , $active_trail , $item );
2007-05-16 13:45:17 +00:00
// Don't show a link to the current page in the breadcrumb trail.
2010-09-24 00:37:45 +00:00
$end = end ( $active_trail );
2013-07-11 17:39:42 +00:00
if ( Drupal :: request () -> attributes -> get ( 'system_path' ) == $end [ 'href' ]) {
2010-09-24 00:37:45 +00:00
array_pop ( $active_trail );
}
// Remove the tab root (parent) if the current path links to its parent.
// Normally, the tab root link is included in the breadcrumb, as soon as we
// are on a local task or any other child link. However, if we are on a
// default local task (e.g., node/%/view), then we do not want the tab root
// link (e.g., node/%) to appear, as it would be identical to the current
// page. Since this behavior also needs to work recursively (i.e., on
// default local tasks of default local tasks), and since the last non-task
// link in the trail is used as page title (see menu_get_active_title()),
// this condition cannot be cleanly integrated into menu_get_active_trail().
// menu_get_active_trail() already skips all links that link to their parent
// (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link
// itself, we always remove the last link in the trail, if the current
// router item links to its parent.
if (( $item [ 'type' ] & MENU_LINKS_TO_PARENT ) == MENU_LINKS_TO_PARENT ) {
array_pop ( $active_trail );
}
foreach ( $active_trail as $parent ) {
$breadcrumb [] = l ( $parent [ 'title' ], $parent [ 'href' ], $parent [ 'localized_options' ]);
2007-04-15 14:38:16 +00:00
}
2007-03-12 13:01:10 +00:00
}
return $breadcrumb ;
2006-07-07 07:57:30 +00:00
}
2007-01-24 14:48:36 +00:00
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the title of the current page , as determined by the active trail .
2007-07-04 15:49:44 +00:00
*/
2007-01-31 21:55:37 +00:00
function menu_get_active_title () {
2007-05-16 13:45:17 +00:00
$active_trail = menu_get_active_trail ();
foreach ( array_reverse ( $active_trail ) as $item ) {
2010-05-06 05:59:31 +00:00
if ( ! ( bool ) ( $item [ 'type' ] & MENU_IS_LOCAL_TASK )) {
2007-05-27 20:31:13 +00:00
return $item [ 'title' ];
2007-03-12 13:01:10 +00:00
}
}
2007-02-11 09:30:51 +00:00
}
2007-04-06 04:39:51 +00:00
2007-07-04 15:49:44 +00:00
/**
* Clears the cached cached data for a single named menu .
*/
2012-10-25 15:53:18 +00:00
function menu_cache_clear ( $menu_name = 'tools' ) {
2012-11-28 21:36:29 +00:00
cache ( 'menu' ) -> deleteTags ( array ( 'menu' => $menu_name ));
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
2009-12-02 07:28:22 +00:00
// Also clear the menu system static caches.
menu_reset_static_cache ();
2007-05-16 13:45:17 +00:00
}
/**
2012-08-31 15:56:36 +00:00
* Clears all cached menu data .
*
* This should be called any time broad changes
2007-07-04 15:49:44 +00:00
* might have been made to the router items or menu links .
2007-05-16 13:45:17 +00:00
*/
function menu_cache_clear_all () {
2012-11-28 21:36:29 +00:00
cache ( 'menu' ) -> deleteAll ();
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
2009-12-02 07:28:22 +00:00
menu_reset_static_cache ();
}
/**
* Resets the menu system static cache .
*/
function menu_reset_static_cache () {
2013-05-02 06:42:27 +00:00
Drupal :: entityManager ()
2013-02-08 23:55:25 +00:00
-> getStorageController ( 'menu_link' ) -> resetCache ();
2010-07-06 07:27:07 +00:00
drupal_static_reset ( '_menu_build_tree' );
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
2009-12-02 07:28:22 +00:00
drupal_static_reset ( 'menu_tree' );
drupal_static_reset ( 'menu_tree_all_data' );
drupal_static_reset ( 'menu_tree_page_data' );
2010-09-24 00:37:45 +00:00
drupal_static_reset ( 'menu_link_get_preferred' );
2007-05-16 13:45:17 +00:00
}
/**
2012-08-31 15:56:36 +00:00
* Populates the database tables used by various menu functions .
2008-01-03 10:51:30 +00:00
*
* This function will clear and populate the { menu_router } table , add entries
2012-08-31 15:56:36 +00:00
* to { menu_links } for new router items , and then remove stale items from
2011-04-12 20:42:29 +00:00
* { menu_links } .
2009-08-17 20:32:30 +00:00
*
* @ return
* TRUE if the menu was rebuilt , FALSE if another thread was rebuilding
* in parallel and the current thread just waited for completion .
2007-05-16 13:45:17 +00:00
*/
2012-05-03 15:09:39 +00:00
function menu_router_rebuild () {
2012-09-17 09:49:18 +00:00
if ( ! lock () -> acquire ( __FUNCTION__ )) {
2009-08-17 20:32:30 +00:00
// Wait for another request that is already doing this work.
2009-08-24 00:14:23 +00:00
// We choose to block here since otherwise the router item may not
2012-07-09 20:20:56 +00:00
// be available during routing resulting in a 404.
2012-09-17 09:49:18 +00:00
lock () -> wait ( __FUNCTION__ );
2009-08-17 20:32:30 +00:00
return FALSE ;
}
2010-01-03 01:32:41 +00:00
$transaction = db_transaction ();
2009-08-17 20:32:30 +00:00
2010-01-03 01:32:41 +00:00
try {
2013-03-11 10:50:25 +00:00
list ( $menu , $masks ) = menu_router_build ( TRUE );
2010-01-03 01:32:41 +00:00
_menu_navigation_links_rebuild ( $menu );
// Clear the menu, page and block caches.
menu_cache_clear_all ();
_menu_clear_page_cache ();
2011-04-12 20:42:29 +00:00
// Indicate that the menu has been successfully rebuilt.
2013-06-05 14:24:40 +00:00
Drupal :: state () -> delete ( 'menu_rebuild_needed' );
2008-01-03 10:51:30 +00:00
}
2010-01-03 01:32:41 +00:00
catch ( Exception $e ) {
2010-05-26 07:52:13 +00:00
$transaction -> rollback ();
2010-05-28 10:18:57 +00:00
watchdog_exception ( 'menu' , $e );
2009-08-17 20:32:30 +00:00
}
2010-01-03 01:32:41 +00:00
2012-09-17 09:49:18 +00:00
lock () -> release ( __FUNCTION__ );
2009-08-17 20:32:30 +00:00
return TRUE ;
2007-05-16 13:45:17 +00:00
}
/**
2012-08-31 15:56:36 +00:00
* Collects and alters the menu definitions .
2013-03-11 10:50:25 +00:00
*
* @ param bool $save
* ( optional ) Save the new router to the database . Defaults to FALSE .
2007-05-16 13:45:17 +00:00
*/
2013-03-11 10:50:25 +00:00
function menu_router_build ( $save = FALSE ) {
2009-04-02 03:40:05 +00:00
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array ();
2013-07-27 14:30:10 +00:00
foreach ( Drupal :: moduleHandler () -> getImplementations ( 'menu' ) as $module ) {
2009-05-24 17:39:35 +00:00
$router_items = call_user_func ( $module . '_menu' );
2009-04-02 03:40:05 +00:00
if ( isset ( $router_items ) && is_array ( $router_items )) {
foreach ( array_keys ( $router_items ) as $path ) {
$router_items [ $path ][ 'module' ] = $module ;
2007-05-22 05:52:17 +00:00
}
2009-04-02 03:40:05 +00:00
$callbacks = array_merge ( $callbacks , $router_items );
2007-05-22 05:52:17 +00:00
}
}
2009-04-02 03:40:05 +00:00
// Alter the menu as defined in modules, keys are like user/%user.
drupal_alter ( 'menu' , $callbacks );
2013-06-10 22:28:11 +00:00
foreach ( $callbacks as $path => $router_item ) {
// If the menu item is a default local task and incorrectly references a
// route, remove it.
// @todo This may be removed later depending on the outcome of
// http://drupal.org/node/1889790
if ( isset ( $router_item [ 'type' ]) && $router_item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK ) {
unset ( $callbacks [ $path ][ 'route_name' ]);
}
// If the menu item references a route, normalize the route information
// into the old structure. Note that routes are keyed by name, not path,
// so the path of the route takes precedence.
if ( isset ( $router_item [ 'route_name' ])) {
$router_item [ 'page callback' ] = 'USES_ROUTE' ;
$router_item [ 'access callback' ] = TRUE ;
$new_path = _menu_router_translate_route ( $router_item [ 'route_name' ]);
unset ( $callbacks [ $path ]);
$callbacks [ $new_path ] = $router_item ;
}
}
2013-03-11 10:50:25 +00:00
list ( $menu , $masks ) = _menu_router_build ( $callbacks , $save );
2009-04-12 19:52:38 +00:00
_menu_router_cache ( $menu );
2009-04-02 03:40:05 +00:00
2009-04-12 19:52:38 +00:00
return array ( $menu , $masks );
2009-04-02 03:40:05 +00:00
}
2013-03-09 05:46:28 +00:00
/**
2013-05-28 01:20:55 +00:00
* Translates a route name to its router item path .
2013-03-09 05:46:28 +00:00
*
2013-05-28 01:20:55 +00:00
* @ param string $route_name
* The route name to translate .
2013-03-09 05:46:28 +00:00
*
2013-05-28 01:20:55 +00:00
* @ return string
* The translated path pattern from the route .
2013-03-09 05:46:28 +00:00
*/
2013-05-28 01:20:55 +00:00
function _menu_router_translate_route ( $route_name ) {
$route = Drupal :: service ( 'router.route_provider' ) -> getRouteByName ( $route_name );
$path = trim ( $route -> getPattern (), '/' );
2013-03-09 05:46:28 +00:00
2013-03-29 15:54:24 +00:00
// Translate placeholders, e.g. {foo} -> %.
2013-05-28 01:20:55 +00:00
return preg_replace ( '/{' . DRUPAL_PHP_FUNCTION_PATTERN . '}/' , '%' , $path );
2013-03-09 05:46:28 +00:00
}
2009-04-02 03:40:05 +00:00
/**
2012-08-31 15:56:36 +00:00
* Stores the menu router if we have it in memory .
2009-04-02 03:40:05 +00:00
*/
2009-04-12 19:52:38 +00:00
function _menu_router_cache ( $new_menu = NULL ) {
$menu = & drupal_static ( __FUNCTION__ );
2009-05-24 17:39:35 +00:00
2009-04-02 03:40:05 +00:00
if ( isset ( $new_menu )) {
$menu = $new_menu ;
}
2007-05-16 13:45:17 +00:00
return $menu ;
}
2007-05-06 05:47:52 +00:00
2009-04-12 19:52:38 +00:00
/**
2012-08-31 15:56:36 +00:00
* Gets the menu router .
2009-04-12 19:52:38 +00:00
*/
function menu_get_router () {
// Check first if we have it in memory already.
$menu = _menu_router_cache ();
if ( empty ( $menu )) {
list ( $menu , $masks ) = menu_router_build ();
}
return $menu ;
}
2007-06-05 09:15:02 +00:00
/**
2013-02-08 23:55:25 +00:00
* Builds menu links for the items in the menu router .
*
* @ todo This function should be removed / refactored .
2007-06-05 09:15:02 +00:00
*/
2013-02-08 23:55:25 +00:00
function _menu_navigation_links_rebuild ( $menu ) {
if ( module_exists ( 'menu_link' )) {
2013-05-02 06:42:27 +00:00
$menu_link_controller = Drupal :: entityManager ()
2013-02-08 23:55:25 +00:00
-> getStorageController ( 'menu_link' );
2007-06-05 09:15:02 +00:00
}
2013-02-08 23:55:25 +00:00
else {
// The Menu link module is not available at install time, so we need to
// hardcode the default storage controller.
2013-05-25 23:36:57 +00:00
$menu_link_controller = new MenuLinkStorageController ( 'menu_link' , Drupal :: service ( 'database' ), Drupal :: service ( 'router.route_provider' ));
2009-12-14 20:23:01 +00:00
}
2007-06-05 09:15:02 +00:00
2007-05-16 13:45:17 +00:00
// Add normal and suggested items as links.
2013-02-08 23:55:25 +00:00
$router_items = array ();
foreach ( $menu as $path => $router_item ) {
if ( $router_item [ '_visible' ]) {
$router_items [ $path ] = $router_item ;
$sort [ $path ] = $router_item [ '_number_parts' ];
2007-05-16 13:45:17 +00:00
}
2007-05-06 05:47:52 +00:00
}
2013-02-08 23:55:25 +00:00
if ( $router_items ) {
// Keep an array of processed menu links, to allow
// Drupal\menu_link\MenuLinkStorageController::save() to check this for
// parents instead of querying the database.
2011-07-14 01:41:00 +00:00
$parent_candidates = array ();
2007-05-16 13:45:17 +00:00
// Make sure no child comes before its parent.
2013-02-08 23:55:25 +00:00
array_multisort ( $sort , SORT_NUMERIC , $router_items );
2007-05-16 13:45:17 +00:00
2013-02-08 23:55:25 +00:00
foreach ( $router_items as $key => $router_item ) {
// For performance reasons, do a straight query now and convert to a menu
// link entity later.
// @todo revisit before release.
2008-12-03 14:38:59 +00:00
$existing_item = db_select ( 'menu_links' )
2011-07-14 01:41:00 +00:00
-> fields ( 'menu_links' )
2013-02-08 23:55:25 +00:00
-> condition ( 'link_path' , $router_item [ 'path' ])
2008-12-03 14:38:59 +00:00
-> condition ( 'module' , 'system' )
2013-02-08 23:55:25 +00:00
-> execute () -> fetchAll ();
2007-07-04 15:49:44 +00:00
if ( $existing_item ) {
2013-02-08 23:55:25 +00:00
$existing_item = reset ( $existing_item );
$existing_item -> options = unserialize ( $existing_item -> options );
$router_item [ 'mlid' ] = $existing_item -> mlid ;
$router_item [ 'uuid' ] = $existing_item -> uuid ;
2008-09-02 19:23:02 +00:00
// A change in hook_menu may move the link to a different menu
2013-02-08 23:55:25 +00:00
if ( empty ( $router_item [ 'menu_name' ]) || ( $router_item [ 'menu_name' ] == $existing_item -> menu_name )) {
$router_item [ 'menu_name' ] = $existing_item -> menu_name ;
$router_item [ 'plid' ] = $existing_item -> plid ;
2008-09-02 19:23:02 +00:00
}
2009-03-20 19:18:11 +00:00
else {
2013-02-08 23:55:25 +00:00
// It moved to a new menu.
// Let Drupal\menu_link\MenuLinkStorageController::save() try to find
// a new parent based on the path.
unset ( $router_item [ 'plid' ]);
2009-03-20 19:18:11 +00:00
}
2013-02-08 23:55:25 +00:00
$router_item [ 'has_children' ] = $existing_item -> has_children ;
$router_item [ 'updated' ] = $existing_item -> updated ;
// Convert the existing item to a typed object.
$existing_item = $menu_link_controller -> create ( get_object_vars ( $existing_item ));
2007-07-04 15:49:44 +00:00
}
2013-02-08 23:55:25 +00:00
else {
$existing_item = NULL ;
}
if ( $existing_item && $existing_item -> customized ) {
$parent_candidates [ $existing_item -> mlid ] = $existing_item ;
2011-07-14 01:41:00 +00:00
}
else {
2013-02-08 23:55:25 +00:00
$menu_link = MenuLink :: buildFromRouterItem ( $router_item );
$menu_link -> original = $existing_item ;
$menu_link -> parentCandidates = $parent_candidates ;
$menu_link_controller -> save ( $menu_link );
$parent_candidates [ $menu_link -> id ()] = $menu_link ;
unset ( $router_items [ $key ]);
2007-07-04 15:49:44 +00:00
}
2007-05-16 13:45:17 +00:00
}
2007-05-06 05:47:52 +00:00
}
2013-02-08 23:55:25 +00:00
2008-01-30 21:01:20 +00:00
$paths = array_keys ( $menu );
2008-03-21 08:32:24 +00:00
// Updated and customized items whose router paths are gone need new ones.
2013-02-08 23:55:25 +00:00
$menu_links = $menu_link_controller -> loadUpdatedCustomized ( $paths );
foreach ( $menu_links as $menu_link ) {
$router_path = _menu_find_router_path ( $menu_link -> link_path );
if ( ! empty ( $router_path ) && ( $router_path != $menu_link -> router_path || $menu_link -> updated )) {
2007-12-06 21:35:14 +00:00
// If the router path and the link path matches, it's surely a working
// item, so we clear the updated flag.
2013-02-08 23:55:25 +00:00
$updated = $menu_link -> updated && $router_path != $menu_link -> link_path ;
$menu_link -> router_path = $router_path ;
$menu_link -> updated = ( int ) $updated ;
$menu_link_controller -> save ( $menu_link );
2007-08-29 20:46:18 +00:00
}
2007-08-19 09:46:15 +00:00
}
2013-02-08 23:55:25 +00:00
2008-09-05 08:24:08 +00:00
// Find any item whose router path does not exist any more.
2013-04-11 12:55:05 +00:00
$query = Drupal :: entityQuery ( 'menu_link' )
2008-12-03 14:38:59 +00:00
-> condition ( 'router_path' , $paths , 'NOT IN' )
-> condition ( 'external' , 0 )
-> condition ( 'updated' , 0 )
-> condition ( 'customized' , 0 )
2013-02-08 23:55:25 +00:00
-> sort ( 'depth' , 'DESC' );
$result = $query -> execute ();
2007-05-06 05:47:52 +00:00
2013-02-08 23:55:25 +00:00
// Remove all such items. Starting from those with the greatest depth will
// minimize the amount of re-parenting done by the menu link controller.
if ( ! empty ( $result )) {
menu_link_delete_multiple ( $result , TRUE );
2009-10-17 01:15:40 +00:00
}
}
/**
* Returns an array containing all links for a menu .
*
* @ param $menu_name
* The name of the menu whose links should be returned .
2010-07-16 02:54:09 +00:00
*
2009-10-17 01:15:40 +00:00
* @ return
* An array of menu links .
*/
function menu_load_links ( $menu_name ) {
2013-02-08 23:55:25 +00:00
$links = array ();
2013-04-11 12:55:05 +00:00
$query = Drupal :: entityQuery ( 'menu_link' )
2013-02-08 23:55:25 +00:00
-> condition ( 'menu_name' , $menu_name )
2009-10-17 01:15:40 +00:00
// Order by weight so as to be helpful for menus that are only one level
// deep.
2013-02-08 23:55:25 +00:00
-> sort ( 'weight' );
$result = $query -> execute ();
2009-10-17 01:15:40 +00:00
2013-02-08 23:55:25 +00:00
if ( ! empty ( $result )) {
$links = menu_link_load_multiple ( $result );
2009-10-17 01:15:40 +00:00
}
2013-02-08 23:55:25 +00:00
2009-10-17 01:15:40 +00:00
return $links ;
}
/**
* Deletes all links for a menu .
*
* @ param $menu_name
* The name of the menu whose links will be deleted .
*/
function menu_delete_links ( $menu_name ) {
$links = menu_load_links ( $menu_name );
2013-02-08 23:55:25 +00:00
menu_link_delete_multiple ( array_keys ( $links ), FALSE , TRUE );
2010-11-20 07:19:15 +00:00
}
2007-11-26 08:49:03 +00:00
/**
2012-08-31 15:56:36 +00:00
* Clears the page and block caches at most twice per page load .
2007-12-08 14:06:23 +00:00
*/
2007-11-26 08:49:03 +00:00
function _menu_clear_page_cache () {
2009-04-25 15:19:12 +00:00
$cache_cleared = & drupal_static ( __FUNCTION__ , 0 );
2007-11-26 08:49:03 +00:00
2007-11-26 16:19:37 +00:00
// Clear the page and block caches, but at most twice, including at
2008-12-30 16:43:20 +00:00
// the end of the page load when there are multiple links saved or deleted.
2009-04-25 15:19:12 +00:00
if ( $cache_cleared == 0 ) {
2012-11-28 21:36:29 +00:00
cache_invalidate_tags ( array ( 'content' => TRUE ));
2007-11-26 08:49:03 +00:00
// Keep track of which menus have expanded items.
_menu_set_expanded_menus ();
$cache_cleared = 1 ;
}
elseif ( $cache_cleared == 1 ) {
2013-07-20 01:26:35 +00:00
drupal_register_shutdown_function ( 'cache_invalidate_tags' , array ( 'content' => TRUE ));
2007-11-26 08:49:03 +00:00
// Keep track of which menus have expanded items.
2010-02-17 22:44:52 +00:00
drupal_register_shutdown_function ( '_menu_set_expanded_menus' );
2007-11-26 08:49:03 +00:00
$cache_cleared = 2 ;
}
}
/**
2012-08-31 15:56:36 +00:00
* Updates a list of menus with expanded items .
2007-12-08 14:06:23 +00:00
*/
2007-11-26 08:49:03 +00:00
function _menu_set_expanded_menus () {
2008-12-03 14:38:59 +00:00
$names = db_query ( " SELECT menu_name FROM { menu_links} WHERE expanded <> 0 GROUP BY menu_name " ) -> fetchCol ();
2013-06-05 14:24:40 +00:00
Drupal :: state () -> set ( 'menu_expanded' , $names );
2007-05-16 13:45:17 +00:00
}
2007-08-29 20:46:18 +00:00
/**
2012-08-31 15:56:36 +00:00
* Finds the router path which will serve this path .
2007-08-29 20:46:18 +00:00
*
* @ param $link_path
* The path for we are looking up its router path .
2010-03-26 17:14:46 +00:00
*
2007-08-29 20:46:18 +00:00
* @ return
* A path from $menu keys or empty if $link_path points to a nonexisting
* place .
*/
2009-04-02 03:40:05 +00:00
function _menu_find_router_path ( $link_path ) {
// $menu will only have data during a menu rebuild.
2009-04-12 19:52:38 +00:00
$menu = _menu_router_cache ();
2009-04-02 03:40:05 +00:00
2007-08-29 20:46:18 +00:00
$router_path = $link_path ;
2009-04-02 03:40:05 +00:00
$parts = explode ( '/' , $link_path , MENU_MAX_PARTS );
$ancestors = menu_get_ancestors ( $parts );
if ( empty ( $menu )) {
// Not during a menu rebuild, so look up in the database.
$router_path = ( string ) db_select ( 'menu_router' )
-> fields ( 'menu_router' , array ( 'path' ))
-> condition ( 'path' , $ancestors , 'IN' )
-> orderBy ( 'fit' , 'DESC' )
-> range ( 0 , 1 )
-> execute () -> fetchField ();
}
elseif ( ! isset ( $menu [ $router_path ])) {
// Add an empty router path as a fallback.
2007-08-29 20:46:18 +00:00
$ancestors [] = '' ;
2007-09-10 12:21:30 +00:00
foreach ( $ancestors as $key => $router_path ) {
if ( isset ( $menu [ $router_path ])) {
2009-04-02 03:40:05 +00:00
// Exit the loop leaving $router_path as the first match.
2007-09-10 12:21:30 +00:00
break ;
}
2007-08-29 20:46:18 +00:00
}
2009-04-02 03:40:05 +00:00
// If we did not find the path, $router_path will be the empty string
// at the end of $ancestors.
2007-08-29 20:46:18 +00:00
}
return $router_path ;
}
2007-07-04 15:49:44 +00:00
/**
2012-08-31 15:56:36 +00:00
* Builds the router table based on the data from hook_menu () .
2007-07-04 15:49:44 +00:00
*/
2013-03-11 10:50:25 +00:00
function _menu_router_build ( $callbacks , $save = FALSE ) {
2007-05-16 13:45:17 +00:00
// First pass: separate callbacks from paths, making paths ready for
// matching. Calculate fitness, and fill some default values.
$menu = array ();
2009-04-12 19:52:38 +00:00
$masks = array ();
2013-03-11 10:50:25 +00:00
$path_roots = array ();
2007-05-16 13:45:17 +00:00
foreach ( $callbacks as $path => $item ) {
$load_functions = array ();
$to_arg_functions = array ();
$fit = 0 ;
$move = FALSE ;
$parts = explode ( '/' , $path , MENU_MAX_PARTS );
2013-03-11 10:50:25 +00:00
$path_roots [ $parts [ 0 ]] = $parts [ 0 ];
2007-05-16 13:45:17 +00:00
$number_parts = count ( $parts );
// We store the highest index of parts here to save some work in the fit
// calculation loop.
$slashes = $number_parts - 1 ;
2007-07-04 15:49:44 +00:00
// Extract load and to_arg functions.
2007-05-16 13:45:17 +00:00
foreach ( $parts as $k => $part ) {
$match = FALSE ;
2008-10-14 13:31:38 +00:00
// Look for wildcards in the form allowed to be used in PHP functions,
// because we are using these to construct the load function names.
2010-11-19 20:35:31 +00:00
if ( preg_match ( '/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/' , $part , $matches )) {
2007-05-16 13:45:17 +00:00
if ( empty ( $matches [ 1 ])) {
$match = TRUE ;
$load_functions [ $k ] = NULL ;
}
else {
2009-08-24 00:14:23 +00:00
if ( function_exists ( $matches [ 1 ] . '_to_arg' )) {
2008-04-14 17:48:46 +00:00
$to_arg_functions [ $k ] = $matches [ 1 ] . '_to_arg' ;
2007-05-16 13:45:17 +00:00
$load_functions [ $k ] = NULL ;
$match = TRUE ;
}
2009-08-24 00:14:23 +00:00
if ( function_exists ( $matches [ 1 ] . '_load' )) {
2008-04-14 17:48:46 +00:00
$function = $matches [ 1 ] . '_load' ;
2007-10-17 14:46:34 +00:00
// Create an array of arguments that will be passed to the _load
// function when this menu path is checked, if 'load arguments'
// exists.
$load_functions [ $k ] = isset ( $item [ 'load arguments' ]) ? array ( $function => $item [ 'load arguments' ]) : $function ;
2007-05-16 13:45:17 +00:00
$match = TRUE ;
}
}
}
if ( $match ) {
$parts [ $k ] = '%' ;
}
else {
$fit |= 1 << ( $slashes - $k );
}
}
if ( $fit ) {
$move = TRUE ;
}
else {
// If there is no %, it fits maximally.
$fit = ( 1 << $number_parts ) - 1 ;
}
2007-08-11 14:06:15 +00:00
$masks [ $fit ] = 1 ;
2010-11-21 08:53:18 +00:00
$item [ '_load_functions' ] = $load_functions ;
2007-05-16 13:45:17 +00:00
$item [ 'to_arg_functions' ] = empty ( $to_arg_functions ) ? '' : serialize ( $to_arg_functions );
$item += array (
'title' => '' ,
'type' => MENU_NORMAL_ITEM ,
2010-09-16 17:28:24 +00:00
'module' => '' ,
2007-05-16 13:45:17 +00:00
'_number_parts' => $number_parts ,
'_parts' => $parts ,
'_fit' => $fit ,
);
$item += array (
2013-02-18 17:03:05 +00:00
// Default MENU_DEFAULT_LOCAL_TASKs to a weight of -10, so they appear as
// first tab by default.
'weight' => ( $item [ 'type' ] & MENU_DEFAULT_LOCAL_TASK ) == MENU_DEFAULT_LOCAL_TASK ? - 10 : 0 ,
2010-05-06 05:59:31 +00:00
'_visible' => ( bool ) ( $item [ 'type' ] & MENU_VISIBLE_IN_BREADCRUMB ),
'_tab' => ( bool ) ( $item [ 'type' ] & MENU_IS_LOCAL_TASK ),
2007-05-16 13:45:17 +00:00
);
if ( $move ) {
$new_path = implode ( '/' , $item [ '_parts' ]);
$menu [ $new_path ] = $item ;
$sort [ $new_path ] = $number_parts ;
}
else {
$menu [ $path ] = $item ;
$sort [ $path ] = $number_parts ;
}
}
array_multisort ( $sort , SORT_NUMERIC , $menu );
// Apply inheritance rules.
foreach ( $menu as $path => $v ) {
$item = & $menu [ $path ];
if ( ! $item [ '_tab' ]) {
2007-07-04 21:33:55 +00:00
// Non-tab items.
2007-05-16 13:45:17 +00:00
$item [ 'tab_parent' ] = '' ;
$item [ 'tab_root' ] = $path ;
}
2009-10-17 05:50:29 +00:00
// If not specified, assign the default tab type for local tasks.
elseif ( ! isset ( $item [ 'context' ])) {
$item [ 'context' ] = MENU_CONTEXT_PAGE ;
}
2007-05-16 13:45:17 +00:00
for ( $i = $item [ '_number_parts' ] - 1 ; $i ; $i -- ) {
$parent_path = implode ( '/' , array_slice ( $item [ '_parts' ], 0 , $i ));
if ( isset ( $menu [ $parent_path ])) {
2009-12-14 20:23:01 +00:00
$parent = & $menu [ $parent_path ];
// If we have no menu name, try to inherit it from parent items.
if ( ! isset ( $item [ 'menu_name' ])) {
// If the parent item of this item does not define a menu name (and no
// previous iteration assigned one already), try to find the menu name
// of the parent item in the currently stored menu links.
if ( ! isset ( $parent [ 'menu_name' ])) {
2010-05-17 18:47:25 +00:00
$menu_name = db_query ( " SELECT menu_name FROM { menu_links} WHERE router_path = :router_path AND module = 'system' " , array ( ':router_path' => $parent_path )) -> fetchField ();
2009-12-14 20:23:01 +00:00
if ( $menu_name ) {
$parent [ 'menu_name' ] = $menu_name ;
}
}
// If the parent item defines a menu name, inherit it.
if ( ! empty ( $parent [ 'menu_name' ])) {
$item [ 'menu_name' ] = $parent [ 'menu_name' ];
}
}
2007-05-16 13:45:17 +00:00
if ( ! isset ( $item [ 'tab_parent' ])) {
2007-07-04 21:33:55 +00:00
// Parent stores the parent of the path.
2007-05-16 13:45:17 +00:00
$item [ 'tab_parent' ] = $parent_path ;
}
if ( ! isset ( $item [ 'tab_root' ]) && ! $parent [ '_tab' ]) {
$item [ 'tab_root' ] = $parent_path ;
}
2008-04-23 20:01:56 +00:00
// If an access callback is not found for a default local task we use
// the callback from the parent, since we expect them to be identical.
// In all other cases, the access parameters must be specified.
if (( $item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK ) && ! isset ( $item [ 'access callback' ]) && isset ( $parent [ 'access callback' ])) {
2007-05-16 13:45:17 +00:00
$item [ 'access callback' ] = $parent [ 'access callback' ];
if ( ! isset ( $item [ 'access arguments' ]) && isset ( $parent [ 'access arguments' ])) {
$item [ 'access arguments' ] = $parent [ 'access arguments' ];
}
}
// Same for page callbacks.
if ( ! isset ( $item [ 'page callback' ]) && isset ( $parent [ 'page callback' ])) {
$item [ 'page callback' ] = $parent [ 'page callback' ];
if ( ! isset ( $item [ 'page arguments' ]) && isset ( $parent [ 'page arguments' ])) {
$item [ 'page arguments' ] = $parent [ 'page arguments' ];
}
2009-08-24 00:14:23 +00:00
if ( ! isset ( $item [ 'file path' ]) && isset ( $parent [ 'file path' ])) {
$item [ 'file path' ] = $parent [ 'file path' ];
}
2010-04-26 14:06:23 +00:00
if ( ! isset ( $item [ 'file' ]) && isset ( $parent [ 'file' ])) {
$item [ 'file' ] = $parent [ 'file' ];
if ( empty ( $item [ 'file path' ]) && isset ( $item [ 'module' ]) && isset ( $parent [ 'module' ]) && $item [ 'module' ] != $parent [ 'module' ]) {
$item [ 'file path' ] = drupal_get_path ( 'module' , $parent [ 'module' ]);
}
}
2007-05-16 13:45:17 +00:00
}
2009-09-30 13:09:30 +00:00
// Same for theme callbacks.
if ( ! isset ( $item [ 'theme callback' ]) && isset ( $parent [ 'theme callback' ])) {
$item [ 'theme callback' ] = $parent [ 'theme callback' ];
if ( ! isset ( $item [ 'theme arguments' ]) && isset ( $parent [ 'theme arguments' ])) {
$item [ 'theme arguments' ] = $parent [ 'theme arguments' ];
}
}
2010-12-02 17:34:24 +00:00
// Same for load arguments: if a loader doesn't have any explict
// arguments, try to find arguments in the parent.
if ( ! isset ( $item [ 'load arguments' ])) {
foreach ( $item [ '_load_functions' ] as $k => $function ) {
// This loader doesn't have any explict arguments...
if ( ! is_array ( $function )) {
// ... check the parent for a loader at the same position
// using the same function name and defining arguments...
2010-12-02 20:18:23 +00:00
if ( isset ( $parent [ '_load_functions' ][ $k ]) && is_array ( $parent [ '_load_functions' ][ $k ]) && key ( $parent [ '_load_functions' ][ $k ]) === $function ) {
2010-12-02 17:34:24 +00:00
// ... and inherit the arguments on the child.
2010-12-02 20:18:23 +00:00
$item [ '_load_functions' ][ $k ] = $parent [ '_load_functions' ][ $k ];
2010-12-02 17:34:24 +00:00
}
}
}
2010-11-21 08:53:18 +00:00
}
2007-05-16 13:45:17 +00:00
}
}
2007-12-26 19:02:24 +00:00
if ( ! isset ( $item [ 'access callback' ]) && isset ( $item [ 'access arguments' ])) {
// Default callback.
$item [ 'access callback' ] = 'user_access' ;
}
2007-05-16 13:45:17 +00:00
if ( ! isset ( $item [ 'access callback' ]) || empty ( $item [ 'page callback' ])) {
$item [ 'access callback' ] = 0 ;
}
if ( is_bool ( $item [ 'access callback' ])) {
$item [ 'access callback' ] = intval ( $item [ 'access callback' ]);
}
2010-11-21 08:53:18 +00:00
$item [ 'load_functions' ] = empty ( $item [ '_load_functions' ]) ? '' : serialize ( $item [ '_load_functions' ]);
2007-05-16 13:45:17 +00:00
$item += array (
'access arguments' => array (),
'access callback' => '' ,
'page arguments' => array (),
'page callback' => '' ,
'title arguments' => array (),
'title callback' => 't' ,
2009-09-30 13:09:30 +00:00
'theme arguments' => array (),
'theme callback' => '' ,
2007-05-16 13:45:17 +00:00
'description' => '' ,
2012-07-18 10:03:02 +00:00
'description arguments' => array (),
'description callback' => 't' ,
2007-05-16 13:45:17 +00:00
'position' => '' ,
2009-10-17 05:50:29 +00:00
'context' => 0 ,
2007-05-16 13:45:17 +00:00
'tab_parent' => '' ,
'tab_root' => $path ,
'path' => $path ,
2009-08-24 00:14:23 +00:00
'file' => '' ,
'file path' => '' ,
'include file' => '' ,
2013-05-02 04:09:48 +00:00
'route_name' => '' ,
2007-05-16 13:45:17 +00:00
);
2009-08-24 00:14:23 +00:00
// Calculate out the file to be included for each callback, if any.
if ( $item [ 'file' ]) {
$file_path = $item [ 'file path' ] ? $item [ 'file path' ] : drupal_get_path ( 'module' , $item [ 'module' ]);
$item [ 'include file' ] = $file_path . '/' . $item [ 'file' ];
}
2009-04-12 19:52:38 +00:00
}
// Sort the masks so they are in order of descending fit.
$masks = array_keys ( $masks );
rsort ( $masks );
2013-03-11 10:50:25 +00:00
if ( $save ) {
$path_roots = array_values ( $path_roots );
// Update the path roots variable and reset the path alias whitelist cache
// if the list has changed.
2013-06-05 14:24:40 +00:00
if ( $path_roots != Drupal :: state () -> get ( 'menu_path_roots' )) {
Drupal :: state () -> set ( 'menu_path_roots' , array_values ( $path_roots ));
2013-03-11 10:50:25 +00:00
drupal_container () -> get ( 'path.alias_manager' ) -> cacheClear ();
}
_menu_router_save ( $menu , $masks );
}
2009-04-12 19:52:38 +00:00
return array ( $menu , $masks );
}
/**
2012-08-31 15:56:36 +00:00
* Saves data from menu_router_build () to the router table .
2009-04-12 19:52:38 +00:00
*/
function _menu_router_save ( $menu , $masks ) {
// Delete the existing router since we have some data to replace it.
2009-12-04 16:47:15 +00:00
db_truncate ( 'menu_router' ) -> execute ();
2007-05-22 05:52:17 +00:00
2009-04-12 19:52:38 +00:00
// Prepare insert object.
$insert = db_insert ( 'menu_router' )
-> fields ( array (
'path' ,
'load_functions' ,
'to_arg_functions' ,
'access_callback' ,
'access_arguments' ,
'page_callback' ,
'page_arguments' ,
'fit' ,
'number_parts' ,
2009-10-17 05:50:29 +00:00
'context' ,
2009-04-12 19:52:38 +00:00
'tab_parent' ,
'tab_root' ,
'title' ,
'title_callback' ,
'title_arguments' ,
2009-09-30 13:09:30 +00:00
'theme_callback' ,
'theme_arguments' ,
2009-04-12 19:52:38 +00:00
'type' ,
'description' ,
2012-07-18 10:03:02 +00:00
'description_callback' ,
'description_arguments' ,
2009-04-12 19:52:38 +00:00
'position' ,
'weight' ,
2010-04-10 17:30:15 +00:00
'include_file' ,
2013-05-02 04:09:48 +00:00
'route_name' ,
2009-04-12 19:52:38 +00:00
));
2010-01-03 01:32:41 +00:00
$num_records = 0 ;
2009-04-12 19:52:38 +00:00
foreach ( $menu as $path => $item ) {
2008-12-03 14:38:59 +00:00
// Fill in insert object values.
$insert -> values ( array (
'path' => $item [ 'path' ],
'load_functions' => $item [ 'load_functions' ],
'to_arg_functions' => $item [ 'to_arg_functions' ],
'access_callback' => $item [ 'access callback' ],
'access_arguments' => serialize ( $item [ 'access arguments' ]),
'page_callback' => $item [ 'page callback' ],
'page_arguments' => serialize ( $item [ 'page arguments' ]),
'fit' => $item [ '_fit' ],
'number_parts' => $item [ '_number_parts' ],
2009-10-17 05:50:29 +00:00
'context' => $item [ 'context' ],
2008-12-03 14:38:59 +00:00
'tab_parent' => $item [ 'tab_parent' ],
'tab_root' => $item [ 'tab_root' ],
'title' => $item [ 'title' ],
'title_callback' => $item [ 'title callback' ],
'title_arguments' => ( $item [ 'title arguments' ] ? serialize ( $item [ 'title arguments' ]) : '' ),
2009-09-30 13:09:30 +00:00
'theme_callback' => $item [ 'theme callback' ],
'theme_arguments' => serialize ( $item [ 'theme arguments' ]),
2008-12-03 14:38:59 +00:00
'type' => $item [ 'type' ],
'description' => $item [ 'description' ],
2012-07-18 10:03:02 +00:00
'description_callback' => $item [ 'description callback' ],
'description_arguments' => ( $item [ 'description arguments' ] ? serialize ( $item [ 'description arguments' ]) : '' ),
2008-12-03 14:38:59 +00:00
'position' => $item [ 'position' ],
'weight' => $item [ 'weight' ],
2010-04-10 17:30:15 +00:00
'include_file' => $item [ 'include file' ],
2013-05-02 04:09:48 +00:00
'route_name' => $item [ 'route_name' ],
2008-12-03 14:38:59 +00:00
));
2010-01-03 01:32:41 +00:00
// Execute in batches to avoid the memory overhead of all of those records
// in the query object.
if ( ++ $num_records == 20 ) {
$insert -> execute ();
$num_records = 0 ;
}
2008-12-03 14:38:59 +00:00
}
2010-01-03 01:32:41 +00:00
// Insert any remaining records.
2008-12-03 14:38:59 +00:00
$insert -> execute ();
2009-04-12 19:52:38 +00:00
// Store the masks.
2013-06-05 14:24:40 +00:00
Drupal :: state () -> set ( 'menu.masks' , $masks );
2009-04-02 03:40:05 +00:00
2007-05-16 13:45:17 +00:00
return $menu ;
}
2007-05-26 10:54:12 +00:00
/**
2009-08-22 18:24:14 +00:00
* Checks whether the site is in maintenance mode .
2008-01-02 14:29:32 +00:00
*
* This function will log the current user out and redirect to front page
2009-08-22 18:24:14 +00:00
* if the current user has no 'access site in maintenance mode' permission .
2008-01-02 14:29:32 +00:00
*
2009-09-30 13:09:30 +00:00
* @ param $check_only
* If this is set to TRUE , the function will perform the access checks and
* return the site offline status , but not log the user out or display any
* messages .
2010-03-26 17:14:46 +00:00
*
2008-01-02 14:29:32 +00:00
* @ return
2009-08-22 18:24:14 +00:00
* FALSE if the site is not in maintenance mode , the user login page is
* displayed , or the user has the 'access site in maintenance mode'
* permission . TRUE for anonymous users not being on the login page when the
* site is in maintenance mode .
2007-05-26 10:54:12 +00:00
*/
2009-09-30 13:09:30 +00:00
function _menu_site_is_offline ( $check_only = FALSE ) {
2009-08-22 18:24:14 +00:00
// Check if site is in maintenance mode.
2012-07-28 13:39:43 +00:00
if ( config ( 'system.maintenance' ) -> get ( 'enabled' )) {
2009-08-22 18:24:14 +00:00
if ( user_access ( 'access site in maintenance mode' )) {
// Ensure that the maintenance mode message is displayed only once
// (allowing for page redirects) and specifically suppress its display on
// the maintenance mode settings page.
2012-04-29 15:16:27 +00:00
if ( ! $check_only && current_path () != 'admin/config/development/maintenance' ) {
2009-08-22 18:24:14 +00:00
if ( user_access ( 'administer site configuration' )) {
drupal_set_message ( t ( 'Operating in maintenance mode. <a href="@url">Go online.</a>' , array ( '@url' => url ( 'admin/config/development/maintenance' ))), 'status' , FALSE );
}
else {
drupal_set_message ( t ( 'Operating in maintenance mode.' ), 'status' , FALSE );
}
2008-02-10 07:35:40 +00:00
}
2008-01-02 14:29:32 +00:00
}
else {
2010-07-07 08:05:01 +00:00
return TRUE ;
2007-05-26 10:54:12 +00:00
}
}
return FALSE ;
}
2008-01-03 09:59:00 +00:00
2008-01-28 16:05:17 +00:00
/**
* @ } End of " defgroup menu " .
*/