/[drupal]/drupal/includes/menu.inc
ViewVC logotype

Contents of /drupal/includes/menu.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


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