2002-12-24 15:40:32 +00:00
< ? php
2005-08-11 12:57:41 +00:00
// $Id$
2003-12-27 21:29:20 +00:00
2004-07-22 16:06:54 +00:00
/**
* @ file
* API for the Drupal menu system .
*/
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
* menu system is fundamental to the creation of complex modules .
*
* Drupal ' s menu system follows a simple hierarchy defined by paths .
* Implementations of hook_menu () define menu items and assign them to
* paths ( which should be unique ) . The menu system aggregates these items
* and determines the menu hierarchy from the paths . For example , if the
* paths defined were a , a / b , e , a / b / c / d , f / g , and a / b / h , the menu system
* would form the structure :
* - a
* - a / b
* - a / b / c / d
* - a / b / h
* - e
* - f / g
* Note that the number of elements in the path does not necessarily
* determine the depth of the menu item in the tree .
*
* When responding to a page request , the menu system looks to see if the
* path requested by the browser is registered as a menu item with a
* callback . If not , the system searches up the menu tree for the most
* complete match with a callback it can find . If the path a / b / i is
* requested in the tree above , the callback for a / b would be used .
*
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 ,
* then access is granted ; if FALSE , then access is denied . Menu items may
* omit this attribute to use the value provided by an ancestor item .
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
/**
2004-09-09 05:51:08 +00:00
* @ name 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 .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_IS_ROOT' , 0x0001 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is visible in the menu tree .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_VISIBLE_IN_TREE' , 0x0002 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is visible in the breadcrumb .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_VISIBLE_IN_BREADCRUMB' , 0x0004 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item links back to its parnet .
*/
2007-06-05 09:15:02 +00:00
define ( 'MENU_LINKS_TO_PARENT' , 0x0008 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item can be modified by administrator .
*/
2007-06-05 09:15:02 +00:00
define ( 'MENU_MODIFIED_BY_ADMIN' , 0x0020 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item was created by administrator .
*/
2007-06-05 09:15:02 +00:00
define ( 'MENU_CREATED_BY_ADMIN' , 0x0040 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu flag -- menu item is a local task .
*/
2007-06-05 09:15:02 +00:00
define ( '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 .
*/
define ( 'MENU_IS_LOCAL_ACTION' , 0x0100 );
2004-07-10 15:51:48 +00:00
/**
2004-09-09 05:51:08 +00:00
* @ } End of " Menu flags " .
2004-07-10 15:51:48 +00:00
*/
/**
2004-09-09 05:51:08 +00:00
* @ name Menu item types
2004-07-10 15:51:48 +00:00
* @ {
* Menu item definitions provide one of these constants , which are shortcuts for
* combinations of the above flags .
*/
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
2004-07-10 18:10:36 +00:00
* when the URL is accessed . They are not shown in the menu .
2004-06-18 15:04:37 +00:00
*/
define ( 'MENU_CALLBACK' , MENU_VISIBLE_IN_BREADCRUMB );
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
*/
define ( 'MENU_LOCAL_TASK' , MENU_IS_LOCAL_TASK );
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 .
*/
define ( 'MENU_DEFAULT_LOCAL_TASK' , MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT );
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 .
*/
define ( 'MENU_LOCAL_ACTION' , MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION );
2004-06-18 15:04:37 +00:00
/**
2004-09-09 05:51:08 +00:00
* @ } End of " Menu item types " .
2004-07-10 15:51:48 +00:00
*/
/**
* @ name Menu status codes
* @ {
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 found .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_FOUND' , 1 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item was not found .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_NOT_FOUND' , 2 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item access is denied .
*/
2004-06-18 15:04:37 +00:00
define ( 'MENU_ACCESS_DENIED' , 3 );
2008-05-26 17:12:55 +00:00
/**
* Internal menu status code -- Menu item inaccessible because site is offline .
*/
2005-10-08 12:38:20 +00:00
define ( 'MENU_SITE_OFFLINE' , 4 );
2004-09-09 05:51:08 +00:00
2004-07-10 15:51:48 +00:00
/**
2004-09-09 05:51:08 +00:00
* @ } End of " 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
/**
2007-05-16 13:45:17 +00:00
* @ Name Menu tree parameters
2007-04-06 04:39:51 +00:00
* @ {
2007-05-16 13:45:17 +00:00
* 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
*/
2009-08-19 23:29:13 +00:00
define ( '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 .
2007-04-06 04:39:51 +00:00
*/
2007-08-11 14:06:15 +00:00
define ( 'MENU_MAX_DEPTH' , 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
* @ } End of " Menu tree parameters " .
2007-02-11 09:30:51 +00:00
*/
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
2008-12-20 18:24:41 +00:00
* actually exists . This list of 'masks' is built in menu_rebuild () .
2007-01-24 14:48:36 +00:00
*
* @ param $parts
* An array of path parts , for the above example
* array ( 'node' , '12345' , 'edit' ) .
* @ return
* An array which contains the ancestors and placeholders . Placeholders
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 ;
$masks = variable_get ( 'menu_masks' , array ());
// Only examine patterns that actually exist as router items (the masks).
foreach ( $masks as $i ) {
if ( $i > $end ) {
// Only look at masks that are not longer than the path of interest.
continue ;
}
elseif ( $i < ( 1 << $length )) {
// We have exhausted the masks of a given length, so decrease the length.
-- $length ;
}
2007-01-24 14:48:36 +00:00
$current = '' ;
for ( $j = $length ; $j >= 0 ; $j -- ) {
if ( $i & ( 1 << $j )) {
$current .= $parts [ $length - $j ];
}
else {
$current .= '%' ;
}
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
}
/**
2007-01-24 14:48:36 +00:00
* The menu system uses serialized arrays stored in the database for
* arguments . However , often these need to change according to the
* current path . This function unserializes such an array and does the
* necessary change .
2004-07-06 07:33:59 +00:00
*
2007-01-24 14:48:36 +00:00
* Integer values are mapped according to the $map parameter . For
2007-05-16 13:45:17 +00:00
* example , if unserialize ( $data ) is array ( 'view' , 1 ) and $map is
* array ( 'node' , '12345' ) then 'view' will not be changed because
* it is not an integer , but 1 will as it is an integer . As $map [ 1 ]
* is '12345' , 1 will be replaced with '12345' . So the result will
* be array ( 'node_load' , '12345' ) .
2006-01-19 08:58:00 +00:00
*
2007-01-24 14:48:36 +00:00
* @ param @ data
* A serialized array .
* @ param @ map
* An array of potential replacements .
2006-01-19 08:58:00 +00:00
* @ return
2007-01-24 14:48:36 +00:00
* The $data array unserialized and mapped .
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
* The router item . Usually you take a router entry from menu_get_item and
* set it back either modified or to a different path . This lets you modify the
* navigation block , the page title , the breadcrumb and the page help in one
* call .
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 );
}
/**
* Get a router item .
*
* @ param $path
* The path , for example node / 5. The function will find the corresponding
* node /% item and return that .
* @ param $router_item
* Internal use only .
* @ return
* The router item , an associate array corresponding to one row in the
* menu_router table . The value of key map holds the loaded objects . The
* value of key access is TRUE if the current user can access this page .
2009-09-30 13:09:30 +00:00
* The values for key title , page_arguments , access_arguments , and
* theme_arguments will be filled in based on the database values and the
* objects loaded .
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 )) {
$path = $_GET [ 'q' ];
}
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 ])) {
2007-03-12 13:01:10 +00:00
$original_map = arg ( NULL , $path );
2007-05-16 13:45:17 +00:00
$parts = array_slice ( $original_map , 0 , MENU_MAX_PARTS );
2008-12-03 14:38:59 +00:00
$ancestors = menu_get_ancestors ( $parts );
$router_item = db_select ( 'menu_router' )
-> fields ( 'menu_router' )
-> condition ( 'path' , $ancestors , 'IN' )
-> orderBy ( 'fit' , 'DESC' )
-> range ( 0 , 1 )
2009-05-14 08:35:58 +00:00
-> addTag ( 'menu_get_item' )
2008-12-03 14:38:59 +00:00
-> execute () -> fetchAssoc ();
if ( $router_item ) {
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-05-16 13:45:17 +00:00
* Execute the page callback associated with the current path
2004-06-18 15:04:37 +00:00
*/
2007-05-27 20:31:13 +00:00
function menu_execute_active_handler ( $path = NULL ) {
2007-05-26 10:54:12 +00:00
if ( _menu_site_is_offline ()) {
return MENU_SITE_OFFLINE ;
2007-06-05 09:15:02 +00:00
}
2008-10-08 01:42:16 +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.
if ( variable_get ( 'menu_rebuild_needed' , FALSE ) || ! variable_get ( 'menu_masks' , array ())) {
2008-01-03 10:51:30 +00:00
menu_rebuild ();
}
2007-06-05 09:15:02 +00:00
if ( $router_item = menu_get_item ( $path )) {
2007-05-27 20:31:13 +00:00
if ( $router_item [ 'access' ]) {
2009-08-24 00:14:23 +00:00
if ( $router_item [ 'file' ]) {
2009-09-11 15:05:42 +00:00
require_once DRUPAL_ROOT . '/' . $router_item [ 'file' ];
2007-05-22 05:52:17 +00:00
}
2009-08-24 00:14:23 +00:00
return call_user_func_array ( $router_item [ 'page_callback' ], $router_item [ 'page_arguments' ]);
2007-05-22 05:52:17 +00:00
}
else {
return MENU_ACCESS_DENIED ;
}
2003-09-26 10:04:09 +00:00
}
2007-01-24 14:48:36 +00:00
return MENU_NOT_FOUND ;
}
2003-09-26 10:04:09 +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
* An array of path arguments ( ex : array ( 'node' , '5' ))
* @ 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.
if ( $load_functions_unserialized = unserialize ( $load_functions )) {
$load_functions = $load_functions_unserialized ;
}
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.
if ( $return === FALSE ) {
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 ;
}
/**
* Check access to a menu item using the access callback
*
* @ 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
* An array of path arguments ( ex : array ( 'node' , '5' ))
* @ 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 )) {
2007-08-29 20:46:18 +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
}
2009-08-24 00:14:23 +00:00
elseif ( function_exists ( $callback )) {
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
/**
2008-01-10 20:04:19 +00:00
* Localize 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 .
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 .
* $item [ 'description' ] 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 ) {
2007-05-27 20:31:13 +00:00
$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.
if ( $callback == 't' ) {
if ( empty ( $item [ 'title_arguments' ])) {
$item [ 'title' ] = t ( $item [ 'title' ]);
}
else {
$item [ 'title' ] = t ( $item [ 'title' ], menu_unserialize ( $item [ 'title_arguments' ], $map ));
}
2007-04-30 17:03:29 +00:00
}
2009-08-24 00:14:23 +00:00
elseif ( $callback && function_exists ( $callback )) {
2008-01-10 20:04:19 +00:00
if ( empty ( $item [ 'title_arguments' ])) {
$item [ 'title' ] = $callback ( $item [ 'title' ]);
}
else {
$item [ 'title' ] = call_user_func_array ( $callback , menu_unserialize ( $item [ 'title_arguments' ], $map ));
}
2008-01-22 17:19:28 +00:00
// Avoid calling check_plain again on l() function.
if ( $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' ];
2007-05-27 20:31:13 +00:00
$item [ 'description' ] = t ( $item [ 'description' ]);
2008-05-20 20:32:03 +00:00
if ( $link_translate && isset ( $item [ 'options' ][ 'attributes' ][ 'title' ]) && $item [ 'options' ][ 'attributes' ][ 'title' ] == $original_description ) {
2008-02-04 12:07:23 +00:00
$item [ 'localized_options' ][ 'attributes' ][ 'title' ] = $item [ 'description' ];
2008-01-10 20:04:19 +00:00
}
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
* An array of path arguments ( ex : array ( 'node' , '5' ))
* @ 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 .
* @ 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
* a non existing node ) then this function return FALSE .
*/
2007-05-27 20:31:13 +00:00
function _menu_translate ( & $router_item , $map , $to_arg = FALSE ) {
2009-02-09 16:27:35 +00:00
if ( $to_arg ) {
// 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 ;
2007-05-27 20:31:13 +00:00
if ( ! _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' ]);
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 ];
}
}
2007-05-27 20:31:13 +00:00
$router_item [ 'href' ] = implode ( '/' , $link_map );
2007-09-07 20:31:02 +00:00
$router_item [ 'options' ] = array ();
2007-05-27 20:31:13 +00:00
_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 ;
}
/**
* This function translates the path elements in the map using any to_arg
* helper function . These functions take an argument and return an object .
* See http :// drupal . org / node / 109153 for more information .
*
2009-07-20 01:28:16 +00:00
* @ param $map
2007-05-16 13:45:17 +00:00
* An array of path arguments ( ex : array ( 'node' , '5' ))
* @ param $to_arg_functions
2007-08-25 10:29:18 +00:00
* An array of helper function ( ex : array ( 2 => 'menu_tail_to_arg' ))
2007-05-16 13:45:17 +00:00
*/
function _menu_link_map_translate ( & $map , $to_arg_functions ) {
if ( $to_arg_functions ) {
$to_arg_functions = unserialize ( $to_arg_functions );
foreach ( $to_arg_functions as $index => $function ) {
// Translate place-holders into real values.
2007-08-25 10:29:18 +00:00
$arg = $function ( ! empty ( $map [ $index ]) ? $map [ $index ] : '' , $map , $index );
2007-05-16 13:45:17 +00:00
if ( ! empty ( $map [ $index ]) || isset ( $arg )) {
$map [ $index ] = $arg ;
}
else {
unset ( $map [ $index ]);
}
}
}
}
2007-08-25 10:29:18 +00:00
function menu_tail_to_arg ( $arg , $map , $index ) {
return implode ( '/' , array_slice ( $map , $index ));
}
2007-05-16 13:45:17 +00:00
/**
* This function is similar to _menu_translate () but does link - specific
* preparation such as always calling to_arg functions
*
* @ param $item
2007-05-27 20:31:13 +00:00
* A menu link
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
*/
function _menu_link_translate ( & $item ) {
2007-12-27 14:03:37 +00:00
$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 {
2007-05-27 20:31:13 +00:00
$map = explode ( '/' , $item [ 'link_path' ]);
_menu_link_map_translate ( $map , $item [ 'to_arg_functions' ]);
$item [ 'href' ] = implode ( '/' , $map );
2007-05-16 13:45:17 +00:00
2007-07-04 21:33:55 +00:00
// Note - skip callbacks without real values for their 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' ])) {
if ( ! _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 ;
}
2007-05-16 13:45:17 +00:00
_menu_check_access ( $item , $map );
}
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
}
2007-12-20 09:20:41 +00:00
/**
* Get a loaded object from a router item .
*
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
/**
2007-07-16 12:51:03 +00:00
* Render a menu tree based on the current path .
*
* The tree is expanded based on the current path and dynamic paths are also
* changed according to the defined to_arg functions ( for example the 'My account'
* link is changed from user /% to a link with the current user ' s uid ) .
2007-05-16 13:45:17 +00:00
*
* @ param $menu_name
* The name of the menu .
* @ return
* The rendered HTML of that menu on the current page .
*/
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 .
* @ 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 ) {
2007-05-27 20:31:13 +00:00
if ( ! $data [ 'link' ][ 'hidden' ]) {
2007-10-08 14:15:09 +00:00
$items [] = $data ;
}
}
2007-10-12 14:10:18 +00:00
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
}
2009-09-18 10:54:20 +00:00
// Set a class if the link has children.
2007-10-08 14:15:09 +00:00
if ( $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' ;
$data [ 'localized_options' ][ 'attributes' ][ 'class' ][] = 'active-trail' ;
}
$element [ '#theme' ] = 'menu_link' ;
$element [ '#attributes' ][ 'class' ] = $class ;
$element [ '#title' ] = $data [ 'link' ][ 'title' ];
$element [ '#href' ] = $data [ 'link' ][ 'href' ];
$element [ '#localized_options' ] = ! empty ( $data [ 'localized_options' ]) ? $data [ 'localized_options' ] : array ();
$element [ '#below' ] = $data [ 'below' ] ? menu_tree_output ( $data [ 'below' ]) : $data [ 'below' ];
$element [ '#original_link' ] = $data [ 'link' ];
// Index using the link's unique mlid.
$build [ $data [ 'link' ][ 'mlid' ]] = $element ;
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.
$build [ '#theme_wrappers' ][] = 'menu_tree' ;
}
return $build ;
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
/**
2007-07-16 12:51:03 +00:00
* Get the data structure representing a named menu tree .
*
* Since this can be the full tree including hidden items , the data returned
* may be used for generating an an admin interface or a select .
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 ());
2007-05-27 20:31:13 +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 ;
// Generate a cache ID (cid) specific for this $menu_name, $item, and depth.
$cid = 'links:' . $menu_name . ':all-cid:' . $mlid . ':' . ( 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}.
2007-05-27 20:31:13 +00:00
$cache = cache_get ( $cid , 'cache_menu' );
if ( $cache && isset ( $cache -> data )) {
2008-03-14 08:51:37 +00:00
// If the cache entry exists, it will just be the cid for the actual data.
// This avoids duplication of large amounts of data.
$cache = cache_get ( $cache -> data , 'cache_menu' );
if ( $cache && isset ( $cache -> data )) {
$data = $cache -> data ;
}
2007-05-27 20:31:13 +00:00
}
2008-03-14 08:51:37 +00:00
// If the tree data was not in the cache, $data will be NULL.
if ( ! isset ( $data )) {
2009-08-24 01:49:41 +00:00
// Build the query using a LEFT JOIN since there is no match in
// {menu_router} for an external link.
2009-08-11 07:45:45 +00:00
$query = db_select ( 'menu_links' , 'ml' , array ( 'fetch' => PDO :: FETCH_ASSOC ));
2008-12-03 14:38:59 +00:00
$query -> leftJoin ( 'menu_router' , 'm' , 'm.path = ml.router_path' );
$query -> fields ( 'ml' );
$query -> fields ( 'm' , array (
'load_functions' ,
'to_arg_functions' ,
'access_callback' ,
'access_arguments' ,
'page_callback' ,
'page_arguments' ,
'title' ,
'title_callback' ,
'title_arguments' ,
2009-09-30 13:09:30 +00:00
'theme_callback' ,
'theme_arguments' ,
2008-12-03 14:38:59 +00:00
'type' ,
'description' ,
));
for ( $i = 1 ; $i <= MENU_MAX_DEPTH ; $i ++ ) {
$query -> orderBy ( 'p' . $i , 'ASC' );
}
$query -> condition ( 'ml.menu_name' , $menu_name );
2009-08-24 01:49:41 +00:00
if ( isset ( $max_depth )) {
$query -> condition ( 'ml.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.
2007-08-11 14:06:15 +00:00
$args = array ( 0 );
for ( $i = 1 ; $i < MENU_MAX_DEPTH ; $i ++ ) {
2009-08-24 01:49:41 +00:00
$args [] = $link [ " p $i " ];
2007-08-11 14:06:15 +00:00
}
2007-05-27 20:31:13 +00:00
$args = array_unique ( $args );
2008-12-03 14:38:59 +00:00
$query -> condition ( 'ml.plid' , $args , 'IN' );
2007-05-27 20:31:13 +00:00
$parents = $args ;
2009-08-24 01:49:41 +00:00
$parents [] = $link [ 'mlid' ];
2007-05-27 20:31:13 +00:00
}
else {
2007-07-04 15:49:44 +00:00
// Get all links in this menu.
2007-05-27 20:31:13 +00:00
$parents = array ();
}
2009-08-24 01:49:41 +00:00
// Select the links from the table, and build an ordered array of links
// using the query result object.
$links = array ();
foreach ( $query -> execute () as $item ) {
$links [] = $item ;
}
$data [ 'tree' ] = menu_tree_data ( $links , $parents );
2007-07-16 12:51:03 +00:00
$data [ 'node_links' ] = array ();
menu_tree_collect_node_links ( $data [ 'tree' ], $data [ 'node_links' ]);
2008-03-14 08:51:37 +00:00
// Cache the data, if it is not already in the cache.
$tree_cid = _menu_tree_cid ( $menu_name , $data );
if ( ! cache_get ( $tree_cid , 'cache_menu' )) {
cache_set ( $tree_cid , $data , 'cache_menu' );
}
// Cache the cid of the (shared) data using the menu and item-specific cid.
cache_set ( $cid , $tree_cid , 'cache_menu' );
2007-05-27 20:31:13 +00:00
}
2007-07-04 15:49:44 +00:00
// Check access for the current user to each item in the tree.
2007-08-11 14:06:15 +00:00
menu_tree_check_access ( $data [ 'tree' ], $data [ 'node_links' ]);
2007-07-16 12:51:03 +00:00
$tree [ $cid ] = $data [ 'tree' ];
2007-05-27 20:31:13 +00:00
}
return $tree [ $cid ];
}
2007-05-16 13:45:17 +00:00
/**
2007-07-16 12:51:03 +00:00
* Get the data structure representing a named menu tree , based on the current page .
*
* 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
* The named menu links to return
2009-08-24 01:49:41 +00:00
* @ param $max_depth
* Optional maximum depth of links to retrieve .
*
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
*/
2009-08-24 01:49:41 +00:00
function menu_tree_page_data ( $menu_name , $max_depth = NULL ) {
2009-04-25 15:19:12 +00:00
$tree = & drupal_static ( __FUNCTION__ , array ());
2007-05-16 13:45:17 +00:00
2007-07-04 15:49:44 +00:00
// Load the menu item corresponding to the current page.
2007-05-16 13:45:17 +00:00
if ( $item = menu_get_item ()) {
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.
2009-08-24 01:49:41 +00:00
$cid = 'links:' . $menu_name . ':page-cid:' . $item [ 'href' ] . ':' . ( int ) $item [ 'access' ] . ':' . ( int ) $max_depth ;
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}.
2007-05-27 20:31:13 +00:00
$cache = cache_get ( $cid , 'cache_menu' );
if ( $cache && isset ( $cache -> data )) {
2008-03-14 08:51:37 +00:00
// If the cache entry exists, it will just be the cid for the actual data.
// This avoids duplication of large amounts of data.
$cache = cache_get ( $cache -> data , 'cache_menu' );
if ( $cache && isset ( $cache -> data )) {
$data = $cache -> data ;
}
2007-05-16 13:45:17 +00:00
}
2008-03-14 08:51:37 +00:00
// If the tree data was not in the cache, $data will be NULL.
if ( ! isset ( $data )) {
2007-07-04 15:49:44 +00:00
// Build and run the query, and build the tree.
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2007-07-04 15:49:44 +00:00
// Check whether a menu link exists that corresponds to the current path.
2008-12-03 14:38:59 +00:00
$args [] = $item [ 'href' ];
2008-01-28 19:09:19 +00:00
if ( drupal_is_front_page ()) {
$args [] = '<front>' ;
}
2008-12-03 14:38:59 +00:00
$parents = db_select ( 'menu_links' )
-> fields ( 'menu_links' , array (
'p1' ,
'p2' ,
'p3' ,
'p4' ,
'p5' ,
'p6' ,
'p7' ,
'p8' ,
))
-> condition ( 'menu_name' , $menu_name )
-> condition ( 'link_path' , $args , 'IN' )
-> execute () -> fetchAssoc ();
2007-07-04 15:49:44 +00:00
2007-05-27 20:31:13 +00:00
if ( empty ( $parents )) {
2007-07-04 15:49:44 +00:00
// If no link exists, we may be on a local task that's not in the links.
2007-07-04 21:33:55 +00:00
// TODO: Handle the case like a local task on a specific node in the menu.
2008-12-03 14:38:59 +00:00
$parents = db_select ( 'menu_links' )
-> fields ( 'menu_links' , array (
'p1' ,
'p2' ,
'p3' ,
'p4' ,
'p5' ,
'p6' ,
'p7' ,
'p8' ,
))
-> condition ( 'menu_name' , $menu_name )
-> condition ( 'link_path' , $item [ 'tab_root' ])
-> execute () -> fetchAssoc ();
2007-05-27 20:31:13 +00:00
}
2007-07-04 15:49:44 +00:00
// We always want all the top-level links with plid == 0.
2007-05-27 20:31:13 +00:00
$parents [] = '0' ;
2007-08-11 14:06:15 +00:00
// Use array_values() so that the indices are numeric for array_merge().
$args = $parents = array_unique ( array_values ( $parents ));
2007-05-27 20:31:13 +00:00
$expanded = variable_get ( 'menu_expanded' , array ());
2007-07-04 15:49:44 +00:00
// Check whether the current menu has any links set to be expanded.
2007-05-27 20:31:13 +00:00
if ( 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 {
2008-12-03 14:38:59 +00:00
$result = db_select ( 'menu_links' , NULL , array ( 'fetch' => PDO :: FETCH_ASSOC ))
-> fields ( 'menu_links' , array ( 'mlid' ))
-> condition ( 'menu_name' , $menu_name )
-> condition ( 'expanded' , 1 )
-> condition ( 'has_children' , 1 )
-> condition ( 'plid' , $args , 'IN' )
-> condition ( 'mlid' , $args , 'NOT IN' )
-> execute ();
2007-08-12 16:12:00 +00:00
$num_rows = FALSE ;
2008-12-03 14:38:59 +00:00
foreach ( $result as $item ) {
2007-05-27 20:31:13 +00:00
$args [] = $item [ 'mlid' ];
2007-08-12 16:12:00 +00:00
$num_rows = TRUE ;
2007-05-27 20:31:13 +00:00
}
2007-08-12 16:12:00 +00:00
} while ( $num_rows );
2007-05-27 20:31:13 +00:00
}
}
else {
2007-07-04 15:49:44 +00:00
// Show only the top-level menu items when access is denied.
2008-12-03 14:38:59 +00:00
$args = array ( 0 );
2007-05-27 20:31:13 +00:00
$parents = array ();
}
2007-07-04 21:33:55 +00:00
// Select the links from the table, and recursively build the tree. We
// LEFT JOIN since there is no match in {menu_router} for an external
2007-08-11 14:06:15 +00:00
// link.
2009-08-11 07:45:45 +00:00
$query = db_select ( 'menu_links' , 'ml' , array ( 'fetch' => PDO :: FETCH_ASSOC ));
2008-12-03 14:38:59 +00:00
$query -> leftJoin ( 'menu_router' , 'm' , 'm.path = ml.router_path' );
$query -> fields ( 'ml' );
$query -> fields ( 'm' , array (
'load_functions' ,
'to_arg_functions' ,
'access_callback' ,
'access_arguments' ,
'page_callback' ,
'page_arguments' ,
'title' ,
'title_callback' ,
'title_arguments' ,
2009-09-30 13:09:30 +00:00
'theme_callback' ,
'theme_arguments' ,
2008-12-03 14:38:59 +00:00
'type' ,
'description' ,
));
for ( $i = 1 ; $i <= MENU_MAX_DEPTH ; $i ++ ) {
$query -> orderBy ( 'p' . $i , 'ASC' );
}
$query -> condition ( 'ml.menu_name' , $menu_name );
$query -> condition ( 'ml.plid' , $args , 'IN' );
2009-08-24 01:49:41 +00:00
if ( isset ( $max_depth )) {
$query -> condition ( 'ml.depth' , $max_depth , '<=' );
}
// Build an ordered array of links using the query result object.
$links = array ();
foreach ( $query -> execute () as $item ) {
$links [] = $item ;
}
$data [ 'tree' ] = menu_tree_data ( $links , $parents );
2007-07-16 12:51:03 +00:00
$data [ 'node_links' ] = array ();
menu_tree_collect_node_links ( $data [ 'tree' ], $data [ 'node_links' ]);
2008-03-14 08:51:37 +00:00
// Cache the data, if it is not already in the cache.
$tree_cid = _menu_tree_cid ( $menu_name , $data );
if ( ! cache_get ( $tree_cid , 'cache_menu' )) {
cache_set ( $tree_cid , $data , 'cache_menu' );
}
// Cache the cid of the (shared) data using the page-specific cid.
cache_set ( $cid , $tree_cid , 'cache_menu' );
2007-05-16 13:45:17 +00:00
}
2007-07-04 15:49:44 +00:00
// Check access for the current user to each item in the tree.
2007-07-16 12:51:03 +00:00
menu_tree_check_access ( $data [ 'tree' ], $data [ 'node_links' ]);
$tree [ $cid ] = $data [ 'tree' ];
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
/**
* Helper function - compute the real cache ID for menu tree data .
*/
function _menu_tree_cid ( $menu_name , $data ) {
2008-04-14 17:48:46 +00:00
return 'links:' . $menu_name . ':tree-data:' . md5 ( serialize ( $data ));
2008-03-14 08:51:37 +00:00
}
2007-07-16 12:51:03 +00:00
/**
* Recursive helper function - collect node links .
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
/**
* Check access and perform other 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 );
2008-12-03 14:38:59 +00:00
$select = db_select ( 'node' );
$select -> addField ( 'node' , 'nid' );
$select -> condition ( 'status' , 1 );
$select -> condition ( 'nid' , $nids , 'IN' );
$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-16 12:51:03 +00:00
return ;
2007-07-04 15:49:44 +00:00
}
/**
* Recursive helper function for menu_tree_check_access ()
*/
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 );
2007-07-16 12:51:03 +00:00
if ( $item [ 'access' ]) {
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
/**
2007-05-16 13:45:17 +00:00
* Build the data representing a menu tree .
2007-03-12 13:01:10 +00:00
*
2009-08-24 01:49:41 +00:00
* @ param $links
* An array of links ( associative arrays ) ordered by p1 .. p9 .
2007-05-16 13:45:17 +00:00
* @ param $parents
* An array of the plid values that represent the path from the current page
* to the root of the menu tree .
2007-03-12 13:01:10 +00:00
* @ param $depth
2009-08-24 01:49:41 +00:00
* The minimum depth of any link in the $links array .
2007-03-12 13:01:10 +00:00
* @ return
2007-07-04 15:49:44 +00:00
* See menu_tree_page_data for a description of the data structure .
*/
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
}
/**
* Recursive helper function to build the data representing a menu tree .
*
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
/**
2009-09-18 10:54:20 +00:00
* Preprocess the rendered tree for theme_menu_tree .
2007-12-06 09:58:34 +00:00
*
* @ ingroup themeable
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
/**
2009-09-18 10:54:20 +00:00
* Theme wrapper for the HTML output for a menu sub - tree .
2007-12-06 09:58:34 +00:00
*
* @ ingroup themeable
2003-12-08 06:32:19 +00:00
*/
2007-03-12 13:01:10 +00:00
function theme_menu_tree ( $tree ) {
2008-04-14 17:48:46 +00:00
return '<ul class="menu">' . $tree . '</ul>' ;
2007-03-12 13:01:10 +00:00
}
/**
2009-09-18 10:54:20 +00:00
* Generate the HTML output for a menu link and submenu .
2007-12-06 09:58:34 +00:00
*
2009-09-18 10:54:20 +00:00
* @ param $element
* Structured array data for a menu link .
2009-06-10 21:52:36 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2009-09-18 10:54:20 +00:00
*/
function theme_menu_link ( array $element ) {
$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' ]);
return '<li' . drupal_attributes ( $element [ '#attributes' ]) . '>' . $output . $sub_menu . " </li> \n " ;
2007-02-11 09:30:51 +00:00
}
2007-07-04 15:49:44 +00:00
/**
* Generate the HTML output for a single local task link .
2007-12-06 09:58:34 +00:00
*
2009-09-18 10:54:20 +00:00
* @ param $link
* A menu link array with 'title' , 'href' , and 'localized_options' keys .
* @ param $active
* A boolean indicating whether the local task is active .
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2007-07-04 15:49:44 +00:00
*/
2007-02-11 09:30:51 +00:00
function theme_menu_local_task ( $link , $active = FALSE ) {
2009-09-18 10:54:20 +00:00
return '<li ' . ( $active ? 'class="active" ' : '' ) . '>' . l ( $link [ 'title' ], $link [ 'href' ], $link [ 'localized_options' ]) . " </li> \n " ;
2003-09-28 10:51:40 +00:00
}
2002-12-24 15:40:32 +00:00
2009-08-22 19:58:28 +00:00
/**
* Generate the HTML output for a single local action link .
*
2009-09-18 10:54:20 +00:00
* @ param $link
* A menu link array with 'title' , 'href' , and 'localized_options' keys .
*
2009-08-22 19:58:28 +00:00
* @ ingroup themeable
*/
function theme_menu_local_action ( $link ) {
2009-09-18 10:54:20 +00:00
return '<li>' . l ( $link [ 'title' ], $link [ 'href' ], $link [ 'localized_options' ]) . " </li> \n " ;
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 ));
$empty_arg = drupal_help_arg ();
2003-12-18 21:13:17 +00:00
2008-11-15 11:45:04 +00:00
foreach ( module_implements ( 'help' ) as $module ) {
$function = $module . '_help' ;
// Lookup help for this path.
if ( $help = $function ( $router_path , $arg )) {
$output .= $help . " \n " ;
}
// Add "more help" link on admin pages if the module provides a
// standalone help page.
2009-06-01 22:07:10 +00:00
if ( $arg [ 0 ] == " admin " && user_access ( 'access administration pages' ) && module_exists ( 'help' ) && $function ( 'admin/help#' . $arg [ 2 ], $empty_arg ) && $help ) {
2008-11-15 11:45:04 +00:00
$output .= theme ( " more_help_link " , url ( 'admin/help/' . $arg [ 2 ]));
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
* to force the custom theme to be initialized from the menu router item for
* the current page .
* @ 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' ))) {
$router_item = menu_get_item ();
if ( ! empty ( $router_item [ 'access' ]) && ! empty ( $router_item [ 'theme_callback' ]) && function_exists ( $router_item [ 'theme_callback' ])) {
$custom_theme = call_user_func_array ( $router_item [ 'theme_callback' ], $router_item [ 'theme_arguments' ]);
}
}
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 );
}
2004-04-15 20:49:42 +00:00
/**
2007-05-16 13:45:17 +00:00
* Build a list of named menus .
2004-04-15 20:49:42 +00:00
*/
2009-04-25 15:19:12 +00:00
function menu_get_names () {
$names = & drupal_static ( __FUNCTION__ );
2007-05-16 13:45:17 +00:00
2009-04-25 15:19:12 +00:00
if ( empty ( $names )) {
2008-12-03 14:38:59 +00:00
$names = db_select ( 'menu_links' )
-> distinct ()
2009-09-09 21:33:00 +00:00
-> fields ( 'menu_links' , array ( 'menu_name' ))
2008-12-03 14:38:59 +00:00
-> orderBy ( 'menu_name' )
-> execute () -> fetchCol ();
2007-03-08 19:03:48 +00:00
}
2007-05-16 13:45:17 +00:00
return $names ;
2007-03-08 19:03:48 +00:00
}
2007-07-25 14:44:03 +00:00
/**
* Return an array containing the names of system - defined ( default ) menus .
*/
function menu_list_system_menus () {
2009-03-20 19:18:11 +00:00
return array ( 'navigation' => 'Navigation' , 'management' => 'Management' , 'user-menu' => 'User menu' , 'main-menu' => 'Main menu' , 'secondary-menu' => 'Secondary menu' );
2007-07-25 14:44:03 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2008-06-25 09:12:25 +00:00
* Return 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 () {
2008-09-18 10:44:19 +00:00
return menu_navigation_links ( variable_get ( 'menu_main_links_source' , 'main-menu' ));
2004-06-18 15:04:37 +00:00
}
2007-08-23 16:41:19 +00:00
2007-07-04 15:49:44 +00:00
/**
* Return an array of links to be rendered as the Secondary links .
*/
2008-06-25 09:12:25 +00:00
function menu_secondary_menu () {
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.
2009-03-20 19:18:11 +00:00
if ( variable_get ( 'menu_secondary_links_source' , 'user-menu' ) == variable_get ( 'menu_main_links_source' , 'main-menu' )) {
2008-09-18 10:44:19 +00:00
return menu_navigation_links ( variable_get ( 'menu_main_links_source' , 'main-menu' ), 1 );
2007-08-20 18:26:41 +00:00
}
else {
2009-03-20 19:18:11 +00:00
return menu_navigation_links ( variable_get ( 'menu_secondary_links_source' , 'user-menu' ), 0 );
2007-08-20 18:26:41 +00:00
}
}
/**
* Return an array of links for a navigation menu .
*
* @ param $menu_name
* The name of the menu .
* @ param $level
* Optional , the depth of the menu to be returned .
* @ 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.
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' ;
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.
2007-02-11 09:30:51 +00:00
* @ return
2009-08-22 19:58:28 +00:00
* An array containing
* - tabs : Local tasks for the requested level :
* - count : The number of local tasks .
* - output : The themed output of local tasks .
* - actions : Action links for the requested level :
* - count : The number of action links .
* - output : The themed output of action links .
* - 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 .
*/
function menu_local_tasks ( $level = 0 ) {
$data = & drupal_static ( __FUNCTION__ );
$root_path = & drupal_static ( __FUNCTION__ . ':root_path' , '' );
$empty = array (
'tabs' => array ( 'count' => 0 , 'output' => '' ),
'actions' => array ( 'count' => 0 , 'output' => '' ),
'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 ();
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
}
2007-06-17 14:55:39 +00:00
// Get all tabs 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' ])
-> orderBy ( 'weight' )
-> orderBy ( 'title' )
-> execute ();
2007-05-16 13:45:17 +00:00
$map = arg ();
$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
}
2007-06-17 14:55:39 +00:00
// Find all tabs below the current path.
2007-05-27 20:31:13 +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 ;
2007-05-16 13:45:17 +00:00
while ( isset ( $children [ $path ])) {
$tabs_current = '' ;
2009-08-22 19:58:28 +00:00
$actions_current = '' ;
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 ) {
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2009-09-18 10:54:20 +00:00
$link = $item ;
2007-05-16 13:45:17 +00:00
// The default task is always active.
2007-05-27 20:31:13 +00:00
if ( $item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK ) {
2009-08-22 19:58:28 +00:00
// Find the first parent which is not a default local task or action.
2007-06-17 14:55:39 +00:00
for ( $p = $item [ 'tab_parent' ]; $tasks [ $p ][ 'type' ] == MENU_DEFAULT_LOCAL_TASK ; $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-05-16 13:45:17 +00:00
$tabs_current .= theme ( 'menu_local_task' , $link , TRUE );
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 {
2009-08-22 19:58:28 +00:00
if ( $item [ 'type' ] == MENU_LOCAL_TASK ) {
$tabs_current .= theme ( 'menu_local_task' , $link );
$tab_count ++ ;
}
else {
$actions_current .= theme ( 'menu_local_action' , $link );
$action_count ++ ;
}
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 ;
2009-08-22 19:58:28 +00:00
$tabs [ $depth ][ 'count' ] = $tab_count ;
2007-10-01 09:49:14 +00:00
$tabs [ $depth ][ 'output' ] = $tabs_current ;
2009-08-22 19:58:28 +00:00
$actions [ 'count' ] = $action_count ;
$actions [ 'output' ] = $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 ])) {
2007-02-11 09:30:51 +00:00
$tabs_current = '' ;
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 ) {
2009-08-22 19:58:28 +00:00
if ( $item [ 'type' ] == MENU_LOCAL_ACTION ) {
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 ;
2007-06-17 14:55:39 +00:00
if ( $item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK ) {
// Find the first parent which is not a default local task.
for ( $p = $item [ 'tab_parent' ]; $tasks [ $p ][ 'type' ] == MENU_DEFAULT_LOCAL_TASK ; $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 ) {
2007-02-11 09:30:51 +00:00
$tabs_current .= theme ( 'menu_local_task' , $link , TRUE );
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 {
$tabs_current .= theme ( 'menu_local_task' , $link );
}
}
2007-01-31 21:26:56 +00:00
}
2007-05-16 13:45:17 +00:00
$path = $next_path ;
$parent = $next_parent ;
2007-10-01 09:49:14 +00:00
$tabs [ $depth ][ 'count' ] = $count ;
$tabs [ $depth ][ 'output' ] = $tabs_current ;
$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 ;
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
}
2009-08-22 19:58:28 +00:00
return $empty ;
2007-02-11 09:30:51 +00:00
}
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.
return ( $links [ 'tabs' ][ 'count' ] > 1 ? $links [ 'tabs' ][ 'output' ] : '' );
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.
return ( $links [ 'tabs' ][ 'count' ] > 1 ? $links [ 'tabs' ][ 'output' ] : '' );
}
/**
* Returns the rendered local actions at the current level .
*/
function menu_local_actions () {
$links = menu_local_tasks ();
return $links [ 'actions' ][ 'output' ];
2004-09-16 07:17:56 +00:00
}
2007-05-27 20:31:13 +00:00
/**
2007-06-30 19:46:58 +00:00
* Returns the router path , or the path of the parent tab of a default local task .
*/
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
}
/**
* Returns the rendered local tasks . The default implementation renders them as tabs .
2007-05-27 20:31:13 +00:00
*
* @ ingroup themeable
*/
function theme_menu_local_tasks () {
2007-07-05 08:48:58 +00:00
$output = '' ;
2007-05-27 20:31:13 +00:00
2007-07-05 08:48:58 +00:00
if ( $primary = menu_primary_local_tasks ()) {
2008-04-14 17:48:46 +00:00
$output .= " <ul class= \" tabs primary \" > \n " . $primary . " </ul> \n " ;
2007-07-05 08:48:58 +00:00
}
if ( $secondary = menu_secondary_local_tasks ()) {
2008-04-14 17:48:46 +00:00
$output .= " <ul class= \" tabs secondary \" > \n " . $secondary . " </ul> \n " ;
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
/**
* Set ( or get ) the active menu for the current page - determines the active trail .
*/
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 )) {
2009-03-20 19:18:11 +00:00
$active = variable_get ( 'menu_default_active_menus' , array ( 'management' , 'navigation' , 'user-menu' , 'main-menu' ));
2007-05-16 13:45:17 +00:00
}
return $active ;
}
2007-07-04 15:49:44 +00:00
/**
* Get the active menu for the current page - determines the active trail .
*/
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
/**
* Set the active path , which determines which page is loaded .
*
* @ param $path
* A Drupal path - not a path alias .
*
* Note that this may not have the desired effect unless invoked very early
* in the page load , such as during hook_boot , or unless you call
* menu_execute_active_handler () to generate your page output .
*/
function menu_set_active_item ( $path ) {
$_GET [ 'q' ] = $path ;
2004-09-16 07:17:56 +00:00
}
2007-07-04 15:49:44 +00:00
/**
2009-10-01 19:07:12 +00:00
* Sets or gets the active trail ( path to root menu root ) of the current page .
*
* @ param $new_trail
* Menu trail to set , or NULL to use previously - set or calculated trail . If
* supplying a trail , use the same format as the return value ( see below ) .
* @ 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 ) .
* If $new_trail is supplied , the value is saved in a static variable and
* returned . If $new_trail is not supplied , and there is a saved value from
* a previous call , the saved value is returned . If $new_trail is not supplied
* and there is no saved value , the path to the current page is calculated ,
* saved as the static value , and returned .
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 ();
2008-02-04 12:07:23 +00:00
$trail [] = array ( 'title' => t ( 'Home' ), 'href' => '<front>' , 'localized_options' => array (), 'type' => 0 );
2007-05-16 13:45:17 +00:00
$item = menu_get_item ();
2007-09-07 20:31:02 +00:00
// Check whether the current item is a local task (displayed as a tab).
2007-05-27 20:31:13 +00:00
if ( $item [ 'tab_parent' ]) {
2007-09-07 20:31:02 +00:00
// The title of a local task is used for the tab, never the page title.
// Thus, replace it with the item corresponding to the root path to get
2008-12-20 18:24:41 +00:00
// the relevant href and title. For example, the menu item corresponding
2007-09-07 20:31:02 +00:00
// to 'admin' is used when on the 'By module' tab at 'admin/by-module'.
$parts = explode ( '/' , $item [ 'tab_root' ]);
$args = arg ();
// Replace wildcards in the root path using the current path.
foreach ( $parts as $index => $part ) {
if ( $part == '%' ) {
$parts [ $index ] = $args [ $index ];
}
}
// Retrieve the menu item using the root path after wildcard replacement.
$root_item = menu_get_item ( implode ( '/' , $parts ));
if ( $root_item && $root_item [ 'access' ]) {
$item = $root_item ;
}
2007-05-16 13:45:17 +00:00
}
2009-03-20 19:18:11 +00:00
$menu_names = menu_get_active_menu_names ();
$curr = FALSE ;
// Determine if the current page is a link in any of the active menus.
if ( $menu_names ) {
$query = db_select ( 'menu_links' , 'ml' );
$query -> fields ( 'ml' , array ( 'menu_name' ));
$query -> condition ( 'ml.link_path' , $item [ 'href' ]);
$query -> condition ( 'ml.menu_name' , $menu_names , 'IN' );
$result = $query -> execute ();
$found = array ();
foreach ( $result as $menu ) {
$found [] = $menu -> menu_name ;
}
// The $menu_names array is ordered, so take the first one that matches.
$name = current ( array_intersect ( $menu_names , $found ));
if ( $name !== FALSE ) {
$tree = menu_tree_page_data ( $name );
list ( $key , $curr ) = each ( $tree );
}
}
2007-05-16 13:45:17 +00:00
while ( $curr ) {
2007-09-07 20:31:02 +00:00
// Terminate the loop when we find the current path in the active trail.
if ( $curr [ 'link' ][ 'href' ] == $item [ 'href' ]) {
2007-05-16 13:45:17 +00:00
$trail [] = $curr [ 'link' ];
$curr = FALSE ;
}
else {
2008-06-24 21:29:47 +00:00
// Add the link if it's in the active trail, then move to the link below.
if ( $curr [ 'link' ][ 'in_active_trail' ]) {
2007-05-16 13:45:17 +00:00
$trail [] = $curr [ 'link' ];
2008-06-24 21:29:47 +00:00
$tree = $curr [ 'below' ] ? $curr [ 'below' ] : array ();
2007-05-16 13:45:17 +00:00
}
2007-09-10 12:21:30 +00:00
list ( $key , $curr ) = each ( $tree );
2007-05-16 13:45:17 +00:00
}
}
2007-09-07 20:31:02 +00:00
// Make sure the current page is in the trail (needed for the page title),
// but exclude tabs and the front page.
$last = count ( $trail ) - 1 ;
if ( $trail [ $last ][ 'href' ] != $item [ 'href' ] && ! ( bool )( $item [ 'type' ] & MENU_IS_LOCAL_TASK ) && ! drupal_is_front_page ()) {
$trail [] = $item ;
}
2007-05-16 13:45:17 +00:00
}
return $trail ;
}
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 .
*
* See menu_set_active_trail () for details of return value .
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
/**
* Get the breadcrumb for the current page , as determined by the active trail .
*/
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 ();
2007-05-27 20:31:13 +00:00
if ( $item && $item [ 'access' ]) {
2007-05-16 13:45:17 +00:00
$active_trail = menu_get_active_trail ();
foreach ( $active_trail as $parent ) {
2008-02-04 12:07:23 +00:00
$breadcrumb [] = l ( $parent [ 'title' ], $parent [ 'href' ], $parent [ 'localized_options' ]);
2007-05-16 13:45:17 +00:00
}
$end = end ( $active_trail );
// Don't show a link to the current page in the breadcrumb trail.
2007-05-27 20:31:13 +00:00
if ( $item [ 'href' ] == $end [ 'href' ] || ( $item [ 'type' ] == MENU_DEFAULT_LOCAL_TASK && $end [ 'href' ] != '<front>' )) {
2007-05-16 13:45:17 +00:00
array_pop ( $breadcrumb );
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
/**
* Get the title of the current page , as determined by the active trail .
*/
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 ) {
2007-05-27 20:31:13 +00:00
if ( ! ( bool )( $item [ 'type' ] & MENU_IS_LOCAL_TASK )) {
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-12-28 10:47:59 +00:00
* Get a menu link by its mlid , access checked and link translated for rendering .
*
* This function should never be called from within node_load () or any other
* function used as a menu object load function since an infinite recursion may
* occur .
2007-04-06 04:39:51 +00:00
*
2007-05-16 13:45:17 +00:00
* @ param $mlid
* The mlid of the menu item .
2007-04-06 04:39:51 +00:00
* @ return
2007-05-27 20:31:13 +00:00
* A menu link , with $item [ 'access' ] filled and link translated for
2007-04-06 04:39:51 +00:00
* rendering .
*/
2007-05-27 20:31:13 +00:00
function menu_link_load ( $mlid ) {
2008-12-03 14:38:59 +00:00
if ( is_numeric ( $mlid )) {
$query = db_select ( 'menu_links' , 'ml' );
$query -> leftJoin ( 'menu_router' , 'm' , 'm.path = ml.router_path' );
$query -> fields ( 'ml' );
$query -> fields ( 'm' );
$query -> condition ( 'ml.mlid' , $mlid );
if ( $item = $query -> execute () -> fetchAssoc ()) {
_menu_link_translate ( $item );
return $item ;
}
2007-04-06 04:39:51 +00:00
}
return FALSE ;
}
2007-05-06 05:47:52 +00:00
2007-07-04 15:49:44 +00:00
/**
* Clears the cached cached data for a single named menu .
*/
2007-05-16 13:45:17 +00:00
function menu_cache_clear ( $menu_name = 'navigation' ) {
2009-04-25 15:19:12 +00:00
$cache_cleared = & drupal_static ( __FUNCTION__ , array ());
2007-11-26 08:49:03 +00:00
if ( empty ( $cache_cleared [ $menu_name ])) {
2008-04-14 17:48:46 +00:00
cache_clear_all ( 'links:' . $menu_name . ':' , 'cache_menu' , TRUE );
2007-11-26 08:49:03 +00:00
$cache_cleared [ $menu_name ] = 1 ;
}
elseif ( $cache_cleared [ $menu_name ] == 1 ) {
2008-04-14 17:48:46 +00:00
register_shutdown_function ( 'cache_clear_all' , 'links:' . $menu_name . ':' , 'cache_menu' , TRUE );
2007-11-26 08:49:03 +00:00
$cache_cleared [ $menu_name ] = 2 ;
}
2007-05-16 13:45:17 +00:00
}
/**
2008-12-20 18:24:41 +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 () {
2007-05-27 20:31:13 +00:00
cache_clear_all ( '*' , 'cache_menu' , TRUE );
2007-05-16 13:45:17 +00:00
}
/**
2008-01-03 10:51:30 +00:00
* ( Re ) populate the database tables used by various menu functions .
*
* This function will clear and populate the { menu_router } table , add entries
* to { menu_links } for new router items , then remove stale items from
* { menu_links } . If called from update . php or install . php , it will also
* schedule a call to itself on the first real page load from
* menu_execute_active_handler (), because the maintenance page environment
2008-01-10 20:04:19 +00:00
* is different and leaves stale data in the menu tables .
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
*/
function menu_rebuild () {
2009-08-17 20:32:30 +00:00
if ( ! lock_acquire ( 'menu_rebuild' )) {
// 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
2009-08-17 20:32:30 +00:00
// be available in menu_execute_active_handler() resulting in a 404.
lock_wait ( 'menu_rebuild' );
return FALSE ;
}
2009-04-12 19:52:38 +00:00
list ( $menu , $masks ) = menu_router_build ();
_menu_router_save ( $menu , $masks );
2007-05-16 13:45:17 +00:00
_menu_navigation_links_rebuild ( $menu );
2009-08-17 20:32:30 +00:00
// Clear the menu, page and block caches.
2009-04-12 19:52:38 +00:00
menu_cache_clear_all ();
2007-11-26 08:49:03 +00:00
_menu_clear_page_cache ();
2009-08-17 20:32:30 +00:00
2008-01-03 10:51:30 +00:00
if ( defined ( 'MAINTENANCE_MODE' )) {
2008-01-10 20:16:50 +00:00
variable_set ( 'menu_rebuild_needed' , TRUE );
2008-01-03 10:51:30 +00:00
}
2009-08-17 20:32:30 +00:00
else {
variable_del ( 'menu_rebuild_needed' );
}
lock_release ( 'menu_rebuild' );
return TRUE ;
2007-05-16 13:45:17 +00:00
}
/**
2009-04-12 19:52:38 +00:00
* Collect and alter the menu definitions .
2007-05-16 13:45:17 +00:00
*/
2009-04-02 03:40:05 +00:00
function menu_router_build () {
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array ();
foreach ( module_implements ( '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 );
2009-04-12 19:52:38 +00:00
list ( $menu , $masks ) = _menu_router_build ( $callbacks );
_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
}
/**
* Helper function to store the menu router if we have it in memory .
*/
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
/**
* Get the menu router .
*/
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
/**
* Builds a link from a router item .
*/
function _menu_link_build ( $item ) {
if ( $item [ 'type' ] == MENU_CALLBACK ) {
$item [ 'hidden' ] = - 1 ;
}
elseif ( $item [ 'type' ] == MENU_SUGGESTED_ITEM ) {
$item [ 'hidden' ] = 1 ;
}
// Note, we set this as 'system', so that we can be sure to distinguish all
// the menu links generated automatically from entries in {menu_router}.
$item [ 'module' ] = 'system' ;
$item += array (
'menu_name' => 'navigation' ,
'link_title' => $item [ 'title' ],
'link_path' => $item [ 'path' ],
'hidden' => 0 ,
'options' => empty ( $item [ 'description' ]) ? array () : array ( 'attributes' => array ( 'title' => $item [ 'description' ])),
);
return $item ;
}
2007-07-04 15:49:44 +00:00
/**
* Helper function to build menu links for the items in the menu router .
*/
2007-05-16 13:45:17 +00:00
function _menu_navigation_links_rebuild ( $menu ) {
// Add normal and suggested items as links.
$menu_links = array ();
foreach ( $menu as $path => $item ) {
2007-07-04 15:49:44 +00:00
if ( $item [ '_visible' ]) {
2007-05-16 13:45:17 +00:00
$menu_links [ $path ] = $item ;
$sort [ $path ] = $item [ '_number_parts' ];
}
2007-05-06 05:47:52 +00:00
}
2007-05-16 13:45:17 +00:00
if ( $menu_links ) {
// Make sure no child comes before its parent.
array_multisort ( $sort , SORT_NUMERIC , $menu_links );
foreach ( $menu_links as $item ) {
2008-12-03 14:38:59 +00:00
$existing_item = db_select ( 'menu_links' )
-> fields ( 'menu_links' , array (
'mlid' ,
'menu_name' ,
'plid' ,
'customized' ,
'has_children' ,
'updated' ,
))
2009-03-20 19:18:11 +00:00
-> condition ( 'link_path' , $item [ 'path' ])
2008-12-03 14:38:59 +00:00
-> condition ( 'module' , 'system' )
-> execute () -> fetchAssoc ();
2007-07-04 15:49:44 +00:00
if ( $existing_item ) {
$item [ 'mlid' ] = $existing_item [ 'mlid' ];
2008-09-02 19:23:02 +00:00
// A change in hook_menu may move the link to a different menu
if ( empty ( $item [ 'menu_name' ]) || ( $item [ 'menu_name' ] == $existing_item [ 'menu_name' ])) {
$item [ 'menu_name' ] = $existing_item [ 'menu_name' ];
$item [ 'plid' ] = $existing_item [ 'plid' ];
}
2009-03-20 19:18:11 +00:00
else {
// If it moved, put it at the top level in the new menu.
$item [ 'plid' ] = 0 ;
}
2008-02-23 08:10:03 +00:00
$item [ 'has_children' ] = $existing_item [ 'has_children' ];
$item [ 'updated' ] = $existing_item [ 'updated' ];
2007-07-04 15:49:44 +00:00
}
if ( ! $existing_item || ! $existing_item [ 'customized' ]) {
2009-03-20 19:18:11 +00:00
$item = _menu_link_build ( $item );
2007-07-04 15:49:44 +00:00
menu_link_save ( $item );
}
2007-05-16 13:45:17 +00:00
}
2007-05-06 05:47:52 +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.
2009-03-20 19:18:11 +00:00
$result = db_select ( 'menu_links' , NULL , array ( 'fetch' => PDO :: FETCH_ASSOC ))
2008-12-03 14:38:59 +00:00
-> fields ( 'menu_links' , array (
'link_path' ,
'mlid' ,
'router_path' ,
'updated' ,
))
-> condition ( db_or ()
-> condition ( 'updated' , 1 )
-> condition ( db_and ()
-> condition ( 'router_path' , $paths , 'NOT IN' )
-> condition ( 'external' , 0 )
-> condition ( 'customized' , 1 )
)
)
-> execute ();
foreach ( $result as $item ) {
2009-04-02 03:40:05 +00:00
$router_path = _menu_find_router_path ( $item [ 'link_path' ]);
2007-12-06 21:35:14 +00:00
if ( ! empty ( $router_path ) && ( $router_path != $item [ 'router_path' ] || $item [ 'updated' ])) {
// If the router path and the link path matches, it's surely a working
// item, so we clear the updated flag.
2008-01-30 21:01:20 +00:00
$updated = $item [ 'updated' ] && $router_path != $item [ 'link_path' ];
2008-12-03 14:38:59 +00:00
db_update ( 'menu_links' )
-> fields ( array (
'router_path' => $router_path ,
2009-03-20 19:18:11 +00:00
'updated' => ( int ) $updated ,
2008-12-03 14:38:59 +00:00
))
-> condition ( 'mlid' , $item [ 'mlid' ])
-> execute ();
2007-08-29 20:46:18 +00:00
}
2007-08-19 09:46:15 +00:00
}
2008-09-05 08:24:08 +00:00
// Find any item whose router path does not exist any more.
2008-12-03 14:38:59 +00:00
$result = db_select ( 'menu_links' )
-> fields ( 'menu_links' )
-> condition ( 'router_path' , $paths , 'NOT IN' )
-> condition ( 'external' , 0 )
-> condition ( 'updated' , 0 )
-> condition ( 'customized' , 0 )
-> orderBy ( 'depth' , 'DESC' )
-> execute ();
2008-01-30 21:01:20 +00:00
// Remove all such items. Starting from those with the greatest depth will
// minimize the amount of re-parenting done by menu_link_delete().
2008-12-03 14:38:59 +00:00
foreach ( $result as $item ) {
2008-01-30 21:01:20 +00:00
_menu_delete_item ( $item , TRUE );
}
2007-05-16 13:45:17 +00:00
}
2007-05-06 05:47:52 +00:00
2007-05-27 20:31:13 +00:00
/**
* Delete one or several menu links .
*
* @ param $mlid
* A valid menu link mlid or NULL . If NULL , $path is used .
* @ param $path
* The path to the menu items to be deleted . $mlid must be NULL .
*/
function menu_link_delete ( $mlid , $path = NULL ) {
if ( isset ( $mlid )) {
2008-12-03 14:38:59 +00:00
_menu_delete_item ( db_query ( " SELECT * FROM { menu_links} WHERE mlid = :mlid " , array ( ':mlid' => $mlid )) -> fetchAssoc ());
2007-05-27 20:31:13 +00:00
}
else {
2008-12-03 14:38:59 +00:00
$result = db_query ( " SELECT * FROM { menu_links} WHERE link_path = :link_path " , array ( ':link_path' => $path ));
foreach ( $result as $link ) {
2007-05-27 20:31:13 +00:00
_menu_delete_item ( $link );
}
}
}
2007-07-04 15:49:44 +00:00
/**
* Helper function for menu_link_delete ; deletes a single menu link .
2008-01-30 21:01:20 +00:00
*
* @ param $item
* Item to be deleted .
* @ param $force
* Forces deletion . Internal use only , setting to TRUE is discouraged .
2007-07-04 15:49:44 +00:00
*/
2008-01-30 21:01:20 +00:00
function _menu_delete_item ( $item , $force = FALSE ) {
2008-12-03 14:38:59 +00:00
$item = is_object ( $item ) ? get_object_vars ( $item ) : $item ;
2008-01-30 21:01:20 +00:00
if ( $item && ( $item [ 'module' ] != 'system' || $item [ 'updated' ] || $force )) {
2007-07-04 21:33:55 +00:00
// Children get re-attached to the item's parent.
2007-05-27 20:31:13 +00:00
if ( $item [ 'has_children' ]) {
2008-12-03 14:38:59 +00:00
$result = db_query ( " SELECT mlid FROM { menu_links} WHERE plid = :plid " , array ( ':plid' => $item [ 'mlid' ]));
foreach ( $result as $m ) {
$child = menu_link_load ( $m -> mlid );
2007-05-27 20:31:13 +00:00
$child [ 'plid' ] = $item [ 'plid' ];
menu_link_save ( $child );
}
}
2008-12-03 14:38:59 +00:00
db_delete ( 'menu_links' ) -> condition ( 'mlid' , $item [ 'mlid' ]) -> execute ();
2007-05-28 06:24:41 +00:00
2009-09-17 04:07:40 +00:00
// Notify modules we have deleted the item.
module_invoke_all ( 'menu_link_delete' , $item );
2007-07-04 21:33:55 +00:00
// Update the has_children status of the parent.
2007-08-11 14:06:15 +00:00
_menu_update_parental_status ( $item );
2007-11-26 08:49:03 +00:00
menu_cache_clear ( $item [ 'menu_name' ]);
_menu_clear_page_cache ();
2007-05-27 20:31:13 +00:00
}
}
2007-05-16 13:45:17 +00:00
/**
* Save a menu link .
*
* @ param $item
2007-05-27 20:31:13 +00:00
* An array representing a menu link item . The only mandatory keys are
2008-03-19 07:38:29 +00:00
* link_path and link_title . Possible keys are :
* - menu_name default is navigation
* - weight default is 0
* - expanded whether the item is expanded .
* - options An array of options , @ see l for more .
* - mlid Set to an existing value , or 0 or NULL to insert a new link .
* - plid The mlid of the parent .
* - router_path The path of the relevant router item .
2008-08-31 15:53:37 +00:00
* @ return
2008-11-11 16:49:38 +00:00
* The mlid of the saved menu link , or FALSE if the menu link could not be
2008-08-31 15:53:37 +00:00
* saved .
2007-05-16 13:45:17 +00:00
*/
2007-05-27 20:31:13 +00:00
function menu_link_save ( & $item ) {
2007-05-16 13:45:17 +00:00
2009-04-02 03:40:05 +00:00
drupal_alter ( 'menu_link' , $item );
2007-05-16 13:45:17 +00:00
2007-07-04 15:49:44 +00:00
// This is the easiest way to handle the unique internal path '<front>',
// since a path marked as external does not need to match a router path.
2008-10-19 21:15:58 +00:00
$item [ 'external' ] = ( menu_path_is_external ( $item [ 'link_path' ]) || $item [ 'link_path' ] == '<front>' ) ? 1 : 0 ;
2007-07-04 21:33:55 +00:00
// Load defaults.
2007-05-16 13:45:17 +00:00
$item += array (
2007-05-27 20:31:13 +00:00
'menu_name' => 'navigation' ,
2007-05-16 13:45:17 +00:00
'weight' => 0 ,
'link_title' => '' ,
'hidden' => 0 ,
'has_children' => 0 ,
'expanded' => 0 ,
2007-05-27 20:31:13 +00:00
'options' => array (),
'module' => 'menu' ,
2007-07-04 15:49:44 +00:00
'customized' => 0 ,
2007-08-29 20:46:18 +00:00
'updated' => 0 ,
2007-05-16 13:45:17 +00:00
);
2007-05-27 20:31:13 +00:00
$existing_item = FALSE ;
2007-05-16 13:45:17 +00:00
if ( isset ( $item [ 'mlid' ])) {
2008-12-03 14:38:59 +00:00
if ( $existing_item = db_query ( " SELECT * FROM { menu_links} WHERE mlid = :mlid " , array ( ':mlid' => $item [ 'mlid' ])) -> fetchAssoc ()) {
2008-11-13 05:54:35 +00:00
$existing_item [ 'options' ] = unserialize ( $existing_item [ 'options' ]);
}
2007-05-16 13:45:17 +00:00
}
if ( isset ( $item [ 'plid' ])) {
2008-10-19 21:15:58 +00:00
if ( $item [ 'plid' ]) {
2008-12-03 14:38:59 +00:00
$parent = db_query ( " SELECT * FROM { menu_links} WHERE mlid = :mlid " , array ( ':mlid' => $item [ 'plid' ])) -> fetchAssoc ();
2008-10-19 21:15:58 +00:00
}
else {
// Don't bother with the query - mlid can never equal zero..
$parent = FALSE ;
}
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
else {
2008-12-03 14:38:59 +00:00
$query = db_select ( 'menu_links' );
2008-02-10 07:33:02 +00:00
// Only links derived from router items should have module == 'system', and
// we want to find the parent even if it's in a different menu.
if ( $item [ 'module' ] == 'system' ) {
2008-12-03 14:38:59 +00:00
$query -> condition ( 'module' , 'system' );
2008-02-10 07:33:02 +00:00
}
else {
// If not derived from a router item, we respect the specified menu name.
2008-12-03 14:38:59 +00:00
$query -> condition ( 'menu_name' , $item [ 'menu_name' ]);
2008-02-10 07:33:02 +00:00
}
2009-03-14 20:56:06 +00:00
2008-12-03 14:38:59 +00:00
// Find the parent - it must be unique.
$parent_path = $item [ 'link_path' ];
2007-05-16 13:45:17 +00:00
do {
2008-02-10 07:33:02 +00:00
$parent = FALSE ;
2007-05-16 13:45:17 +00:00
$parent_path = substr ( $parent_path , 0 , strrpos ( $parent_path , '/' ));
2009-03-14 20:56:06 +00:00
$new_query = clone $query ;
$new_query -> condition ( 'link_path' , $parent_path );
2008-02-10 07:33:02 +00:00
// Only valid if we get a unique result.
2009-03-14 20:56:06 +00:00
if ( $new_query -> countQuery () -> execute () -> fetchField () == 1 ) {
$parent = $new_query -> fields ( 'menu_links' ) -> execute () -> fetchAssoc ();
2008-02-10 07:33:02 +00:00
}
2007-05-16 13:45:17 +00:00
} while ( $parent === FALSE && $parent_path );
}
2008-02-10 07:33:02 +00:00
if ( $parent !== FALSE ) {
$item [ 'menu_name' ] = $parent [ 'menu_name' ];
}
$menu_name = $item [ 'menu_name' ];
2007-05-16 13:45:17 +00:00
// Menu callbacks need to be in the links table for breadcrumbs, but can't
2007-07-04 15:49:44 +00:00
// be parents if they are generated directly from a router item.
2007-05-27 20:31:13 +00:00
if ( empty ( $parent [ 'mlid' ]) || $parent [ 'hidden' ] < 0 ) {
2007-05-16 13:45:17 +00:00
$item [ 'plid' ] = 0 ;
}
else {
$item [ 'plid' ] = $parent [ 'mlid' ];
}
2007-06-05 12:13:23 +00:00
2007-06-06 06:26:15 +00:00
if ( ! $existing_item ) {
2008-12-03 14:38:59 +00:00
$item [ 'mlid' ] = db_insert ( 'menu_links' )
-> fields ( array (
'menu_name' => $item [ 'menu_name' ],
'plid' => $item [ 'plid' ],
'link_path' => $item [ 'link_path' ],
'hidden' => $item [ 'hidden' ],
'external' => $item [ 'external' ],
'has_children' => $item [ 'has_children' ],
'expanded' => $item [ 'expanded' ],
'weight' => $item [ 'weight' ],
'module' => $item [ 'module' ],
'link_title' => $item [ 'link_title' ],
'options' => serialize ( $item [ 'options' ]),
'customized' => $item [ 'customized' ],
'updated' => $item [ 'updated' ],
))
-> execute ();
2007-06-06 06:26:15 +00:00
}
2007-05-16 13:45:17 +00:00
if ( ! $item [ 'plid' ]) {
$item [ 'p1' ] = $item [ 'mlid' ];
2007-08-11 14:06:15 +00:00
for ( $i = 2 ; $i <= MENU_MAX_DEPTH ; $i ++ ) {
$item [ " p $i " ] = 0 ;
}
2007-05-16 13:45:17 +00:00
$item [ 'depth' ] = 1 ;
}
else {
2007-07-04 21:33:55 +00:00
// Cannot add beyond the maximum depth.
2007-05-27 20:31:13 +00:00
if ( $item [ 'has_children' ] && $existing_item ) {
$limit = MENU_MAX_DEPTH - menu_link_children_relative_depth ( $existing_item ) - 1 ;
}
else {
$limit = MENU_MAX_DEPTH - 1 ;
}
if ( $parent [ 'depth' ] > $limit ) {
2007-05-16 13:45:17 +00:00
return FALSE ;
}
$item [ 'depth' ] = $parent [ 'depth' ] + 1 ;
2007-05-27 20:31:13 +00:00
_menu_link_parents_set ( $item , $parent );
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
// Need to check both plid and menu_name, since plid can be 0 in any menu.
if ( $existing_item && ( $item [ 'plid' ] != $existing_item [ 'plid' ] || $menu_name != $existing_item [ 'menu_name' ])) {
_menu_link_move_children ( $item , $existing_item );
2007-05-16 13:45:17 +00:00
}
2008-10-19 21:15:58 +00:00
// Find the router_path.
if ( empty ( $item [ 'router_path' ]) || ! $existing_item || ( $existing_item [ 'link_path' ] != $item [ 'link_path' ])) {
if ( $item [ 'external' ]) {
2007-05-16 13:45:17 +00:00
$item [ 'router_path' ] = '' ;
}
else {
// Find the router path which will serve this path.
2007-05-27 20:31:13 +00:00
$item [ 'parts' ] = explode ( '/' , $item [ 'link_path' ], MENU_MAX_PARTS );
2009-04-02 03:40:05 +00:00
$item [ 'router_path' ] = _menu_find_router_path ( $item [ 'link_path' ]);
2007-05-16 13:45:17 +00:00
}
}
2008-10-19 21:15:58 +00:00
// If every value in $existing_item is the same in the $item, there is no
// reason to run the update queries or clear the caches. We use
2009-05-07 16:24:33 +00:00
// array_intersect_assoc() with the $item as the first parameter because
// $item may have additional keys left over from building a router entry.
// The intersect removes the extra keys, allowing a meaningful comparison.
if ( ! $existing_item || ( array_intersect_assoc ( $item , $existing_item )) != $existing_item ) {
2008-12-03 14:38:59 +00:00
db_update ( 'menu_links' )
-> fields ( array (
'menu_name' => $item [ 'menu_name' ],
'plid' => $item [ 'plid' ],
'link_path' => $item [ 'link_path' ],
'router_path' => $item [ 'router_path' ],
'hidden' => $item [ 'hidden' ],
'external' => $item [ 'external' ],
'has_children' => $item [ 'has_children' ],
'expanded' => $item [ 'expanded' ],
'weight' => $item [ 'weight' ],
'depth' => $item [ 'depth' ],
'p1' => $item [ 'p1' ],
'p2' => $item [ 'p2' ],
'p3' => $item [ 'p3' ],
'p4' => $item [ 'p4' ],
'p5' => $item [ 'p5' ],
'p6' => $item [ 'p6' ],
'p7' => $item [ 'p7' ],
'p8' => $item [ 'p8' ],
'p9' => $item [ 'p9' ],
'module' => $item [ 'module' ],
'link_title' => $item [ 'link_title' ],
'options' => serialize ( $item [ 'options' ]),
'customized' => $item [ 'customized' ],
))
-> condition ( 'mlid' , $item [ 'mlid' ])
-> execute ();
2008-10-19 21:15:58 +00:00
// Check the has_children status of the parent.
_menu_update_parental_status ( $item );
menu_cache_clear ( $menu_name );
if ( $existing_item && $menu_name != $existing_item [ 'menu_name' ]) {
menu_cache_clear ( $existing_item [ 'menu_name' ]);
}
2009-09-17 04:07:40 +00:00
// Notify modules we have acted on a menu item.
$hook = 'menu_link_insert' ;
if ( $existing_item ) {
$hook = 'menu_link_update' ;
}
module_invoke_all ( $hook , $item );
// Now clear the cache.
2008-10-19 21:15:58 +00:00
_menu_clear_page_cache ();
2007-05-27 20:31:13 +00:00
}
2007-11-26 08:49:03 +00:00
return $item [ 'mlid' ];
}
/**
2007-12-08 14:06:23 +00:00
* Helper function to clear the page and block caches at most twice per page load .
*/
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 ) {
2007-11-26 08:49:03 +00:00
cache_clear_all ();
// Keep track of which menus have expanded items.
_menu_set_expanded_menus ();
$cache_cleared = 1 ;
}
elseif ( $cache_cleared == 1 ) {
register_shutdown_function ( 'cache_clear_all' );
// Keep track of which menus have expanded items.
register_shutdown_function ( '_menu_set_expanded_menus' );
$cache_cleared = 2 ;
}
}
/**
2007-12-08 14:06:23 +00:00
* Helper function to update a list of menus with expanded items
*/
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 ();
2007-05-16 13:45:17 +00:00
variable_set ( 'menu_expanded' , $names );
}
2007-08-29 20:46:18 +00:00
/**
* Find the router path which will serve this path .
*
* @ param $link_path
* The path for we are looking up its router path .
* @ 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-10-16 13:48:11 +00:00
/**
* Insert , update or delete an uncustomized menu link related to a module .
*
* @ param $module
* The name of the module .
* @ param $op
* Operation to perform : insert , update or delete .
* @ param $link_path
* The path this link points to .
* @ param $link_title
* Title of the link to insert or new title to update the link to .
* Unused for delete .
2007-10-17 19:42:02 +00:00
* @ return
* The insert op returns the mlid of the new item . Others op return NULL .
2007-10-16 13:48:11 +00:00
*/
function menu_link_maintain ( $module , $op , $link_path , $link_title ) {
switch ( $op ) {
case 'insert' :
$menu_link = array (
'link_title' => $link_title ,
'link_path' => $link_path ,
'module' => $module ,
);
2007-10-17 19:42:02 +00:00
return menu_link_save ( $menu_link );
2007-10-16 13:48:11 +00:00
break ;
case 'update' :
2009-09-17 04:07:40 +00:00
$result = db_query ( " SELECT * FROM { menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0 " , array ( ':link_path' => $link_path , ':module' => $module )) -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $result as $link ) {
$link [ 'link_title' ] = $link_title ;
$link [ 'options' ] = unserialize ( $link [ 'options' ]);
menu_link_save ( $link );
2008-07-10 10:58:01 +00:00
}
2007-10-16 13:48:11 +00:00
break ;
case 'delete' :
menu_link_delete ( NULL , $link_path );
break ;
}
}
2007-08-29 20:46:18 +00:00
2007-05-27 20:31:13 +00:00
/**
2007-08-04 13:58:06 +00:00
* Find the depth of an item ' s children relative to its depth .
*
* For example , if the item has a depth of 2 , and the maximum of any child in
* the menu link tree is 5 , the relative depth is 3.
2007-05-27 20:31:13 +00:00
*
* @ param $item
* An array representing a menu link item .
* @ return
* The relative depth , or zero .
*
*/
function menu_link_children_relative_depth ( $item ) {
2008-12-03 14:38:59 +00:00
$query = db_select ( 'menu_links' );
$query -> addField ( 'menu_links' , 'depth' );
$query -> condition ( 'menu_name' , $item [ 'menu_name' ]);
$query -> orderBy ( 'depth' , 'DESC' );
$query -> range ( 0 , 1 );
2007-05-27 20:31:13 +00:00
$i = 1 ;
$p = 'p1' ;
while ( $i <= MENU_MAX_DEPTH && $item [ $p ]) {
2008-12-03 14:38:59 +00:00
$query -> condition ( $p , $item [ $p ]);
2008-04-14 17:48:46 +00:00
$p = 'p' . ++ $i ;
2007-05-27 20:31:13 +00:00
}
2008-12-03 14:38:59 +00:00
$max_depth = $query -> execute () -> fetchField ();
2007-05-27 20:31:13 +00:00
return ( $max_depth > $item [ 'depth' ]) ? $max_depth - $item [ 'depth' ] : 0 ;
}
/**
2007-08-04 13:58:06 +00:00
* Update the children of a menu link that ' s being moved .
*
* The menu name , parents ( p1 - p6 ), and depth are updated for all children of
* the link , and the has_children status of the previous parent is updated .
2007-05-27 20:31:13 +00:00
*/
function _menu_link_move_children ( $item , $existing_item ) {
2008-12-03 14:38:59 +00:00
$query = db_update ( 'menu_links' );
2007-05-27 20:31:13 +00:00
2008-12-03 14:38:59 +00:00
$query -> fields ( array ( 'menu_name' => $item [ 'menu_name' ]));
2007-08-04 13:58:06 +00:00
2008-12-03 14:38:59 +00:00
$p = 'p1' ;
for ( $i = 1 ; $i <= $item [ 'depth' ]; $p = 'p' . ++ $i ) {
$query -> fields ( array ( $p => $item [ $p ]));
2007-05-27 20:31:13 +00:00
}
$j = $existing_item [ 'depth' ] + 1 ;
while ( $i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH ) {
2008-12-03 14:38:59 +00:00
$query -> expression ( 'p' . $i ++ , 'p' . $j ++ );
2007-05-27 20:31:13 +00:00
}
while ( $i <= MENU_MAX_DEPTH ) {
2008-12-03 14:38:59 +00:00
$query -> fields ( array ( 'p' . $i ++ => 0 ));
2007-08-04 13:58:06 +00:00
}
$shift = $item [ 'depth' ] - $existing_item [ 'depth' ];
if ( $shift < 0 ) {
2008-12-03 14:38:59 +00:00
$query -> expression ( 'depth' , 'depth - :depth' , array ( ':depth' => - $shift ));
2007-08-04 13:58:06 +00:00
}
elseif ( $shift > 0 ) {
2008-12-03 14:38:59 +00:00
$query -> expression ( 'depth' , 'depth + :depth' , array ( ':depth' => $shift ));
}
$query -> condition ( 'menu_name' , $existing_item [ 'menu_name' ]);
2007-05-27 20:31:13 +00:00
$p = 'p1' ;
2008-04-14 17:48:46 +00:00
for ( $i = 1 ; $i <= MENU_MAX_DEPTH && $existing_item [ $p ]; $p = 'p' . ++ $i ) {
2008-12-03 14:38:59 +00:00
$query -> condition ( $p , $existing_item [ $p ]);
2007-05-27 20:31:13 +00:00
}
2008-12-03 14:38:59 +00:00
$query -> execute ();
2007-08-11 14:06:15 +00:00
// Check the has_children status of the parent, while excluding this item.
_menu_update_parental_status ( $existing_item , TRUE );
}
2007-05-27 20:31:13 +00:00
2007-08-11 14:06:15 +00:00
/**
* Check and update the has_children status for the parent of a link .
*/
function _menu_update_parental_status ( $item , $exclude = FALSE ) {
// If plid == 0, there is nothing to update.
if ( $item [ 'plid' ]) {
// Check if at least one visible child exists in the table.
2008-12-03 14:38:59 +00:00
$query = db_select ( 'menu_links' );
$query -> addField ( 'menu_links' , 'mlid' );
2008-08-21 19:36:39 +00:00
$query -> condition ( 'menu_name' , $item [ 'menu_name' ]);
2008-09-05 08:24:08 +00:00
$query -> condition ( 'hidden' , 0 );
2008-08-21 19:36:39 +00:00
$query -> condition ( 'plid' , $item [ 'plid' ]);
$query -> range ( 0 , 1 );
if ( $exclude ) {
2008-12-03 14:38:59 +00:00
$query -> condition ( 'mlid' , $item [ 'mlid' ], '<>' );
2008-08-21 19:36:39 +00:00
}
$parent_has_children = (( bool ) $query -> execute () -> fetchField ()) ? 1 : 0 ;
2008-12-03 14:38:59 +00:00
db_update ( 'menu_links' )
-> fields ( array ( 'has_children' => $parent_has_children ))
-> condition ( 'mlid' , $item [ 'plid' ])
-> execute ();
2007-05-27 20:31:13 +00:00
}
}
2007-07-04 15:49:44 +00:00
/**
2007-08-11 14:06:15 +00:00
* Helper function that sets the p1 .. p9 values for a menu link being saved .
2007-07-04 15:49:44 +00:00
*/
2007-05-27 20:31:13 +00:00
function _menu_link_parents_set ( & $item , $parent ) {
$i = 1 ;
while ( $i < $item [ 'depth' ]) {
2008-04-14 17:48:46 +00:00
$p = 'p' . $i ++ ;
2007-05-27 20:31:13 +00:00
$item [ $p ] = $parent [ $p ];
}
2008-04-14 17:48:46 +00:00
$p = 'p' . $i ++ ;
2007-08-11 14:06:15 +00:00
// The parent (p1 - p9) corresponding to the depth always equals the mlid.
2007-05-27 20:31:13 +00:00
$item [ $p ] = $item [ 'mlid' ];
while ( $i <= MENU_MAX_DEPTH ) {
2008-04-14 17:48:46 +00:00
$p = 'p' . $i ++ ;
2007-05-27 20:31:13 +00:00
$item [ $p ] = 0 ;
2007-05-16 13:45:17 +00:00
}
}
2007-07-04 15:49:44 +00:00
/**
* Helper function to build the router table based on the data from hook_menu .
*/
2007-05-16 13:45:17 +00:00
function _menu_router_build ( $callbacks ) {
// 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 ();
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 );
$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.
// See http://php.net/manual/en/language.functions.php for reference.
2008-10-14 20:17:29 +00:00
if ( preg_match ( '/^%(|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/' , $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 ;
2007-05-16 13:45:17 +00:00
$item [ 'load_functions' ] = empty ( $load_functions ) ? '' : serialize ( $load_functions );
$item [ 'to_arg_functions' ] = empty ( $to_arg_functions ) ? '' : serialize ( $to_arg_functions );
$item += array (
'title' => '' ,
'weight' => 0 ,
'type' => MENU_NORMAL_ITEM ,
'_number_parts' => $number_parts ,
'_parts' => $parts ,
'_fit' => $fit ,
);
$item += array (
'_visible' => ( bool )( $item [ 'type' ] & MENU_VISIBLE_IN_BREADCRUMB ),
'_tab' => ( bool )( $item [ 'type' ] & MENU_IS_LOCAL_TASK ),
);
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 ;
}
for ( $i = $item [ '_number_parts' ] - 1 ; $i ; $i -- ) {
$parent_path = implode ( '/' , array_slice ( $item [ '_parts' ], 0 , $i ));
if ( isset ( $menu [ $parent_path ])) {
$parent = $menu [ $parent_path ];
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' ]) && isset ( $parent [ 'file' ])) {
$item [ 'file' ] = $parent [ 'file' ];
}
if ( ! isset ( $item [ 'file path' ]) && isset ( $parent [ 'file path' ])) {
$item [ 'file path' ] = $parent [ 'file path' ];
}
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' ];
}
}
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' ]);
}
$item += array (
'access arguments' => array (),
'access callback' => '' ,
'page arguments' => array (),
'page callback' => '' ,
'block 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' => '' ,
'position' => '' ,
'tab_parent' => '' ,
'tab_root' => $path ,
'path' => $path ,
2009-08-24 00:14:23 +00:00
'file' => '' ,
'file path' => '' ,
'include file' => '' ,
'module' => '' ,
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 );
return array ( $menu , $masks );
}
/**
* Helper function to save data from menu_router_build () to the router table .
*/
function _menu_router_save ( $menu , $masks ) {
// Delete the existing router since we have some data to replace it.
db_delete ( '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' ,
'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' ,
'block_callback' ,
'description' ,
'position' ,
'weight' ,
2009-08-24 00:14:23 +00:00
'file' ,
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' ],
'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' ],
'block_callback' => $item [ 'block callback' ],
'description' => $item [ 'description' ],
'position' => $item [ 'position' ],
'weight' => $item [ 'weight' ],
2009-08-24 00:14:23 +00:00
'file' => $item [ 'include file' ],
2008-12-03 14:38:59 +00:00
));
}
// Execute insert object.
$insert -> execute ();
2009-04-12 19:52:38 +00:00
// Store the masks.
2007-08-11 14:06:15 +00:00
variable_set ( 'menu_masks' , $masks );
2009-04-02 03:40:05 +00:00
2007-05-16 13:45:17 +00:00
return $menu ;
}
2007-07-04 15:49:44 +00:00
/**
* Returns TRUE if a path is external ( e . g . http :// example . com ) .
*/
2007-05-16 13:45:17 +00:00
function menu_path_is_external ( $path ) {
$colonpos = strpos ( $path , ':' );
return $colonpos !== FALSE && ! preg_match ( '![/?#]!' , substr ( $path , 0 , $colonpos )) && filter_xss_bad_protocol ( $path , FALSE ) == check_plain ( $path );
2007-05-06 05:47:52 +00:00
}
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 .
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.
if ( variable_get ( 'maintenance_mode' , 0 )) {
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.
2009-09-30 13:09:30 +00:00
if ( ! $check_only && $_GET [ 'q' ] != '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 {
// Anonymous users get a FALSE at the login prompt, TRUE otherwise.
if ( user_is_anonymous ()) {
2009-08-22 18:24:14 +00:00
return ( $_GET [ 'q' ] != 'user' && $_GET [ 'q' ] != 'user/login' );
2007-05-26 10:54:12 +00:00
}
2008-01-02 14:29:32 +00:00
// Logged in users are unprivileged here, so they are logged out.
2009-09-30 13:09:30 +00:00
if ( ! $check_only ) {
require_once DRUPAL_ROOT . '/' . drupal_get_path ( 'module' , 'user' ) . '/user.pages.inc' ;
user_logout ();
}
2007-05-26 10:54:12 +00:00
}
}
return FALSE ;
}
2008-01-03 09:59:00 +00:00
/**
* Validates the path of a menu link being created or edited .
*
* @ return
* TRUE if it is a valid path AND the current user has access permission ,
* FALSE otherwise .
*/
function menu_valid_path ( $form_item ) {
global $menu_admin ;
$item = array ();
$path = $form_item [ 'link_path' ];
// We indicate that a menu administrator is running the menu access check.
$menu_admin = TRUE ;
if ( $path == '<front>' || menu_path_is_external ( $path )) {
$item = array ( 'access' => TRUE );
}
elseif ( preg_match ( '/\/\%/' , $path )) {
// Path is dynamic (ie 'user/%'), so check directly against menu_router table.
2008-12-03 14:38:59 +00:00
if ( $item = db_query ( " SELECT * FROM { menu_router} where path = :path " , array ( ':path' => $path )) -> fetchAssoc ()) {
2008-01-03 09:59:00 +00:00
$item [ 'link_path' ] = $form_item [ 'link_path' ];
$item [ 'link_title' ] = $form_item [ 'link_title' ];
$item [ 'external' ] = FALSE ;
$item [ 'options' ] = '' ;
_menu_link_translate ( $item );
}
}
else {
$item = menu_get_item ( $path );
}
$menu_admin = FALSE ;
return $item && $item [ 'access' ];
}
2008-01-28 16:05:17 +00:00
/**
* @ } End of " defgroup menu " .
*/