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
2004-06-18 15:04:37 +00:00
define ( 'MENU_IS_ROOT' , 0x0001 );
define ( 'MENU_VISIBLE_IN_TREE' , 0x0002 );
define ( 'MENU_VISIBLE_IN_BREADCRUMB' , 0x0004 );
2007-06-05 09:15:02 +00:00
define ( 'MENU_LINKS_TO_PARENT' , 0x0008 );
define ( 'MENU_MODIFIED_BY_ADMIN' , 0x0020 );
define ( 'MENU_CREATED_BY_ADMIN' , 0x0040 );
define ( 'MENU_IS_LOCAL_TASK' , 0x0080 );
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 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
/**
* 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
/**
* 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
/**
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
/**
2004-07-10 18:10:36 +00:00
* Local tasks are rendered as tabs by default . Use this for 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
/**
* 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 );
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
2004-06-18 15:04:37 +00:00
define ( 'MENU_FOUND' , 1 );
define ( 'MENU_NOT_FOUND' , 2 );
define ( 'MENU_ACCESS_DENIED' , 3 );
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
*/
2007-08-11 14:06:15 +00:00
define ( 'MENU_MAX_PARTS' , 7 );
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
2007-08-11 14:06:15 +00:00
* any argument matches that part . We limit ourselves to using binary
* numbers that correspond the patterns of wildcards of router items that
* actually exists . This list of 'masks' is built in menu_rebuild () .
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
$placeholders = array ();
$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
$placeholders [] = " '%s' " ;
$ancestors [] = $current ;
2004-04-15 20:49:42 +00:00
}
2007-01-24 14:48:36 +00:00
return array ( $ancestors , $placeholders );
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 .
* The values for key title , page_arguments , access_arguments will be
* filled in based on the database values and the objects loaded .
*/
function menu_get_item ( $path = NULL , $router_item = NULL ) {
2007-05-27 20:31:13 +00:00
static $router_items ;
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 );
2007-01-24 14:48:36 +00:00
list ( $ancestors , $placeholders ) = menu_get_ancestors ( $parts );
2007-05-16 13:45:17 +00:00
2008-04-14 17:48:46 +00:00
if ( $router_item = db_fetch_array ( db_query_range ( 'SELECT * FROM {menu_router} WHERE path IN (' . implode ( ',' , $placeholders ) . ') ORDER BY fit DESC' , $ancestors , 0 , 1 ))) {
2007-05-27 20:31:13 +00:00
$map = _menu_translate ( $router_item , $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' ]));
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-01-10 20:16:50 +00:00
if ( variable_get ( 'menu_rebuild_needed' , FALSE )) {
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' ]) {
if ( $router_item [ 'file' ]) {
require_once ( $router_item [ 'file' ]);
2007-05-22 05:52:17 +00:00
}
2007-05-27 20:31:13 +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
// occurances of the same placeholder can be identified.
$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
}
else {
2007-05-27 20:31:13 +00:00
$item [ 'access' ] = call_user_func_array ( $callback , $arguments );
2007-03-08 19:03:48 +00:00
}
2004-06-18 15:04:37 +00:00
}
2007-05-16 13:45:17 +00:00
}
2007-04-30 17:03:29 +00:00
2007-07-04 15:49:44 +00:00
/**
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' ];
2008-01-10 20:04:19 +00:00
// If we are not doing link translation or if the title matches the
// link title of its router item, localize it.
if ( ! $link_translate || ( ! empty ( $item [ 'title' ]) && ( $item [ 'title' ] == $item [ 'link_title' ]))) {
// t() is a special case. Since it is used very close to all the time,
// we handle it directly instead of using indirect, slower methods.
if ( $callback == 't' ) {
if ( empty ( $item [ 'title_arguments' ])) {
$item [ 'title' ] = t ( $item [ 'title' ]);
}
else {
$item [ 'title' ] = t ( $item [ 'title' ], menu_unserialize ( $item [ 'title_arguments' ], $map ));
}
2007-04-30 17:03:29 +00:00
}
2008-01-10 20:04:19 +00:00
elseif ( $callback ) {
if ( empty ( $item [ 'title_arguments' ])) {
$item [ 'title' ] = $callback ( $item [ 'title' ]);
}
else {
$item [ 'title' ] = call_user_func_array ( $callback , menu_unserialize ( $item [ 'title_arguments' ], $map ));
}
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-01-10 20:04:19 +00:00
if ( $link_translate && $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
* to the language required to generate the current page
*
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
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 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 ) {
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 ;
}
if ( $to_arg ) {
2007-05-27 20:31:13 +00:00
_menu_link_map_translate ( $path_map , $router_item [ 'to_arg_functions' ]);
2007-05-16 13:45:17 +00:00
}
// 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 );
2007-05-16 13:45:17 +00:00
2007-08-11 14:06:15 +00:00
_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 .
*
* @ param map
* 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 );
}
2007-12-28 10:47:59 +00:00
2008-01-10 20:04:19 +00:00
_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 .
*
* menu_get_object () will provide you the current node on paths like node / 5 ,
* node / 5 / revisions / 48 etc . menu_get_object ( 'user' ) will give you the user
2007-12-28 10:47:59 +00:00
* account on user / 5 etc . Note - this function should never be called within a
* _to_arg function ( like user_current_to_arg ()) since this may result in an
* infinite recursion .
2007-12-20 09:20:41 +00:00
*
* @ param $type
* Type of the object . These appear in hook_menu definitons as % type . Core
* provides aggregator_feed , aggregator_category , contact , filter_format ,
* forum_term , menu , menu_link , node , taxonomy_vocabulary , user . See the
* relevant { $type } _load function for more on each . Defaults to node .
* @ param $position
* The expected position for $type object . For node /% node this is 1 , for
* comment / reply /% node this is 2. Defaults to 1.
* @ 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 .
*/
function menu_tree ( $menu_name = 'navigation' ) {
static $menu_output = array ();
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
*
* @ param $tree
* A data structure representing the tree as returned from menu_tree_data .
* @ return
* The rendered HTML of that data structure .
2003-09-28 11:08:17 +00:00
*/
2007-05-16 13:45:17 +00:00
function menu_tree_output ( $tree ) {
$output = '' ;
2007-10-08 15:01:05 +00:00
$items = array ();
2007-05-16 13:45:17 +00:00
2007-10-12 14:10:18 +00:00
// Pull out just the menu items 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 ) {
$extra_class = NULL ;
if ( $i == 0 ) {
$extra_class = 'first' ;
}
if ( $i == $num_items - 1 ) {
$extra_class = 'last' ;
}
$link = theme ( 'menu_item_link' , $data [ 'link' ]);
if ( $data [ 'below' ]) {
$output .= theme ( 'menu_item' , $link , $data [ 'link' ][ 'has_children' ], menu_tree_output ( $data [ 'below' ]), $data [ 'link' ][ 'in_active_trail' ], $extra_class );
}
else {
$output .= theme ( 'menu_item' , $link , $data [ 'link' ][ 'has_children' ], '' , $data [ 'link' ][ 'in_active_trail' ], $extra_class );
2007-04-15 14:38:16 +00:00
}
2007-05-16 13:45:17 +00:00
}
return $output ? theme ( 'menu_tree' , $output ) : '' ;
}
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
* @ param $item
* A fully loaded menu link , or NULL . If a link is supplied , only the
* path to root will be included in the returned tree - as if this link
* represented the current page in a visible menu .
* @ return
* An tree of menu links in an array , in the order they should be rendered .
*/
2007-08-11 14:06:15 +00:00
function menu_tree_all_data ( $menu_name = 'navigation' , $item = NULL ) {
2007-05-27 20:31:13 +00:00
static $tree = array ();
2007-07-04 15:49:44 +00:00
// Use $mlid as a flag for whether the data being loaded is for the whole tree.
2007-05-27 20:31:13 +00:00
$mlid = isset ( $item [ 'mlid' ]) ? $item [ 'mlid' ] : 0 ;
2008-03-14 08:51:37 +00:00
// Generate a cache ID (cid) specific for this $menu_name and $item.
2008-04-14 17:48:46 +00:00
$cid = 'links:' . $menu_name . ':all-cid:' . $mlid ;
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 )) {
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 ( $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 ++ ) {
$args [] = $item [ " p $i " ];
}
2007-05-27 20:31:13 +00:00
$args = array_unique ( $args );
$placeholders = implode ( ', ' , array_fill ( 0 , count ( $args ), '%d' ));
2008-04-14 17:48:46 +00:00
$where = ' AND ml.plid IN (' . $placeholders . ')' ;
2007-05-27 20:31:13 +00:00
$parents = $args ;
$parents [] = $item [ 'mlid' ];
}
else {
2007-07-04 15:49:44 +00:00
// Get all links in this menu.
2007-05-27 20:31:13 +00:00
$where = '' ;
$args = array ();
$parents = array ();
}
array_unshift ( $args , $menu_name );
2007-07-04 15:49:44 +00:00
// Select the links from the table, and recursively build the tree. We
2007-07-04 21:33:55 +00:00
// LEFT JOIN since there is no match in {menu_router} for an external
2007-08-11 14:06:15 +00:00
// link.
2007-07-16 12:51:03 +00:00
$data [ 'tree' ] = menu_tree_data ( db_query ( "
2007-12-27 14:03:37 +00:00
SELECT m . load_functions , m . to_arg_functions , m . access_callback , m . access_arguments , m . page_callback , m . page_arguments , m . title , m . title_callback , m . title_arguments , m . type , m . description , ml .*
2007-05-30 08:35:56 +00:00
FROM { menu_links } ml LEFT JOIN { menu_router } m ON m . path = ml . router_path
2008-04-14 17:48:46 +00:00
WHERE ml . menu_name = '%s' " . $where . "
2007-08-11 14:06:15 +00:00
ORDER BY p1 ASC , p2 ASC , p3 ASC , p4 ASC , p5 ASC , p6 ASC , p7 ASC , p8 ASC , p9 ASC " , $args ), $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
* @ 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
*/
2007-05-27 20:31:13 +00:00
function menu_tree_page_data ( $menu_name = 'navigation' ) {
2007-05-16 13:45:17 +00:00
static $tree = array ();
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 ()) {
2008-03-14 08:51:37 +00:00
// Generate a cache ID (cid) specific for this page.
2008-04-14 17:48:46 +00:00
$cid = 'links:' . $menu_name . ':page-cid:' . $item [ 'href' ] . ':' . ( int ) $item [ 'access' ];
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-01-28 19:09:19 +00:00
$args = array ( $menu_name , $item [ 'href' ]);
$placeholders = " '%s' " ;
if ( drupal_is_front_page ()) {
$args [] = '<front>' ;
$placeholders .= " , '%s' " ;
}
2008-04-14 17:48:46 +00:00
$parents = db_fetch_array ( db_query ( " SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM { menu_links} WHERE menu_name = '%s' AND link_path IN ( " . $placeholders . " ) " , $args ));
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.
2007-08-11 14:06:15 +00:00
$parents = db_fetch_array ( db_query ( " SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM { menu_links} WHERE menu_name = '%s' AND link_path = '%s' " , $menu_name , $item [ 'tab_root' ]));
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
$placeholders = implode ( ', ' , array_fill ( 0 , count ( $args ), '%d' ));
$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-04-14 17:48:46 +00:00
$result = db_query ( " SELECT mlid FROM { menu_links} WHERE menu_name = '%s' AND expanded = 1 AND has_children = 1 AND plid IN ( " . $placeholders . ') AND mlid NOT IN (' . $placeholders . ')' , array_merge ( array ( $menu_name ), $args , $args ));
2007-08-12 16:12:00 +00:00
$num_rows = FALSE ;
2007-05-27 20:31:13 +00:00
while ( $item = db_fetch_array ( $result )) {
$args [] = $item [ 'mlid' ];
2007-08-12 16:12:00 +00:00
$num_rows = TRUE ;
2007-05-27 20:31:13 +00:00
}
$placeholders = implode ( ', ' , array_fill ( 0 , count ( $args ), '%d' ));
2007-08-12 16:12:00 +00:00
} while ( $num_rows );
2007-05-27 20:31:13 +00:00
}
array_unshift ( $args , $menu_name );
}
else {
2007-07-04 15:49:44 +00:00
// Show only the top-level menu items when access is denied.
$args = array ( $menu_name , '0' );
2007-05-27 20:31:13 +00:00
$placeholders = '%d' ;
$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.
2007-07-16 12:51:03 +00:00
$data [ 'tree' ] = menu_tree_data ( db_query ( "
2007-12-27 14:03:37 +00:00
SELECT m . load_functions , m . to_arg_functions , m . access_callback , m . access_arguments , m . page_callback , m . page_arguments , m . title , m . title_callback , m . title_arguments , m . type , m . description , ml .*
2007-06-01 09:40:40 +00:00
FROM { menu_links } ml LEFT JOIN { menu_router } m ON m . path = ml . router_path
2008-04-14 17:48:46 +00:00
WHERE ml . menu_name = '%s' AND ml . plid IN ( " . $placeholders . " )
2007-08-11 14:06:15 +00:00
ORDER BY p1 ASC , p2 ASC , p3 ASC , p4 ASC , p5 ASC , p6 ASC , p7 ASC , p8 ASC , p9 ASC " , $args ), $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 .
*/
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 .
*/
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 ) {
// Use db_rewrite_sql to evaluate view access without loading each full node.
$nids = array_keys ( $node_links );
2008-04-14 17:48:46 +00:00
$placeholders = '%d' . str_repeat ( ', %d' , count ( $nids ) - 1 );
$result = db_query ( db_rewrite_sql ( " SELECT n.nid FROM { node} n WHERE n.status = 1 AND n.nid IN ( " . $placeholders . " ) " ), $nids );
2007-07-16 12:51:03 +00:00
while ( $node = db_fetch_array ( $result )) {
2007-10-01 09:53:03 +00:00
$nid = $node [ 'nid' ];
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
*
* @ param $result
* The database result .
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
* The depth of the current menu tree .
* @ return
2007-07-04 15:49:44 +00:00
* See menu_tree_page_data for a description of the data structure .
*/
function menu_tree_data ( $result = NULL , $parents = array (), $depth = 1 ) {
list (, $tree ) = _menu_tree_data ( $result , $parents , $depth );
return $tree ;
}
/**
* Recursive helper function to build the data representing a menu tree .
*
* The function is a bit complex because the rendering of an item depends on
* the next menu item . So we are always rendering the element previously
* processed not the current one .
2007-03-12 13:01:10 +00:00
*/
2007-07-04 15:49:44 +00:00
function _menu_tree_data ( $result , $parents , $depth , $previous_element = '' ) {
2007-03-12 13:01:10 +00:00
$remnant = NULL ;
2007-05-16 13:45:17 +00:00
$tree = array ();
2007-05-27 20:31:13 +00:00
while ( $item = db_fetch_array ( $result )) {
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 );
2007-03-12 13:01:10 +00:00
// The current item is the first in a new submenu.
2007-05-27 20:31:13 +00:00
if ( $item [ 'depth' ] > $depth ) {
2007-05-16 13:45:17 +00:00
// _menu_tree returns an item and the menu tree structure.
2007-05-27 20:31:13 +00:00
list ( $item , $below ) = _menu_tree_data ( $result , $parents , $item [ 'depth' ], $item );
2007-11-09 22:14:41 +00:00
if ( $previous_element ) {
$tree [ $previous_element [ 'mlid' ]] = array (
'link' => $previous_element ,
'below' => $below ,
);
}
else {
$tree = $below ;
}
2007-05-16 13:45:17 +00:00
// We need to fall back one level.
2007-05-27 20:31:13 +00:00
if ( ! isset ( $item ) || $item [ 'depth' ] < $depth ) {
2007-05-16 13:45:17 +00:00
return array ( $item , $tree );
}
2007-03-12 13:01:10 +00:00
// This will be the link to be output in the next iteration.
2007-05-16 13:45:17 +00:00
$previous_element = $item ;
2003-12-16 21:06:34 +00:00
}
2007-07-04 21:33:55 +00:00
// We are at the same depth, so we use the previous element.
2007-05-27 20:31:13 +00:00
elseif ( $item [ 'depth' ] == $depth ) {
2007-07-04 21:33:55 +00:00
if ( $previous_element ) {
// Only the first time.
2007-11-09 22:14:41 +00:00
$tree [ $previous_element [ 'mlid' ]] = array (
2007-05-16 13:45:17 +00:00
'link' => $previous_element ,
2007-08-11 14:06:15 +00:00
'below' => FALSE ,
2007-05-16 13:45:17 +00:00
);
}
2007-03-12 13:01:10 +00:00
// This will be the link to be output in the next iteration.
2007-05-16 13:45:17 +00:00
$previous_element = $item ;
2003-02-20 22:44:51 +00:00
}
2007-05-16 13:45:17 +00:00
// The submenu ended with the previous item, so pass back the current item.
2007-01-24 14:48:36 +00:00
else {
2007-03-12 13:01:10 +00:00
$remnant = $item ;
2007-01-24 14:48:36 +00:00
break ;
2004-07-10 15:51:48 +00:00
}
2002-12-24 15:40:32 +00:00
}
2007-05-16 13:45:17 +00:00
if ( $previous_element ) {
2007-07-04 21:33:55 +00:00
// We have one more link dangling.
2007-07-16 12:51:03 +00:00
$tree [ $previous_element [ 'mlid' ]] = array (
2007-05-16 13:45:17 +00:00
'link' => $previous_element ,
2007-08-11 14:06:15 +00:00
'below' => FALSE ,
2007-05-16 13:45:17 +00:00
);
2007-01-24 14:48:36 +00:00
}
return array ( $remnant , $tree );
2002-12-24 15:40:32 +00:00
}
2004-06-18 15:04:37 +00:00
/**
2007-03-12 13:01:10 +00:00
* Generate the HTML output for a single menu link .
2007-12-06 09:58:34 +00:00
*
* @ ingroup themeable
2004-06-18 15:04:37 +00:00
*/
2007-05-16 13:45:17 +00:00
function theme_menu_item_link ( $link ) {
2008-02-04 12:07:23 +00:00
if ( empty ( $link [ 'localized_options' ])) {
$link [ 'localized_options' ] = array ();
2007-10-08 18:49:03 +00:00
}
2008-02-04 12:07:23 +00:00
return l ( $link [ 'title' ], $link [ 'href' ], $link [ 'localized_options' ]);
2004-06-18 15:04:37 +00:00
}
2003-09-28 11:08:17 +00:00
/**
2007-03-12 13:01:10 +00:00
* Generate the HTML output for a menu 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
}
/**
* Generate the HTML output for a menu item and submenu .
2007-12-06 09:58:34 +00:00
*
* @ ingroup themeable
2007-03-12 13:01:10 +00:00
*/
2007-10-08 14:15:09 +00:00
function theme_menu_item ( $link , $has_children , $menu = '' , $in_active_trail = FALSE , $extra_class = NULL ) {
2007-05-27 20:31:13 +00:00
$class = ( $menu ? 'expanded' : ( $has_children ? 'collapsed' : 'leaf' ));
2007-10-08 14:15:09 +00:00
if ( ! empty ( $extra_class )) {
2008-04-14 17:48:46 +00:00
$class .= ' ' . $extra_class ;
2007-10-08 14:15:09 +00:00
}
2007-05-27 20:31:13 +00:00
if ( $in_active_trail ) {
$class .= ' active-trail' ;
}
2008-04-14 17:48:46 +00:00
return '<li class="' . $class . '">' . $link . $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
*
* @ 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 ) {
2008-04-14 17:48:46 +00:00
return '<li ' . ( $active ? 'class="active" ' : '' ) . '>' . $link . " </li> \n " ;
2003-09-28 10:51:40 +00:00
}
2002-12-24 15:40:32 +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 ();
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
2004-08-10 05:44:17 +00:00
foreach ( module_list () as $name ) {
if ( module_hook ( $name , 'help' )) {
2007-06-30 19:46:58 +00:00
// Lookup help for this path.
if ( $help = module_invoke ( $name , 'help' , $router_path , $arg )) {
2008-04-14 17:48:46 +00:00
$output .= $help . " \n " ;
2004-08-10 05:44:17 +00:00
}
2007-07-04 21:33:55 +00:00
// Add "more help" link on admin pages if the module provides a
// standalone help page.
2008-04-14 17:48:46 +00:00
if ( $arg [ 0 ] == " admin " && module_exists ( 'help' ) && module_invoke ( $name , 'help' , 'admin/help#' . $arg [ 2 ], $empty_arg ) && $help ) {
$output .= theme ( " more_help_link " , url ( 'admin/help/' . $arg [ 2 ]));
2004-08-10 05:44:17 +00:00
}
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
}
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
*/
2007-05-16 13:45:17 +00:00
function menu_get_names ( $reset = FALSE ) {
static $names ;
if ( $reset || empty ( $names )) {
$names = array ();
$result = db_query ( " SELECT DISTINCT(menu_name) FROM { menu_links} ORDER BY menu_name " );
while ( $name = db_fetch_array ( $result )) {
$names [] = $name [ 'menu_name' ];
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 () {
return array ( 'navigation' , 'primary-links' , 'secondary-links' );
}
2007-07-04 15:49:44 +00:00
/**
* Return an array of links to be rendered as the Primary links .
*/
2007-01-24 14:48:36 +00:00
function menu_primary_links () {
2008-02-23 08:13:09 +00:00
return menu_navigation_links ( variable_get ( 'menu_primary_links_source' , 'primary-links' ));
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 .
*/
2007-01-24 14:48:36 +00:00
function menu_secondary_links () {
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.
2008-02-23 08:13:09 +00:00
if ( variable_get ( 'menu_secondary_links_source' , 'secondary-links' ) == variable_get ( 'menu_primary_links_source' , 'primary-links' )) {
return menu_navigation_links ( variable_get ( 'menu_primary_links_source' , 'primary-links' ), 1 );
2007-08-20 18:26:41 +00:00
}
else {
2008-02-23 08:13:09 +00:00
return menu_navigation_links ( variable_get ( 'menu_secondary_links_source' , 'secondary-links' ), 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.
$tree = menu_tree_page_data ( $menu_name );
// 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-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' ];
2007-09-26 18:14:05 +00:00
// Keyed with unique menu id to generate classes from theme_links().
2008-04-14 17:48:46 +00:00
$links [ 'menu-' . $item [ 'link' ][ 'mlid' ]] = $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
/**
* Collects the local tasks ( tabs ) for a given level .
*
* @ 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-06-30 19:46:58 +00:00
* @ param $return_root
* Whether to return the root path for the current page .
2007-02-11 09:30:51 +00:00
* @ return
2007-09-29 08:12:06 +00:00
* Themed output corresponding to the tabs of the requested level , or
* router path if $return_root == TRUE . This router path corresponds to
2007-06-30 19:46:58 +00:00
* a parent tab , if the current page is a default local task .
2007-02-11 09:30:51 +00:00
*/
2007-06-30 19:46:58 +00:00
function menu_local_tasks ( $level = 0 , $return_root = FALSE ) {
2007-08-11 14:06:15 +00:00
static $tabs ;
2007-06-30 19:46:58 +00:00
static $root_path ;
2007-05-16 13:45:17 +00:00
2007-08-11 14:06:15 +00:00
if ( ! isset ( $tabs )) {
$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' ]) {
2007-06-22 06:12:09 +00:00
return '' ;
2007-04-15 14:38:16 +00:00
}
2007-06-17 14:55:39 +00:00
// Get all tabs and the root page.
$result = db_query ( " SELECT * FROM { menu_router} WHERE tab_root = '%s' ORDER BY weight, title " , $router_item [ 'tab_root' ]);
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
2007-05-27 20:31:13 +00:00
while ( $item = db_fetch_array ( $result )) {
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 = '' ;
$next_path = '' ;
2007-06-22 06:12:09 +00:00
$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' ]) {
2007-06-22 06:12:09 +00:00
$count ++ ;
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 ) {
2007-06-17 14:55:39 +00:00
// 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' ]);
2008-01-28 15:55:25 +00:00
$link = theme ( 'menu_item_link' , array ( 'href' => $tasks [ $p ][ 'href' ]) + $item );
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' ];
2007-05-16 13:45:17 +00:00
}
else {
2007-10-08 18:49:03 +00:00
$link = theme ( 'menu_item_link' , $item );
2007-05-16 13:45:17 +00:00
$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 ;
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-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 ) {
2007-05-27 20:31:13 +00:00
if ( $item [ 'access' ]) {
2007-06-22 06:12:09 +00:00
$count ++ ;
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' ]);
2008-01-28 15:55:25 +00:00
$link = theme ( 'menu_item_link' , array ( 'href' => $tasks [ $p ][ 'href' ]) + $item );
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
}
else {
2007-10-08 18:49:03 +00:00
$link = theme ( 'menu_item_link' , $item );
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 );
2007-01-31 21:26:56 +00:00
}
2007-06-30 19:46:58 +00:00
if ( $return_root ) {
return $root_path ;
}
else {
// We do not display single tabs.
return ( isset ( $tabs [ $level ]) && $tabs [ $level ][ 'count' ] > 1 ) ? $tabs [ $level ][ 'output' ] : '' ;
}
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 () {
2007-05-16 13:45:17 +00:00
return menu_local_tasks ( 0 );
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 () {
2007-02-11 09:30:51 +00:00
return menu_local_tasks ( 1 );
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 () {
return menu_local_tasks ( 0 , TRUE );
}
/**
* 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 .
*/
2007-05-16 13:45:17 +00:00
function menu_set_active_menu_name ( $menu_name = NULL ) {
static $active ;
if ( isset ( $menu_name )) {
$active = $menu_name ;
}
elseif ( ! isset ( $active )) {
$active = 'navigation' ;
}
return $active ;
}
2007-07-04 15:49:44 +00:00
/**
* Get the active menu for the current page - determines the active trail .
*/
2007-05-16 13:45:17 +00:00
function menu_get_active_menu_name () {
return menu_set_active_menu_name ();
}
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
/**
2007-07-04 21:33:55 +00:00
* Set ( or get ) the active trail for the current page - the path to root in the menu tree .
2007-07-04 15:49:44 +00:00
*/
2007-05-16 13:45:17 +00:00
function menu_set_active_trail ( $new_trail = NULL ) {
static $trail ;
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
2007-09-26 18:14:05 +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
}
2007-09-07 20:31:02 +00:00
2007-05-27 20:31:13 +00:00
$tree = menu_tree_page_data ( menu_get_active_menu_name ());
2007-09-10 12:21:30 +00:00
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 {
2007-09-07 20:31:02 +00:00
// Move to the child link if it's in the active trail.
2007-05-27 20:31:13 +00:00
if ( $curr [ 'below' ] && $curr [ 'link' ][ 'in_active_trail' ]) {
2007-05-16 13:45:17 +00:00
$trail [] = $curr [ 'link' ];
$tree = $curr [ 'below' ];
}
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
/**
2007-07-04 21:33:55 +00:00
* Get the active trail for the current page - the path to root in the menu tree .
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 ) {
2007-07-04 15:49:44 +00:00
if ( is_numeric ( $mlid ) && $item = db_fetch_array ( db_query ( " SELECT m.*, ml.* FROM { menu_links} ml LEFT JOIN { menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d " , $mlid ))) {
2007-05-16 13:45:17 +00:00
_menu_link_translate ( $item );
2007-05-27 20:31:13 +00:00
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' ) {
2007-11-26 08:49:03 +00:00
static $cache_cleared = array ();
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
}
/**
2007-07-04 15:49:44 +00:00
* Clears all cached menu data . This should be called any time broad changes
* 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 .
2007-05-16 13:45:17 +00:00
*/
function menu_rebuild () {
2008-01-10 20:16:50 +00:00
variable_del ( 'menu_rebuild_needed' );
2007-05-16 13:45:17 +00:00
menu_cache_clear_all ();
$menu = menu_router_build ( TRUE );
_menu_navigation_links_rebuild ( $menu );
2007-10-05 13:17:09 +00:00
// Clear the page and block caches.
2007-11-26 08:49:03 +00:00
_menu_clear_page_cache ();
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
}
2007-05-16 13:45:17 +00:00
}
/**
* Collect , alter and store the menu definitions .
*/
2007-05-27 20:31:13 +00:00
function menu_router_build ( $reset = FALSE ) {
static $menu ;
if ( ! isset ( $menu ) || $reset ) {
2007-12-07 18:24:55 +00:00
if ( ! $reset && ( $cache = cache_get ( 'router:' , 'cache_menu' )) && isset ( $cache -> data )) {
2007-05-27 20:31:13 +00:00
$menu = $cache -> data ;
}
else {
db_query ( 'DELETE FROM {menu_router}' );
2007-07-04 21:33:55 +00:00
// We need to manually call each module so that we can know which module
// a given item came from.
2007-05-27 20:31:13 +00:00
$callbacks = array ();
foreach ( module_implements ( 'menu' ) as $module ) {
2008-04-14 17:48:46 +00:00
$router_items = call_user_func ( $module . '_menu' );
2007-05-27 20:31:13 +00:00
if ( isset ( $router_items ) && is_array ( $router_items )) {
foreach ( array_keys ( $router_items ) as $path ) {
$router_items [ $path ][ 'module' ] = $module ;
}
$callbacks = array_merge ( $callbacks , $router_items );
}
2007-05-22 05:52:17 +00:00
}
2007-05-27 20:31:13 +00:00
// Alter the menu as defined in modules, keys are like user/%user.
drupal_alter ( 'menu' , $callbacks );
$menu = _menu_router_build ( $callbacks );
cache_set ( 'router:' , $menu , 'cache_menu' );
2007-05-22 05:52:17 +00:00
}
}
2007-05-16 13:45:17 +00:00
return $menu ;
}
2007-05-06 05:47:52 +00:00
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' ]) {
$item = _menu_link_build ( $item );
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-02-23 08:10:03 +00:00
$existing_item = db_fetch_array ( db_query ( " SELECT mlid, menu_name, plid, customized, has_children, updated FROM { menu_links} WHERE link_path = '%s' AND module = '%s' " , $item [ 'link_path' ], 'system' ));
2007-07-04 15:49:44 +00:00
if ( $existing_item ) {
$item [ 'mlid' ] = $existing_item [ 'mlid' ];
2007-07-09 18:08:15 +00:00
$item [ 'menu_name' ] = $existing_item [ 'menu_name' ];
$item [ 'plid' ] = $existing_item [ 'plid' ];
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' ]) {
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
$placeholders = db_placeholders ( $menu , 'varchar' );
$paths = array_keys ( $menu );
2008-03-21 08:32:24 +00:00
// Updated and customized items whose router paths are gone need new ones.
2008-01-30 21:01:20 +00:00
$result = db_query ( " SELECT ml.link_path, ml.mlid, ml.router_path, ml.updated FROM { menu_links} ml WHERE ml.updated = 1 OR (router_path NOT IN ( $placeholders ) AND external = 0 AND customized = 1) " , $paths );
2007-08-19 09:46:15 +00:00
while ( $item = db_fetch_array ( $result )) {
2007-08-29 20:46:18 +00:00
$router_path = _menu_find_router_path ( $menu , $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' ];
db_query ( " UPDATE { menu_links} SET router_path = '%s', updated = %d WHERE mlid = %d " , $router_path , $updated , $item [ 'mlid' ]);
2007-08-29 20:46:18 +00:00
}
2007-08-19 09:46:15 +00:00
}
2008-03-21 08:32:24 +00:00
// Find any item whose router path does not exist any more.
2008-02-23 08:10:03 +00:00
$result = db_query ( " SELECT * FROM { menu_links} WHERE router_path NOT IN ( $placeholders ) AND external = 0 AND updated = 0 AND customized = 0 ORDER BY depth DESC " , $paths );
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().
while ( $item = db_fetch_array ( $result )) {
_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 )) {
_menu_delete_item ( db_fetch_array ( db_query ( " SELECT * FROM { menu_links} WHERE mlid = %d " , $mlid )));
}
else {
$result = db_query ( " SELECT * FROM { menu_links} WHERE link_path = '%s' " , $path );
while ( $link = db_fetch_array ( $result )) {
_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 ) {
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' ]) {
$result = db_query ( " SELECT mlid FROM { menu_links} WHERE plid = %d " , $item [ 'mlid' ]);
while ( $m = db_fetch_array ( $result )) {
$child = menu_link_load ( $m [ 'mlid' ]);
$child [ 'plid' ] = $item [ 'plid' ];
menu_link_save ( $child );
}
}
db_query ( 'DELETE FROM {menu_links} WHERE mlid = %d' , $item [ 'mlid' ]);
2007-05-28 06:24:41 +00:00
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 .
2007-05-16 13:45:17 +00:00
*/
2007-05-27 20:31:13 +00:00
function menu_link_save ( & $item ) {
$menu = menu_router_build ();
2007-05-16 13:45:17 +00:00
drupal_alter ( 'menu_link' , $item , $menu );
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.
$item [ '_external' ] = menu_path_is_external ( $item [ 'link_path' ]) || $item [ 'link_path' ] == '<front>' ;
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' ])) {
$existing_item = db_fetch_array ( db_query ( " SELECT * FROM { menu_links} WHERE mlid = %d " , $item [ 'mlid' ]));
}
if ( isset ( $item [ 'plid' ])) {
2008-02-10 07:33:02 +00:00
$parent = db_fetch_array ( db_query ( " SELECT * FROM { menu_links} WHERE mlid = %d " , $item [ 'plid' ]));
2007-05-16 13:45:17 +00:00
}
2007-05-27 20:31:13 +00:00
else {
2008-02-10 07:33:02 +00:00
// Find the parent - it must be unique.
2007-05-27 20:31:13 +00:00
$parent_path = $item [ 'link_path' ];
2008-02-10 07:33:02 +00:00
$where = " WHERE link_path = '%s' " ;
// 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' ) {
$where .= " AND module = '%s' " ;
$arg2 = 'system' ;
}
else {
// If not derived from a router item, we respect the specified menu name.
$where .= " AND menu_name = '%s' " ;
$arg2 = $item [ 'menu_name' ];
}
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 , '/' ));
2008-04-14 17:48:46 +00:00
$result = db_query ( " SELECT COUNT(*) FROM { menu_links} " . $where , $parent_path , $arg2 );
2008-02-10 07:33:02 +00:00
// Only valid if we get a unique result.
if ( db_result ( $result ) == 1 ) {
2008-04-14 17:48:46 +00:00
$parent = db_fetch_array ( db_query ( " SELECT * FROM { menu_links} " . $where , $parent_path , $arg2 ));
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 ) {
db_query ( " INSERT INTO { menu_links} (
menu_name , plid , link_path ,
hidden , external , has_children ,
expanded , weight ,
2007-08-29 20:46:18 +00:00
module , link_title , options ,
customized , updated ) VALUES (
2007-06-06 06:26:15 +00:00
'%s' , % d , '%s' ,
% d , % d , % d ,
% d , % d ,
2007-08-29 20:46:18 +00:00
'%s' , '%s' , '%s' , % d , % d ) " ,
2007-06-06 06:26:15 +00:00
$item [ 'menu_name' ], $item [ 'plid' ], $item [ 'link_path' ],
$item [ 'hidden' ], $item [ '_external' ], $item [ 'has_children' ],
$item [ 'expanded' ], $item [ 'weight' ],
2007-08-29 20:46:18 +00:00
$item [ 'module' ], $item [ 'link_title' ], serialize ( $item [ 'options' ]),
$item [ 'customized' ], $item [ 'updated' ]);
2007-06-06 06:26:15 +00:00
$item [ 'mlid' ] = db_last_insert_id ( 'menu_links' , 'mlid' );
}
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
}
2007-08-29 20:46:18 +00:00
// Find the callback. During the menu update we store empty paths to be
// fixed later, so we skip this.
if ( ! isset ( $_SESSION [ 'system_update_6021' ]) && ( empty ( $item [ 'router_path' ]) || ! $existing_item || ( $existing_item [ 'link_path' ] != $item [ 'link_path' ]))) {
2007-05-16 13:45:17 +00:00
if ( $item [ '_external' ]) {
$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 );
2007-08-29 20:46:18 +00:00
$item [ 'router_path' ] = _menu_find_router_path ( $menu , $item [ 'link_path' ]);
2007-05-16 13:45:17 +00:00
}
}
2007-06-05 12:13:23 +00:00
db_query ( " UPDATE { menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
router_path = '%s' , hidden = % d , external = % d , has_children = % d ,
2007-12-22 23:24:26 +00:00
expanded = % d , weight = % d , depth = % d ,
2007-08-11 14:06:15 +00:00
p1 = % d , p2 = % d , p3 = % d , p4 = % d , p5 = % d , p6 = % d , p7 = % d , p8 = % d , p9 = % d ,
2007-07-04 15:49:44 +00:00
module = '%s' , link_title = '%s' , options = '%s' , customized = % d WHERE mlid = % d " ,
2007-06-05 12:13:23 +00:00
$item [ 'menu_name' ], $item [ 'plid' ], $item [ 'link_path' ],
$item [ 'router_path' ], $item [ 'hidden' ], $item [ '_external' ], $item [ 'has_children' ],
$item [ 'expanded' ], $item [ 'weight' ], $item [ 'depth' ],
2007-08-11 14:06:15 +00:00
$item [ 'p1' ], $item [ 'p2' ], $item [ 'p3' ], $item [ 'p4' ], $item [ 'p5' ], $item [ 'p6' ], $item [ 'p7' ], $item [ 'p8' ], $item [ 'p9' ],
2007-07-04 15:49:44 +00:00
$item [ 'module' ], $item [ 'link_title' ], serialize ( $item [ 'options' ]), $item [ 'customized' ], $item [ 'mlid' ]);
2007-05-27 20:31:13 +00:00
// Check the has_children status of the parent.
2007-08-11 14:06:15 +00:00
_menu_update_parental_status ( $item );
2007-05-27 20:31:13 +00:00
menu_cache_clear ( $menu_name );
if ( $existing_item && $menu_name != $existing_item [ 'menu_name' ]) {
menu_cache_clear ( $existing_item [ 'menu_name' ]);
}
2007-11-26 08:49:03 +00:00
_menu_clear_page_cache ();
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 () {
static $cache_cleared = 0 ;
2007-11-26 16:19:37 +00:00
// Clear the page and block caches, but at most twice, including at
2007-11-26 08:49:03 +00:00
// the end of the page load when there are multple links saved or deleted.
if ( empty ( $cache_cleared )) {
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 () {
2007-05-16 13:45:17 +00:00
$names = array ();
$result = db_query ( " SELECT menu_name FROM { menu_links} WHERE expanded != 0 GROUP BY menu_name " );
while ( $n = db_fetch_array ( $result )) {
$names [] = $n [ 'menu_name' ];
}
variable_set ( 'menu_expanded' , $names );
}
2007-08-29 20:46:18 +00:00
/**
* Find the router path which will serve this path .
*
* @ param $menu
* The full built menu .
* @ 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 .
*/
2007-09-06 12:07:31 +00:00
function _menu_find_router_path ( $menu , $link_path ) {
2007-08-29 20:46:18 +00:00
$parts = explode ( '/' , $link_path , MENU_MAX_PARTS );
$router_path = $link_path ;
if ( ! isset ( $menu [ $router_path ])) {
list ( $ancestors ) = menu_get_ancestors ( $parts );
$ancestors [] = '' ;
2007-09-10 12:21:30 +00:00
foreach ( $ancestors as $key => $router_path ) {
if ( isset ( $menu [ $router_path ])) {
break ;
}
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' :
2007-12-22 23:24:26 +00:00
db_query ( " UPDATE { menu_links} SET link_title = '%s' WHERE link_path = '%s' AND customized = 0 AND module = '%s' " , $link_title , $link_path , $module );
2007-10-16 13:48:11 +00:00
menu_cache_clear ();
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 ) {
$i = 1 ;
$match = '' ;
$args [] = $item [ 'menu_name' ];
$p = 'p1' ;
while ( $i <= MENU_MAX_DEPTH && $item [ $p ]) {
$match .= " AND $p = %d " ;
$args [] = $item [ $p ];
2008-04-14 17:48:46 +00:00
$p = 'p' . ++ $i ;
2007-05-27 20:31:13 +00:00
}
2008-04-14 17:48:46 +00:00
$max_depth = db_result ( db_query_range ( " SELECT depth FROM { menu_links} WHERE menu_name = '%s' " . $match . " ORDER BY depth DESC " , $args , 0 , 1 ));
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 ) {
$args [] = $item [ 'menu_name' ];
2007-08-04 13:58:06 +00:00
$set [] = " menu_name = '%s' " ;
2007-05-27 20:31:13 +00:00
$i = 1 ;
while ( $i <= $item [ 'depth' ]) {
2008-04-14 17:48:46 +00:00
$p = 'p' . $i ++ ;
2007-08-04 13:58:06 +00:00
$set [] = " $p = %d " ;
2007-05-27 20:31:13 +00:00
$args [] = $item [ $p ];
}
$j = $existing_item [ 'depth' ] + 1 ;
while ( $i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH ) {
2008-04-14 17:48:46 +00:00
$set [] = 'p' . $i ++ . ' = p' . $j ++ ;
2007-05-27 20:31:13 +00:00
}
while ( $i <= MENU_MAX_DEPTH ) {
2008-04-14 17:48:46 +00:00
$set [] = 'p' . $i ++ . ' = 0' ;
2007-08-04 13:58:06 +00:00
}
$shift = $item [ 'depth' ] - $existing_item [ 'depth' ];
if ( $shift < 0 ) {
$args [] = - $shift ;
$set [] = 'depth = depth - %d' ;
}
elseif ( $shift > 0 ) {
// The order of $set must be reversed so the new values don't overwrite the
2007-08-07 08:41:24 +00:00
// old ones before they can be used because "Single-table UPDATE
2007-08-04 13:58:06 +00:00
// assignments are generally evaluated from left to right"
// see: http://dev.mysql.com/doc/refman/5.0/en/update.html
$set = array_reverse ( $set );
$args = array_reverse ( $args );
$args [] = $shift ;
$set [] = 'depth = depth + %d' ;
2007-05-27 20:31:13 +00:00
}
2007-08-11 14:06:15 +00:00
$where [] = " menu_name = '%s' " ;
$args [] = $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 ) {
2007-08-04 13:58:06 +00:00
$where [] = " $p = %d " ;
2007-05-27 20:31:13 +00:00
$args [] = $existing_item [ $p ];
}
2008-04-14 17:48:46 +00:00
db_query ( " UPDATE { menu_links} SET " . implode ( ', ' , $set ) . " WHERE " . implode ( ' AND ' , $where ), $args );
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' ]) {
// We may want to exclude the passed link as a possible child.
$where = $exclude ? " AND mlid != %d " : '' ;
// Check if at least one visible child exists in the table.
2008-04-14 17:48:46 +00:00
$parent_has_children = ( bool ) db_result ( db_query_range ( " SELECT mlid FROM { menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0 " . $where , $item [ 'menu_name' ], $item [ 'plid' ], $item [ 'mlid' ], 0 , 1 ));
2007-08-11 14:06:15 +00:00
db_query ( " UPDATE { menu_links} SET has_children = %d WHERE mlid = %d " , $parent_has_children , $item [ 'plid' ]);
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 ();
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 ;
if ( preg_match ( '/^%([a-z_]*)$/' , $part , $matches )) {
if ( empty ( $matches [ 1 ])) {
$match = TRUE ;
$load_functions [ $k ] = NULL ;
}
else {
2008-04-14 17:48:46 +00:00
if ( function_exists ( $matches [ 1 ] . '_to_arg' )) {
$to_arg_functions [ $k ] = $matches [ 1 ] . '_to_arg' ;
2007-05-16 13:45:17 +00:00
$load_functions [ $k ] = NULL ;
$match = TRUE ;
}
2008-04-14 17:48:46 +00:00
if ( function_exists ( $matches [ 1 ] . '_load' )) {
$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' ];
}
2007-05-22 05:52:17 +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
}
}
}
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' ,
'description' => '' ,
'position' => '' ,
'tab_parent' => '' ,
'tab_root' => $path ,
'path' => $path ,
2007-05-22 05:52:17 +00:00
'file' => '' ,
'file path' => '' ,
'include file' => '' ,
2007-05-16 13:45:17 +00:00
);
2007-05-22 05:52:17 +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' ]);
2008-04-14 17:48:46 +00:00
$item [ 'include file' ] = $file_path . '/' . $item [ 'file' ];
2007-05-22 05:52:17 +00:00
}
2008-01-06 16:47:19 +00:00
$title_arguments = $item [ 'title arguments' ] ? serialize ( $item [ 'title arguments' ]) : '' ;
2007-05-16 13:45:17 +00:00
db_query ( " INSERT INTO { menu_router}
( 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 ,
2007-05-22 05:52:17 +00:00
type , block_callback , description , position , weight , file )
2007-05-16 13:45:17 +00:00
VALUES ( '%s' , '%s' , '%s' , '%s' ,
'%s' , '%s' , '%s' , % d ,
% d , '%s' , '%s' ,
'%s' , '%s' , '%s' ,
2007-05-22 05:52:17 +00:00
% d , '%s' , '%s' , '%s' , % d , '%s' ) " ,
2007-05-16 13:45:17 +00:00
$path , $item [ 'load_functions' ], $item [ 'to_arg_functions' ], $item [ 'access callback' ],
serialize ( $item [ 'access arguments' ]), $item [ 'page callback' ], serialize ( $item [ 'page arguments' ]), $item [ '_fit' ],
$item [ '_number_parts' ], $item [ 'tab_parent' ], $item [ 'tab_root' ],
2008-01-06 16:47:19 +00:00
$item [ 'title' ], $item [ 'title callback' ], $title_arguments ,
2007-05-22 05:52:17 +00:00
$item [ 'type' ], $item [ 'block callback' ], $item [ 'description' ], $item [ 'position' ], $item [ 'weight' ], $item [ 'include file' ]);
2007-05-16 13:45:17 +00:00
}
2007-08-11 14:06:15 +00:00
// Sort the masks so they are in order of descending fit, and store them.
$masks = array_keys ( $masks );
rsort ( $masks );
variable_set ( 'menu_masks' , $masks );
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
/**
2008-01-02 14:29:32 +00:00
* Checks whether the site is off - line for maintenance .
*
* This function will log the current user out and redirect to front page
* if the current user has no 'administer site configuration' permission .
*
* @ return
* FALSE if the site is not off - line or its the login page or the user has
* 'administer site configuration' permission .
* TRUE for anonymous users not on the login page if the site is off - line .
2007-05-26 10:54:12 +00:00
*/
function _menu_site_is_offline () {
2007-07-04 15:49:44 +00:00
// Check if site is set to off-line mode.
2007-05-26 10:54:12 +00:00
if ( variable_get ( 'site_offline' , 0 )) {
2007-07-04 15:49:44 +00:00
// Check if the user has administration privileges.
2008-01-02 14:29:32 +00:00
if ( user_access ( 'administer site configuration' )) {
2007-07-04 21:33:55 +00:00
// Ensure that the off-line message is displayed only once [allowing for
2008-02-10 07:35:40 +00:00
// page redirects], and specifically suppress its display on the site
// maintenance page.
if ( drupal_get_normal_path ( $_GET [ 'q' ]) != 'admin/settings/site-maintenance' ) {
drupal_set_message ( t ( 'Operating in off-line mode.' ), 'status' , FALSE );
}
2008-01-02 14:29:32 +00:00
}
else {
// Anonymous users get a FALSE at the login prompt, TRUE otherwise.
if ( user_is_anonymous ()) {
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.
2008-04-14 17:48:46 +00:00
require_once drupal_get_path ( 'module' , 'user' ) . '/user.pages.inc' ;
2008-01-02 14:29:32 +00:00
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.
if ( $item = db_fetch_array ( db_query ( " SELECT * FROM { menu_router} where path = '%s' " , $path ))) {
$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 " .
*/