[ Index ] |
|
Code source de Drupal 5.3 |
1 <?php 2 // $Id: menu.inc,v 1.146.2.1 2007/06/17 01:50:50 drumm 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 "callback 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" attribute of each menu item is checked as the search for a 51 * callback proceeds. If this attribute is TRUE, then access is granted; if 52 * FALSE, then access is denied. The first found "access" attribute 53 * determines the accessibility of the target. Menu items may omit this 54 * attribute to use the value provided by an ancestor 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 70 /** 71 * @name Menu flags 72 * @{ 73 * Flags for use in the "type" attribute of menu items. 74 */ 75 76 define('MENU_IS_ROOT', 0x0001); 77 define('MENU_VISIBLE_IN_TREE', 0x0002); 78 define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004); 79 define('MENU_VISIBLE_IF_HAS_CHILDREN', 0x0008); 80 define('MENU_MODIFIABLE_BY_ADMIN', 0x0010); 81 define('MENU_MODIFIED_BY_ADMIN', 0x0020); 82 define('MENU_CREATED_BY_ADMIN', 0x0040); 83 define('MENU_IS_LOCAL_TASK', 0x0080); 84 define('MENU_EXPANDED', 0x0100); 85 define('MENU_LINKS_TO_PARENT', 0x0200); 86 87 /** 88 * @} End of "Menu flags". 89 */ 90 91 /** 92 * @name Menu item types 93 * @{ 94 * Menu item definitions provide one of these constants, which are shortcuts for 95 * combinations of the above flags. 96 */ 97 98 /** 99 * Normal menu items show up in the menu tree and can be moved/hidden by 100 * the administrator. Use this for most menu items. It is the default value if 101 * no menu item type is specified. 102 */ 103 define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN); 104 105 /** 106 * Item groupings are used for pages like "node/add" that simply list 107 * subpages to visit. They are distinguished from other pages in that they will 108 * disappear from the menu if no subpages exist. 109 */ 110 define('MENU_ITEM_GROUPING', MENU_VISIBLE_IF_HAS_CHILDREN | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN); 111 112 /** 113 * Callbacks simply register a path so that the correct function is fired 114 * when the URL is accessed. They are not shown in the menu. 115 */ 116 define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB); 117 118 /** 119 * Dynamic menu items change frequently, and so should not be stored in the 120 * database for administrative customization. 121 */ 122 define('MENU_DYNAMIC_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB); 123 124 /** 125 * Modules may "suggest" menu items that the administrator may enable. They act 126 * just as callbacks do until enabled, at which time they act like normal items. 127 */ 128 define('MENU_SUGGESTED_ITEM', MENU_MODIFIABLE_BY_ADMIN | MENU_VISIBLE_IN_BREADCRUMB); 129 130 /** 131 * Local tasks are rendered as tabs by default. Use this for menu items that 132 * describe actions to be performed on their parent item. An example is the path 133 * "node/52/edit", which performs the "edit" task on "node/52". 134 */ 135 define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK); 136 137 /** 138 * Every set of local tasks should provide one "default" task, that links to the 139 * same path as its parent when clicked. 140 */ 141 define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT); 142 143 /** 144 * Custom items are those defined by the administrator. Reserved for internal 145 * use; do not return from hook_menu() implementations. 146 */ 147 define('MENU_CUSTOM_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN); 148 149 /** 150 * Custom menus are those defined by the administrator. Reserved for internal 151 * use; do not return from hook_menu() implementations. 152 */ 153 define('MENU_CUSTOM_MENU', MENU_IS_ROOT | MENU_VISIBLE_IN_TREE | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN); 154 155 /** 156 * @} End of "Menu item types". 157 */ 158 159 /** 160 * @name Menu status codes 161 * @{ 162 * Status codes for menu callbacks. 163 */ 164 165 define('MENU_FOUND', 1); 166 define('MENU_NOT_FOUND', 2); 167 define('MENU_ACCESS_DENIED', 3); 168 define('MENU_SITE_OFFLINE', 4); 169 170 /** 171 * @} End of "Menu status codes". 172 */ 173 174 /** 175 * Return the menu data structure. 176 * 177 * The returned structure contains much information that is useful only 178 * internally in the menu system. External modules are likely to need only 179 * the ['visible'] element of the returned array. All menu items that are 180 * accessible to the current user and not hidden will be present here, so 181 * modules and themes can use this structure to build their own representations 182 * of the menu. 183 * 184 * $menu['visible'] will contain an associative array, the keys of which 185 * are menu IDs. The values of this array are themselves associative arrays, 186 * with the following key-value pairs defined: 187 * - 'title' - The displayed title of the menu or menu item. It will already 188 * have been translated by the locale system. 189 * - 'description' - The description (link title attribute) of the menu item. 190 * It will already have been translated by the locale system. 191 * - 'path' - The Drupal path to the menu item. A link to a particular item 192 * can thus be constructed with 193 * l($item['title'], $item['path'], array('title' => $item['description'])). 194 * - 'children' - A linear list of the menu ID's of this item's children. 195 * 196 * Menu ID 0 is the "root" of the menu. The children of this item are the 197 * menus themselves (they will have no associated path). Menu ID 1 will 198 * always be one of these children; it is the default "Navigation" menu. 199 */ 200 function menu_get_menu() { 201 global $_menu; 202 global $user; 203 global $locale; 204 205 if (!isset($_menu['items'])) { 206 // _menu_build() may indirectly call this function, so prevent infinite loops. 207 $_menu['items'] = array(); 208 209 $cid = "$user->uid:$locale"; 210 if ($cached = cache_get($cid, 'cache_menu')) { 211 $_menu = unserialize($cached->data); 212 } 213 else { 214 _menu_build(); 215 // Cache the menu structure for this user, to expire after one day. 216 cache_set($cid, 'cache_menu', serialize($_menu), time() + (60 * 60 * 24)); 217 } 218 219 // Make sure items that cannot be cached are added. 220 _menu_append_contextual_items(); 221 222 // Reset the cached $menu in menu_get_item(). 223 menu_get_item(NULL, NULL, TRUE); 224 } 225 226 return $_menu; 227 } 228 229 /** 230 * Return the local task tree. 231 * 232 * Unlike the rest of the menu structure, the local task tree cannot be cached 233 * nor determined too early in the page request, because the user's current 234 * location may be changed by a menu_set_location() call, and the tasks shown 235 * (just as the breadcrumb trail) need to reflect the changed location. 236 */ 237 function menu_get_local_tasks() { 238 global $_menu; 239 240 // Don't cache the local task tree, as it varies by location and tasks are 241 // allowed to be dynamically determined. 242 if (!isset($_menu['local tasks'])) { 243 // _menu_build_local_tasks() may indirectly call this function, so prevent 244 // infinite loops. 245 $_menu['local tasks'] = array(); 246 $pid = menu_get_active_nontask_item(); 247 if (!_menu_build_local_tasks($pid)) { 248 // If the build returned FALSE, the tasks need not be displayed. 249 $_menu['local tasks'][$pid]['children'] = array(); 250 } 251 } 252 253 return $_menu['local tasks']; 254 } 255 256 /** 257 * Retrieves the menu item specified by $mid, or by $path if $mid is not given. 258 * 259 * @param $mid 260 * The menu ID of the menu item to retrieve. 261 * @param $path 262 * The internal path of the menu item to retrieve. Defaults to NULL. Only 263 * used if $mid is not set. 264 * @param $reset 265 * Optional flag that resets the static variable cache of the menu tree, if 266 * set to TRUE. Default is FALSE. 267 * 268 * @return 269 * The menu item found in the site menu, or an empty array if none could be 270 * found. 271 */ 272 function menu_get_item($mid, $path = NULL, $reset = FALSE) { 273 static $menu; 274 275 if (!isset($menu) || $reset) { 276 $menu = menu_get_menu(); 277 } 278 279 if (isset($mid)) { 280 return $menu['items'][$mid]; 281 } 282 283 if (isset($path)) { 284 return $menu['items'][$menu['path index'][$path]]; 285 } 286 287 return array(); 288 } 289 290 /** 291 * Retrieves the menu ID and title of all root menus. 292 * 293 * @return 294 * Array containing all menus (but not menu items), in the form mid => title. 295 */ 296 function menu_get_root_menus() { 297 $menu = menu_get_menu(); 298 $root_menus = array(); 299 300 foreach ($menu['items'][0]['children'] as $mid) { 301 $root_menus[$mid] = $menu['items'][$mid]['title']; 302 } 303 304 return $root_menus; 305 } 306 307 /** 308 * Change the current menu location of the user. 309 * 310 * Frequently, modules may want to make a page or node act as if it were 311 * in the menu tree somewhere, even though it was not registered in a 312 * hook_menu() implementation. If the administrator has rearranged the menu, 313 * the newly set location should respect this in the breadcrumb trail and 314 * expanded/collapsed status of menu items in the tree. This function 315 * allows this behavior. 316 * 317 * @param $location 318 * An array specifying a complete or partial breadcrumb trail for the 319 * new location, in the same format as the return value of hook_menu(). 320 * The last element of this array should be the new location itself. 321 * 322 * This function will set the new breadcrumb trail to the passed-in value, 323 * but if any elements of this trail are visible in the site tree, the 324 * trail will be "spliced in" to the existing site navigation at that point. 325 */ 326 function menu_set_location($location) { 327 global $_menu; 328 $temp_id = min(array_keys($_menu['items'])) - 1; 329 $prev_id = 0; 330 331 // Don't allow this function to change the actual current path, just the 332 // position in the menu tree. 333 $location[count($location) - 1]['path'] = $_GET['q']; 334 335 foreach (array_reverse($location) as $item) { 336 if (isset($_menu['path index'][$item['path']])) { 337 $mid = $_menu['path index'][$item['path']]; 338 if (isset($_menu['visible'][$mid])) { 339 // Splice in the breadcrumb at this location. 340 if ($prev_id) { 341 $_menu['items'][$prev_id]['pid'] = $mid; 342 } 343 $prev_id = 0; 344 break; 345 } 346 else { 347 // A hidden item; show it, but only temporarily. 348 $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB; 349 if ($prev_id) { 350 $_menu['items'][$prev_id]['pid'] = $mid; 351 } 352 $prev_id = $mid; 353 } 354 } 355 else { 356 $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB; 357 if ($prev_id) { 358 $_menu['items'][$prev_id]['pid'] = $temp_id; 359 } 360 $_menu['items'][$temp_id] = $item; 361 $_menu['path index'][$item['path']] = $temp_id; 362 363 $prev_id = $temp_id; 364 $temp_id--; 365 } 366 } 367 368 if ($prev_id) { 369 // Didn't find a home, so attach this to the main navigation menu. 370 $_menu['items'][$prev_id]['pid'] = 1; 371 } 372 373 $final_item = array_pop($location); 374 menu_set_active_item($final_item['path']); 375 } 376 377 /** 378 * Execute the handler associated with the active menu item. 379 * 380 * This is called early in the page request. The active menu item is at 381 * this point determined exclusively by the URL. The handler that is called 382 * here may, as a side effect, change the active menu item so that later 383 * menu functions (that display the menus and breadcrumbs, for example) 384 * act as if the user were in a different location on the site. 385 */ 386 function menu_execute_active_handler() { 387 if (_menu_site_is_offline()) { 388 return MENU_SITE_OFFLINE; 389 } 390 391 $menu = menu_get_menu(); 392 393 // Determine the menu item containing the callback. 394 $path = $_GET['q']; 395 while ($path && !isset($menu['callbacks'][$path])) { 396 $path = substr($path, 0, strrpos($path, '/')); 397 } 398 399 if ($path === '' || !isset($menu['callbacks'][$path])) { 400 return MENU_NOT_FOUND; 401 } 402 403 if (!function_exists($menu['callbacks'][$path]['callback'])) { 404 return MENU_NOT_FOUND; 405 } 406 407 if (!_menu_item_is_accessible(menu_get_active_item())) { 408 return MENU_ACCESS_DENIED; 409 } 410 411 // We found one, and are allowed to execute it. 412 $arguments = isset($menu['callbacks'][$path]['callback arguments']) ? $menu['callbacks'][$path]['callback arguments'] : array(); 413 $arg = substr($_GET['q'], strlen($path) + 1); 414 if (strlen($arg)) { 415 $arguments = array_merge($arguments, explode('/', $arg)); 416 } 417 418 return call_user_func_array($menu['callbacks'][$path]['callback'], $arguments); 419 } 420 421 /** 422 * Returns the ID of the active menu item. 423 */ 424 function menu_get_active_item() { 425 return menu_set_active_item(); 426 } 427 428 /** 429 * Sets the path of the active menu item. 430 */ 431 function menu_set_active_item($path = NULL) { 432 static $stored_mid; 433 434 if (!isset($stored_mid) || isset($path)) { 435 if (!isset($path)) { 436 $path = $_GET['q']; 437 } 438 else { 439 $_GET['q'] = $path; 440 } 441 $menu = menu_get_menu(); 442 443 while ($path && !isset($menu['path index'][$path])) { 444 $path = substr($path, 0, strrpos($path, '/')); 445 } 446 $stored_mid = isset($menu['path index'][$path]) ? $menu['path index'][$path] : 0; 447 448 // Search for default local tasks to activate instead of this item. 449 $continue = TRUE; 450 while ($continue) { 451 $continue = FALSE; 452 if (isset($menu['items'][$stored_mid]['children'])) { 453 foreach ($menu['items'][$stored_mid]['children'] as $cid) { 454 if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) { 455 $stored_mid = $cid; 456 $continue = TRUE; 457 } 458 } 459 } 460 } 461 462 // Reset the cached $menu in menu_get_item(). 463 menu_get_item(NULL, NULL, TRUE); 464 } 465 466 return $stored_mid; 467 } 468 469 /** 470 * Returns the ID of the current menu item or, if the current item is a 471 * local task, the menu item to which this task is attached. 472 */ 473 function menu_get_active_nontask_item() { 474 $mid = menu_get_active_item(); 475 476 // Find the first non-task item: 477 while ($mid) { 478 $item = menu_get_item($mid); 479 480 if (!($item['type'] & MENU_IS_LOCAL_TASK)) { 481 return $mid; 482 } 483 484 $mid = $item['pid']; 485 } 486 } 487 488 /** 489 * Returns the title of the active menu item. 490 */ 491 function menu_get_active_title() { 492 if ($mid = menu_get_active_nontask_item()) { 493 $item = menu_get_item($mid); 494 return $item['title']; 495 } 496 } 497 498 /** 499 * Returns the help associated with the active menu item. 500 */ 501 function menu_get_active_help() { 502 $path = $_GET['q']; 503 $output = ''; 504 505 if (!_menu_item_is_accessible(menu_get_active_item())) { 506 // Don't return help text for areas the user cannot access. 507 return; 508 } 509 510 foreach (module_list() as $name) { 511 if (module_hook($name, 'help')) { 512 if ($temp = module_invoke($name, 'help', $path)) { 513 $output .= $temp . "\n"; 514 } 515 if (module_hook('help', 'page')) { 516 if (arg(0) == "admin") { 517 if (module_invoke($name, 'help', 'admin/help#'. arg(2)) && !empty($output)) { 518 $output .= theme("more_help_link", url('admin/help/'. arg(2))); 519 } 520 } 521 } 522 } 523 } 524 return $output; 525 } 526 527 /** 528 * Returns an array of rendered menu items in the active breadcrumb trail. 529 */ 530 function menu_get_active_breadcrumb() { 531 532 // No breadcrumb for the front page. 533 if (drupal_is_front_page()) { 534 return array(); 535 } 536 537 // We do *not* want to use "variable_get('site_frontpage', 'node)" here 538 // as that will create the link '/node'. This is unsightly and creates 539 // a second URL for the homepage ('/' *and* '/node'). 540 $links[] = l(t('Home'), ''); 541 542 $trail = _menu_get_active_trail(); 543 foreach ($trail as $mid) { 544 $item = menu_get_item($mid); 545 if ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB) { 546 $links[] = menu_item_link($mid); 547 } 548 } 549 550 // The last item in the trail is the page title; don't display it here. 551 array_pop($links); 552 553 return $links; 554 } 555 556 /** 557 * Returns TRUE when the menu item is in the active trail. 558 */ 559 function menu_in_active_trail($mid) { 560 $trail = _menu_get_active_trail(); 561 562 return in_array($mid, $trail); 563 } 564 565 /** 566 * Returns TRUE when the menu item is in the active trail within a 567 * specific subsection of the menu tree. 568 * 569 * @param $mid 570 * The menu item being considered. 571 * @param $pid 572 * The root of the subsection of the menu tree in which to look. 573 */ 574 function menu_in_active_trail_in_submenu($mid, $pid) { 575 $trail = _menu_get_active_trail_in_submenu($pid); 576 577 if (!$trail) { 578 return FALSE; 579 } 580 581 return in_array($mid, $trail); 582 } 583 584 /** 585 * Populate the database representation of the menu. 586 * 587 * This need only be called at the start of pages that modify the menu. 588 */ 589 function menu_rebuild() { 590 // Clear the page cache, so that changed menus are reflected for anonymous users. 591 cache_clear_all('*', 'cache_page', TRUE); 592 // Also clear the menu cache. 593 cache_clear_all('*', 'cache_menu', TRUE); 594 595 _menu_build(); 596 597 if (module_exists('menu')) { 598 $menu = menu_get_menu(); 599 600 // Fill a queue of new menu items which are modifiable. 601 $new_items = array(); 602 foreach ($menu['items'] as $mid => $item) { 603 if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) { 604 $new_items[$mid] = $item; 605 } 606 } 607 608 $old_count = -1; 609 // Save the new items updating the pids in each iteration 610 while (($c = count($new_items)) && ($c != $old_count)) { 611 $old_count = count($new_items); 612 foreach ($new_items as $mid => $item) { 613 // If the item has a valid parent, save it 614 if ($item['pid'] >= 0) { 615 // The new menu ID gets passed back by reference as $item['mid'] 616 menu_save_item($item); 617 // Fix parent IDs for the children of the menu item just saved 618 if ($item['children']) { 619 foreach ($item['children'] as $child) { 620 if (isset($new_items[$child])) { 621 $new_items[$child]['pid'] = $item['mid']; 622 } 623 } 624 } 625 // remove the item 626 unset($new_items[$mid]); 627 } 628 } 629 } 630 // Rebuild the menu to account for the changes. 631 _menu_build(); 632 } 633 634 // Reset the cached $menu in menu_get_item(). 635 menu_get_item(NULL, NULL, TRUE); 636 637 } 638 639 /** 640 * Generate the HTML for a menu tree. 641 * 642 * @param $pid 643 * The parent id of the menu. 644 * 645 * @ingroup themeable 646 */ 647 function theme_menu_tree($pid = 1) { 648 if ($tree = menu_tree($pid)) { 649 return "\n<ul class=\"menu\">\n". $tree ."\n</ul>\n"; 650 } 651 } 652 653 /** 654 * Returns a rendered menu tree. 655 * 656 * @param $pid 657 * The parent id of the menu. 658 */ 659 function menu_tree($pid = 1) { 660 $menu = menu_get_menu(); 661 $output = ''; 662 663 if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) { 664 foreach ($menu['visible'][$pid]['children'] as $mid) { 665 $type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL; 666 $children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL; 667 $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0); 668 } 669 } 670 671 return $output; 672 } 673 674 /** 675 * Generate the HTML output for a single menu item. 676 * 677 * @param $mid 678 * The menu id of the item. 679 * @param $children 680 * A string containing any rendered child items of this menu. 681 * @param $leaf 682 * A boolean indicating whether this menu item is a leaf. 683 * 684 * @ingroup themeable 685 */ 686 function theme_menu_item($mid, $children = '', $leaf = TRUE) { 687 return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid) . $children ."</li>\n"; 688 } 689 690 /** 691 * Generate the HTML representing a given menu item ID. 692 * 693 * @param $item 694 * The menu item to render. 695 * @param $link_item 696 * The menu item which should be used to find the correct path. 697 * 698 * @ingroup themeable 699 */ 700 function theme_menu_item_link($item, $link_item) { 701 return l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array(), isset($item['query']) ? $item['query'] : NULL); 702 } 703 704 /** 705 * Returns the rendered link to a menu item. 706 * 707 * @param $mid 708 * The menu item id to render. 709 * @param $theme 710 * Whether to return a themed link or the link as an array 711 */ 712 function menu_item_link($mid, $theme = TRUE) { 713 $item = menu_get_item($mid); 714 $link_item = $item; 715 $link = ''; 716 717 while ($link_item['type'] & MENU_LINKS_TO_PARENT) { 718 $link_item = menu_get_item($link_item['pid']); 719 } 720 721 if ($theme) { 722 $link = theme('menu_item_link', $item, $link_item); 723 } 724 else { 725 $link = array( 726 'title' => $item['title'], 727 'href' => $link_item['path'], 728 'attributes' => !empty($item['description']) ? array('title' => $item['description']) : array() 729 ); 730 } 731 732 return $link; 733 } 734 735 /** 736 * Returns the rendered local tasks. The default implementation renders 737 * them as tabs. 738 * 739 * @ingroup themeable 740 */ 741 function theme_menu_local_tasks() { 742 $output = ''; 743 744 if ($primary = menu_primary_local_tasks()) { 745 $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n"; 746 } 747 if ($secondary = menu_secondary_local_tasks()) { 748 $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n"; 749 } 750 751 return $output; 752 } 753 754 /** 755 * Returns the rendered HTML of the primary local tasks. 756 */ 757 function menu_primary_local_tasks() { 758 $local_tasks = menu_get_local_tasks(); 759 $pid = menu_get_active_nontask_item(); 760 $output = ''; 761 762 if (count($local_tasks[$pid]['children'])) { 763 foreach ($local_tasks[$pid]['children'] as $mid) { 764 $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE); 765 } 766 } 767 768 return $output; 769 } 770 771 /** 772 * Returns the rendered HTML of the secondary local tasks. 773 */ 774 function menu_secondary_local_tasks() { 775 $local_tasks = menu_get_local_tasks(); 776 $pid = menu_get_active_nontask_item(); 777 $output = ''; 778 779 if (count($local_tasks[$pid]['children'])) { 780 foreach ($local_tasks[$pid]['children'] as $mid) { 781 if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) { 782 foreach ($local_tasks[$mid]['children'] as $cid) { 783 $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE); 784 } 785 } 786 } 787 } 788 789 return $output; 790 } 791 792 /** 793 * Generate the HTML representing a given menu item ID as a tab. 794 * 795 * @param $mid 796 * The menu ID to render. 797 * @param $active 798 * Whether this tab or a subtab is the active menu item. 799 * @param $primary 800 * Whether this tab is a primary tab or a subtab. 801 * 802 * @ingroup themeable 803 */ 804 function theme_menu_local_task($mid, $active, $primary) { 805 if ($active) { 806 return '<li class="active">'. menu_item_link($mid) ."</li>\n"; 807 } 808 else { 809 return '<li>'. menu_item_link($mid) ."</li>\n"; 810 } 811 } 812 813 /** 814 * Returns an array containing the primary links. 815 * Can optionally descend from the root of the Primary links menu towards the 816 * current node for a specified number of levels and return that submenu. 817 * Used to generate a primary/secondary menu from different levels of one menu. 818 * 819 * @param $start_level 820 * This optional parameter can be used to retrieve a context-sensitive array 821 * of links at $start_level levels deep into the Primary links menu. 822 * The default is to return the top-level links. 823 * @param $pid 824 * The parent menu ID from which to search for children. Defaults to the 825 * menu_primary_menu setting. 826 * @return A nested array of links and their properties. The keys of 827 * the array contain some extra encoded information about the results. 828 * The format of the key is {level}-{num}{-active}. 829 * level is the depth within the menu tree of this list. 830 * num is the number within this array, used only to make the key unique. 831 * -active is appended if this element is in the active trail. 832 */ 833 function menu_primary_links($start_level = 1, $pid = 0) { 834 if (!module_exists('menu')) { 835 return NULL; 836 } 837 if (!$pid) { 838 $pid = variable_get('menu_primary_menu', 0); 839 } 840 if (!$pid) { 841 return NULL; 842 } 843 844 if ($start_level < 1) { 845 $start_level = 1; 846 } 847 848 if ($start_level > 1) { 849 $trail = _menu_get_active_trail_in_submenu($pid); 850 if (!$trail) { 851 return NULL; 852 } 853 else { 854 $pid = $trail[$start_level - 1]; 855 } 856 } 857 858 $menu = menu_get_menu(); 859 $links = array(); 860 if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) { 861 $count = 1; 862 foreach ($menu['visible'][$pid]['children'] as $cid) { 863 $index = "menu-$start_level-$count-$pid"; 864 if (menu_in_active_trail_in_submenu($cid, $pid)) { 865 $index .= "-active"; 866 } 867 $links[$index] = menu_item_link($cid, FALSE); 868 $count++; 869 } 870 } 871 872 // Special case - provide link to admin/build/menu if primary links is empty. 873 if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0) && user_access('administer menu')) { 874 $links['1-1'] = array( 875 'title' => t('Edit primary links'), 876 'href' => 'admin/build/menu' 877 ); 878 } 879 880 return $links; 881 } 882 883 /** 884 * Returns an array containing the secondary links. 885 * Secondary links can be either a second level of the Primary links 886 * menu or generated from their own menu. 887 */ 888 function menu_secondary_links() { 889 $msm = variable_get('menu_secondary_menu', 0); 890 if ($msm == 0) { 891 return NULL; 892 } 893 894 if ($msm == variable_get('menu_primary_menu', 0)) { 895 return menu_primary_links(2, $msm); 896 } 897 898 return menu_primary_links(1, $msm); 899 } 900 901 /** 902 * Returns the themed HTML for primary and secondary links. 903 * Note that this function is overridden by most core themes because 904 * those themes display links in "link | link" format, not from a list. 905 * Also note that by default links rendered with this function are 906 * displayed with the same CSS as is used for the local tasks. 907 * If a theme wishes to render links from a ul it is expected that 908 * the theme will provide suitable CSS. 909 * 910 * @param $links 911 * An array containing links to render. 912 * @return 913 * A string containing the themed links. 914 * 915 * @ingroup themeable 916 */ 917 function theme_menu_links($links) { 918 if (!count($links)) { 919 return ''; 920 } 921 $level_tmp = explode('-', key($links)); 922 $level = $level_tmp[0]; 923 $output = "<ul class=\"links-$level\">\n"; 924 foreach ($links as $index => $link) { 925 $output .= '<li'; 926 if (stristr($index, 'active')) { 927 $output .= ' class="active"'; 928 } 929 $output .= ">". l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment']) ."</li>\n"; 930 } 931 $output .= '</ul>'; 932 933 return $output; 934 } 935 936 /** 937 * @} End of "defgroup menu". 938 */ 939 940 /** 941 * Returns an array with the menu items that lead to the current menu item. 942 */ 943 function _menu_get_active_trail() { 944 static $trail; 945 946 if (!isset($trail)) { 947 $trail = array(); 948 949 $mid = menu_get_active_item(); 950 951 // Follow the parents up the chain to get the trail. 952 while ($mid && ($item = menu_get_item($mid))) { 953 array_unshift($trail, $mid); 954 $mid = $item['pid']; 955 } 956 } 957 return $trail; 958 } 959 960 /** 961 * Find the active trail through a specific subsection of the menu tree. 962 * 963 * @param $pid 964 * The root item from which the active trail must descend. 965 */ 966 function _menu_get_active_trail_in_submenu($pid) { 967 static $trails; 968 969 if (!isset($trails)) { 970 // Find all menu items which point to the current node and for each 971 // follow the parents up the chain to build an active trail. 972 $trails = array(); 973 $menu = menu_get_menu(); 974 $path = $_GET['q']; 975 $count = 0; 976 while ($path && !$count) { 977 foreach ($menu['items'] as $key => $item) { 978 if (isset($item['path']) && $item['path'] == $path) { 979 $trails[$count] = array(); 980 $mid = $key; 981 while ($mid && $menu['items'][$mid]) { 982 array_unshift($trails[$count], $mid); 983 $mid = $menu['items'][$mid]['pid']; 984 } 985 $count ++; 986 } 987 } 988 $path = substr($path, 0, strrpos($path, '/')); 989 } 990 } 991 992 if ($trails) { 993 foreach ($trails as $trail) { 994 $count_trail = count($trail); 995 for ($i = 0; $i < $count_trail; $i++) { 996 if ($trail[$i] == $pid) { 997 // Return a trail from $pid down to the current page inclusive. 998 for ( ; $i < $count_trail; $i++) { 999 $subtrail[] = $trail[$i]; 1000 } 1001 return $subtrail; 1002 } 1003 } 1004 } 1005 } 1006 1007 return NULL; 1008 } 1009 1010 /** 1011 * Comparator routine for use in sorting menu items. 1012 */ 1013 function _menu_sort($a, $b) { 1014 $menu = menu_get_menu(); 1015 1016 $a = $menu['items'][$a]; 1017 $b = $menu['items'][$b]; 1018 1019 if ($a['weight'] < $b['weight']) { 1020 return -1; 1021 } 1022 elseif ($a['weight'] > $b['weight']) { 1023 return 1; 1024 } 1025 elseif (isset($a['title']) && isset($b['title'])) { 1026 return strnatcasecmp($a['title'], $b['title']); 1027 } 1028 else { 1029 return 1; 1030 } 1031 } 1032 1033 /** 1034 * Build the menu by querying both modules and the database. 1035 */ 1036 function _menu_build() { 1037 global $_menu; 1038 global $user; 1039 1040 // Start from a clean slate. 1041 $_menu = array(); 1042 1043 $_menu['path index'] = array(); 1044 // Set up items array, including default "Navigation" menu. 1045 $_menu['items'] = array( 1046 0 => array('path' => '', 'title' => '', 'type' => MENU_IS_ROOT), 1047 1 => array('pid' => 0, 'path' => '', 'title' => t('Navigation'), 'weight' => -50, 'access' => TRUE, 'type' => MENU_IS_ROOT | MENU_VISIBLE_IN_TREE) 1048 ); 1049 $_menu['callbacks'] = array(); 1050 1051 // Build a sequential list of all menu items. 1052 $menu_item_list = module_invoke_all('menu', TRUE); 1053 1054 // Menu items not in the DB get temporary negative IDs. 1055 $temp_mid = -1; 1056 1057 foreach ($menu_item_list as $item) { 1058 if (!isset($item['path'])) { 1059 $item['path'] = ''; 1060 } 1061 if (!isset($item['type'])) { 1062 $item['type'] = MENU_NORMAL_ITEM; 1063 } 1064 if (!isset($item['weight'])) { 1065 $item['weight'] = 0; 1066 } 1067 $mid = $temp_mid; 1068 if (isset($_menu['path index'][$item['path']])) { 1069 // Newer menu items overwrite older ones. 1070 unset($_menu['items'][$_menu['path index'][$item['path']]]); 1071 } 1072 if (isset($item['callback'])) { 1073 $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']); 1074 if (isset($item['callback arguments'])) { 1075 $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments']; 1076 unset($item['callback arguments']); 1077 } 1078 unset($item['callback']); 1079 } 1080 $_menu['items'][$mid] = $item; 1081 $_menu['path index'][$item['path']] = $mid; 1082 1083 $temp_mid--; 1084 } 1085 1086 // Now fetch items from the DB, reassigning menu IDs as needed. 1087 if (module_exists('menu')) { 1088 $result = db_query(db_rewrite_sql('SELECT m.mid, m.* FROM {menu} m ORDER BY m.mid ASC', 'm', 'mid')); 1089 while ($item = db_fetch_object($result)) { 1090 // Handle URL aliases if entered in menu administration. 1091 if (!isset($_menu['path index'][$item->path])) { 1092 $item->path = drupal_get_normal_path($item->path); 1093 } 1094 if (isset($_menu['path index'][$item->path])) { 1095 // The path is already declared. 1096 $old_mid = $_menu['path index'][$item->path]; 1097 if ($old_mid < 0) { 1098 // It had a temporary ID, so use a permanent one. 1099 $_menu['items'][$item->mid] = $_menu['items'][$old_mid]; 1100 if ($_menu['items'][$item->mid]['type'] & $item->type) { 1101 // If the item is of the same type, delete the old item. 1102 unset($_menu['items'][$old_mid]); 1103 $_menu['path index'][$item->path] = $item->mid; 1104 } 1105 // The new menu item gets all the custom type flags from the database 1106 $_menu['items'][$item->mid]['type'] &= $item->type; 1107 } 1108 else { 1109 // It has a permanent ID. Only replace with non-custom menu items. 1110 if ($item->type & MENU_CREATED_BY_ADMIN) { 1111 $_menu['items'][$item->mid] = array('path' => $item->path); 1112 } 1113 else { 1114 // Leave the old item around as a shortcut to this one. 1115 $_menu['items'][$item->mid] = $_menu['items'][$old_mid]; 1116 $_menu['path index'][$item->path] = $item->mid; 1117 } 1118 } 1119 } 1120 else { 1121 // The path was not declared, so this is a custom item or an orphaned one. 1122 if ($item->type & MENU_CREATED_BY_ADMIN) { 1123 $_menu['items'][$item->mid] = array('path' => $item->path); 1124 if (!empty($item->path)) { 1125 $_menu['path index'][$item->path] = $item->mid; 1126 } 1127 } 1128 } 1129 1130 // If the administrator has changed the item, reflect the change. 1131 if ($item->type & MENU_MODIFIED_BY_ADMIN) { 1132 $_menu['items'][$item->mid]['title'] = $item->title; 1133 $_menu['items'][$item->mid]['description'] = $item->description; 1134 $_menu['items'][$item->mid]['pid'] = $item->pid; 1135 $_menu['items'][$item->mid]['weight'] = $item->weight; 1136 $_menu['items'][$item->mid]['type'] = $item->type; 1137 } 1138 } 1139 } 1140 1141 // Associate parent and child menu items. 1142 _menu_find_parents($_menu['items']); 1143 1144 // Prepare to display trees to the user as required. 1145 _menu_build_visible_tree(); 1146 } 1147 1148 /** 1149 * Determine whether the given menu item is accessible to the current user. 1150 * 1151 * Use this instead of just checking the "access" property of a menu item 1152 * to properly handle items with fall-through semantics. 1153 */ 1154 function _menu_item_is_accessible($mid) { 1155 $menu = menu_get_menu(); 1156 1157 // Follow the path up to find the first "access" attribute. 1158 $path = isset($menu['items'][$mid]['path']) ? $menu['items'][$mid]['path'] : NULL; 1159 while ($path && (!isset($menu['path index'][$path]) || !isset($menu['items'][$menu['path index'][$path]]['access']))) { 1160 $path = substr($path, 0, strrpos($path, '/')); 1161 } 1162 if (empty($path)) { 1163 // Items without any access attribute up the chain are denied, unless they 1164 // were created by the admin. They most likely point to non-Drupal directories 1165 // or to an external URL and should be allowed. 1166 return $menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN; 1167 } 1168 return $menu['items'][$menu['path index'][$path]]['access']; 1169 } 1170 1171 /** 1172 * Find all visible items in the menu tree, for ease in displaying to user. 1173 * 1174 * Since this is only for display, we only need title, path, and children 1175 * for each item. 1176 */ 1177 function _menu_build_visible_tree($pid = 0) { 1178 global $_menu; 1179 1180 if (isset($_menu['items'][$pid])) { 1181 $parent = $_menu['items'][$pid]; 1182 1183 $children = array(); 1184 if (isset($parent['children'])) { 1185 usort($parent['children'], '_menu_sort'); 1186 foreach ($parent['children'] as $mid) { 1187 $children = array_merge($children, _menu_build_visible_tree($mid)); 1188 } 1189 } 1190 $visible = ($parent['type'] & MENU_VISIBLE_IN_TREE) || 1191 ($parent['type'] & MENU_VISIBLE_IF_HAS_CHILDREN && count($children) > 0); 1192 $allowed = _menu_item_is_accessible($pid); 1193 1194 if (($parent['type'] & MENU_IS_ROOT) || ($visible && $allowed)) { 1195 $_menu['visible'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children, 'type' => $parent['type']); 1196 foreach ($children as $mid) { 1197 $_menu['visible'][$mid]['pid'] = $pid; 1198 } 1199 return array($pid); 1200 } 1201 else { 1202 return $children; 1203 } 1204 } 1205 1206 return array(); 1207 } 1208 1209 /** 1210 * Account for menu items that are only defined at certain paths, so will not 1211 * be cached. 1212 * 1213 * We don't support the full range of menu item options for these menu items. We 1214 * don't support MENU_VISIBLE_IF_HAS_CHILDREN, and we require parent items to be 1215 * declared before their children. 1216 */ 1217 function _menu_append_contextual_items() { 1218 global $_menu; 1219 1220 // Build a sequential list of all menu items. 1221 $menu_item_list = module_invoke_all('menu', FALSE); 1222 1223 // Menu items not in the DB get temporary negative IDs. 1224 $temp_mid = min(array_keys($_menu['items'])) - 1; 1225 $new_items = array(); 1226 1227 foreach ($menu_item_list as $item) { 1228 if (isset($item['callback'])) { 1229 $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']); 1230 if (isset($item['callback arguments'])) { 1231 $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments']; 1232 unset($item['callback arguments']); 1233 } 1234 unset($item['callback']); 1235 } 1236 if (!isset($_menu['path index'][$item['path']])) { 1237 if (!isset($item['path'])) { 1238 $item['path'] = ''; 1239 } 1240 if (!isset($item['type'])) { 1241 $item['type'] = MENU_NORMAL_ITEM; 1242 } 1243 if (!isset($item['weight'])) { 1244 $item['weight'] = 0; 1245 } 1246 $_menu['items'][$temp_mid] = $item; 1247 $_menu['path index'][$item['path']] = $temp_mid; 1248 $new_items[$temp_mid] = $item; 1249 $temp_mid--; 1250 } 1251 else { 1252 $mid = $_menu['path index'][$item['path']]; 1253 if ($_menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN) { 1254 $_menu['items'][$mid]['access'] = $item['access']; 1255 if (isset($_menu['items'][$mid]['callback'])) { 1256 $_menu['items'][$mid]['callback'] = $item['callback']; 1257 } 1258 if (isset($_menu['items'][$mid]['callback arguments'])) { 1259 $_menu['items'][$mid]['callback arguments'] = $item['callback arguments']; 1260 } 1261 } 1262 if ($item['type'] & MENU_LOCAL_TASK && !($_menu['items'][$mid]['type'] & MENU_LOCAL_TASK)) { 1263 // A local task is in the menu table and the path is already present 1264 $_menu['items'][$mid]['type'] = MENU_LOCAL_TASK; 1265 $new_items[$mid] = $item; 1266 } 1267 } 1268 } 1269 1270 // Establish parent-child relationships. 1271 _menu_find_parents($new_items); 1272 1273 // Add new items to the visible tree if necessary. 1274 foreach ($new_items as $mid => $item) { 1275 $item = $_menu['items'][$mid]; 1276 if (($item['type'] & MENU_VISIBLE_IN_TREE) && _menu_item_is_accessible($mid)) { 1277 $pid = $item['pid']; 1278 while ($pid && !isset($_menu['visible'][$pid])) { 1279 $pid = $_menu['items'][$pid]['pid']; 1280 } 1281 $_menu['visible'][$mid] = array('title' => $item['title'], 'path' => $item['path'], 'pid' => $pid); 1282 $_menu['visible'][$pid]['children'][] = $mid; 1283 usort($_menu['visible'][$pid]['children'], '_menu_sort'); 1284 } 1285 } 1286 } 1287 1288 /** 1289 * Establish parent-child relationships. 1290 */ 1291 function _menu_find_parents(&$items) { 1292 global $_menu; 1293 1294 foreach ($items as $mid => $item) { 1295 if (!isset($item['pid'])) { 1296 // Parent's location has not been customized, so figure it out using the path. 1297 $parent = $item['path']; 1298 if ($parent) { 1299 do { 1300 $parent = substr($parent, 0, strrpos($parent, '/')); 1301 } 1302 while ($parent && !isset($_menu['path index'][$parent])); 1303 } 1304 1305 $pid = $parent ? $_menu['path index'][$parent] : 1; 1306 $_menu['items'][$mid]['pid'] = $pid; 1307 } 1308 else { 1309 $pid = $item['pid']; 1310 } 1311 1312 // Don't make root a child of itself. 1313 if ($mid) { 1314 if (isset ($_menu['items'][$pid])) { 1315 $_menu['items'][$pid]['children'][] = $mid; 1316 } 1317 else { 1318 // If parent is missing, it is a menu item that used to be defined 1319 // but is no longer. Default to a root-level "Navigation" menu item. 1320 $_menu['items'][1]['children'][] = $mid; 1321 } 1322 } 1323 } 1324 } 1325 1326 /** 1327 * Find all the items in the current local task tree. 1328 * 1329 * Since this is only for display, we only need title, path, and children 1330 * for each item. 1331 * 1332 * At the close of this function, $_menu['local tasks'] is populated with the 1333 * menu items in the local task tree. 1334 * 1335 * @return 1336 * TRUE if the local task tree is forked. It does not need to be displayed 1337 * otherwise. 1338 */ 1339 function _menu_build_local_tasks($pid) { 1340 global $_menu; 1341 1342 $forked = FALSE; 1343 1344 if (isset($_menu['items'][$pid])) { 1345 $parent = $_menu['items'][$pid]; 1346 1347 $children = array(); 1348 if (isset($parent['children'])) { 1349 foreach ($parent['children'] as $mid) { 1350 if (($_menu['items'][$mid]['type'] & MENU_IS_LOCAL_TASK) && _menu_item_is_accessible($mid)) { 1351 $children[] = $mid; 1352 // Beware short-circuiting || operator! 1353 $forked = _menu_build_local_tasks($mid) || $forked; 1354 } 1355 } 1356 } 1357 usort($children, '_menu_sort'); 1358 $forked = $forked || count($children) > 1; 1359 1360 $_menu['local tasks'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children); 1361 foreach ($children as $mid) { 1362 $_menu['local tasks'][$mid]['pid'] = $pid; 1363 } 1364 } 1365 1366 return $forked; 1367 } 1368 1369 /** 1370 * Returns TRUE if the site is off-line for maintenance. 1371 */ 1372 function _menu_site_is_offline() { 1373 // Check if site is set to off-line mode 1374 if (variable_get('site_offline', 0)) { 1375 // Check if the user has administration privileges 1376 if (!user_access('administer site configuration')) { 1377 // Check if this is an attempt to login 1378 if (drupal_get_normal_path($_GET['q']) != 'user') { 1379 return TRUE; 1380 } 1381 } 1382 else { 1383 $offline_message = t('Operating in off-line mode.'); 1384 $messages = drupal_set_message(); 1385 // Ensure that the off-line message is displayed only once [allowing for page redirects]. 1386 if (!isset($messages) || !isset($messages['status']) || !in_array($offline_message, $messages['status'])) { 1387 drupal_set_message($offline_message); 1388 } 1389 } 1390 } 1391 return FALSE; 1392 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Nov 30 16:20:15 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |