PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/menu.inc

https://github.com/talavishay/avihai
Pascal | 3891 lines | 2254 code | 123 blank | 1514 comment | 260 complexity | 9ce121f9042e16e941b5078dc51480c9 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @file
  4. * API for the Drupal menu system.
  5. */
  6. /**
  7. * @defgroup menu Menu system
  8. * @{
  9. * Define the navigation menus, and route page requests to code based on URLs.
  10. *
  11. * The Drupal menu system drives both the navigation system from a user
  12. * perspective and the callback system that Drupal uses to respond to URLs
  13. * passed from the browser. For this reason, a good understanding of the
  14. * menu system is fundamental to the creation of complex modules. As a note,
  15. * this is related to, but separate from menu.module, which allows menus
  16. * (which in this context are hierarchical lists of links) to be customized from
  17. * the Drupal administrative interface.
  18. *
  19. * Drupal's menu system follows a simple hierarchy defined by paths.
  20. * Implementations of hook_menu() define menu items and assign them to
  21. * paths (which should be unique). The menu system aggregates these items
  22. * and determines the menu hierarchy from the paths. For example, if the
  23. * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
  24. * would form the structure:
  25. * - a
  26. * - a/b
  27. * - a/b/c/d
  28. * - a/b/h
  29. * - e
  30. * - f/g
  31. * Note that the number of elements in the path does not necessarily
  32. * determine the depth of the menu item in the tree.
  33. *
  34. * When responding to a page request, the menu system looks to see if the
  35. * path requested by the browser is registered as a menu item with a
  36. * callback. If not, the system searches up the menu tree for the most
  37. * complete match with a callback it can find. If the path a/b/i is
  38. * requested in the tree above, the callback for a/b would be used.
  39. *
  40. * The found callback function is called with any arguments specified
  41. * in the "page arguments" attribute of its menu item. The
  42. * attribute must be an array. After these arguments, any remaining
  43. * components of the path are appended as further arguments. In this
  44. * way, the callback for a/b above could respond to a request for
  45. * a/b/i differently than a request for a/b/j.
  46. *
  47. * For an illustration of this process, see page_example.module.
  48. *
  49. * Access to the callback functions is also protected by the menu system.
  50. * The "access callback" with an optional "access arguments" of each menu
  51. * item is called before the page callback proceeds. If this returns TRUE,
  52. * then access is granted; if FALSE, then access is denied. Default local task
  53. * menu items (see next paragraph) may omit this attribute to use the value
  54. * provided by the parent item.
  55. *
  56. * In the default Drupal interface, you will notice many links rendered as
  57. * tabs. These are known in the menu system as "local tasks", and they are
  58. * rendered as tabs by default, though other presentations are possible.
  59. * Local tasks function just as other menu items in most respects. It is
  60. * convention that the names of these tasks should be short verbs if
  61. * possible. In addition, a "default" local task should be provided for
  62. * each set. When visiting a local task's parent menu item, the default
  63. * local task will be rendered as if it is selected; this provides for a
  64. * normal tab user experience. This default task is special in that it
  65. * links not to its provided path, but to its parent item's path instead.
  66. * The default task's path is only used to place it appropriately in the
  67. * menu hierarchy.
  68. *
  69. * Everything described so far is stored in the menu_router table. The
  70. * menu_links table holds the visible menu links. By default these are
  71. * derived from the same hook_menu definitions, however you are free to
  72. * add more with menu_link_save().
  73. */
  74. /**
  75. * @defgroup menu_flags Menu flags
  76. * @{
  77. * Flags for use in the "type" attribute of menu items.
  78. */
  79. /**
  80. * Internal menu flag -- menu item is the root of the menu tree.
  81. */
  82. define('MENU_IS_ROOT', 0x0001);
  83. /**
  84. * Internal menu flag -- menu item is visible in the menu tree.
  85. */
  86. define('MENU_VISIBLE_IN_TREE', 0x0002);
  87. /**
  88. * Internal menu flag -- menu item is visible in the breadcrumb.
  89. */
  90. define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
  91. /**
  92. * Internal menu flag -- menu item links back to its parent.
  93. */
  94. define('MENU_LINKS_TO_PARENT', 0x0008);
  95. /**
  96. * Internal menu flag -- menu item can be modified by administrator.
  97. */
  98. define('MENU_MODIFIED_BY_ADMIN', 0x0020);
  99. /**
  100. * Internal menu flag -- menu item was created by administrator.
  101. */
  102. define('MENU_CREATED_BY_ADMIN', 0x0040);
  103. /**
  104. * Internal menu flag -- menu item is a local task.
  105. */
  106. define('MENU_IS_LOCAL_TASK', 0x0080);
  107. /**
  108. * Internal menu flag -- menu item is a local action.
  109. */
  110. define('MENU_IS_LOCAL_ACTION', 0x0100);
  111. /**
  112. * @} End of "Menu flags".
  113. */
  114. /**
  115. * @defgroup menu_item_types Menu item types
  116. * @{
  117. * Definitions for various menu item types.
  118. *
  119. * Menu item definitions provide one of these constants, which are shortcuts for
  120. * combinations of @link menu_flags Menu flags @endlink.
  121. */
  122. /**
  123. * Menu type -- A "normal" menu item that's shown in menu and breadcrumbs.
  124. *
  125. * Normal menu items show up in the menu tree and can be moved/hidden by
  126. * the administrator. Use this for most menu items. It is the default value if
  127. * no menu item type is specified.
  128. */
  129. define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
  130. /**
  131. * Menu type -- A hidden, internal callback, typically used for API calls.
  132. *
  133. * Callbacks simply register a path so that the correct function is fired
  134. * when the URL is accessed. They do not appear in menus or breadcrumbs.
  135. */
  136. define('MENU_CALLBACK', 0x0000);
  137. /**
  138. * Menu type -- A normal menu item, hidden until enabled by an administrator.
  139. *
  140. * Modules may "suggest" menu items that the administrator may enable. They act
  141. * just as callbacks do until enabled, at which time they act like normal items.
  142. * Note for the value: 0x0010 was a flag which is no longer used, but this way
  143. * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate.
  144. */
  145. define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010);
  146. /**
  147. * Menu type -- A task specific to the parent item, usually rendered as a tab.
  148. *
  149. * Local tasks are menu items that describe actions to be performed on their
  150. * parent item. An example is the path "node/52/edit", which performs the
  151. * "edit" task on "node/52".
  152. */
  153. define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB);
  154. /**
  155. * Menu type -- The "default" local task, which is initially active.
  156. *
  157. * Every set of local tasks should provide one "default" task, that links to the
  158. * same path as its parent when clicked.
  159. */
  160. define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB);
  161. /**
  162. * Menu type -- An action specific to the parent, usually rendered as a link.
  163. *
  164. * Local actions are menu items that describe actions on the parent item such
  165. * as adding a new user, taxonomy term, etc.
  166. */
  167. define('MENU_LOCAL_ACTION', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
  168. /**
  169. * @} End of "Menu item types".
  170. */
  171. /**
  172. * @defgroup menu_context_types Menu context types
  173. * @{
  174. * Flags for use in the "context" attribute of menu router items.
  175. */
  176. /**
  177. * Internal menu flag: Invisible local task.
  178. *
  179. * This flag may be used for local tasks like "Delete", so custom modules and
  180. * themes can alter the default context and expose the task by altering menu.
  181. */
  182. define('MENU_CONTEXT_NONE', 0x0000);
  183. /**
  184. * Internal menu flag: Local task should be displayed in page context.
  185. */
  186. define('MENU_CONTEXT_PAGE', 0x0001);
  187. /**
  188. * Internal menu flag: Local task should be displayed inline.
  189. */
  190. define('MENU_CONTEXT_INLINE', 0x0002);
  191. /**
  192. * @} End of "Menu context types".
  193. */
  194. /**
  195. * @defgroup menu_status_codes Menu status codes
  196. * @{
  197. * Status codes for menu callbacks.
  198. */
  199. /**
  200. * Internal menu status code -- Menu item was found.
  201. */
  202. define('MENU_FOUND', 1);
  203. /**
  204. * Internal menu status code -- Menu item was not found.
  205. */
  206. define('MENU_NOT_FOUND', 2);
  207. /**
  208. * Internal menu status code -- Menu item access is denied.
  209. */
  210. define('MENU_ACCESS_DENIED', 3);
  211. /**
  212. * Internal menu status code -- Menu item inaccessible because site is offline.
  213. */
  214. define('MENU_SITE_OFFLINE', 4);
  215. /**
  216. * Internal menu status code -- Everything is working fine.
  217. */
  218. define('MENU_SITE_ONLINE', 5);
  219. /**
  220. * @} End of "Menu status codes".
  221. */
  222. /**
  223. * @defgroup menu_tree_parameters Menu tree parameters
  224. * @{
  225. * Parameters for a menu tree.
  226. */
  227. /**
  228. * The maximum number of path elements for a menu callback
  229. */
  230. define('MENU_MAX_PARTS', 9);
  231. /**
  232. * The maximum depth of a menu links tree - matches the number of p columns.
  233. */
  234. define('MENU_MAX_DEPTH', 9);
  235. /**
  236. * @} End of "Menu tree parameters".
  237. */
  238. /**
  239. * Reserved key to identify the most specific menu link for a given path.
  240. *
  241. * The value of this constant is a hash of the constant name. We use the hash
  242. * so that the reserved key is over 32 characters in length and will not
  243. * collide with allowed menu names:
  244. * @code
  245. * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
  246. * @endcode
  247. *
  248. * @see menu_link_get_preferred()
  249. */
  250. define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91');
  251. /**
  252. * Returns the ancestors (and relevant placeholders) for any given path.
  253. *
  254. * For example, the ancestors of node/12345/edit are:
  255. * - node/12345/edit
  256. * - node/12345/%
  257. * - node/%/edit
  258. * - node/%/%
  259. * - node/12345
  260. * - node/%
  261. * - node
  262. *
  263. * To generate these, we will use binary numbers. Each bit represents a
  264. * part of the path. If the bit is 1, then it represents the original
  265. * value while 0 means wildcard. If the path is node/12/edit/foo
  266. * then the 1011 bitstring represents node/%/edit/foo where % means that
  267. * any argument matches that part. We limit ourselves to using binary
  268. * numbers that correspond the patterns of wildcards of router items that
  269. * actually exists. This list of 'masks' is built in menu_rebuild().
  270. *
  271. * @param $parts
  272. * An array of path parts; for the above example,
  273. * array('node', '12345', 'edit').
  274. *
  275. * @return
  276. * An array which contains the ancestors and placeholders. Placeholders
  277. * simply contain as many '%s' as the ancestors.
  278. */
  279. function menu_get_ancestors($parts) {
  280. $number_parts = count($parts);
  281. $ancestors = array();
  282. $length = $number_parts - 1;
  283. $end = (1 << $number_parts) - 1;
  284. $masks = variable_get('menu_masks');
  285. // If the optimized menu_masks array is not available use brute force to get
  286. // the correct $ancestors and $placeholders returned. Do not use this as the
  287. // default value of the menu_masks variable to avoid building such a big
  288. // array.
  289. if (!$masks) {
  290. $masks = range(511, 1);
  291. }
  292. // Only examine patterns that actually exist as router items (the masks).
  293. foreach ($masks as $i) {
  294. if ($i > $end) {
  295. // Only look at masks that are not longer than the path of interest.
  296. continue;
  297. }
  298. elseif ($i < (1 << $length)) {
  299. // We have exhausted the masks of a given length, so decrease the length.
  300. --$length;
  301. }
  302. $current = '';
  303. for ($j = $length; $j >= 0; $j--) {
  304. // Check the bit on the $j offset.
  305. if ($i & (1 << $j)) {
  306. // Bit one means the original value.
  307. $current .= $parts[$length - $j];
  308. }
  309. else {
  310. // Bit zero means means wildcard.
  311. $current .= '%';
  312. }
  313. // Unless we are at offset 0, add a slash.
  314. if ($j) {
  315. $current .= '/';
  316. }
  317. }
  318. $ancestors[] = $current;
  319. }
  320. return $ancestors;
  321. }
  322. /**
  323. * Unserializes menu data, using a map to replace path elements.
  324. *
  325. * The menu system stores various path-related information (such as the 'page
  326. * arguments' and 'access arguments' components of a menu item) in the database
  327. * using serialized arrays, where integer values in the arrays represent
  328. * arguments to be replaced by values from the path. This function first
  329. * unserializes such menu information arrays, and then does the path
  330. * replacement.
  331. *
  332. * The path replacement acts on each integer-valued element of the unserialized
  333. * menu data array ($data) using a map array ($map, which is typically an array
  334. * of path arguments) as a list of replacements. For instance, if there is an
  335. * element of $data whose value is the number 2, then it is replaced in $data
  336. * with $map[2]; non-integer values in $data are left alone.
  337. *
  338. * As an example, an unserialized $data array with elements ('node_load', 1)
  339. * represents instructions for calling the node_load() function. Specifically,
  340. * this instruction says to use the path component at index 1 as the input
  341. * parameter to node_load(). If the path is 'node/123', then $map will be the
  342. * array ('node', 123), and the returned array from this function will have
  343. * elements ('node_load', 123), since $map[1] is 123. This return value will
  344. * indicate specifically that node_load(123) is to be called to load the node
  345. * whose ID is 123 for this menu item.
  346. *
  347. * @param $data
  348. * A serialized array of menu data, as read from the database.
  349. * @param $map
  350. * A path argument array, used to replace integer values in $data; an integer
  351. * value N in $data will be replaced by value $map[N]. Typically, the $map
  352. * array is generated from a call to the arg() function.
  353. *
  354. * @return
  355. * The unserialized $data array, with path arguments replaced.
  356. */
  357. function menu_unserialize($data, $map) {
  358. if ($data = unserialize($data)) {
  359. foreach ($data as $k => $v) {
  360. if (is_int($v)) {
  361. $data[$k] = isset($map[$v]) ? $map[$v] : '';
  362. }
  363. }
  364. return $data;
  365. }
  366. else {
  367. return array();
  368. }
  369. }
  370. /**
  371. * Replaces the statically cached item for a given path.
  372. *
  373. * @param $path
  374. * The path.
  375. * @param $router_item
  376. * The router item. Usually a router entry from menu_get_item() is either
  377. * modified or set to a different path. This allows the navigation block,
  378. * the page title, the breadcrumb, and the page help to be modified in one
  379. * call.
  380. */
  381. function menu_set_item($path, $router_item) {
  382. menu_get_item($path, $router_item);
  383. }
  384. /**
  385. * Gets a router item.
  386. *
  387. * @param $path
  388. * The path; for example, 'node/5'. The function will find the corresponding
  389. * node/% item and return that.
  390. * @param $router_item
  391. * Internal use only.
  392. *
  393. * @return
  394. * The router item or, if an error occurs in _menu_translate(), FALSE. A
  395. * router item is an associative array corresponding to one row in the
  396. * menu_router table. The value corresponding to the key 'map' holds the
  397. * loaded objects. The value corresponding to the key 'access' is TRUE if the
  398. * current user can access this page. The values corresponding to the keys
  399. * 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will
  400. * be filled in based on the database values and the objects loaded.
  401. */
  402. function menu_get_item($path = NULL, $router_item = NULL) {
  403. $router_items = &drupal_static(__FUNCTION__);
  404. if (!isset($path)) {
  405. $path = $_GET['q'];
  406. }
  407. if (isset($router_item)) {
  408. $router_items[$path] = $router_item;
  409. }
  410. if (!isset($router_items[$path])) {
  411. // Rebuild if we know it's needed, or if the menu masks are missing which
  412. // occurs rarely, likely due to a race condition of multiple rebuilds.
  413. if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
  414. menu_rebuild();
  415. }
  416. $original_map = arg(NULL, $path);
  417. $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
  418. $ancestors = menu_get_ancestors($parts);
  419. $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
  420. if ($router_item) {
  421. // Allow modules to alter the router item before it is translated and
  422. // checked for access.
  423. drupal_alter('menu_get_item', $router_item, $path, $original_map);
  424. $map = _menu_translate($router_item, $original_map);
  425. $router_item['original_map'] = $original_map;
  426. if ($map === FALSE) {
  427. $router_items[$path] = FALSE;
  428. return FALSE;
  429. }
  430. if ($router_item['access']) {
  431. $router_item['map'] = $map;
  432. $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
  433. $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
  434. }
  435. }
  436. $router_items[$path] = $router_item;
  437. }
  438. return $router_items[$path];
  439. }
  440. /**
  441. * Execute the page callback associated with the current path.
  442. *
  443. * @param $path
  444. * The drupal path whose handler is to be be executed. If set to NULL, then
  445. * the current path is used.
  446. * @param $deliver
  447. * (optional) A boolean to indicate whether the content should be sent to the
  448. * browser using the appropriate delivery callback (TRUE) or whether to return
  449. * the result to the caller (FALSE).
  450. */
  451. function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
  452. // Check if site is offline.
  453. $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
  454. // Allow other modules to change the site status but not the path because that
  455. // would not change the global variable. hook_url_inbound_alter() can be used
  456. // to change the path. Code later will not use the $read_only_path variable.
  457. $read_only_path = !empty($path) ? $path : $_GET['q'];
  458. drupal_alter('menu_site_status', $page_callback_result, $read_only_path);
  459. // Only continue if the site status is not set.
  460. if ($page_callback_result == MENU_SITE_ONLINE) {
  461. if ($router_item = menu_get_item($path)) {
  462. if ($router_item['access']) {
  463. if ($router_item['include_file']) {
  464. require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
  465. }
  466. $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
  467. }
  468. else {
  469. $page_callback_result = MENU_ACCESS_DENIED;
  470. }
  471. }
  472. else {
  473. $page_callback_result = MENU_NOT_FOUND;
  474. }
  475. }
  476. // Deliver the result of the page callback to the browser, or if requested,
  477. // return it raw, so calling code can do more processing.
  478. if ($deliver) {
  479. $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;
  480. drupal_deliver_page($page_callback_result, $default_delivery_callback);
  481. }
  482. else {
  483. return $page_callback_result;
  484. }
  485. }
  486. /**
  487. * Loads objects into the map as defined in the $item['load_functions'].
  488. *
  489. * @param $item
  490. * A menu router or menu link item
  491. * @param $map
  492. * An array of path arguments; for example, array('node', '5').
  493. *
  494. * @return
  495. * Returns TRUE for success, FALSE if an object cannot be loaded.
  496. * Names of object loading functions are placed in $item['load_functions'].
  497. * Loaded objects are placed in $map[]; keys are the same as keys in the
  498. * $item['load_functions'] array.
  499. * $item['access'] is set to FALSE if an object cannot be loaded.
  500. */
  501. function _menu_load_objects(&$item, &$map) {
  502. if ($load_functions = $item['load_functions']) {
  503. // If someone calls this function twice, then unserialize will fail.
  504. if (!is_array($load_functions)) {
  505. $load_functions = unserialize($load_functions);
  506. }
  507. $path_map = $map;
  508. foreach ($load_functions as $index => $function) {
  509. if ($function) {
  510. $value = isset($path_map[$index]) ? $path_map[$index] : '';
  511. if (is_array($function)) {
  512. // Set up arguments for the load function. These were pulled from
  513. // 'load arguments' in the hook_menu() entry, but they need
  514. // some processing. In this case the $function is the key to the
  515. // load_function array, and the value is the list of arguments.
  516. list($function, $args) = each($function);
  517. $load_functions[$index] = $function;
  518. // Some arguments are placeholders for dynamic items to process.
  519. foreach ($args as $i => $arg) {
  520. if ($arg === '%index') {
  521. // Pass on argument index to the load function, so multiple
  522. // occurrences of the same placeholder can be identified.
  523. $args[$i] = $index;
  524. }
  525. if ($arg === '%map') {
  526. // Pass on menu map by reference. The accepting function must
  527. // also declare this as a reference if it wants to modify
  528. // the map.
  529. $args[$i] = &$map;
  530. }
  531. if (is_int($arg)) {
  532. $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : '';
  533. }
  534. }
  535. array_unshift($args, $value);
  536. $return = call_user_func_array($function, $args);
  537. }
  538. else {
  539. $return = $function($value);
  540. }
  541. // If callback returned an error or there is no callback, trigger 404.
  542. if ($return === FALSE) {
  543. $item['access'] = FALSE;
  544. $map = FALSE;
  545. return FALSE;
  546. }
  547. $map[$index] = $return;
  548. }
  549. }
  550. $item['load_functions'] = $load_functions;
  551. }
  552. return TRUE;
  553. }
  554. /**
  555. * Checks access to a menu item using the access callback.
  556. *
  557. * @param $item
  558. * A menu router or menu link item
  559. * @param $map
  560. * An array of path arguments; for example, array('node', '5').
  561. *
  562. * @return
  563. * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
  564. */
  565. function _menu_check_access(&$item, $map) {
  566. $item['access'] = FALSE;
  567. // Determine access callback, which will decide whether or not the current
  568. // user has access to this path.
  569. $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
  570. // Check for a TRUE or FALSE value.
  571. if (is_numeric($callback)) {
  572. $item['access'] = (bool) $callback;
  573. }
  574. else {
  575. $arguments = menu_unserialize($item['access_arguments'], $map);
  576. // As call_user_func_array is quite slow and user_access is a very common
  577. // callback, it is worth making a special case for it.
  578. if ($callback == 'user_access') {
  579. $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
  580. }
  581. elseif (function_exists($callback)) {
  582. $item['access'] = call_user_func_array($callback, $arguments);
  583. }
  584. }
  585. }
  586. /**
  587. * Localizes the router item title using t() or another callback.
  588. *
  589. * Translate the title and description to allow storage of English title
  590. * strings in the database, yet display of them in the language required
  591. * by the current user.
  592. *
  593. * @param $item
  594. * A menu router item or a menu link item.
  595. * @param $map
  596. * The path as an array with objects already replaced. E.g., for path
  597. * node/123 $map would be array('node', $node) where $node is the node
  598. * object for node 123.
  599. * @param $link_translate
  600. * TRUE if we are translating a menu link item; FALSE if we are
  601. * translating a menu router item.
  602. *
  603. * @return
  604. * No return value.
  605. * $item['title'] is localized according to $item['title_callback'].
  606. * If an item's callback is check_plain(), $item['options']['html'] becomes
  607. * TRUE.
  608. * $item['description'] is translated using t().
  609. * When doing link translation and the $item['options']['attributes']['title']
  610. * (link title attribute) matches the description, it is translated as well.
  611. */
  612. function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
  613. $callback = $item['title_callback'];
  614. $item['localized_options'] = $item['options'];
  615. // All 'class' attributes are assumed to be an array during rendering, but
  616. // links stored in the database may use an old string value.
  617. // @todo In order to remove this code we need to implement a database update
  618. // including unserializing all existing link options and running this code
  619. // on them, as well as adding validation to menu_link_save().
  620. if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
  621. $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
  622. }
  623. // If we are translating the title of a menu link, and its title is the same
  624. // as the corresponding router item, then we can use the title information
  625. // from the router. If it's customized, then we need to use the link title
  626. // itself; can't localize.
  627. // If we are translating a router item (tabs, page, breadcrumb), then we
  628. // can always use the information from the router item.
  629. if (!$link_translate || ($item['title'] == $item['link_title'])) {
  630. // t() is a special case. Since it is used very close to all the time,
  631. // we handle it directly instead of using indirect, slower methods.
  632. if ($callback == 't') {
  633. if (empty($item['title_arguments'])) {
  634. $item['title'] = t($item['title']);
  635. }
  636. else {
  637. $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
  638. }
  639. }
  640. elseif ($callback && function_exists($callback)) {
  641. if (empty($item['title_arguments'])) {
  642. $item['title'] = $callback($item['title']);
  643. }
  644. else {
  645. $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
  646. }
  647. // Avoid calling check_plain again on l() function.
  648. if ($callback == 'check_plain') {
  649. $item['localized_options']['html'] = TRUE;
  650. }
  651. }
  652. }
  653. elseif ($link_translate) {
  654. $item['title'] = $item['link_title'];
  655. }
  656. // Translate description, see the motivation above.
  657. if (!empty($item['description'])) {
  658. $original_description = $item['description'];
  659. $item['description'] = t($item['description']);
  660. if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
  661. $item['localized_options']['attributes']['title'] = $item['description'];
  662. }
  663. }
  664. }
  665. /**
  666. * Handles dynamic path translation and menu access control.
  667. *
  668. * When a user arrives on a page such as node/5, this function determines
  669. * what "5" corresponds to, by inspecting the page's menu path definition,
  670. * node/%node. This will call node_load(5) to load the corresponding node
  671. * object.
  672. *
  673. * It also works in reverse, to allow the display of tabs and menu items which
  674. * contain these dynamic arguments, translating node/%node to node/5.
  675. *
  676. * Translation of menu item titles and descriptions are done here to
  677. * allow for storage of English strings in the database, and translation
  678. * to the language required to generate the current page.
  679. *
  680. * @param $router_item
  681. * A menu router item
  682. * @param $map
  683. * An array of path arguments; for example, array('node', '5').
  684. * @param $to_arg
  685. * Execute $item['to_arg_functions'] or not. Use only if you want to render a
  686. * path from the menu table, for example tabs.
  687. *
  688. * @return
  689. * Returns the map with objects loaded as defined in the
  690. * $item['load_functions']. $item['access'] becomes TRUE if the item is
  691. * accessible, FALSE otherwise. $item['href'] is set according to the map.
  692. * If an error occurs during calling the load_functions (like trying to load
  693. * a non-existent node) then this function returns FALSE.
  694. */
  695. function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
  696. if ($to_arg && !empty($router_item['to_arg_functions'])) {
  697. // Fill in missing path elements, such as the current uid.
  698. _menu_link_map_translate($map, $router_item['to_arg_functions']);
  699. }
  700. // The $path_map saves the pieces of the path as strings, while elements in
  701. // $map may be replaced with loaded objects.
  702. $path_map = $map;
  703. if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) {
  704. // An error occurred loading an object.
  705. $router_item['access'] = FALSE;
  706. return FALSE;
  707. }
  708. // Generate the link path for the page request or local tasks.
  709. $link_map = explode('/', $router_item['path']);
  710. if (isset($router_item['tab_root'])) {
  711. $tab_root_map = explode('/', $router_item['tab_root']);
  712. }
  713. if (isset($router_item['tab_parent'])) {
  714. $tab_parent_map = explode('/', $router_item['tab_parent']);
  715. }
  716. for ($i = 0; $i < $router_item['number_parts']; $i++) {
  717. if ($link_map[$i] == '%') {
  718. $link_map[$i] = $path_map[$i];
  719. }
  720. if (isset($tab_root_map[$i]) && $tab_root_map[$i] == '%') {
  721. $tab_root_map[$i] = $path_map[$i];
  722. }
  723. if (isset($tab_parent_map[$i]) && $tab_parent_map[$i] == '%') {
  724. $tab_parent_map[$i] = $path_map[$i];
  725. }
  726. }
  727. $router_item['href'] = implode('/', $link_map);
  728. $router_item['tab_root_href'] = implode('/', $tab_root_map);
  729. $router_item['tab_parent_href'] = implode('/', $tab_parent_map);
  730. $router_item['options'] = array();
  731. _menu_check_access($router_item, $map);
  732. // For performance, don't localize an item the user can't access.
  733. if ($router_item['access']) {
  734. _menu_item_localize($router_item, $map);
  735. }
  736. return $map;
  737. }
  738. /**
  739. * Translates the path elements in the map using any to_arg helper function.
  740. *
  741. * @param $map
  742. * An array of path arguments; for example, array('node', '5').
  743. * @param $to_arg_functions
  744. * An array of helper functions; for example, array(2 => 'menu_tail_to_arg').
  745. *
  746. * @see hook_menu()
  747. */
  748. function _menu_link_map_translate(&$map, $to_arg_functions) {
  749. $to_arg_functions = unserialize($to_arg_functions);
  750. foreach ($to_arg_functions as $index => $function) {
  751. // Translate place-holders into real values.
  752. $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index);
  753. if (!empty($map[$index]) || isset($arg)) {
  754. $map[$index] = $arg;
  755. }
  756. else {
  757. unset($map[$index]);
  758. }
  759. }
  760. }
  761. /**
  762. * Returns a string containing the path relative to the current index.
  763. */
  764. function menu_tail_to_arg($arg, $map, $index) {
  765. return implode('/', array_slice($map, $index));
  766. }
  767. /**
  768. * Loads the path as one string relative to the current index.
  769. *
  770. * To use this load function, you must specify the load arguments
  771. * in the router item as:
  772. * @code
  773. * $item['load arguments'] = array('%map', '%index');
  774. * @endcode
  775. *
  776. * @see search_menu().
  777. */
  778. function menu_tail_load($arg, &$map, $index) {
  779. $arg = implode('/', array_slice($map, $index));
  780. $map = array_slice($map, 0, $index);
  781. return $arg;
  782. }
  783. /**
  784. * Provides menu link access control, translation, and argument handling.
  785. *
  786. * This function is similar to _menu_translate(), but it also does
  787. * link-specific preparation (such as always calling to_arg() functions).
  788. *
  789. * @param $item
  790. * A menu link.
  791. * @param $translate
  792. * (optional) Whether to try to translate a link containing dynamic path
  793. * argument placeholders (%) based on the menu router item of the current
  794. * path. Defaults to FALSE. Internally used for breadcrumbs.
  795. *
  796. * @return
  797. * Returns the map of path arguments with objects loaded as defined in the
  798. * $item['load_functions'].
  799. * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
  800. * $item['href'] is generated from link_path, possibly by to_arg functions.
  801. * $item['title'] is generated from link_title, and may be localized.
  802. * $item['options'] is unserialized; it is also changed within the call here
  803. * to $item['localized_options'] by _menu_item_localize().
  804. */
  805. function _menu_link_translate(&$item, $translate = FALSE) {
  806. if (!is_array($item['options'])) {
  807. $item['options'] = unserialize($item['options']);
  808. }
  809. if ($item['external']) {
  810. $item['access'] = 1;
  811. $map = array();
  812. $item['href'] = $item['link_path'];
  813. $item['title'] = $item['link_title'];
  814. $item['localized_options'] = $item['options'];
  815. }
  816. else {
  817. // Complete the path of the menu link with elements from the current path,
  818. // if it contains dynamic placeholders (%).
  819. $map = explode('/', $item['link_path']);
  820. if (strpos($item['link_path'], '%') !== FALSE) {
  821. // Invoke registered to_arg callbacks.
  822. if (!empty($item['to_arg_functions'])) {
  823. _menu_link_map_translate($map, $item['to_arg_functions']);
  824. }
  825. // Or try to derive the path argument map from the current router item,
  826. // if this $item's path is within the router item's path. This means
  827. // that if we are on the current path 'foo/%/bar/%/baz', then
  828. // menu_get_item() will have translated the menu router item for the
  829. // current path, and we can take over the argument map for a link like
  830. // 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
  831. // @see _menu_tree_check_access()
  832. // @see menu_get_active_breadcrumb()
  833. elseif ($translate && ($current_router_item = menu_get_item())) {
  834. // If $translate is TRUE, then this link is in the active trail.
  835. // Only translate paths within the current path.
  836. if (strpos($current_router_item['path'], $item['link_path']) === 0) {
  837. $count = count($map);
  838. $map = array_slice($current_router_item['original_map'], 0, $count);
  839. $item['original_map'] = $map;
  840. if (isset($current_router_item['map'])) {
  841. $item['map'] = array_slice($current_router_item['map'], 0, $count);
  842. }
  843. // Reset access to check it (for the first time).
  844. unset($item['access']);
  845. }
  846. }
  847. }
  848. $item['href'] = implode('/', $map);
  849. // Skip links containing untranslated arguments.
  850. if (strpos($item['href'], '%') !== FALSE) {
  851. $item['access'] = FALSE;
  852. return FALSE;
  853. }
  854. // menu_tree_check_access() may set this ahead of time for links to nodes.
  855. if (!isset($item['access'])) {
  856. if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
  857. // An error occurred loading an object.
  858. $item['access'] = FALSE;
  859. return FALSE;
  860. }
  861. _menu_check_access($item, $map);
  862. }
  863. // For performance, don't localize a link the user can't access.
  864. if ($item['access']) {
  865. _menu_item_localize($item, $map, TRUE);
  866. }
  867. }
  868. // Allow other customizations - e.g. adding a page-specific query string to the
  869. // options array. For performance reasons we only invoke this hook if the link
  870. // has the 'alter' flag set in the options array.
  871. if (!empty($item['options']['alter'])) {
  872. drupal_alter('translated_menu_link', $item, $map);
  873. }
  874. return $map;
  875. }
  876. /**
  877. * Gets a loaded object from a router item.
  878. *
  879. * menu_get_object() provides access to objects loaded by the current router
  880. * item. For example, on the page node/%node, the router loads the %node object,
  881. * and calling menu_get_object() will return that. Normally, it is necessary to
  882. * specify the type of object referenced, however node is the default.
  883. * The following example tests to see whether the node being displayed is of the
  884. * "story" content type:
  885. * @code
  886. * $node = menu_get_object();
  887. * $story = $node->type == 'story';
  888. * @endcode
  889. *
  890. * @param $type
  891. * Type of the object. These appear in hook_menu definitions as %type. Core
  892. * provides aggregator_feed, aggregator_category, contact, filter_format,
  893. * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
  894. * relevant {$type}_load function for more on each. Defaults to node.
  895. * @param $position
  896. * The position of the object in the path, where the first path segment is 0.
  897. * For node/%node, the position of %node is 1, but for comment/reply/%node,
  898. * it's 2. Defaults to 1.
  899. * @param $path
  900. * See menu_get_item() for more on this. Defaults to the current path.
  901. */
  902. function menu_get_object($type = 'node', $position = 1, $path = NULL) {
  903. $router_item = menu_get_item($path);
  904. if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') {
  905. return $router_item['map'][$position];
  906. }
  907. }
  908. /**
  909. * Renders a menu tree based on the current path.
  910. *
  911. * The tree is expanded based on the current path and dynamic paths are also
  912. * changed according to the defined to_arg functions (for example the 'My
  913. * account' link is changed from user/% to a link with the current user's uid).
  914. *
  915. * @param $menu_name
  916. * The name of the menu.
  917. *
  918. * @return
  919. * A structured array representing the specified menu on the current page, to
  920. * be rendered by drupal_render().
  921. */
  922. function menu_tree($menu_name) {
  923. $menu_output = &drupal_static(__FUNCTION__, array());
  924. if (!isset($menu_output[$menu_name])) {
  925. $tree = menu_tree_page_data($menu_name);
  926. $menu_output[$menu_name] = menu_tree_output($tree);
  927. }
  928. return $menu_output[$menu_name];
  929. }
  930. /**
  931. * Returns an output structure for rendering a menu tree.
  932. *
  933. * The menu item's LI element is given one of the following classes:
  934. * - expanded: The menu item is showing its submenu.
  935. * - collapsed: The menu item has a submenu which is not shown.
  936. * - leaf: The menu item has no submenu.
  937. *
  938. * @param $tree
  939. * A data structure representing the tree as returned from menu_tree_data.
  940. *
  941. * @return
  942. * A structured array to be rendered by drupal_render().
  943. */
  944. function menu_tree_output($tree) {
  945. $build = array();
  946. $items = array();
  947. // Pull out just the menu links we are going to render so that we
  948. // get an accurate count for the first/last classes.
  949. foreach ($tree as $data) {
  950. if ($data['link']['access'] && !$data['link']['hidden']) {
  951. $items[] = $data;
  952. }
  953. }
  954. $router_item = menu_get_item();
  955. $num_items = count($items);
  956. foreach ($items as $i => $data) {
  957. $class = array();
  958. if ($i == 0) {
  959. $class[] = 'first';
  960. }
  961. if ($i == $num_items - 1) {
  962. $class[] = 'last';
  963. }
  964. // Set a class for the <li>-tag. Since $data['below'] may contain local
  965. // tasks, only set 'expanded' class if the link also has children within
  966. // the current menu.
  967. if ($data['link']['has_children'] && $data['below']) {
  968. $class[] = 'expanded';
  969. }
  970. elseif ($data['link']['has_children']) {
  971. $class[] = 'collapsed';
  972. }
  973. else {
  974. $class[] = 'leaf';
  975. }
  976. // Set a class if the link is in the active trail.
  977. if ($data['link']['in_active_trail']) {
  978. $class[] = 'active-trail';
  979. $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
  980. }
  981. // Normally, l() compares the href of every link with $_GET['q'] and sets
  982. // the active class accordingly. But local tasks do not appear in menu
  983. // trees, so if the current path is a local task, and this link is its
  984. // tab root, then we have to set the class manually.
  985. if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) {
  986. $data['link']['localized_options']['attributes']['class'][] = 'active';
  987. }
  988. // Allow menu-specific theme overrides.
  989. $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
  990. $element['#attributes']['class'] = $class;
  991. $element['#title'] = $data['link']['title'];
  992. $element['#href'] = $data['link']['href'];
  993. $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
  994. $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
  995. $element['#original_link'] = $data['link'];
  996. // Index using the link's unique mlid.
  997. $build[$data['link']['mlid']] = $element;
  998. }
  999. if ($build) {
  1000. // Make sure drupal_render() does not re-order the links.
  1001. $build['#sorted'] = TRUE;
  1002. // Add the theme wrapper for outer markup.
  1003. // Allow menu-specific theme overrides.
  1004. $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
  1005. }
  1006. return $build;
  1007. }
  1008. /**
  1009. * Gets the data structure representing a named menu tree.
  1010. *
  1011. * Since this can be the full tree including hidden items, the data returned
  1012. * may be used for generating an an admin interface or a select.
  1013. *
  1014. * @param $menu_name
  1015. * The named menu links to return
  1016. * @param $link
  1017. * A fully loaded menu link, or NULL. If a link is supplied, only the
  1018. * path to root will be included in the returned tree - as if this link
  1019. * represented the current page in a visible menu.
  1020. * @param $max_depth
  1021. * Optional maximum depth of links to retrieve. Typically useful if only one
  1022. * or two levels of a sub tree are needed in conjunction with a non-NULL
  1023. * $link, in which case $max_depth should be greater than $link['depth'].
  1024. *
  1025. * @return
  1026. * An tree of menu links in an array, in the order they should be rendered.
  1027. */
  1028. function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
  1029. $tree = &drupal_static(__FUNCTION__, array());
  1030. // Use $mlid as a flag for whether the data being loaded is for the whole tree.
  1031. $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
  1032. // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
  1033. $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth;
  1034. if (!isset($tree[$cid])) {
  1035. // If the static variable doesn't have the data, check {cache_menu}.
  1036. $cache = cache_get($cid, 'cache_menu');
  1037. if ($cache && isset($cache->data)) {
  1038. // If the cache entry exists, it contains the parameters for
  1039. // menu_build_tree().
  1040. $tree_parameters = $cache->data;
  1041. }
  1042. // If the tree data was not in the cache, build $tree_parameters.
  1043. if (!isset($tree_parameters)) {
  1044. $tree_parameters = array(
  1045. 'min_depth' => 1,
  1046. 'max_depth' => $max_depth,
  1047. );
  1048. if ($mlid) {
  1049. // The tree is for a single item, so we need to match the values in its
  1050. // p columns and 0 (the top level) with the plid values of other links.
  1051. $parents = array(0);
  1052. for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
  1053. if (!empty($link["p$i"])) {
  1054. $parents[] = $link["p$i"];
  1055. }
  1056. }
  1057. $tree_parameters['expanded'] = $parents;
  1058. $tree_parameters['active_trail'] = $parents;
  1059. $tree_parameters['active_trail'][] = $mlid;
  1060. }
  1061. // Cache the tree building parameters using the page-specific cid.
  1062. cache_set($cid, $tree_parameters, 'cache_menu');
  1063. }
  1064. // Build the tree using the parameters; the resulting tree will be cached
  1065. // by _menu_build_tree()).
  1066. $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
  1067. }
  1068. return $tree[$cid];
  1069. }
  1070. /**
  1071. * Sets the path for determining the active trail of the specified menu tree.
  1072. *
  1073. * This path will also affect the breadcrumbs under some circumstances.
  1074. * Breadcrumbs are built using the preferred link returned by
  1075. * menu_link_get_preferred(). If the preferred link is inside one of the menus
  1076. * specified in calls to menu_tree_set_path(), the preferred link will be
  1077. * overridden by the corresponding path returned by menu_tree_get_path().
  1078. *
  1079. * Setting this path does not affect the main content; for that use
  1080. * menu_set_active_item() instead.
  1081. *
  1082. * @param $menu_name
  1083. * The name of the affected menu tree.
  1084. * @param $path
  1085. * The path to use when finding the active trail.
  1086. */
  1087. function menu_tree_set_path($menu_name, $path = NULL) {
  1088. $paths = &drupal_static(__FUNCTION__);
  1089. if (isset($path)) {
  1090. $paths[$menu_name] = $path;
  1091. }
  1092. return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
  1093. }
  1094. /**
  1095. * Gets the path for determining the active trail of the specified menu tree.
  1096. *
  1097. * @param $menu_name
  1098. * The menu name of the requested tree.
  1099. *
  1100. * @return
  1101. * A string containing the path. If no path has been specified with
  1102. * menu_tree_set_path(), NULL is returned.
  1103. */
  1104. function menu_tree_get_path($menu_name) {
  1105. return menu_tree_set_path($menu_name);
  1106. }
  1107. /**
  1108. * Gets the data structure for a named menu tree, based on the current page.
  1109. *
  1110. * The tree order is maintained by storing each parent in an individual
  1111. * field, see http://drupal.org/node/141866 for more.
  1112. *
  1113. * @param $menu_name
  1114. * The named menu links to return.
  1115. * @param $max_depth
  1116. * (optional) The maximum depth of links to retrieve.
  1117. * @param $only_active_trail
  1118. * (optional) Whether to only return the links in the active trail (TRUE)
  1119. * instead of all links on every level of the menu link tree (FALSE). Defaults
  1120. * to FALSE. Internally used for breadcrumbs only.
  1121. *
  1122. * @return
  1123. * An array of menu links, in the order they should be rendered. The array
  1124. * is a list of associative arrays -- these have two keys, link and below.
  1125. * link is a menu item, ready for theming as a link. Below represents the
  1126. * submenu below the link if there is one, and it is a subtree that has the
  1127. * same structure described for the top-level array.
  1128. */
  1129. function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
  1130. $tree = &drupal_static(__FUNCTION__, array());
  1131. // Check if the active trail has been overridden for this menu tree.
  1132. $active_path = menu_tree_get_path($menu_name);
  1133. // Load the menu item corresponding to the current page.
  1134. if ($item = menu_get_item($active_path)) {
  1135. if (isset($max_depth)) {
  1136. $max_depth = min($max_depth, MENU_MAX_DEPTH);
  1137. }
  1138. // Generate a cache ID (cid) specific for this page.
  1139. $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth;
  1140. // If we are asked for the active trail only, and $menu_name has not been
  1141. // built and cached for this page yet, then this likely means that it
  1142. // won't be built anymore, as this function is invoked from
  1143. // template_process_page(). So in order to not build a giant menu tree
  1144. // that needs to be checked for access on all levels, we simply check
  1145. // whether we have the menu already in cache, or otherwise, build a minimum
  1146. // tree containing the breadcrumb/active trail only.
  1147. // @see menu_set_active_trail()
  1148. if (!isset($tree[$cid]) && $only_active_trail) {
  1149. $cid .= ':trail';
  1150. }
  1151. if (!isset($tree[$cid])) {
  1152. // If the static variable doesn't have the data, check {cache_menu}.
  1153. $cache = cache_get($cid, 'cache_menu');
  1154. if ($cache && isset($cache->data)) {
  1155. // If the cache entry exists, it contains the parameters for
  1156. // menu_build_tree().
  1157. $tree_parameters = $cache->data;
  1158. }
  1159. // If the tree data was not in the cache, build $tree_parameters.
  1160. if (!isset($tree_parameters)) {
  1161. $tree_parameters = array(
  1162. 'min_depth' => 1,
  1163. 'max_depth' => $max_depth,
  1164. );
  1165. // Parent mlids; used both as key and value to ensure uniqueness.
  1166. // We always want all the top-level links with plid == 0.
  1167. $active_trail = array(0 => 0);
  1168. // If the item for the current page is accessible, build the tree
  1169. // parameters accordingly.
  1170. if ($item['access']) {
  1171. // Find a menu link corresponding to the current path. If $active_path
  1172. // is NULL, let menu_link_get_preferred() determine the path.
  1173. if ($active_link = menu_link_get_preferred($active_path, $menu_name)) {
  1174. // The active link may only be taken into account to build the
  1175. // active trail, if it resides in the requested menu. Otherwise,
  1176. // we'd needlessly re-run _menu_build_tree() queries for every menu
  1177. // on every page.
  1178. if ($active_link['menu_name'] == $menu_name) {
  1179. // Use all the coordinates, except the last one because there
  1180. // can be no child beyond the last column.
  1181. for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
  1182. if ($active_link['p' . $i]) {
  1183. $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
  1184. }
  1185. }
  1186. // If we are asked to build links for the active trail only, skip
  1187. // the entire 'expanded' handling.
  1188. if ($only_active_trail) {
  1189. $tree_parameters['only_active_trail'] = TRUE;
  1190. }
  1191. }
  1192. }
  1193. $parents = $active_trail;
  1194. $expanded = variable_get('menu_expanded', array());
  1195. // Check whether the current menu has any links set to be expanded.
  1196. if (!$only_active_trail && in_array($menu_name, $expanded)) {
  1197. // Collect all the links set to be expanded, and then add all of
  1198. // their children to the list as well.
  1199. do {
  1200. $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
  1201. ->fields('menu_links', array('mlid'))
  1202. ->condition('menu_name', $menu_name)
  1203. ->condition('expanded', 1)
  1204. ->condition('has_children', 1)
  1205. ->condition('plid', $parents, 'IN')
  1206. ->condition('mlid', $parents, 'NOT IN')
  1207. ->execute();
  1208. $num_rows = FALSE;
  1209. foreach ($result as $item) {
  1210. $parents[$item['mlid']] = $item['mlid'];
  1211. $num_rows = TRUE;
  1212. }
  1213. } while ($num_rows);
  1214. }
  1215. $tree_parameters['expanded'] = $parents;
  1216. $tree_parameters['active_trail'] = $active_trail;
  1217. }
  1218. // If access is denied, we only show top-level links in menus.
  1219. else {
  1220. $tree_parameters['expanded'] = $active_trail;
  1221. $tree_parameters['active_trail'] = $active_trail;
  1222. }
  1223. // Cache the tree building parameters using the page-specific cid.
  1224. cache_set($cid, $tree_parameters, 'cache_menu');
  1225. }
  1226. // Build the tree using the parameters; the resulting tree will be cached
  1227. // by _menu_build_tree().
  1228. $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
  1229. }
  1230. return $tree[$cid];
  1231. }
  1232. return array();
  1233. }
  1234. /**
  1235. * Builds a menu tree, translates links, and checks access.
  1236. *
  1237. * @param $menu_name
  1238. * The name of the menu.
  1239. * @param $parameters
  1240. * (optional) An associative array of build parameters. Possible keys:
  1241. * - expanded: An array of parent link ids to return only menu links that are
  1242. * children of one of the plids in this list. If empty, the whole menu tree
  1243. * is built, unless 'only_active_trail' is TRUE.
  1244. * - active_trail: An array…

Large files files are truncated, but you can click here to view the full file