Drupal PHP Cross Reference Content Management Systems

Source: /includes/menu.inc - 3882 lines - 138717 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /**
   4   * @file
   5   * API for the Drupal menu system.
   6   */
   7  
   8  /**
   9   * @defgroup menu Menu system
  10   * @{
  11   * Define the navigation menus, and route page requests to code based on URLs.
  12   *
  13   * The Drupal menu system drives both the navigation system from a user
  14   * perspective and the callback system that Drupal uses to respond to URLs
  15   * passed from the browser. For this reason, a good understanding of the
  16   * menu system is fundamental to the creation of complex modules. As a note,
  17   * this is related to, but separate from menu.module, which allows menus
  18   * (which in this context are hierarchical lists of links) to be customized from
  19   * the Drupal administrative interface.
  20   *
  21   * Drupal's menu system follows a simple hierarchy defined by paths.
  22   * Implementations of hook_menu() define menu items and assign them to
  23   * paths (which should be unique). The menu system aggregates these items
  24   * and determines the menu hierarchy from the paths. For example, if the
  25   * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
  26   * would form the structure:
  27   * - a
  28   *   - a/b
  29   *     - a/b/c/d
  30   *     - a/b/h
  31   * - e
  32   * - f/g
  33   * Note that the number of elements in the path does not necessarily
  34   * determine the depth of the menu item in the tree.
  35   *
  36   * When responding to a page request, the menu system looks to see if the
  37   * path requested by the browser is registered as a menu item with a
  38   * callback. If not, the system searches up the menu tree for the most
  39   * complete match with a callback it can find. If the path a/b/i is
  40   * requested in the tree above, the callback for a/b would be used.
  41   *
  42   * The found callback function is called with any arguments specified
  43   * in the "page arguments" attribute of its menu item. The
  44   * attribute must be an array. After these arguments, any remaining
  45   * components of the path are appended as further arguments. In this
  46   * way, the callback for a/b above could respond to a request for
  47   * a/b/i differently than a request for a/b/j.
  48   *
  49   * For an illustration of this process, see page_example.module.
  50   *
  51   * Access to the callback functions is also protected by the menu system.
  52   * The "access callback" with an optional "access arguments" of each menu
  53   * item is called before the page callback proceeds. If this returns TRUE,
  54   * then access is granted; if FALSE, then access is denied. Default local task
  55   * menu items (see next paragraph) may omit this attribute to use the value
  56   * provided by the parent item.
  57   *
  58   * In the default Drupal interface, you will notice many links rendered as
  59   * tabs. These are known in the menu system as "local tasks", and they are
  60   * rendered as tabs by default, though other presentations are possible.
  61   * Local tasks function just as other menu items in most respects. It is
  62   * convention that the names of these tasks should be short verbs if
  63   * possible. In addition, a "default" local task should be provided for
  64   * each set. When visiting a local task's parent menu item, the default
  65   * local task will be rendered as if it is selected; this provides for a
  66   * normal tab user experience. This default task is special in that it
  67   * links not to its provided path, but to its parent item's path instead.
  68   * The default task's path is only used to place it appropriately in the
  69   * menu hierarchy.
  70   *
  71   * Everything described so far is stored in the menu_router table. The
  72   * menu_links table holds the visible menu links. By default these are
  73   * derived from the same hook_menu definitions, however you are free to
  74   * add more with menu_link_save().
  75   */
  76  
  77  /**
  78   * @defgroup menu_flags Menu flags
  79   * @{
  80   * Flags for use in the "type" attribute of menu items.
  81   */
  82  
  83  /**
  84   * Internal menu flag -- menu item is the root of the menu tree.
  85   */
  86  define('MENU_IS_ROOT', 0x0001);
  87  
  88  /**
  89   * Internal menu flag -- menu item is visible in the menu tree.
  90   */
  91  define('MENU_VISIBLE_IN_TREE', 0x0002);
  92  
  93  /**
  94   * Internal menu flag -- menu item is visible in the breadcrumb.
  95   */
  96  define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
  97  
  98  /**
  99   * Internal menu flag -- menu item links back to its parent.
 100   */
 101  define('MENU_LINKS_TO_PARENT', 0x0008);
 102  
 103  /**
 104   * Internal menu flag -- menu item can be modified by administrator.
 105   */
 106  define('MENU_MODIFIED_BY_ADMIN', 0x0020);
 107  
 108  /**
 109   * Internal menu flag -- menu item was created by administrator.
 110   */
 111  define('MENU_CREATED_BY_ADMIN', 0x0040);
 112  
 113  /**
 114   * Internal menu flag -- menu item is a local task.
 115   */
 116  define('MENU_IS_LOCAL_TASK', 0x0080);
 117  
 118  /**
 119   * Internal menu flag -- menu item is a local action.
 120   */
 121  define('MENU_IS_LOCAL_ACTION', 0x0100);
 122  
 123  /**
 124   * @} End of "Menu flags".
 125   */
 126  
 127  /**
 128   * @defgroup menu_item_types Menu item types
 129   * @{
 130   * Definitions for various menu item types.
 131   *
 132   * Menu item definitions provide one of these constants, which are shortcuts for
 133   * combinations of @link menu_flags Menu flags @endlink.
 134   */
 135  
 136  /**
 137   * Menu type -- A "normal" menu item that's shown in menu and breadcrumbs.
 138   *
 139   * Normal menu items show up in the menu tree and can be moved/hidden by
 140   * the administrator. Use this for most menu items. It is the default value if
 141   * no menu item type is specified.
 142   */
 143  define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
 144  
 145  /**
 146   * Menu type -- A hidden, internal callback, typically used for API calls.
 147   *
 148   * Callbacks simply register a path so that the correct function is fired
 149   * when the URL is accessed. They do not appear in menus or breadcrumbs.
 150   */
 151  define('MENU_CALLBACK', 0x0000);
 152  
 153  /**
 154   * Menu type -- A normal menu item, hidden until enabled by an administrator.
 155   *
 156   * Modules may "suggest" menu items that the administrator may enable. They act
 157   * just as callbacks do until enabled, at which time they act like normal items.
 158   * Note for the value: 0x0010 was a flag which is no longer used, but this way
 159   * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate.
 160   */
 161  define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010);
 162  
 163  /**
 164   * Menu type -- A task specific to the parent item, usually rendered as a tab.
 165   *
 166   * Local tasks are menu items that describe actions to be performed on their
 167   * parent item. An example is the path "node/52/edit", which performs the
 168   * "edit" task on "node/52".
 169   */
 170  define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB);
 171  
 172  /**
 173   * Menu type -- The "default" local task, which is initially active.
 174   *
 175   * Every set of local tasks should provide one "default" task, that links to the
 176   * same path as its parent when clicked.
 177   */
 178  define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB);
 179  
 180  /**
 181   * Menu type -- An action specific to the parent, usually rendered as a link.
 182   *
 183   * Local actions are menu items that describe actions on the parent item such
 184   * as adding a new user, taxonomy term, etc.
 185   */
 186  define('MENU_LOCAL_ACTION', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
 187  
 188  /**
 189   * @} End of "Menu item types".
 190   */
 191  
 192  /**
 193   * @defgroup menu_context_types Menu context types
 194   * @{
 195   * Flags for use in the "context" attribute of menu router items.
 196   */
 197  
 198  /**
 199   * Internal menu flag: Invisible local task.
 200   *
 201   * This flag may be used for local tasks like "Delete", so custom modules and
 202   * themes can alter the default context and expose the task by altering menu.
 203   */
 204  define('MENU_CONTEXT_NONE', 0x0000);
 205  
 206  /**
 207   * Internal menu flag: Local task should be displayed in page context.
 208   */
 209  define('MENU_CONTEXT_PAGE', 0x0001);
 210  
 211  /**
 212   * Internal menu flag: Local task should be displayed inline.
 213   */
 214  define('MENU_CONTEXT_INLINE', 0x0002);
 215  
 216  /**
 217   * @} End of "Menu context types".
 218   */
 219  
 220  /**
 221   * @defgroup menu_status_codes Menu status codes
 222   * @{
 223   * Status codes for menu callbacks.
 224   */
 225  
 226  /**
 227   * Internal menu status code -- Menu item was found.
 228   */
 229  define('MENU_FOUND', 1);
 230  
 231  /**
 232   * Internal menu status code -- Menu item was not found.
 233   */
 234  define('MENU_NOT_FOUND', 2);
 235  
 236  /**
 237   * Internal menu status code -- Menu item access is denied.
 238   */
 239  define('MENU_ACCESS_DENIED', 3);
 240  
 241  /**
 242   * Internal menu status code -- Menu item inaccessible because site is offline.
 243   */
 244  define('MENU_SITE_OFFLINE', 4);
 245  
 246  /**
 247   * Internal menu status code -- Everything is working fine.
 248   */
 249  define('MENU_SITE_ONLINE', 5);
 250  
 251  /**
 252   * @} End of "Menu status codes".
 253   */
 254  
 255  /**
 256   * @defgroup menu_tree_parameters Menu tree parameters
 257   * @{
 258   * Parameters for a menu tree.
 259   */
 260  
 261   /**
 262   * The maximum number of path elements for a menu callback
 263   */
 264  define('MENU_MAX_PARTS', 9);
 265  
 266  
 267  /**
 268   * The maximum depth of a menu links tree - matches the number of p columns.
 269   */
 270  define('MENU_MAX_DEPTH', 9);
 271  
 272  
 273  /**
 274   * @} End of "Menu tree parameters".
 275   */
 276  
 277  /**
 278   * Reserved key to identify the most specific menu link for a given path.
 279   *
 280   * The value of this constant is a hash of the constant name. We use the hash
 281   * so that the reserved key is over 32 characters in length and will not
 282   * collide with allowed menu names:
 283   * @code
 284   * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
 285   * @endcode
 286   *
 287   * @see menu_link_get_preferred()
 288   */
 289  define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91');
 290  
 291  /**
 292   * Returns the ancestors (and relevant placeholders) for any given path.
 293   *
 294   * For example, the ancestors of node/12345/edit are:
 295   * - node/12345/edit
 296   * - node/12345/%
 297   * - node/%/edit
 298   * - node/%/%
 299   * - node/12345
 300   * - node/%
 301   * - node
 302   *
 303   * To generate these, we will use binary numbers. Each bit represents a
 304   * part of the path. If the bit is 1, then it represents the original
 305   * value while 0 means wildcard. If the path is node/12/edit/foo
 306   * then the 1011 bitstring represents node/%/edit/foo where % means that
 307   * any argument matches that part. We limit ourselves to using binary
 308   * numbers that correspond the patterns of wildcards of router items that
 309   * actually exists. This list of 'masks' is built in menu_rebuild().
 310   *
 311   * @param $parts
 312   *   An array of path parts, for the above example
 313   *   array('node', '12345', 'edit').
 314   *
 315   * @return
 316   *   An array which contains the ancestors and placeholders. Placeholders
 317   *   simply contain as many '%s' as the ancestors.
 318   */
 319  function menu_get_ancestors($parts) {
 320    $number_parts = count($parts);
 321    $ancestors = array();
 322    $length =  $number_parts - 1;
 323    $end = (1 << $number_parts) - 1;
 324    $masks = variable_get('menu_masks');
 325    // If the optimized menu_masks array is not available use brute force to get
 326    // the correct $ancestors and $placeholders returned. Do not use this as the
 327    // default value of the menu_masks variable to avoid building such a big
 328    // array.
 329    if (!$masks) {
 330      $masks = range(511, 1);
 331    }
 332    // Only examine patterns that actually exist as router items (the masks).
 333    foreach ($masks as $i) {
 334      if ($i > $end) {
 335        // Only look at masks that are not longer than the path of interest.
 336        continue;
 337      }
 338      elseif ($i < (1 << $length)) {
 339        // We have exhausted the masks of a given length, so decrease the length.
 340        --$length;
 341      }
 342      $current = '';
 343      for ($j = $length; $j >= 0; $j--) {
 344        // Check the bit on the $j offset.
 345        if ($i & (1 << $j)) {
 346          // Bit one means the original value.
 347          $current .= $parts[$length - $j];
 348        }
 349        else {
 350          // Bit zero means means wildcard.
 351          $current .= '%';
 352        }
 353        // Unless we are at offset 0, add a slash.
 354        if ($j) {
 355          $current .= '/';
 356        }
 357      }
 358      $ancestors[] = $current;
 359    }
 360    return $ancestors;
 361  }
 362  
 363  /**
 364   * Unserializes menu data, using a map to replace path elements.
 365   *
 366   * The menu system stores various path-related information (such as the 'page
 367   * arguments' and 'access arguments' components of a menu item) in the database
 368   * using serialized arrays, where integer values in the arrays represent
 369   * arguments to be replaced by values from the path. This function first
 370   * unserializes such menu information arrays, and then does the path
 371   * replacement.
 372   *
 373   * The path replacement acts on each integer-valued element of the unserialized
 374   * menu data array ($data) using a map array ($map, which is typically an array
 375   * of path arguments) as a list of replacements. For instance, if there is an
 376   * element of $data whose value is the number 2, then it is replaced in $data
 377   * with $map[2]; non-integer values in $data are left alone.
 378   *
 379   * As an example, an unserialized $data array with elements ('node_load', 1)
 380   * represents instructions for calling the node_load() function. Specifically,
 381   * this instruction says to use the path component at index 1 as the input
 382   * parameter to node_load(). If the path is 'node/123', then $map will be the
 383   * array ('node', 123), and the returned array from this function will have
 384   * elements ('node_load', 123), since $map[1] is 123. This return value will
 385   * indicate specifically that node_load(123) is to be called to load the node
 386   * whose ID is 123 for this menu item.
 387   *
 388   * @param $data
 389   *   A serialized array of menu data, as read from the database.
 390   * @param $map
 391   *   A path argument array, used to replace integer values in $data; an integer
 392   *   value N in $data will be replaced by value $map[N]. Typically, the $map
 393   *   array is generated from a call to the arg() function.
 394   *
 395   * @return
 396   *   The unserialized $data array, with path arguments replaced.
 397   */
 398  function menu_unserialize($data, $map) {
 399    if ($data = unserialize($data)) {
 400      foreach ($data as $k => $v) {
 401        if (is_int($v)) {
 402          $data[$k] = isset($map[$v]) ? $map[$v] : '';
 403        }
 404      }
 405      return $data;
 406    }
 407    else {
 408      return array();
 409    }
 410  }
 411  
 412  
 413  
 414  /**
 415   * Replaces the statically cached item for a given path.
 416   *
 417   * @param $path
 418   *   The path.
 419   * @param $router_item
 420   *   The router item. Usually a router entry from menu_get_item() is either
 421   *   modified or set to a different path. This allows the navigation block,
 422   *   the page title, the breadcrumb, and the page help to be modified in one
 423   *   call.
 424   */
 425  function menu_set_item($path, $router_item) {
 426    menu_get_item($path, $router_item);
 427  }
 428  
 429  /**
 430   * Gets a router item.
 431   *
 432   * @param $path
 433   *   The path, for example node/5. The function will find the corresponding
 434   *   node/% item and return that.
 435   * @param $router_item
 436   *   Internal use only.
 437   *
 438   * @return
 439   *   The router item or, if an error occurs in _menu_translate(), FALSE. A
 440   *   router item is an associative array corresponding to one row in the
 441   *   menu_router table. The value corresponding to the key 'map' holds the
 442   *   loaded objects. The value corresponding to the key 'access' is TRUE if the
 443   *   current user can access this page. The values corresponding to the keys
 444   *   'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will
 445   *   be filled in based on the database values and the objects loaded.
 446   */
 447  function menu_get_item($path = NULL, $router_item = NULL) {
 448    $router_items = &drupal_static(__FUNCTION__);
 449    if (!isset($path)) {
 450      $path = $_GET['q'];
 451    }
 452    if (isset($router_item)) {
 453      $router_items[$path] = $router_item;
 454    }
 455    if (!isset($router_items[$path])) {
 456      // Rebuild if we know it's needed, or if the menu masks are missing which
 457      // occurs rarely, likely due to a race condition of multiple rebuilds.
 458      if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
 459        menu_rebuild();
 460      }
 461      $original_map = arg(NULL, $path);
 462  
 463      $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
 464      $ancestors = menu_get_ancestors($parts);
 465      $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
 466  
 467      if ($router_item) {
 468        // Allow modules to alter the router item before it is translated and
 469        // checked for access.
 470        drupal_alter('menu_get_item', $router_item, $path, $original_map);
 471  
 472        $map = _menu_translate($router_item, $original_map);
 473        $router_item['original_map'] = $original_map;
 474        if ($map === FALSE) {
 475          $router_items[$path] = FALSE;
 476          return FALSE;
 477        }
 478        if ($router_item['access']) {
 479          $router_item['map'] = $map;
 480          $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
 481          $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
 482        }
 483      }
 484      $router_items[$path] = $router_item;
 485    }
 486    return $router_items[$path];
 487  }
 488  
 489  /**
 490   * Execute the page callback associated with the current path.
 491   *
 492   * @param $path
 493   *   The drupal path whose handler is to be be executed. If set to NULL, then
 494   *   the current path is used.
 495   * @param $deliver
 496   *   (optional) A boolean to indicate whether the content should be sent to the
 497   *   browser using the appropriate delivery callback (TRUE) or whether to return
 498   *   the result to the caller (FALSE).
 499   */
 500  function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
 501    // Check if site is offline.
 502    $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
 503  
 504    // Allow other modules to change the site status but not the path because that
 505    // would not change the global variable. hook_url_inbound_alter() can be used
 506    // to change the path. Code later will not use the $read_only_path variable.
 507    $read_only_path = !empty($path) ? $path : $_GET['q'];
 508    drupal_alter('menu_site_status', $page_callback_result, $read_only_path);
 509  
 510    // Only continue if the site status is not set.
 511    if ($page_callback_result == MENU_SITE_ONLINE) {
 512      if ($router_item = menu_get_item($path)) {
 513        if ($router_item['access']) {
 514          if ($router_item['include_file']) {
 515            require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
 516          }
 517          $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
 518        }
 519        else {
 520          $page_callback_result = MENU_ACCESS_DENIED;
 521        }
 522      }
 523      else {
 524        $page_callback_result = MENU_NOT_FOUND;
 525      }
 526    }
 527  
 528    // Deliver the result of the page callback to the browser, or if requested,
 529    // return it raw, so calling code can do more processing.
 530    if ($deliver) {
 531      $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;
 532      drupal_deliver_page($page_callback_result, $default_delivery_callback);
 533    }
 534    else {
 535      return $page_callback_result;
 536    }
 537  }
 538  
 539  /**
 540   * Loads objects into the map as defined in the $item['load_functions'].
 541   *
 542   * @param $item
 543   *   A menu router or menu link item
 544   * @param $map
 545   *   An array of path arguments (ex: array('node', '5'))
 546   *
 547   * @return
 548   *   Returns TRUE for success, FALSE if an object cannot be loaded.
 549   *   Names of object loading functions are placed in $item['load_functions'].
 550   *   Loaded objects are placed in $map[]; keys are the same as keys in the
 551   *   $item['load_functions'] array.
 552   *   $item['access'] is set to FALSE if an object cannot be loaded.
 553   */
 554  function _menu_load_objects(&$item, &$map) {
 555    if ($load_functions = $item['load_functions']) {
 556      // If someone calls this function twice, then unserialize will fail.
 557      if (!is_array($load_functions)) {
 558        $load_functions = unserialize($load_functions);
 559      }
 560      $path_map = $map;
 561      foreach ($load_functions as $index => $function) {
 562        if ($function) {
 563          $value = isset($path_map[$index]) ? $path_map[$index] : '';
 564          if (is_array($function)) {
 565            // Set up arguments for the load function. These were pulled from
 566            // 'load arguments' in the hook_menu() entry, but they need
 567            // some processing. In this case the $function is the key to the
 568            // load_function array, and the value is the list of arguments.
 569            list($function, $args) = each($function);
 570            $load_functions[$index] = $function;
 571  
 572            // Some arguments are placeholders for dynamic items to process.
 573            foreach ($args as $i => $arg) {
 574              if ($arg === '%index') {
 575                // Pass on argument index to the load function, so multiple
 576                // occurrences of the same placeholder can be identified.
 577                $args[$i] = $index;
 578              }
 579              if ($arg === '%map') {
 580                // Pass on menu map by reference. The accepting function must
 581                // also declare this as a reference if it wants to modify
 582                // the map.
 583                $args[$i] = &$map;
 584              }
 585              if (is_int($arg)) {
 586                $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : '';
 587              }
 588            }
 589            array_unshift($args, $value);
 590            $return = call_user_func_array($function, $args);
 591          }
 592          else {
 593            $return = $function($value);
 594          }
 595          // If callback returned an error or there is no callback, trigger 404.
 596          if ($return === FALSE) {
 597            $item['access'] = FALSE;
 598            $map = FALSE;
 599            return FALSE;
 600          }
 601          $map[$index] = $return;
 602        }
 603      }
 604      $item['load_functions'] = $load_functions;
 605    }
 606    return TRUE;
 607  }
 608  
 609  /**
 610   * Checks access to a menu item using the access callback.
 611   *
 612   * @param $item
 613   *   A menu router or menu link item
 614   * @param $map
 615   *   An array of path arguments (ex: array('node', '5'))
 616   *
 617   * @return
 618   *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
 619   */
 620  function _menu_check_access(&$item, $map) {
 621    // Determine access callback, which will decide whether or not the current
 622    // user has access to this path.
 623    $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
 624    // Check for a TRUE or FALSE value.
 625    if (is_numeric($callback)) {
 626      $item['access'] = (bool) $callback;
 627    }
 628    else {
 629      $arguments = menu_unserialize($item['access_arguments'], $map);
 630      // As call_user_func_array is quite slow and user_access is a very common
 631      // callback, it is worth making a special case for it.
 632      if ($callback == 'user_access') {
 633        $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
 634      }
 635      elseif (function_exists($callback)) {
 636        $item['access'] = call_user_func_array($callback, $arguments);
 637      }
 638    }
 639  }
 640  
 641  /**
 642   * Localizes the router item title using t() or another callback.
 643   *
 644   * Translate the title and description to allow storage of English title
 645   * strings in the database, yet display of them in the language required
 646   * by the current user.
 647   *
 648   * @param $item
 649   *   A menu router item or a menu link item.
 650   * @param $map
 651   *   The path as an array with objects already replaced. E.g., for path
 652   *   node/123 $map would be array('node', $node) where $node is the node
 653   *   object for node 123.
 654   * @param $link_translate
 655   *   TRUE if we are translating a menu link item; FALSE if we are
 656   *   translating a menu router item.
 657   *
 658   * @return
 659   *   No return value.
 660   *   $item['title'] is localized according to $item['title_callback'].
 661   *   If an item's callback is check_plain(), $item['options']['html'] becomes
 662   *   TRUE.
 663   *   $item['description'] is translated using t().
 664   *   When doing link translation and the $item['options']['attributes']['title']
 665   *   (link title attribute) matches the description, it is translated as well.
 666   */
 667  function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
 668    $callback = $item['title_callback'];
 669    $item['localized_options'] = $item['options'];
 670    // All 'class' attributes are assumed to be an array during rendering, but
 671    // links stored in the database may use an old string value.
 672    // @todo In order to remove this code we need to implement a database update
 673    //   including unserializing all existing link options and running this code
 674    //   on them, as well as adding validation to menu_link_save().
 675    if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
 676      $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
 677    }
 678    // If we are translating the title of a menu link, and its title is the same
 679    // as the corresponding router item, then we can use the title information
 680    // from the router. If it's customized, then we need to use the link title
 681    // itself; can't localize.
 682    // If we are translating a router item (tabs, page, breadcrumb), then we
 683    // can always use the information from the router item.
 684    if (!$link_translate || ($item['title'] == $item['link_title'])) {
 685      // t() is a special case. Since it is used very close to all the time,
 686      // we handle it directly instead of using indirect, slower methods.
 687      if ($callback == 't') {
 688        if (empty($item['title_arguments'])) {
 689          $item['title'] = t($item['title']);
 690        }
 691        else {
 692          $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
 693        }
 694      }
 695      elseif ($callback && function_exists($callback)) {
 696        if (empty($item['title_arguments'])) {
 697          $item['title'] = $callback($item['title']);
 698        }
 699        else {
 700          $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
 701        }
 702        // Avoid calling check_plain again on l() function.
 703        if ($callback == 'check_plain') {
 704          $item['localized_options']['html'] = TRUE;
 705        }
 706      }
 707    }
 708    elseif ($link_translate) {
 709      $item['title'] = $item['link_title'];
 710    }
 711  
 712    // Translate description, see the motivation above.
 713    if (!empty($item['description'])) {
 714      $original_description = $item['description'];
 715      $item['description'] = t($item['description']);
 716      if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
 717        $item['localized_options']['attributes']['title'] = $item['description'];
 718      }
 719    }
 720  }
 721  
 722  /**
 723   * Handles dynamic path translation and menu access control.
 724   *
 725   * When a user arrives on a page such as node/5, this function determines
 726   * what "5" corresponds to, by inspecting the page's menu path definition,
 727   * node/%node. This will call node_load(5) to load the corresponding node
 728   * object.
 729   *
 730   * It also works in reverse, to allow the display of tabs and menu items which
 731   * contain these dynamic arguments, translating node/%node to node/5.
 732   *
 733   * Translation of menu item titles and descriptions are done here to
 734   * allow for storage of English strings in the database, and translation
 735   * to the language required to generate the current page.
 736   *
 737   * @param $router_item
 738   *   A menu router item
 739   * @param $map
 740   *   An array of path arguments (ex: array('node', '5'))
 741   * @param $to_arg
 742   *   Execute $item['to_arg_functions'] or not. Use only if you want to render a
 743   *   path from the menu table, for example tabs.
 744   *
 745   * @return
 746   *   Returns the map with objects loaded as defined in the
 747   *   $item['load_functions']. $item['access'] becomes TRUE if the item is
 748   *   accessible, FALSE otherwise. $item['href'] is set according to the map.
 749   *   If an error occurs during calling the load_functions (like trying to load
 750   *   a non-existent node) then this function returns FALSE.
 751   */
 752  function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
 753    if ($to_arg && !empty($router_item['to_arg_functions'])) {
 754      // Fill in missing path elements, such as the current uid.
 755      _menu_link_map_translate($map, $router_item['to_arg_functions']);
 756    }
 757    // The $path_map saves the pieces of the path as strings, while elements in
 758    // $map may be replaced with loaded objects.
 759    $path_map = $map;
 760    if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) {
 761      // An error occurred loading an object.
 762      $router_item['access'] = FALSE;
 763      return FALSE;
 764    }
 765  
 766    // Generate the link path for the page request or local tasks.
 767    $link_map = explode('/', $router_item['path']);
 768    if (isset($router_item['tab_root'])) {
 769      $tab_root_map = explode('/', $router_item['tab_root']);
 770    }
 771    if (isset($router_item['tab_parent'])) {
 772      $tab_parent_map = explode('/', $router_item['tab_parent']);
 773    }
 774    for ($i = 0; $i < $router_item['number_parts']; $i++) {
 775      if ($link_map[$i] == '%') {
 776        $link_map[$i] = $path_map[$i];
 777      }
 778      if (isset($tab_root_map[$i]) && $tab_root_map[$i] == '%') {
 779        $tab_root_map[$i] = $path_map[$i];
 780      }
 781      if (isset($tab_parent_map[$i]) && $tab_parent_map[$i] == '%') {
 782        $tab_parent_map[$i] = $path_map[$i];
 783      }
 784    }
 785    $router_item['href'] = implode('/', $link_map);
 786    $router_item['tab_root_href'] = implode('/', $tab_root_map);
 787    $router_item['tab_parent_href'] = implode('/', $tab_parent_map);
 788    $router_item['options'] = array();
 789    _menu_check_access($router_item, $map);
 790  
 791    // For performance, don't localize an item the user can't access.
 792    if ($router_item['access']) {
 793      _menu_item_localize($router_item, $map);
 794    }
 795  
 796    return $map;
 797  }
 798  
 799  /**
 800   * Translates the path elements in the map using any to_arg helper function.
 801   *
 802   * @param $map
 803   *   An array of path arguments (ex: array('node', '5'))
 804   * @param $to_arg_functions
 805   *   An array of helper function (ex: array(2 => 'menu_tail_to_arg'))
 806   *
 807   * @see hook_menu()
 808   */
 809  function _menu_link_map_translate(&$map, $to_arg_functions) {
 810    $to_arg_functions = unserialize($to_arg_functions);
 811    foreach ($to_arg_functions as $index => $function) {
 812      // Translate place-holders into real values.
 813      $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index);
 814      if (!empty($map[$index]) || isset($arg)) {
 815        $map[$index] = $arg;
 816      }
 817      else {
 818        unset($map[$index]);
 819      }
 820    }
 821  }
 822  
 823  /**
 824   * Returns a string containing the path relative to the current index.
 825   */
 826  function menu_tail_to_arg($arg, $map, $index) {
 827    return implode('/', array_slice($map, $index));
 828  }
 829  
 830  /**
 831   * Loads the path as one string relative to the current index.
 832   *
 833   * To use this load function, you must specify the load arguments
 834   * in the router item as:
 835   * @code
 836   * $item['load arguments'] = array('%map', '%index');
 837   * @endcode
 838   *
 839   * @see search_menu().
 840   */
 841  function menu_tail_load($arg, &$map, $index) {
 842    $arg = implode('/', array_slice($map, $index));
 843    $map = array_slice($map, 0, $index);
 844    return $arg;
 845  }
 846  
 847  /**
 848   * Provides menu link access control, translation, and argument handling.
 849   *
 850   * This function is similar to _menu_translate(), but it also does
 851   * link-specific preparation (such as always calling to_arg() functions).
 852   *
 853   * @param $item
 854   *   A menu link.
 855   * @param $translate
 856   *   (optional) Whether to try to translate a link containing dynamic path
 857   *   argument placeholders (%) based on the menu router item of the current
 858   *   path. Defaults to FALSE. Internally used for breadcrumbs.
 859   *
 860   * @return
 861   *   Returns the map of path arguments with objects loaded as defined in the
 862   *   $item['load_functions'].
 863   *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
 864   *   $item['href'] is generated from link_path, possibly by to_arg functions.
 865   *   $item['title'] is generated from link_title, and may be localized.
 866   *   $item['options'] is unserialized; it is also changed within the call here
 867   *   to $item['localized_options'] by _menu_item_localize().
 868   */
 869  function _menu_link_translate(&$item, $translate = FALSE) {
 870    if (!is_array($item['options'])) {
 871      $item['options'] = unserialize($item['options']);
 872    }
 873    if ($item['external']) {
 874      $item['access'] = 1;
 875      $map = array();
 876      $item['href'] = $item['link_path'];
 877      $item['title'] = $item['link_title'];
 878      $item['localized_options'] = $item['options'];
 879    }
 880    else {
 881      // Complete the path of the menu link with elements from the current path,
 882      // if it contains dynamic placeholders (%).
 883      $map = explode('/', $item['link_path']);
 884      if (strpos($item['link_path'], '%') !== FALSE) {
 885        // Invoke registered to_arg callbacks.
 886        if (!empty($item['to_arg_functions'])) {
 887          _menu_link_map_translate($map, $item['to_arg_functions']);
 888        }
 889        // Or try to derive the path argument map from the current router item,
 890        // if this $item's path is within the router item's path. This means
 891        // that if we are on the current path 'foo/%/bar/%/baz', then
 892        // menu_get_item() will have translated the menu router item for the
 893        // current path, and we can take over the argument map for a link like
 894        // 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
 895        // @see _menu_tree_check_access()
 896        // @see menu_get_active_breadcrumb()
 897        elseif ($translate && ($current_router_item = menu_get_item())) {
 898          // If $translate is TRUE, then this link is in the active trail.
 899          // Only translate paths within the current path.
 900          if (strpos($current_router_item['path'], $item['link_path']) === 0) {
 901            $count = count($map);
 902            $map = array_slice($current_router_item['original_map'], 0, $count);
 903            $item['original_map'] = $map;
 904            if (isset($current_router_item['map'])) {
 905              $item['map'] = array_slice($current_router_item['map'], 0, $count);
 906            }
 907            // Reset access to check it (for the first time).
 908            unset($item['access']);
 909          }
 910        }
 911      }
 912      $item['href'] = implode('/', $map);
 913  
 914      // Skip links containing untranslated arguments.
 915      if (strpos($item['href'], '%') !== FALSE) {
 916        $item['access'] = FALSE;
 917        return FALSE;
 918      }
 919      // menu_tree_check_access() may set this ahead of time for links to nodes.
 920      if (!isset($item['access'])) {
 921        if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
 922          // An error occurred loading an object.
 923          $item['access'] = FALSE;
 924          return FALSE;
 925        }
 926        _menu_check_access($item, $map);
 927      }
 928      // For performance, don't localize a link the user can't access.
 929      if ($item['access']) {
 930        _menu_item_localize($item, $map, TRUE);
 931      }
 932    }
 933  
 934    // Allow other customizations - e.g. adding a page-specific query string to the
 935    // options array. For performance reasons we only invoke this hook if the link
 936    // has the 'alter' flag set in the options array.
 937    if (!empty($item['options']['alter'])) {
 938      drupal_alter('translated_menu_link', $item, $map);
 939    }
 940  
 941    return $map;
 942  }
 943  
 944  /**
 945   * Gets a loaded object from a router item.
 946   *
 947   * menu_get_object() provides access to objects loaded by the current router
 948   * item. For example, on the page node/%node, the router loads the %node object,
 949   * and calling menu_get_object() will return that. Normally, it is necessary to
 950   * specify the type of object referenced, however node is the default.
 951   * The following example tests to see whether the node being displayed is of the
 952   * "story" content type:
 953   * @code
 954   * $node = menu_get_object();
 955   * $story = $node->type == 'story';
 956   * @endcode
 957   *
 958   * @param $type
 959   *   Type of the object. These appear in hook_menu definitions as %type. Core
 960   *   provides aggregator_feed, aggregator_category, contact, filter_format,
 961   *   forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
 962   *   relevant {$type}_load function for more on each. Defaults to node.
 963   * @param $position
 964   *   The position of the object in the path, where the first path segment is 0.
 965   *   For node/%node, the position of %node is 1, but for comment/reply/%node,
 966   *   it's 2. Defaults to 1.
 967   * @param $path
 968   *   See menu_get_item() for more on this. Defaults to the current path.
 969   */
 970  function menu_get_object($type = 'node', $position = 1, $path = NULL) {
 971    $router_item = menu_get_item($path);
 972    if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') {
 973      return $router_item['map'][$position];
 974    }
 975  }
 976  
 977  /**
 978   * Renders a menu tree based on the current path.
 979   *
 980   * The tree is expanded based on the current path and dynamic paths are also
 981   * changed according to the defined to_arg functions (for example the 'My
 982   * account' link is changed from user/% to a link with the current user's uid).
 983   *
 984   * @param $menu_name
 985   *   The name of the menu.
 986   *
 987   * @return
 988   *   A structured array representing the specified menu on the current page, to
 989   *   be rendered by drupal_render().
 990   */
 991  function menu_tree($menu_name) {
 992    $menu_output = &drupal_static(__FUNCTION__, array());
 993  
 994    if (!isset($menu_output[$menu_name])) {
 995      $tree = menu_tree_page_data($menu_name);
 996      $menu_output[$menu_name] = menu_tree_output($tree);
 997    }
 998    return $menu_output[$menu_name];
 999  }
1000  
1001  /**
1002   * Returns a rendered menu tree.
1003   *
1004   * The menu item's LI element is given one of the following classes:
1005   * - expanded: The menu item is showing its submenu.
1006   * - collapsed: The menu item has a submenu which is not shown.
1007   * - leaf: The menu item has no submenu.
1008   *
1009   * @param $tree
1010   *   A data structure representing the tree as returned from menu_tree_data.
1011   *
1012   * @return
1013   *   A structured array to be rendered by drupal_render().
1014   */
1015  function menu_tree_output($tree) {
1016    $build = array();
1017    $items = array();
1018  
1019    // Pull out just the menu links we are going to render so that we
1020    // get an accurate count for the first/last classes.
1021    foreach ($tree as $data) {
1022      if ($data['link']['access'] && !$data['link']['hidden']) {
1023        $items[] = $data;
1024      }
1025    }
1026  
1027    $router_item = menu_get_item();
1028    $num_items = count($items);
1029    foreach ($items as $i => $data) {
1030      $class = array();
1031      if ($i == 0) {
1032        $class[] = 'first';
1033      }
1034      if ($i == $num_items - 1) {
1035        $class[] = 'last';
1036      }
1037      // Set a class for the <li>-tag. Since $data['below'] may contain local
1038      // tasks, only set 'expanded' class if the link also has children within
1039      // the current menu.
1040      if ($data['link']['has_children'] && $data['below']) {
1041        $class[] = 'expanded';
1042      }
1043      elseif ($data['link']['has_children']) {
1044        $class[] = 'collapsed';
1045      }
1046      else {
1047        $class[] = 'leaf';
1048      }
1049      // Set a class if the link is in the active trail.
1050      if ($data['link']['in_active_trail']) {
1051        $class[] = 'active-trail';
1052        $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
1053      }
1054      // Normally, l() compares the href of every link with $_GET['q'] and sets
1055      // the active class accordingly. But local tasks do not appear in menu
1056      // trees, so if the current path is a local task, and this link is its
1057      // tab root, then we have to set the class manually.
1058      if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) {
1059        $data['link']['localized_options']['attributes']['class'][] = 'active';
1060      }
1061  
1062      // Allow menu-specific theme overrides.
1063      $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
1064      $element['#attributes']['class'] = $class;
1065      $element['#title'] = $data['link']['title'];
1066      $element['#href'] = $data['link']['href'];
1067      $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
1068      $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
1069      $element['#original_link'] = $data['link'];
1070      // Index using the link's unique mlid.
1071      $build[$data['link']['mlid']] = $element;
1072    }
1073    if ($build) {
1074      // Make sure drupal_render() does not re-order the links.
1075      $build['#sorted'] = TRUE;
1076      // Add the theme wrapper for outer markup.
1077      // Allow menu-specific theme overrides.
1078      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
1079    }
1080  
1081    return $build;
1082  }
1083  
1084  /**
1085   * Gets the data structure representing a named menu tree.
1086   *
1087   * Since this can be the full tree including hidden items, the data returned
1088   * may be used for generating an an admin interface or a select.
1089   *
1090   * @param $menu_name
1091   *   The named menu links to return
1092   * @param $link
1093   *   A fully loaded menu link, or NULL. If a link is supplied, only the
1094   *   path to root will be included in the returned tree - as if this link
1095   *   represented the current page in a visible menu.
1096   * @param $max_depth
1097   *   Optional maximum depth of links to retrieve. Typically useful if only one
1098   *   or two levels of a sub tree are needed in conjunction with a non-NULL
1099   *   $link, in which case $max_depth should be greater than $link['depth'].
1100   *
1101   * @return
1102   *   An tree of menu links in an array, in the order they should be rendered.
1103   */
1104  function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
1105    $tree = &drupal_static(__FUNCTION__, array());
1106  
1107    // Use $mlid as a flag for whether the data being loaded is for the whole tree.
1108    $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
1109    // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
1110    $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth;
1111  
1112    if (!isset($tree[$cid])) {
1113      // If the static variable doesn't have the data, check {cache_menu}.
1114      $cache = cache_get($cid, 'cache_menu');
1115      if ($cache && isset($cache->data)) {
1116        // If the cache entry exists, it contains the parameters for
1117        // menu_build_tree().
1118        $tree_parameters = $cache->data;
1119      }
1120      // If the tree data was not in the cache, build $tree_parameters.
1121      if (!isset($tree_parameters)) {
1122        $tree_parameters = array(
1123          'min_depth' => 1,
1124          'max_depth' => $max_depth,
1125        );
1126        if ($mlid) {
1127          // The tree is for a single item, so we need to match the values in its
1128          // p columns and 0 (the top level) with the plid values of other links.
1129          $parents = array(0);
1130          for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
1131            if (!empty($link["p$i"])) {
1132              $parents[] = $link["p$i"];
1133            }
1134          }
1135          $tree_parameters['expanded'] = $parents;
1136          $tree_parameters['active_trail'] = $parents;
1137          $tree_parameters['active_trail'][] = $mlid;
1138        }
1139  
1140        // Cache the tree building parameters using the page-specific cid.
1141        cache_set($cid, $tree_parameters, 'cache_menu');
1142      }
1143  
1144      // Build the tree using the parameters; the resulting tree will be cached
1145      // by _menu_build_tree()).
1146      $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
1147    }
1148  
1149    return $tree[$cid];
1150  }
1151  
1152  /**
1153   * Sets the path for determining the active trail of the specified menu tree.
1154   *
1155   * This path will also affect the breadcrumbs under some circumstances.
1156   * Breadcrumbs are built using the preferred link returned by
1157   * menu_link_get_preferred(). If the preferred link is inside one of the menus
1158   * specified in calls to menu_tree_set_path(), the preferred link will be
1159   * overridden by the corresponding path returned by menu_tree_get_path().
1160   *
1161   * Setting this path does not affect the main content; for that use
1162   * menu_set_active_item() instead.
1163   *
1164   * @param $menu_name
1165   *   The name of the affected menu tree.
1166   * @param $path
1167   *   The path to use when finding the active trail.
1168   */
1169  function menu_tree_set_path($menu_name, $path = NULL) {
1170    $paths = &drupal_static(__FUNCTION__);
1171    if (isset($path)) {
1172      $paths[$menu_name] = $path;
1173    }
1174    return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
1175  }
1176  
1177  /**
1178   * Gets the path for determining the active trail of the specified menu tree.
1179   *
1180   * @param $menu_name
1181   *   The menu name of the requested tree.
1182   *
1183   * @return
1184   *   A string containing the path. If no path has been specified with
1185   *   menu_tree_set_path(), NULL is returned.
1186   */
1187  function menu_tree_get_path($menu_name) {
1188    return menu_tree_set_path($menu_name);
1189  }
1190  
1191  /**
1192   * Gets the data structure for a named menu tree, based on the current page.
1193   *
1194   * The tree order is maintained by storing each parent in an individual
1195   * field, see http://drupal.org/node/141866 for more.
1196   *
1197   * @param $menu_name
1198   *   The named menu links to return.
1199   * @param $max_depth
1200   *   (optional) The maximum depth of links to retrieve.
1201   * @param $only_active_trail
1202   *   (optional) Whether to only return the links in the active trail (TRUE)
1203   *   instead of all links on every level of the menu link tree (FALSE). Defaults
1204   *   to FALSE. Internally used for breadcrumbs only.
1205   *
1206   * @return
1207   *   An array of menu links, in the order they should be rendered. The array
1208   *   is a list of associative arrays -- these have two keys, link and below.
1209   *   link is a menu item, ready for theming as a link. Below represents the
1210   *   submenu below the link if there is one, and it is a subtree that has the
1211   *   same structure described for the top-level array.
1212   */
1213  function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
1214    $tree = &drupal_static(__FUNCTION__, array());
1215  
1216    // Check if the active trail has been overridden for this menu tree.
1217    $active_path = menu_tree_get_path($menu_name);
1218    // Load the menu item corresponding to the current page.
1219    if ($item = menu_get_item($active_path)) {
1220      if (isset($max_depth)) {
1221        $max_depth = min($max_depth, MENU_MAX_DEPTH);
1222      }
1223      // Generate a cache ID (cid) specific for this page.
1224      $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth;
1225      // If we are asked for the active trail only, and $menu_name has not been
1226      // built and cached for this page yet, then this likely means that it
1227      // won't be built anymore, as this function is invoked from
1228      // template_process_page(). So in order to not build a giant menu tree
1229      // that needs to be checked for access on all levels, we simply check
1230      // whether we have the menu already in cache, or otherwise, build a minimum
1231      // tree containing the breadcrumb/active trail only.
1232      // @see menu_set_active_trail()
1233      if (!isset($tree[$cid]) && $only_active_trail) {
1234        $cid .= ':trail';
1235      }
1236  
1237      if (!isset($tree[$cid])) {
1238        // If the static variable doesn't have the data, check {cache_menu}.
1239        $cache = cache_get($cid, 'cache_menu');
1240        if ($cache && isset($cache->data)) {
1241          // If the cache entry exists, it contains the parameters for
1242          // menu_build_tree().
1243          $tree_parameters = $cache->data;
1244        }
1245        // If the tree data was not in the cache, build $tree_parameters.
1246        if (!isset($tree_parameters)) {
1247          $tree_parameters = array(
1248            'min_depth' => 1,
1249            'max_depth' => $max_depth,
1250          );
1251          // Parent mlids; used both as key and value to ensure uniqueness.
1252          // We always want all the top-level links with plid == 0.
1253          $active_trail = array(0 => 0);
1254  
1255          // If the item for the current page is accessible, build the tree
1256          // parameters accordingly.
1257          if ($item['access']) {
1258            // Find a menu link corresponding to the current path. If $active_path
1259            // is NULL, let menu_link_get_preferred() determine the path.
1260            if ($active_link = menu_link_get_preferred($active_path, $menu_name)) {
1261              // The active link may only be taken into account to build the
1262              // active trail, if it resides in the requested menu. Otherwise,
1263              // we'd needlessly re-run _menu_build_tree() queries for every menu
1264              // on every page.
1265              if ($active_link['menu_name'] == $menu_name) {
1266                // Use all the coordinates, except the last one because there
1267                // can be no child beyond the last column.
1268                for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
1269                  if ($active_link['p' . $i]) {
1270                    $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
1271                  }
1272                }
1273                // If we are asked to build links for the active trail only, skip
1274                // the entire 'expanded' handling.
1275                if ($only_active_trail) {
1276                  $tree_parameters['only_active_trail'] = TRUE;
1277                }
1278              }
1279            }
1280            $parents = $active_trail;
1281  
1282            $expanded = variable_get('menu_expanded', array());
1283            // Check whether the current menu has any links set to be expanded.
1284            if (!$only_active_trail && in_array($menu_name, $expanded)) {
1285              // Collect all the links set to be expanded, and then add all of
1286              // their children to the list as well.
1287              do {
1288                $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
1289                  ->fields('menu_links', array('mlid'))
1290                  ->condition('menu_name', $menu_name)
1291                  ->condition('expanded', 1)
1292                  ->condition('has_children', 1)
1293                  ->condition('plid', $parents, 'IN')
1294                  ->condition('mlid', $parents, 'NOT IN')
1295                  ->execute();
1296                $num_rows = FALSE;
1297                foreach ($result as $item) {
1298                  $parents[$item['mlid']] = $item['mlid'];
1299                  $num_rows = TRUE;
1300                }
1301              } while ($num_rows);
1302            }
1303            $tree_parameters['expanded'] = $parents;
1304            $tree_parameters['active_trail'] = $active_trail;
1305          }
1306          // If access is denied, we only show top-level links in menus.
1307          else {
1308            $tree_parameters['expanded'] = $active_trail;
1309            $tree_parameters['active_trail'] = $active_trail;
1310          }
1311          // Cache the tree building parameters using the page-specific cid.
1312          cache_set($cid, $tree_parameters, 'cache_menu');
1313        }
1314  
1315        // Build the tree using the parameters; the resulting tree will be cached
1316        // by _menu_build_tree().
1317        $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
1318      }
1319      return $tree[$cid];
1320    }
1321  
1322    return array();
1323  }
1324  
1325  /**
1326   * Builds a menu tree, translates links, and checks access.
1327   *
1328   * @param $menu_name
1329   *   The name of the menu.
1330   * @param $parameters
1331   *   (optional) An associative array of build parameters. Possible keys:
1332   *   - expanded: An array of parent link ids to return only menu links that are
1333   *     children of one of the plids in this list. If empty, the whole menu tree
1334   *     is built, unless 'only_active_trail' is TRUE.
1335   *   - active_trail: An array of mlids, representing the coordinates of the
1336   *     currently active menu link.
1337   *   - only_active_trail: Whether to only return links that are in the active
1338   *     trail. This option is ignored, if 'expanded' is non-empty. Internally
1339   *     used for breadcrumbs.
1340   *   - min_depth: The minimum depth of menu links in the resulting tree.
1341   *     Defaults to 1, which is the default to build a whole tree for a menu
1342   *     (excluding menu container itself).
1343   *   - max_depth: The maximum depth of menu links in the resulting tree.
1344   *   - conditions: An associative array of custom database select query
1345   *     condition key/value pairs; see _menu_build_tree() for the actual query.
1346   *
1347   * @return
1348   *   A fully built menu tree.
1349   */
1350  function menu_build_tree($menu_name, array $parameters = array()) {
1351    // Build the menu tree.
1352    $data = _menu_build_tree($menu_name, $parameters);
1353    // Check access for the current user to each item in the tree.
1354    menu_tree_check_access($data['tree'], $data['node_links']);
1355    return $data['tree'];
1356  }
1357  
1358  /**
1359   * Builds a menu tree.
1360   *
1361   * This function may be used build the data for a menu tree only, for example
1362   * to further massage the data manually before further processing happens.
1363   * menu_tree_check_access() needs to be invoked afterwards.
1364   *
1365   * @see menu_build_tree()
1366   */
1367  function _menu_build_tree($menu_name, array $parameters = array()) {
1368    // Static cache of already built menu trees.
1369    $trees = &drupal_static(__FUNCTION__, array());
1370  
1371    // Build the cache id; sort parents to prevent duplicate storage and remove
1372    // default parameter values.
1373    if (isset($parameters['expanded'])) {
1374      sort($parameters['expanded']);
1375    }
1376    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters));
1377  
1378    // If we do not have this tree in the static cache, check {cache_menu}.
1379    if (!isset($trees[$tree_cid])) {
1380      $cache = cache_get($tree_cid, 'cache_menu');
1381      if ($cache && isset($cache->data)) {
1382        $trees[$tree_cid] = $cache->data;
1383      }
1384    }
1385  
1386    if (!isset($trees[$tree_cid])) {
1387      // Select the links from the table, and recursively build the tree. We
1388      // LEFT JOIN since there is no match in {menu_router} for an external
1389      // link.
1390      $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
1391      $query->addTag('translatable');
1392      $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
1393      $query->fields('ml');
1394      $query->fields('m', array(
1395        'load_functions',
1396        'to_arg_functions',
1397        'access_callback',
1398        'access_arguments',
1399        'page_callback',
1400        'page_arguments',
1401        'delivery_callback',
1402        'tab_parent',
1403        'tab_root',
1404        'title',
1405        'title_callback',
1406        'title_arguments',
1407        'theme_callback',
1408        'theme_arguments',
1409        'type',
1410        'description',
1411      ));
1412      for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
1413        $query->orderBy('p' . $i, 'ASC');
1414      }
1415      $query->condition('ml.menu_name', $menu_name);
1416      if (!empty($parameters['expanded'])) {
1417        $query->condition('ml.plid', $parameters['expanded'], 'IN');
1418      }
1419      elseif (!empty($parameters['only_active_trail'])) {
1420        $query->condition('ml.mlid', $parameters['active_trail'], 'IN');
1421      }
1422      $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
1423      if ($min_depth != 1) {
1424        $query->condition('ml.depth', $min_depth, '>=');
1425      }
1426      if (isset($parameters['max_depth'])) {
1427        $query->condition('ml.depth', $parameters['max_depth'], '<=');
1428      }
1429      // Add custom query conditions, if any were passed.
1430      if (isset($parameters['conditions'])) {
1431        foreach ($parameters['conditions'] as $column => $value) {
1432          $query->condition($column, $value);
1433        }
1434      }
1435  
1436      // Build an ordered array of links using the query result object.
1437      $links = array();
1438      foreach ($query->execute() as $item) {
1439        $links[] = $item;
1440      }
1441      $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
1442      $data['tree'] = menu_tree_data($links, $active_trail, $min_depth);
1443      $data['node_links'] = array();
1444      menu_tree_collect_node_links($data['tree'], $data['node_links']);
1445  
1446      // Cache the data, if it is not already in the cache.
1447      cache_set($tree_cid, $data, 'cache_menu');
1448      $trees[$tree_cid] = $data;
1449    }
1450  
1451    return $trees[$tree_cid];
1452  }
1453  
1454  /**
1455   * Collects node links from a given menu tree recursively.
1456   *
1457   * @param $tree
1458   *   The menu tree you wish to collect node links from.
1459   * @param $node_links
1460   *   An array in which to store the collected node links.
1461   */
1462  function menu_tree_collect_node_links(&$tree, &$node_links) {
1463    foreach ($tree as $key => $v) {
1464      if ($tree[$key]['link']['router_path'] == 'node/%') {
1465        $nid = substr($tree[$key]['link']['link_path'], 5);
1466        if (is_numeric($nid)) {
1467          $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
1468          $tree[$key]['link']['access'] = FALSE;
1469        }
1470      }
1471      if ($tree[$key]['below']) {
1472        menu_tree_collect_node_links($tree[$key]['below'], $node_links);
1473      }
1474    }
1475  }
1476  
1477  /**
1478   * Checks access and performs dynamic operations for each link in the tree.
1479   *
1480   * @param $tree
1481   *   The menu tree you wish to operate on.
1482   * @param $node_links
1483   *   A collection of node link references generated from $tree by
1484   *   menu_tree_collect_node_links().
1485   */
1486  function menu_tree_check_access(&$tree, $node_links = array()) {
1487    if ($node_links) {
1488      $nids = array_keys($node_links);
1489      $select = db_select('node', 'n');
1490      $select->addField('n', 'nid');
1491      $select->condition('n.status', 1);
1492      $select->condition('n.nid', $nids, 'IN');
1493      $select->addTag('node_access');
1494      $nids = $select->execute()->fetchCol();
1495      foreach ($nids as $nid) {
1496        foreach ($node_links[$nid] as $mlid => $link) {
1497          $node_links[$nid][$mlid]['access'] = TRUE;
1498        }
1499      }
1500    }
1501    _menu_tree_check_access($tree);
1502  }
1503  
1504  /**
1505   * Sorts the menu tree and recursively checks access for each item.
1506   */
1507  function _menu_tree_check_access(&$tree) {
1508    $new_tree = array();
1509    foreach ($tree as $key => $v) {
1510      $item = &$tree[$key]['link'];
1511      _menu_link_translate($item);
1512      if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
1513        if ($tree[$key]['below']) {
1514          _menu_tree_check_access($tree[$key]['below']);
1515        }
1516        // The weights are made a uniform 5 digits by adding 50000 as an offset.
1517        // After _menu_link_translate(), $item['title'] has the localized link title.
1518        // Adding the mlid to the end of the index insures that it is unique.
1519        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
1520      }
1521    }
1522    // Sort siblings in the tree based on the weights and localized titles.
1523    ksort($new_tree);
1524    $tree = $new_tree;
1525  }
1526  
1527  /**
1528   * Sorts and returns the built data representing a menu tree.
1529   *
1530   * @param $links
1531   *   A flat array of menu links that are part of the menu. Each array element
1532   *   is an associative array of information about the menu link, containing the
1533   *   fields from the {menu_links} table, and optionally additional information
1534   *   from the {menu_router} table, if the menu item appears in both tables.
1535   *   This array must be ordered depth-first. See _menu_build_tree() for a sample
1536   *   query.
1537   * @param $parents
1538   *   An array of the menu link ID values that are in the path from the current
1539   *   page to the root of the menu tree.
1540   * @param $depth
1541   *   The minimum depth to include in the returned menu tree.
1542   *
1543   * @return
1544   *   An array of menu links in the form of a tree. Each item in the tree is an
1545   *   associative array containing:
1546   *   - link: The menu link item from $links, with additional element
1547   *     'in_active_trail' (TRUE if the link ID was in $parents).
1548   *   - below: An array containing the sub-tree of this item, where each element
1549   *     is a tree item array with 'link' and 'below' elements. This array will be
1550   *     empty if the menu item has no items in its sub-tree having a depth
1551   *     greater than or equal to $depth.
1552   */
1553  function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
1554    // Reverse the array so we can use the more efficient array_pop() function.
1555    $links = array_reverse($links);
1556    return _menu_tree_data($links, $parents, $depth);
1557  }
1558  
1559  /**
1560   * Builds the data representing a menu tree.
1561   *
1562   * The function is a bit complex because the rendering of a link depends on
1563   * the next menu link.
1564   */
1565  function _menu_tree_data(&$links, $parents, $depth) {
1566    $tree = array();
1567    while ($item = array_pop($links)) {
1568      // We need to determine if we're on the path to root so we can later build
1569      // the correct active trail and breadcrumb.
1570      $item['in_active_trail'] = in_array($item['mlid'], $parents);
1571      // Add the current link to the tree.
1572      $tree[$item['mlid']] = array(
1573        'link' => $item,
1574        'below' => array(),
1575      );
1576      // Look ahead to the next link, but leave it on the array so it's available
1577      // to other recursive function calls if we return or build a sub-tree.
1578      $next = end($links);
1579      // Check whether the next link is the first in a new sub-tree.
1580      if ($next && $next['depth'] > $depth) {
1581        // Recursively call _menu_tree_data to build the sub-tree.
1582        $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
1583        // Fetch next link after filling the sub-tree.
1584        $next = end($links);
1585      }
1586      // Determine if we should exit the loop and return.
1587      if (!$next || $next['depth'] < $depth) {
1588        break;
1589      }
1590    }
1591    return $tree;
1592  }
1593  
1594  /**
1595   * Implements template_preprocess_HOOK() for theme_menu_tree().
1596   */
1597  function template_preprocess_menu_tree(&$variables) {
1598    $variables['tree'] = $variables['tree']['#children'];
1599  }
1600  
1601  /**
1602   * Returns HTML for a wrapper for a menu sub-tree.
1603   *
1604   * @param $variables
1605   *   An associative array containing:
1606   *   - tree: An HTML string containing the tree's items.
1607   *
1608   * @see template_preprocess_menu_tree()
1609   * @ingroup themeable
1610   */
1611  function theme_menu_tree($variables) {
1612    return '<ul class="menu">' . $variables['tree'] . '</ul>';
1613  }
1614  
1615  /**
1616   * Returns HTML for a menu link and submenu.
1617   *
1618   * @param $variables
1619   *   An associative array containing:
1620   *   - element: Structured array data for a menu link.
1621   *
1622   * @ingroup themeable
1623   */
1624  function theme_menu_link(array $variables) {
1625    $element = $variables['element'];
1626    $sub_menu = '';
1627  
1628    if ($element['#below']) {
1629      $sub_menu = drupal_render($element['#below']);
1630    }
1631    $output = l($element['#title'], $element['#href'], $element['#localized_options']);
1632    return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
1633  }
1634  
1635  /**
1636   * Returns HTML for a single local task link.
1637   *
1638   * @param $variables
1639   *   An associative array containing:
1640   *   - element: A render element containing:
1641   *     - #link: A menu link array with 'title', 'href', and 'localized_options'
1642   *       keys.
1643   *     - #active: A boolean indicating whether the local task is active.
1644   *
1645   * @ingroup themeable
1646   */
1647  function theme_menu_local_task($variables) {
1648    $link = $variables['element']['#link'];
1649    $link_text = $link['title'];
1650  
1651    if (!empty($variables['element']['#active'])) {
1652      // Add text to indicate active tab for non-visual users.
1653      $active = '<span class="element-invisible">' . t('(active tab)') . '</span>';
1654  
1655      // If the link does not contain HTML already, check_plain() it now.
1656      // After we set 'html'=TRUE the link will not be sanitized by l().
1657      if (empty($link['localized_options']['html'])) {
1658        $link['title'] = check_plain($link['title']);
1659      }
1660      $link['localized_options']['html'] = TRUE;
1661      $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
1662    }
1663  
1664    return '<li' . (!empty($variables['element']['#active']) ? ' class="active"' : '') . '>' . l($link_text, $link['href'], $link['localized_options']) . "</li>\n";
1665  }
1666  
1667  /**
1668   * Returns HTML for a single local action link.
1669   *
1670   * @param $variables
1671   *   An associative array containing:
1672   *   - element: A render element containing:
1673   *     - #link: A menu link array with 'title', 'href', and 'localized_options'
1674   *       keys.
1675   *
1676   * @ingroup themeable
1677   */
1678  function theme_menu_local_action($variables) {
1679    $link = $variables['element']['#link'];
1680  
1681    $output = '<li>';
1682    if (isset($link['href'])) {
1683      $output .= l($link['title'], $link['href'], isset($link['localized_options']) ? $link['localized_options'] : array());
1684    }
1685    elseif (!empty($link['localized_options']['html'])) {
1686      $output .= $link['title'];
1687    }
1688    else {
1689      $output .= check_plain($link['title']);
1690    }
1691    $output .= "</li>\n";
1692  
1693    return $output;
1694  }
1695  
1696  /**
1697   * Generates elements for the $arg array in the help hook.
1698   */
1699  function drupal_help_arg($arg = array()) {
1700    // Note - the number of empty elements should be > MENU_MAX_PARTS.
1701    return $arg + array('', '', '', '', '', '', '', '', '', '', '', '');
1702  }
1703  
1704  /**
1705   * Returns the help associated with the active menu item.
1706   */
1707  function menu_get_active_help() {
1708    $output = '';
1709    $router_path = menu_tab_root_path();
1710    // We will always have a path unless we are on a 403 or 404.
1711    if (!$router_path) {
1712      return '';
1713    }
1714  
1715    $arg = drupal_help_arg(arg(NULL));
1716  
1717    foreach (module_implements('help') as $module) {
1718      $function = $module . '_help';
1719      // Lookup help for this path.
1720      if ($help = $function($router_path, $arg)) {
1721        $output .= $help . "\n";
1722      }
1723    }
1724    return $output;
1725  }
1726  
1727  /**
1728   * Gets the custom theme for the current page, if there is one.
1729   *
1730   * @param $initialize
1731   *   This parameter should only be used internally; it is set to TRUE in order
1732   *   to force the custom theme to be initialized for the current page request.
1733   *
1734   * @return
1735   *   The machine-readable name of the custom theme, if there is one.
1736   *
1737   * @see menu_set_custom_theme()
1738   */
1739  function menu_get_custom_theme($initialize = FALSE) {
1740    $custom_theme = &drupal_static(__FUNCTION__);
1741    // Skip this if the site is offline or being installed or updated, since the
1742    // menu system may not be correctly initialized then.
1743    if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) {
1744      // First allow modules to dynamically set a custom theme for the current
1745      // page. Since we can only have one, the last module to return a valid
1746      // theme takes precedence.
1747      $custom_themes = array_filter(module_invoke_all('custom_theme'), 'drupal_theme_access');
1748      if (!empty($custom_themes)) {
1749        $custom_theme = array_pop($custom_themes);
1750      }
1751      // If there is a theme callback function for the current page, execute it.
1752      // If this returns a valid theme, it will override any theme that was set
1753      // by a hook_custom_theme() implementation above.
1754      $router_item = menu_get_item();
1755      if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
1756        $theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
1757        if (drupal_theme_access($theme_name)) {
1758          $custom_theme = $theme_name;
1759        }
1760      }
1761    }
1762    return $custom_theme;
1763  }
1764  
1765  /**
1766   * Sets a custom theme for the current page, if there is one.
1767   */
1768  function menu_set_custom_theme() {
1769    menu_get_custom_theme(TRUE);
1770  }
1771  
1772  /**
1773   * Build a list of named menus.
1774   */
1775  function menu_get_names() {
1776    $names = &drupal_static(__FUNCTION__);
1777  
1778    if (empty($names)) {
1779      $names = db_select('menu_links')
1780        ->distinct()
1781        ->fields('menu_links', array('menu_name'))
1782        ->orderBy('menu_name')
1783        ->execute()->fetchCol();
1784    }
1785    return $names;
1786  }
1787  
1788  /**
1789   * Returns an array containing the names of system-defined (default) menus.
1790   */
1791  function menu_list_system_menus() {
1792    return array(
1793      'navigation' => 'Navigation',
1794      'management' => 'Management',
1795      'user-menu' => 'User menu',
1796      'main-menu' => 'Main menu',
1797    );
1798  }
1799  
1800  /**
1801   * Returns an array of links to be rendered as the Main menu.
1802   */
1803  function menu_main_menu() {
1804    return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu'));
1805  }
1806  
1807  /**
1808   * Returns an array of links to be rendered as the Secondary links.
1809   */
1810  function menu_secondary_menu() {
1811  
1812    // If the secondary menu source is set as the primary menu, we display the
1813    // second level of the primary menu.
1814    if (variable_get('menu_secondary_links_source', 'user-menu') == variable_get('menu_main_links_source', 'main-menu')) {
1815      return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu'), 1);
1816    }
1817    else {
1818      return menu_navigation_links(variable_get('menu_secondary_links_source', 'user-menu'), 0);
1819    }
1820  }
1821  
1822  /**
1823   * Returns an array of links for a navigation menu.
1824   *
1825   * @param $menu_name
1826   *   The name of the menu.
1827   * @param $level
1828   *   Optional, the depth of the menu to be returned.
1829   *
1830   * @return
1831   *   An array of links of the specified menu and level.
1832   */
1833  function menu_navigation_links($menu_name, $level = 0) {
1834    // Don't even bother querying the menu table if no menu is specified.
1835    if (empty($menu_name)) {
1836      return array();
1837    }
1838  
1839    // Get the menu hierarchy for the current page.
1840    $tree = menu_tree_page_data($menu_name, $level + 1);
1841  
1842    // Go down the active trail until the right level is reached.
1843    while ($level-- > 0 && $tree) {
1844      // Loop through the current level's items until we find one that is in trail.
1845      while ($item = array_shift($tree)) {
1846        if ($item['link']['in_active_trail']) {
1847          // If the item is in the active trail, we continue in the subtree.
1848          $tree = empty($item['below']) ? array() : $item['below'];
1849          break;
1850        }
1851      }
1852    }
1853  
1854    // Create a single level of links.
1855    $router_item = menu_get_item();
1856    $links = array();
1857    foreach ($tree as $item) {
1858      if (!$item['link']['hidden']) {
1859        $class = '';
1860        $l = $item['link']['localized_options'];
1861        $l['href'] = $item['link']['href'];
1862        $l['title'] = $item['link']['title'];
1863        if ($item['link']['in_active_trail']) {
1864          $class = ' active-trail';
1865          $l['attributes']['class'][] = 'active-trail';
1866        }
1867        // Normally, l() compares the href of every link with $_GET['q'] and sets
1868        // the active class accordingly. But local tasks do not appear in menu
1869        // trees, so if the current path is a local task, and this link is its
1870        // tab root, then we have to set the class manually.
1871        if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) {
1872          $l['attributes']['class'][] = 'active';
1873        }
1874        // Keyed with the unique mlid to generate classes in theme_links().
1875        $links['menu-' . $item['link']['mlid'] . $class] = $l;
1876      }
1877    }
1878    return $links;
1879  }
1880  
1881  /**
1882   * Collects the local tasks (tabs), action links, and the root path.
1883   *
1884   * @param $level
1885   *   The level of tasks you ask for. Primary tasks are 0, secondary are 1.
1886   *
1887   * @return
1888   *   An array containing
1889   *   - tabs: Local tasks for the requested level:
1890   *     - count: The number of local tasks.
1891   *     - output: The themed output of local tasks.
1892   *   - actions: Action links for the requested level:
1893   *     - count: The number of action links.
1894   *     - output: The themed output of action links.
1895   *   - root_path: The router path for the current page. If the current page is
1896   *     a default local task, then this corresponds to the parent tab.
1897   */
1898  function menu_local_tasks($level = 0) {
1899    $data = &drupal_static(__FUNCTION__);
1900    $root_path = &drupal_static(__FUNCTION__ . ':root_path', '');
1901    $empty = array(
1902      'tabs' => array('count' => 0, 'output' => array()),
1903      'actions' => array('count' => 0, 'output' => array()),
1904      'root_path' => &$root_path,
1905    );
1906  
1907    if (!isset($data)) {
1908      $data = array();
1909      // Set defaults in case there are no actions or tabs.
1910      $actions = $empty['actions'];
1911      $tabs = array();
1912  
1913      $router_item = menu_get_item();
1914  
1915      // If this router item points to its parent, start from the parents to
1916      // compute tabs and actions.
1917      if ($router_item && ($router_item['type'] & MENU_LINKS_TO_PARENT)) {
1918        $router_item = menu_get_item($router_item['tab_parent_href']);
1919      }
1920  
1921      // If we failed to fetch a router item or the current user doesn't have
1922      // access to it, don't bother computing the tabs.
1923      if (!$router_item || !$router_item['access']) {
1924        return $empty;
1925      }
1926  
1927      // Get all tabs (also known as local tasks) and the root page.
1928      $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC))
1929        ->fields('menu_router')
1930        ->condition('tab_root', $router_item['tab_root'])
1931        ->condition('context', MENU_CONTEXT_INLINE, '<>')
1932        ->orderBy('weight')
1933        ->orderBy('title')
1934        ->execute();
1935      $map = $router_item['original_map'];
1936      $children = array();
1937      $tasks = array();
1938      $root_path = $router_item['path'];
1939  
1940      foreach ($result as $item) {
1941        _menu_translate($item, $map, TRUE);
1942        if ($item['tab_parent']) {
1943          // All tabs, but not the root page.
1944          $children[$item['tab_parent']][$item['path']] = $item;
1945        }
1946        // Store the translated item for later use.
1947        $tasks[$item['path']] = $item;
1948      }
1949  
1950      // Find all tabs below the current path.
1951      $path = $router_item['path'];
1952      // Tab parenting may skip levels, so the number of parts in the path may not
1953      // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
1954      $depth = 1001;
1955      $actions['count'] = 0;
1956      $actions['output'] = array();
1957      while (isset($children[$path])) {
1958        $tabs_current = array();
1959        $actions_current = array();
1960        $next_path = '';
1961        $tab_count = 0;
1962        $action_count = 0;
1963        foreach ($children[$path] as $item) {
1964          // Local tasks can be normal items too, so bitmask with
1965          // MENU_IS_LOCAL_TASK before checking.
1966          if (!($item['type'] & MENU_IS_LOCAL_TASK)) {
1967            // This item is not a tab, skip it.
1968            continue;
1969          }
1970          if ($item['access']) {
1971            $link = $item;
1972            // The default task is always active. As tabs can be normal items
1973            // too, so bitmask with MENU_LINKS_TO_PARENT before checking.
1974            if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
1975              // Find the first parent which is not a default local task or action.
1976              for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']);
1977              // Use the path of the parent instead.
1978              $link['href'] = $tasks[$p]['href'];
1979              // Mark the link as active, if the current path happens to be the
1980              // path of the default local task itself (i.e., instead of its
1981              // tab_parent_href or tab_root_href). Normally, links for default
1982              // local tasks link to their parent, but the path of default local
1983              // tasks can still be accessed directly, in which case this link
1984              // would not be marked as active, since l() only compares the href
1985              // with $_GET['q'].
1986              if ($link['href'] != $_GET['q']) {
1987                $link['localized_options']['attributes']['class'][] = 'active';
1988              }
1989              $tabs_current[] = array(
1990                '#theme' => 'menu_local_task',
1991                '#link' => $link,
1992                '#active' => TRUE,
1993              );
1994              $next_path = $item['path'];
1995              $tab_count++;
1996            }
1997            else {
1998              // Actions can be normal items too, so bitmask with
1999              // MENU_IS_LOCAL_ACTION before checking.
2000              if (($item['type'] & MENU_IS_LOCAL_ACTION) == MENU_IS_LOCAL_ACTION) {
2001                // The item is an action, display it as such.
2002                $actions_current[] = array(
2003                  '#theme' => 'menu_local_action',
2004                  '#link' => $link,
2005                );
2006                $action_count++;
2007              }
2008              else {
2009                // Otherwise, it's a normal tab.
2010                $tabs_current[] = array(
2011                  '#theme' => 'menu_local_task',
2012                  '#link' => $link,
2013                );
2014                $tab_count++;
2015              }
2016            }
2017          }
2018        }
2019        $path = $next_path;
2020        $tabs[$depth]['count'] = $tab_count;
2021        $tabs[$depth]['output'] = $tabs_current;
2022        $actions['count'] += $action_count;
2023        $actions['output'] = array_merge($actions['output'], $actions_current);
2024        $depth++;
2025      }
2026      $data['actions'] = $actions;
2027      // Find all tabs at the same level or above the current one.
2028      $parent = $router_item['tab_parent'];
2029      $path = $router_item['path'];
2030      $current = $router_item;
2031      $depth = 1000;
2032      while (isset($children[$parent])) {
2033        $tabs_current = array();
2034        $next_path = '';
2035        $next_parent = '';
2036        $count = 0;
2037        foreach ($children[$parent] as $item) {
2038          // Skip local actions.
2039          if ($item['type'] & MENU_IS_LOCAL_ACTION) {
2040            continue;
2041          }
2042          if ($item['access']) {
2043            $count++;
2044            $link = $item;
2045            // Local tasks can be normal items too, so bitmask with
2046            // MENU_LINKS_TO_PARENT before checking.
2047            if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
2048              // Find the first parent which is not a default local task.
2049              for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']);
2050              // Use the path of the parent instead.
2051              $link['href'] = $tasks[$p]['href'];
2052              if ($item['path'] == $router_item['path']) {
2053                $root_path = $tasks[$p]['path'];
2054              }
2055            }
2056            // We check for the active tab.
2057            if ($item['path'] == $path) {
2058              // Mark the link as active, if the current path is a (second-level)
2059              // local task of a default local task. Since this default local task
2060              // links to its parent, l() will not mark it as active, as it only
2061              // compares the link's href to $_GET['q'].
2062              if ($link['href'] != $_GET['q']) {
2063                $link['localized_options']['attributes']['class'][] = 'active';
2064              }
2065              $tabs_current[] = array(
2066                '#theme' => 'menu_local_task',
2067                '#link' => $link,
2068                '#active' => TRUE,
2069              );
2070              $next_path = $item['tab_parent'];
2071              if (isset($tasks[$next_path])) {
2072                $next_parent = $tasks[$next_path]['tab_parent'];
2073              }
2074            }
2075            else {
2076              $tabs_current[] = array(
2077                '#theme' => 'menu_local_task',
2078                '#link' => $link,
2079              );
2080            }
2081          }
2082        }
2083        $path = $next_path;
2084        $parent = $next_parent;
2085        $tabs[$depth]['count'] = $count;
2086        $tabs[$depth]['output'] = $tabs_current;
2087        $depth--;
2088      }
2089      // Sort by depth.
2090      ksort($tabs);
2091      // Remove the depth, we are interested only in their relative placement.
2092      $tabs = array_values($tabs);
2093      $data['tabs'] = $tabs;
2094  
2095      // Allow modules to alter local tasks or dynamically append further tasks.
2096      drupal_alter('menu_local_tasks', $data, $router_item, $root_path);
2097    }
2098  
2099    if (isset($data['tabs'][$level])) {
2100      return array(
2101        'tabs' => $data['tabs'][$level],
2102        'actions' => $data['actions'],
2103        'root_path' => $root_path,
2104      );
2105    }
2106    // @todo If there are no tabs, then there still can be actions; for example,
2107    //   when added via hook_menu_local_tasks_alter().
2108    elseif (!empty($data['actions']['output'])) {
2109      return array('actions' => $data['actions']) + $empty;
2110    }
2111    return $empty;
2112  }
2113  
2114  /**
2115   * Retrieves contextual links for a path based on registered local tasks.
2116   *
2117   * This leverages the menu system to retrieve the first layer of registered
2118   * local tasks for a given system path. All local tasks of the tab type
2119   * MENU_CONTEXT_INLINE are taken into account.
2120   *
2121   * For example, when considering the following registered local tasks:
2122   * - node/%node/view (default local task) with no 'context' defined
2123   * - node/%node/edit with context: MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE
2124   * - node/%node/revisions with context: MENU_CONTEXT_PAGE
2125   * - node/%node/report-as-spam with context: MENU_CONTEXT_INLINE
2126   *
2127   * If the path "node/123" is passed to this function, then it will return the
2128   * links for 'edit' and 'report-as-spam'.
2129   *
2130   * @param $module
2131   *   The name of the implementing module. This is used to prefix the key for
2132   *   each contextual link, which is transformed into a CSS class during
2133   *   rendering by theme_links(). For example, if $module is 'block' and the
2134   *   retrieved local task path argument is 'edit', then the resulting CSS class
2135   *   will be 'block-edit'.
2136   * @param $parent_path
2137   *   The static menu router path of the object to retrieve local tasks for, for
2138   *   example 'node' or 'admin/structure/block/manage'.
2139   * @param $args
2140   *   A list of dynamic path arguments to append to $parent_path to form the
2141   *   fully-qualified menu router path, for example array(123) for a certain
2142   *   node or array('system', 'navigation') for a certain block.
2143   *
2144   * @return
2145   *   A list of menu router items that are local tasks for the passed-in path.
2146   *
2147   * @see contextual_links_preprocess()
2148   * @see hook_menu()
2149   */
2150  function menu_contextual_links($module, $parent_path, $args) {
2151    static $path_empty = array();
2152  
2153    $links = array();
2154    // Performance: In case a previous invocation for the same parent path did not
2155    // return any links, we immediately return here.
2156    if (isset($path_empty[$parent_path]) && strpos($parent_path, '%') !== FALSE) {
2157      return $links;
2158    }
2159    // Construct the item-specific parent path.
2160    $path = $parent_path . '/' . implode('/', $args);
2161  
2162    // Get the router item for the given parent link path.
2163    $router_item = menu_get_item($path);
2164    if (!$router_item || !$router_item['access']) {
2165      $path_empty[$parent_path] = TRUE;
2166      return $links;
2167    }
2168    $data = &drupal_static(__FUNCTION__, array());
2169    $root_path = $router_item['path'];
2170  
2171    // Performance: For a single, normalized path (such as 'node/%') we only query
2172    // available tasks once per request.
2173    if (!isset($data[$root_path])) {
2174      // Get all contextual links that are direct children of the router item and
2175      // not of the tab type 'view'.
2176      $data[$root_path] = db_select('menu_router', 'm')
2177        ->fields('m')
2178        ->condition('tab_parent', $router_item['tab_root'])
2179        ->condition('context', MENU_CONTEXT_NONE, '<>')
2180        ->condition('context', MENU_CONTEXT_PAGE, '<>')
2181        ->orderBy('weight')
2182        ->orderBy('title')
2183        ->execute()
2184        ->fetchAllAssoc('path', PDO::FETCH_ASSOC);
2185    }
2186    $parent_length = drupal_strlen($root_path) + 1;
2187    $map = $router_item['original_map'];
2188    foreach ($data[$root_path] as $item) {
2189      // Extract the actual "task" string from the path argument.
2190      $key = drupal_substr($item['path'], $parent_length);
2191  
2192      // Denormalize and translate the contextual link.
2193      _menu_translate($item, $map, TRUE);
2194      if (!$item['access']) {
2195        continue;
2196      }
2197      // All contextual links are keyed by the actual "task" path argument,
2198      // prefixed with the name of the implementing module.
2199      $links[$module . '-' . $key] = $item;
2200    }
2201  
2202    // Allow modules to alter contextual links.
2203    drupal_alter('menu_contextual_links', $links, $router_item, $root_path);
2204  
2205    // Performance: If the current user does not have access to any links for this
2206    // router path and no other module added further links, we assign FALSE here
2207    // to skip the entire process the next time the same router path is requested.
2208    if (empty($links)) {
2209      $path_empty[$parent_path] = TRUE;
2210    }
2211  
2212    return $links;
2213  }
2214  
2215  /**
2216   * Returns the rendered local tasks at the top level.
2217   */
2218  function menu_primary_local_tasks() {
2219    $links = menu_local_tasks(0);
2220    // Do not display single tabs.
2221    return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : '');
2222  }
2223  
2224  /**
2225   * Returns the rendered local tasks at the second level.
2226   */
2227  function menu_secondary_local_tasks() {
2228    $links = menu_local_tasks(1);
2229    // Do not display single tabs.
2230    return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : '');
2231  }
2232  
2233  /**
2234   * Returns the rendered local actions at the current level.
2235   */
2236  function menu_local_actions() {
2237    $links = menu_local_tasks();
2238    return $links['actions']['output'];
2239  }
2240  
2241  /**
2242   * Returns the router path, or the path for a default local task's parent.
2243   */
2244  function menu_tab_root_path() {
2245    $links = menu_local_tasks();
2246    return $links['root_path'];
2247  }
2248  
2249  /**
2250   * Returns a renderable element for the primary and secondary tabs.
2251   */
2252  function menu_local_tabs() {
2253    return array(
2254      '#theme' => 'menu_local_tasks',
2255      '#primary' => menu_primary_local_tasks(),
2256      '#secondary' => menu_secondary_local_tasks(),
2257    );
2258  }
2259  
2260  /**
2261   * Returns HTML for primary and secondary local tasks.
2262   *
2263   * @param $variables
2264   *   An associative array containing:
2265   *     - primary: (optional) An array of local tasks (tabs).
2266   *     - secondary: (optional) An array of local tasks (tabs).
2267   *
2268   * @ingroup themeable
2269   * @see menu_local_tasks()
2270   */
2271  function theme_menu_local_tasks(&$variables) {
2272    $output = '';
2273  
2274    if (!empty($variables['primary'])) {
2275      $variables['primary']['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2>';
2276      $variables['primary']['#prefix'] .= '<ul class="tabs primary">';
2277      $variables['primary']['#suffix'] = '</ul>';
2278      $output .= drupal_render($variables['primary']);
2279    }
2280    if (!empty($variables['secondary'])) {
2281      $variables['secondary']['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2>';
2282      $variables['secondary']['#prefix'] .= '<ul class="tabs secondary">';
2283      $variables['secondary']['#suffix'] = '</ul>';
2284      $output .= drupal_render($variables['secondary']);
2285    }
2286  
2287    return $output;
2288  }
2289  
2290  /**
2291   * Sets (or gets) the active menu for the current page.
2292   *
2293   * The active menu for the page determines the active trail.
2294   *
2295   * @return
2296   *   An array of menu machine names, in order of preference. The
2297   *   'menu_default_active_menus' variable may be used to assert a menu order
2298   *   different from the order of creation, or to prevent a particular menu from
2299   *   being used at all in the active trail.
2300   *   E.g., $conf['menu_default_active_menus'] = array('navigation', 'main-menu')
2301   */
2302  function menu_set_active_menu_names($menu_names = NULL) {
2303    $active = &drupal_static(__FUNCTION__);
2304  
2305    if (isset($menu_names) && is_array($menu_names)) {
2306      $active = $menu_names;
2307    }
2308    elseif (!isset($active)) {
2309      $active = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus()));
2310    }
2311    return $active;
2312  }
2313  
2314  /**
2315   * Gets the active menu for the current page.
2316   */
2317  function menu_get_active_menu_names() {
2318    return menu_set_active_menu_names();
2319  }
2320  
2321  /**
2322   * Sets the active path, which determines which page is loaded.
2323   *
2324   * Note that this may not have the desired effect unless invoked very early
2325   * in the page load, such as during hook_boot(), or unless you call
2326   * menu_execute_active_handler() to generate your page output.
2327   *
2328   * @param $path
2329   *   A Drupal path - not a path alias.
2330   */
2331  function menu_set_active_item($path) {
2332    $_GET['q'] = $path;
2333    // Since the active item has changed, the active menu trail may also be out
2334    // of date.
2335    drupal_static_reset('menu_set_active_trail');
2336  }
2337  
2338  /**
2339   * Sets the active trail (path to the menu tree root) of the current page.
2340   *
2341   * Any trail set by this function will only be used for functionality that calls
2342   * menu_get_active_trail(). Drupal core only uses trails set here for
2343   * breadcrumbs and the page title and not for menu trees or page content.
2344   * Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any
2345   * trail set here.
2346   *
2347   * To affect the trail used by menu trees, use menu_tree_set_path(). To affect
2348   * the page content, use menu_set_active_item() instead.
2349   *
2350   * @param $new_trail
2351   *   Menu trail to set; the value is saved in a static variable and can be
2352   *   retrieved by menu_get_active_trail(). The format of this array should be
2353   *   the same as the return value of menu_get_active_trail().
2354   *
2355   * @return
2356   *   The active trail. See menu_get_active_trail() for details.
2357   */
2358  function menu_set_active_trail($new_trail = NULL) {
2359    $trail = &drupal_static(__FUNCTION__);
2360  
2361    if (isset($new_trail)) {
2362      $trail = $new_trail;
2363    }
2364    elseif (!isset($trail)) {
2365      $trail = array();
2366      $trail[] = array(
2367        'title' => t('Home'),
2368        'href' => '<front>',
2369        'link_path' => '',
2370        'localized_options' => array(),
2371        'type' => 0,
2372      );
2373  
2374      // Try to retrieve a menu link corresponding to the current path. If more
2375      // than one exists, the link from the most preferred menu is returned.
2376      $preferred_link = menu_link_get_preferred();
2377      $current_item = menu_get_item();
2378  
2379      // There is a link for the current path.
2380      if ($preferred_link) {
2381        // Pass TRUE for $only_active_trail to make menu_tree_page_data() build
2382        // a stripped down menu tree containing the active trail only, in case
2383        // the given menu has not been built in this request yet.
2384        $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
2385        list($key, $curr) = each($tree);
2386      }
2387      // There is no link for the current path.
2388      else {
2389        $preferred_link = $current_item;
2390        $curr = FALSE;
2391      }
2392  
2393      while ($curr) {
2394        $link = $curr['link'];
2395        if ($link['in_active_trail']) {
2396          // Add the link to the trail, unless it links to its parent.
2397          if (!($link['type'] & MENU_LINKS_TO_PARENT)) {
2398            // The menu tree for the active trail may contain additional links
2399            // that have not been translated yet, since they contain dynamic
2400            // argument placeholders (%). Such links are not contained in regular
2401            // menu trees, and have only been loaded for the additional
2402            // translation that happens here, so as to be able to display them in
2403            // the breadcumb for the current page.
2404            // @see _menu_tree_check_access()
2405            // @see _menu_link_translate()
2406            if (strpos($link['href'], '%') !== FALSE) {
2407              _menu_link_translate($link, TRUE);
2408            }
2409            if ($link['access']) {
2410              $trail[] = $link;
2411            }
2412          }
2413          $tree = $curr['below'] ? $curr['below'] : array();
2414        }
2415        list($key, $curr) = each($tree);
2416      }
2417      // Make sure the current page is in the trail to build the page title, by
2418      // appending either the preferred link or the menu router item for the
2419      // current page. Exclude it if we are on the front page.
2420      $last = end($trail);
2421      if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
2422        $trail[] = $preferred_link;
2423      }
2424    }
2425    return $trail;
2426  }
2427  
2428  /**
2429   * Looks up the preferred menu link for a given system path.
2430   *
2431   * @param $path
2432   *   The path, for example 'node/5'. The function will find the corresponding
2433   *   menu link ('node/5' if it exists, or fallback to 'node/%').
2434   * @param $selected_menu
2435   *   The name of a menu used to restrict the search for a preferred menu link.
2436   *   If not specified, all the menus returned by menu_get_active_menu_names()
2437   *   will be used.
2438   *
2439   * @return
2440   *   A fully translated menu link, or FALSE if no matching menu link was
2441   *   found. The most specific menu link ('node/5' preferred over 'node/%') in
2442   *   the most preferred menu (as defined by menu_get_active_menu_names()) is
2443   *   returned.
2444   */
2445  function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
2446    $preferred_links = &drupal_static(__FUNCTION__);
2447  
2448    if (!isset($path)) {
2449      $path = $_GET['q'];
2450    }
2451  
2452    if (empty($selected_menu)) {
2453      // Use an illegal menu name as the key for the preferred menu link.
2454      $selected_menu = MENU_PREFERRED_LINK;
2455    }
2456  
2457    if (!isset($preferred_links[$path])) {
2458      // Look for the correct menu link by building a list of candidate paths,
2459      // which are ordered by priority (translated hrefs are preferred over
2460      // untranslated paths). Afterwards, the most relevant path is picked from
2461      // the menus, ordered by menu preference.
2462      $item = menu_get_item($path);
2463      $path_candidates = array();
2464      // 1. The current item href.
2465      $path_candidates[$item['href']] = $item['href'];
2466      // 2. The tab root href of the current item (if any).
2467      if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) {
2468        $path_candidates[$tab_root['href']] = $tab_root['href'];
2469      }
2470      // 3. The current item path (with wildcards).
2471      $path_candidates[$item['path']] = $item['path'];
2472      // 4. The tab root path of the current item (if any).
2473      if (!empty($tab_root)) {
2474        $path_candidates[$tab_root['path']] = $tab_root['path'];
2475      }
2476  
2477      // Retrieve a list of menu names, ordered by preference.
2478      $menu_names = menu_get_active_menu_names();
2479      // Put the selected menu at the front of the list.
2480      array_unshift($menu_names, $selected_menu);
2481  
2482      $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
2483      $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
2484      $query->fields('ml');
2485      // Weight must be taken from {menu_links}, not {menu_router}.
2486      $query->addField('ml', 'weight', 'link_weight');
2487      $query->fields('m');
2488      $query->condition('ml.link_path', $path_candidates, 'IN');
2489  
2490      // Sort candidates by link path and menu name.
2491      $candidates = array();
2492      foreach ($query->execute() as $candidate) {
2493        $candidate['weight'] = $candidate['link_weight'];
2494        $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
2495        // Add any menus not already in the menu name search list.
2496        if (!in_array($candidate['menu_name'], $menu_names)) {
2497          $menu_names[] = $candidate['menu_name'];
2498        }
2499      }
2500  
2501      // Store the most specific link for each menu. Also save the most specific
2502      // link of the most preferred menu in $preferred_link.
2503      foreach ($path_candidates as $link_path) {
2504        if (isset($candidates[$link_path])) {
2505          foreach ($menu_names as $menu_name) {
2506            if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
2507              $candidate_item = $candidates[$link_path][$menu_name];
2508              $map = explode('/', $path);
2509              _menu_translate($candidate_item, $map);
2510              if ($candidate_item['access']) {
2511                $preferred_links[$path][$menu_name] = $candidate_item;
2512                if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
2513                  // Store the most specific link.
2514                  $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
2515                }
2516              }
2517            }
2518          }
2519        }
2520      }
2521    }
2522  
2523    return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE;
2524  }
2525  
2526  /**
2527   * Gets the active trail (path to root menu root) of the current page.
2528   *
2529   * If a trail is supplied to menu_set_active_trail(), that value is returned. If
2530   * a trail is not supplied to menu_set_active_trail(), the path to the current
2531   * page is calculated and returned. The calculated trail is also saved as a
2532   * static value for use by subsequent calls to menu_get_active_trail().
2533   *
2534   * @return
2535   *   Path to menu root of the current page, as an array of menu link items,
2536   *   starting with the site's home page. Each link item is an associative array
2537   *   with the following components:
2538   *   - title: Title of the item.
2539   *   - href: Drupal path of the item.
2540   *   - localized_options: Options for passing into the l() function.
2541   *   - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to
2542   *     indicate it's not really in the menu (used for the home page item).
2543   */
2544  function menu_get_active_trail() {
2545    return menu_set_active_trail();
2546  }
2547  
2548  /**
2549   * Gets the breadcrumb for the current page, as determined by the active trail.
2550   *
2551   * @see menu_set_active_trail()
2552   */
2553  function menu_get_active_breadcrumb() {
2554    $breadcrumb = array();
2555  
2556    // No breadcrumb for the front page.
2557    if (drupal_is_front_page()) {
2558      return $breadcrumb;
2559    }
2560  
2561    $item = menu_get_item();
2562    if (!empty($item['access'])) {
2563      $active_trail = menu_get_active_trail();
2564  
2565      // Allow modules to alter the breadcrumb, if possible, as that is much
2566      // faster than rebuilding an entirely new active trail.
2567      drupal_alter('menu_breadcrumb', $active_trail, $item);
2568  
2569      // Don't show a link to the current page in the breadcrumb trail.
2570      $end = end($active_trail);
2571      if ($item['href'] == $end['href']) {
2572        array_pop($active_trail);
2573      }
2574  
2575      // Remove the tab root (parent) if the current path links to its parent.
2576      // Normally, the tab root link is included in the breadcrumb, as soon as we
2577      // are on a local task or any other child link. However, if we are on a
2578      // default local task (e.g., node/%/view), then we do not want the tab root
2579      // link (e.g., node/%) to appear, as it would be identical to the current
2580      // page. Since this behavior also needs to work recursively (i.e., on
2581      // default local tasks of default local tasks), and since the last non-task
2582      // link in the trail is used as page title (see menu_get_active_title()),
2583      // this condition cannot be cleanly integrated into menu_get_active_trail().
2584      // menu_get_active_trail() already skips all links that link to their parent
2585      // (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link
2586      // itself, we always remove the last link in the trail, if the current
2587      // router item links to its parent.
2588      if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
2589        array_pop($active_trail);
2590      }
2591  
2592      foreach ($active_trail as $parent) {
2593        $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
2594      }
2595    }
2596    return $breadcrumb;
2597  }
2598  
2599  /**
2600   * Gets the title of the current page, as determined by the active trail.
2601   */
2602  function menu_get_active_title() {
2603    $active_trail = menu_get_active_trail();
2604  
2605    foreach (array_reverse($active_trail) as $item) {
2606      if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) {
2607        return $item['title'];
2608      }
2609    }
2610  }
2611  
2612  /**
2613   * Gets a translated, access-checked menu link that is ready for rendering.
2614   *
2615   * This function should never be called from within node_load() or any other
2616   * function used as a menu object load function since an infinite recursion may
2617   * occur.
2618   *
2619   * @param $mlid
2620   *   The mlid of the menu item.
2621   *
2622   * @return
2623   *   A menu link, with $item['access'] filled and link translated for
2624   *   rendering.
2625   */
2626  function menu_link_load($mlid) {
2627    if (is_numeric($mlid)) {
2628      $query = db_select('menu_links', 'ml');
2629      $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
2630      $query->fields('ml');
2631      // Weight should be taken from {menu_links}, not {menu_router}.
2632      $query->addField('ml', 'weight', 'link_weight');
2633      $query->fields('m');
2634      $query->condition('ml.mlid', $mlid);
2635      if ($item = $query->execute()->fetchAssoc()) {
2636        $item['weight'] = $item['link_weight'];
2637        _menu_link_translate($item);
2638        return $item;
2639      }
2640    }
2641    return FALSE;
2642  }
2643  
2644  /**
2645   * Clears the cached cached data for a single named menu.
2646   */
2647  function menu_cache_clear($menu_name = 'navigation') {
2648    $cache_cleared = &drupal_static(__FUNCTION__, array());
2649  
2650    if (empty($cache_cleared[$menu_name])) {
2651      cache_clear_all('links:' . $menu_name . ':', 'cache_menu', TRUE);
2652      $cache_cleared[$menu_name] = 1;
2653    }
2654    elseif ($cache_cleared[$menu_name] == 1) {
2655      drupal_register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE);
2656      $cache_cleared[$menu_name] = 2;
2657    }
2658  
2659    // Also clear the menu system static caches.
2660    menu_reset_static_cache();
2661  }
2662  
2663  /**
2664   * Clears all cached menu data.
2665   *
2666   * This should be called any time broad changes
2667   * might have been made to the router items or menu links.
2668   */
2669  function menu_cache_clear_all() {
2670    cache_clear_all('*', 'cache_menu', TRUE);
2671    menu_reset_static_cache();
2672  }
2673  
2674  /**
2675   * Resets the menu system static cache.
2676   */
2677  function menu_reset_static_cache() {
2678    drupal_static_reset('_menu_build_tree');
2679    drupal_static_reset('menu_tree');
2680    drupal_static_reset('menu_tree_all_data');
2681    drupal_static_reset('menu_tree_page_data');
2682    drupal_static_reset('menu_load_all');
2683    drupal_static_reset('menu_link_get_preferred');
2684  }
2685  
2686  /**
2687   * Populates the database tables used by various menu functions.
2688   *
2689   * This function will clear and populate the {menu_router} table, add entries
2690   * to {menu_links} for new router items, and then remove stale items from
2691   * {menu_links}. If called from update.php or install.php, it will also
2692   * schedule a call to itself on the first real page load from
2693   * menu_execute_active_handler(), because the maintenance page environment
2694   * is different and leaves stale data in the menu tables.
2695   *
2696   * @return
2697   *   TRUE if the menu was rebuilt, FALSE if another thread was rebuilding
2698   *   in parallel and the current thread just waited for completion.
2699   */
2700  function menu_rebuild() {
2701    if (!lock_acquire('menu_rebuild')) {
2702      // Wait for another request that is already doing this work.
2703      // We choose to block here since otherwise the router item may not
2704      // be available in menu_execute_active_handler() resulting in a 404.
2705      lock_wait('menu_rebuild');
2706      return FALSE;
2707    }
2708  
2709    $transaction = db_transaction();
2710  
2711    try {
2712      list($menu, $masks) = menu_router_build();
2713      _menu_router_save($menu, $masks);
2714      _menu_navigation_links_rebuild($menu);
2715      // Clear the menu, page and block caches.
2716      menu_cache_clear_all();
2717      _menu_clear_page_cache();
2718  
2719      if (defined('MAINTENANCE_MODE')) {
2720        variable_set('menu_rebuild_needed', TRUE);
2721      }
2722      else {
2723        variable_del('menu_rebuild_needed');
2724      }
2725    }
2726    catch (Exception $e) {
2727      $transaction->rollback();
2728      watchdog_exception('menu', $e);
2729    }
2730  
2731    lock_release('menu_rebuild');
2732    return TRUE;
2733  }
2734  
2735  /**
2736   * Collects and alters the menu definitions.
2737   */
2738  function menu_router_build() {
2739    // We need to manually call each module so that we can know which module
2740    // a given item came from.
2741    $callbacks = array();
2742    foreach (module_implements('menu') as $module) {
2743      $router_items = call_user_func($module . '_menu');
2744      if (isset($router_items) && is_array($router_items)) {
2745        foreach (array_keys($router_items) as $path) {
2746          $router_items[$path]['module'] = $module;
2747        }
2748        $callbacks = array_merge($callbacks, $router_items);
2749      }
2750    }
2751    // Alter the menu as defined in modules, keys are like user/%user.
2752    drupal_alter('menu', $callbacks);
2753    list($menu, $masks) = _menu_router_build($callbacks);
2754    _menu_router_cache($menu);
2755  
2756    return array($menu, $masks);
2757  }
2758  
2759  /**
2760   * Stores the menu router if we have it in memory.
2761   */
2762  function _menu_router_cache($new_menu = NULL) {
2763    $menu = &drupal_static(__FUNCTION__);
2764  
2765    if (isset($new_menu)) {
2766      $menu = $new_menu;
2767    }
2768    return $menu;
2769  }
2770  
2771  /**
2772   * Gets the menu router.
2773   */
2774  function menu_get_router() {
2775    // Check first if we have it in memory already.
2776    $menu = _menu_router_cache();
2777    if (empty($menu)) {
2778      list($menu, $masks) = menu_router_build();
2779    }
2780    return $menu;
2781  }
2782  
2783  /**
2784   * Builds a link from a router item.
2785   */
2786  function _menu_link_build($item) {
2787    // Suggested items are disabled by default.
2788    if ($item['type'] == MENU_SUGGESTED_ITEM) {
2789      $item['hidden'] = 1;
2790    }
2791    // Hide all items that are not visible in the tree.
2792    elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) {
2793      $item['hidden'] = -1;
2794    }
2795    // Note, we set this as 'system', so that we can be sure to distinguish all
2796    // the menu links generated automatically from entries in {menu_router}.
2797    $item['module'] = 'system';
2798    $item += array(
2799      'menu_name' => 'navigation',
2800      'link_title' => $item['title'],
2801      'link_path' => $item['path'],
2802      'hidden' => 0,
2803      'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
2804    );
2805    return $item;
2806  }
2807  
2808  /**
2809   * Builds menu links for the items in the menu router.
2810   */
2811  function _menu_navigation_links_rebuild($menu) {
2812    // Add normal and suggested items as links.
2813    $menu_links = array();
2814    foreach ($menu as $path => $item) {
2815      if ($item['_visible']) {
2816        $menu_links[$path] = $item;
2817        $sort[$path] = $item['_number_parts'];
2818      }
2819    }
2820    if ($menu_links) {
2821      // Keep an array of processed menu links, to allow menu_link_save() to
2822      // check this for parents instead of querying the database.
2823      $parent_candidates = array();
2824      // Make sure no child comes before its parent.
2825      array_multisort($sort, SORT_NUMERIC, $menu_links);
2826  
2827      foreach ($menu_links as $key => $item) {
2828        $existing_item = db_select('menu_links')
2829          ->fields('menu_links')
2830          ->condition('link_path', $item['path'])
2831          ->condition('module', 'system')
2832          ->execute()->fetchAssoc();
2833        if ($existing_item) {
2834          $item['mlid'] = $existing_item['mlid'];
2835          // A change in hook_menu may move the link to a different menu
2836          if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) {
2837            $item['menu_name'] = $existing_item['menu_name'];
2838            $item['plid'] = $existing_item['plid'];
2839          }
2840          else {
2841            // It moved to a new menu. Let menu_link_save() try to find a new
2842            // parent based on the path.
2843            unset($item['plid']);
2844          }
2845          $item['has_children'] = $existing_item['has_children'];
2846          $item['updated'] = $existing_item['updated'];
2847        }
2848        if ($existing_item && $existing_item['customized']) {
2849          $parent_candidates[$existing_item['mlid']] = $existing_item;
2850        }
2851        else {
2852          $item = _menu_link_build($item);
2853          menu_link_save($item, $existing_item, $parent_candidates);
2854          $parent_candidates[$item['mlid']] = $item;
2855          unset($menu_links[$key]);
2856        }
2857      }
2858    }
2859    $paths = array_keys($menu);
2860    // Updated and customized items whose router paths are gone need new ones.
2861    $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
2862      ->fields('menu_links', array(
2863        'link_path',
2864        'mlid',
2865        'router_path',
2866        'updated',
2867      ))
2868      ->condition(db_or()
2869        ->condition('updated', 1)
2870        ->condition(db_and()
2871          ->condition('router_path', $paths, 'NOT IN')
2872          ->condition('external', 0)
2873          ->condition('customized', 1)
2874        )
2875      )
2876      ->execute();
2877    foreach ($result as $item) {
2878      $router_path = _menu_find_router_path($item['link_path']);
2879      if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) {
2880        // If the router path and the link path matches, it's surely a working
2881        // item, so we clear the updated flag.
2882        $updated = $item['updated'] && $router_path != $item['link_path'];
2883        db_update('menu_links')
2884          ->fields(array(
2885            'router_path' => $router_path,
2886            'updated' => (int) $updated,
2887          ))
2888          ->condition('mlid', $item['mlid'])
2889          ->execute();
2890      }
2891    }
2892    // Find any item whose router path does not exist any more.
2893    $result = db_select('menu_links')
2894      ->fields('menu_links')
2895      ->condition('router_path', $paths, 'NOT IN')
2896      ->condition('external', 0)
2897      ->condition('updated', 0)
2898      ->condition('customized', 0)
2899      ->orderBy('depth', 'DESC')
2900      ->execute();
2901    // Remove all such items. Starting from those with the greatest depth will
2902    // minimize the amount of re-parenting done by menu_link_delete().
2903    foreach ($result as $item) {
2904      _menu_delete_item($item, TRUE);
2905    }
2906  }
2907  
2908  /**
2909   * Clones an array of menu links.
2910   *
2911   * @param $links
2912   *   An array of menu links to clone.
2913   * @param $menu_name
2914   *   (optional) The name of a menu that the links will be cloned for. If not
2915   *   set, the cloned links will be in the same menu as the original set of
2916   *   links that were passed in.
2917   *
2918   * @return
2919   *   An array of menu links with the same properties as the passed-in array,
2920   *   but with the link identifiers removed so that a new link will be created
2921   *   when any of them is passed in to menu_link_save().
2922   *
2923   * @see menu_link_save()
2924   */
2925  function menu_links_clone($links, $menu_name = NULL) {
2926    foreach ($links as &$link) {
2927      unset($link['mlid']);
2928      unset($link['plid']);
2929      if (isset($menu_name)) {
2930        $link['menu_name'] = $menu_name;
2931      }
2932    }
2933    return $links;
2934  }
2935  
2936  /**
2937   * Returns an array containing all links for a menu.
2938   *
2939   * @param $menu_name
2940   *   The name of the menu whose links should be returned.
2941   *
2942   * @return
2943   *   An array of menu links.
2944   */
2945  function menu_load_links($menu_name) {
2946    $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC))
2947      ->fields('ml')
2948      ->condition('ml.menu_name', $menu_name)
2949      // Order by weight so as to be helpful for menus that are only one level
2950      // deep.
2951      ->orderBy('weight')
2952      ->execute()
2953      ->fetchAll();
2954  
2955    foreach ($links as &$link) {
2956      $link['options'] = unserialize($link['options']);
2957    }
2958    return $links;
2959  }
2960  
2961  /**
2962   * Deletes all links for a menu.
2963   *
2964   * @param $menu_name
2965   *   The name of the menu whose links will be deleted.
2966   */
2967  function menu_delete_links($menu_name) {
2968    $links = menu_load_links($menu_name);
2969    foreach ($links as $link) {
2970      // To speed up the deletion process, we reset some link properties that
2971      // would trigger re-parenting logic in _menu_delete_item() and
2972      // _menu_update_parental_status().
2973      $link['has_children'] = FALSE;
2974      $link['plid'] = 0;
2975      _menu_delete_item($link);
2976    }
2977  }
2978  
2979  /**
2980   * Delete one or several menu links.
2981   *
2982   * @param $mlid
2983   *   A valid menu link mlid or NULL. If NULL, $path is used.
2984   * @param $path
2985   *   The path to the menu items to be deleted. $mlid must be NULL.
2986   */
2987  function menu_link_delete($mlid, $path = NULL) {
2988    if (isset($mlid)) {
2989      _menu_delete_item(db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc());
2990    }
2991    else {
2992      $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path));
2993      foreach ($result as $link) {
2994        _menu_delete_item($link);
2995      }
2996    }
2997  }
2998  
2999  /**
3000   * Deletes a single menu link.
3001   *
3002   * @param $item
3003   *   Item to be deleted.
3004   * @param $force
3005   *   Forces deletion. Internal use only, setting to TRUE is discouraged.
3006   *
3007   * @see menu_link_delete()
3008   */
3009  function _menu_delete_item($item, $force = FALSE) {
3010    $item = is_object($item) ? get_object_vars($item) : $item;
3011    if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) {
3012      // Children get re-attached to the item's parent.
3013      if ($item['has_children']) {
3014        $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = :plid", array(':plid' => $item['mlid']));
3015        foreach ($result as $m) {
3016          $child = menu_link_load($m->mlid);
3017          $child['plid'] = $item['plid'];
3018          menu_link_save($child);
3019        }
3020      }
3021  
3022      // Notify modules we are deleting the item.
3023      module_invoke_all('menu_link_delete', $item);
3024  
3025      db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
3026  
3027      // Update the has_children status of the parent.
3028      _menu_update_parental_status($item);
3029      menu_cache_clear($item['menu_name']);
3030      _menu_clear_page_cache();
3031    }
3032  }
3033  
3034  /**
3035   * Saves a menu link.
3036   *
3037   * After calling this function, rebuild the menu cache using
3038   * menu_cache_clear_all().
3039   *
3040   * @param $item
3041   *   An associative array representing a menu link item, with elements:
3042   *   - link_path: (required) The path of the menu item, which should be
3043   *     normalized first by calling drupal_get_normal_path() on it.
3044   *   - link_title: (required) Title to appear in menu for the link.
3045   *   - menu_name: (optional) The machine name of the menu for the link.
3046   *     Defaults to 'navigation'.
3047   *   - weight: (optional) Integer to determine position in menu. Default is 0.
3048   *   - expanded: (optional) Boolean that determines if the item is expanded.
3049   *   - options: (optional) An array of options, see l() for more.
3050   *   - mlid: (optional) Menu link identifier, the primary integer key for each
3051   *     menu link. Can be set to an existing value, or to 0 or NULL
3052   *     to insert a new link.
3053   *   - plid: (optional) The mlid of the parent.
3054   *   - router_path: (optional) The path of the relevant router item.
3055   * @param $existing_item
3056   *   Optional, the current record from the {menu_links} table as an array.
3057   * @param $parent_candidates
3058   *   Optional array of menu links keyed by mlid. Used by
3059   *   _menu_navigation_links_rebuild() only.
3060   *
3061   * @return
3062   *   The mlid of the saved menu link, or FALSE if the menu link could not be
3063   *   saved.
3064   */
3065  function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
3066    drupal_alter('menu_link', $item);
3067  
3068    // This is the easiest way to handle the unique internal path '<front>',
3069    // since a path marked as external does not need to match a router path.
3070    $item['external'] = (url_is_external($item['link_path'])  || $item['link_path'] == '<front>') ? 1 : 0;
3071    // Load defaults.
3072    $item += array(
3073      'menu_name' => 'navigation',
3074      'weight' => 0,
3075      'link_title' => '',
3076      'hidden' => 0,
3077      'has_children' => 0,
3078      'expanded' => 0,
3079      'options' => array(),
3080      'module' => 'menu',
3081      'customized' => 0,
3082      'updated' => 0,
3083    );
3084    if (isset($item['mlid'])) {
3085      if (!$existing_item) {
3086        $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
3087      }
3088      if ($existing_item) {
3089        $existing_item['options'] = unserialize($existing_item['options']);
3090      }
3091    }
3092    else {
3093      $existing_item = FALSE;
3094    }
3095  
3096    // Try to find a parent link. If found, assign it and derive its menu.
3097    $parent = _menu_link_find_parent($item, $parent_candidates);
3098    if (!empty($parent['mlid'])) {
3099      $item['plid'] = $parent['mlid'];
3100      $item['menu_name'] = $parent['menu_name'];
3101    }
3102    // If no corresponding parent link was found, move the link to the top-level.
3103    else {
3104      $item['plid'] = 0;
3105    }
3106    $menu_name = $item['menu_name'];
3107  
3108    if (!$existing_item) {
3109      $item['mlid'] = db_insert('menu_links')
3110        ->fields(array(
3111          'menu_name' => $item['menu_name'],
3112          'plid' => $item['plid'],
3113          'link_path' => $item['link_path'],
3114          'hidden' => $item['hidden'],
3115          'external' => $item['external'],
3116          'has_children' => $item['has_children'],
3117          'expanded' => $item['expanded'],
3118          'weight' => $item['weight'],
3119          'module' => $item['module'],
3120          'link_title' => $item['link_title'],
3121          'options' => serialize($item['options']),
3122          'customized' => $item['customized'],
3123          'updated' => $item['updated'],
3124        ))
3125        ->execute();
3126    }
3127  
3128    // Directly fill parents for top-level links.
3129    if ($item['plid'] == 0) {
3130      $item['p1'] = $item['mlid'];
3131      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
3132        $item["p$i"] = 0;
3133      }
3134      $item['depth'] = 1;
3135    }
3136    // Otherwise, ensure that this link's depth is not beyond the maximum depth
3137    // and fill parents based on the parent link.
3138    else {
3139      if ($item['has_children'] && $existing_item) {
3140        $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
3141      }
3142      else {
3143        $limit = MENU_MAX_DEPTH - 1;
3144      }
3145      if ($parent['depth'] > $limit) {
3146        return FALSE;
3147      }
3148      $item['depth'] = $parent['depth'] + 1;
3149      _menu_link_parents_set($item, $parent);
3150    }
3151    // Need to check both plid and menu_name, since plid can be 0 in any menu.
3152    if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
3153      _menu_link_move_children($item, $existing_item);
3154    }
3155    // Find the router_path.
3156    if (empty($item['router_path'])  || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) {
3157      if ($item['external']) {
3158        $item['router_path'] = '';
3159      }
3160      else {
3161        // Find the router path which will serve this path.
3162        $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
3163        $item['router_path'] = _menu_find_router_path($item['link_path']);
3164      }
3165    }
3166    // If every value in $existing_item is the same in the $item, there is no
3167    // reason to run the update queries or clear the caches. We use
3168    // array_intersect_key() with the $item as the first parameter because
3169    // $item may have additional keys left over from building a router entry.
3170    // The intersect removes the extra keys, allowing a meaningful comparison.
3171    if (!$existing_item || (array_intersect_key($item, $existing_item) != $existing_item)) {
3172      db_update('menu_links')
3173        ->fields(array(
3174          'menu_name' => $item['menu_name'],
3175          'plid' => $item['plid'],
3176          'link_path' => $item['link_path'],
3177          'router_path' => $item['router_path'],
3178          'hidden' => $item['hidden'],
3179          'external' => $item['external'],
3180          'has_children' => $item['has_children'],
3181          'expanded' => $item['expanded'],
3182          'weight' => $item['weight'],
3183          'depth' => $item['depth'],
3184          'p1' => $item['p1'],
3185          'p2' => $item['p2'],
3186          'p3' => $item['p3'],
3187          'p4' => $item['p4'],
3188          'p5' => $item['p5'],
3189          'p6' => $item['p6'],
3190          'p7' => $item['p7'],
3191          'p8' => $item['p8'],
3192          'p9' => $item['p9'],
3193          'module' => $item['module'],
3194          'link_title' => $item['link_title'],
3195          'options' => serialize($item['options']),
3196          'customized' => $item['customized'],
3197        ))
3198        ->condition('mlid', $item['mlid'])
3199        ->execute();
3200      // Check the has_children status of the parent.
3201      _menu_update_parental_status($item);
3202      menu_cache_clear($menu_name);
3203      if ($existing_item && $menu_name != $existing_item['menu_name']) {
3204        menu_cache_clear($existing_item['menu_name']);
3205      }
3206      // Notify modules we have acted on a menu item.
3207      $hook = 'menu_link_insert';
3208      if ($existing_item) {
3209        $hook = 'menu_link_update';
3210      }
3211      module_invoke_all($hook, $item);
3212      // Now clear the cache.
3213      _menu_clear_page_cache();
3214    }
3215    return $item['mlid'];
3216  }
3217  
3218  /**
3219   * Finds a possible parent for a given menu link.
3220   *
3221   * Because the parent of a given link might not exist anymore in the database,
3222   * we apply a set of heuristics to determine a proper parent:
3223   *
3224   *  - use the passed parent link if specified and existing.
3225   *  - else, use the first existing link down the previous link hierarchy
3226   *  - else, for system menu links (derived from hook_menu()), reparent
3227   *    based on the path hierarchy.
3228   *
3229   * @param $menu_link
3230   *   A menu link.
3231   * @param $parent_candidates
3232   *   An array of menu links keyed by mlid.
3233   *
3234   * @return
3235   *   A menu link structure of the possible parent or FALSE if no valid parent
3236   *   has been found.
3237   */
3238  function _menu_link_find_parent($menu_link, $parent_candidates = array()) {
3239    $parent = FALSE;
3240  
3241    // This item is explicitely top-level, skip the rest of the parenting.
3242    if (isset($menu_link['plid']) && empty($menu_link['plid'])) {
3243      return $parent;
3244    }
3245  
3246    // If we have a parent link ID, try to use that.
3247    $candidates = array();
3248    if (isset($menu_link['plid'])) {
3249      $candidates[] = $menu_link['plid'];
3250    }
3251  
3252    // Else, if we have a link hierarchy try to find a valid parent in there.
3253    if (!empty($menu_link['depth']) && $menu_link['depth'] > 1) {
3254      for ($depth = $menu_link['depth'] - 1; $depth >= 1; $depth--) {
3255        $candidates[] = $menu_link['p' . $depth];
3256      }
3257    }
3258  
3259    foreach ($candidates as $mlid) {
3260      if (isset($parent_candidates[$mlid])) {
3261        $parent = $parent_candidates[$mlid];
3262      }
3263      else {
3264        $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
3265      }
3266      if ($parent) {
3267        return $parent;
3268      }
3269    }
3270  
3271    // If everything else failed, try to derive the parent from the path
3272    // hierarchy. This only makes sense for links derived from menu router
3273    // items (ie. from hook_menu()).
3274    if ($menu_link['module'] == 'system') {
3275      $query = db_select('menu_links');
3276      $query->condition('module', 'system');
3277      // We always respect the link's 'menu_name'; inheritance for router items is
3278      // ensured in _menu_router_build().
3279      $query->condition('menu_name', $menu_link['menu_name']);
3280  
3281      // Find the parent - it must be unique.
3282      $parent_path = $menu_link['link_path'];
3283      do {
3284        $parent = FALSE;
3285        $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
3286        $new_query = clone $query;
3287        $new_query->condition('link_path', $parent_path);
3288        // Only valid if we get a unique result.
3289        if ($new_query->countQuery()->execute()->fetchField() == 1) {
3290          $parent = $new_query->fields('menu_links')->execute()->fetchAssoc();
3291        }
3292      } while ($parent === FALSE && $parent_path);
3293    }
3294  
3295    return $parent;
3296  }
3297  
3298  /**
3299   * Clears the page and block caches at most twice per page load.
3300   */
3301  function _menu_clear_page_cache() {
3302    $cache_cleared = &drupal_static(__FUNCTION__, 0);
3303  
3304    // Clear the page and block caches, but at most twice, including at
3305    //  the end of the page load when there are multiple links saved or deleted.
3306    if ($cache_cleared == 0) {
3307      cache_clear_all();
3308      // Keep track of which menus have expanded items.
3309      _menu_set_expanded_menus();
3310      $cache_cleared = 1;
3311    }
3312    elseif ($cache_cleared == 1) {
3313      drupal_register_shutdown_function('cache_clear_all');
3314      // Keep track of which menus have expanded items.
3315      drupal_register_shutdown_function('_menu_set_expanded_menus');
3316      $cache_cleared = 2;
3317    }
3318  }
3319  
3320  /**
3321   * Updates a list of menus with expanded items.
3322   */
3323  function _menu_set_expanded_menus() {
3324    $names = db_query("SELECT menu_name FROM {menu_links} WHERE expanded <> 0 GROUP BY menu_name")->fetchCol();
3325    variable_set('menu_expanded', $names);
3326  }
3327  
3328  /**
3329   * Finds the router path which will serve this path.
3330   *
3331   * @param $link_path
3332   *  The path for we are looking up its router path.
3333   *
3334   * @return
3335   *  A path from $menu keys or empty if $link_path points to a nonexisting
3336   *  place.
3337   */
3338  function _menu_find_router_path($link_path) {
3339    // $menu will only have data during a menu rebuild.
3340    $menu = _menu_router_cache();
3341  
3342    $router_path = $link_path;
3343    $parts = explode('/', $link_path, MENU_MAX_PARTS);
3344    $ancestors = menu_get_ancestors($parts);
3345  
3346    if (empty($menu)) {
3347      // Not during a menu rebuild, so look up in the database.
3348      $router_path = (string) db_select('menu_router')
3349        ->fields('menu_router', array('path'))
3350        ->condition('path', $ancestors, 'IN')
3351        ->orderBy('fit', 'DESC')
3352        ->range(0, 1)
3353        ->execute()->fetchField();
3354    }
3355    elseif (!isset($menu[$router_path])) {
3356      // Add an empty router path as a fallback.
3357      $ancestors[] = '';
3358      foreach ($ancestors as $key => $router_path) {
3359        if (isset($menu[$router_path])) {
3360          // Exit the loop leaving $router_path as the first match.
3361          break;
3362        }
3363      }
3364      // If we did not find the path, $router_path will be the empty string
3365      // at the end of $ancestors.
3366    }
3367    return $router_path;
3368  }
3369  
3370  /**
3371   * Inserts, updates, or deletes an uncustomized menu link related to a module.
3372   *
3373   * @param $module
3374   *   The name of the module.
3375   * @param $op
3376   *   Operation to perform: insert, update or delete.
3377   * @param $link_path
3378   *   The path this link points to.
3379   * @param $link_title
3380   *   Title of the link to insert or new title to update the link to.
3381   *   Unused for delete.
3382   *
3383   * @return
3384   *   The insert op returns the mlid of the new item. Others op return NULL.
3385   */
3386  function menu_link_maintain($module, $op, $link_path, $link_title) {
3387    switch ($op) {
3388      case 'insert':
3389        $menu_link = array(
3390          'link_title' => $link_title,
3391          'link_path' => $link_path,
3392          'module' => $module,
3393        );
3394        return menu_link_save($menu_link);
3395        break;
3396      case 'update':
3397        $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0", array(':link_path' => $link_path, ':module' => $module))->fetchAll(PDO::FETCH_ASSOC);
3398        foreach ($result as $link) {
3399          $link['link_title'] = $link_title;
3400          $link['options'] = unserialize($link['options']);
3401          menu_link_save($link);
3402        }
3403        break;
3404      case 'delete':
3405        menu_link_delete(NULL, $link_path);
3406        break;
3407    }
3408  }
3409  
3410  /**
3411   * Finds the depth of an item's children relative to its depth.
3412   *
3413   * For example, if the item has a depth of 2, and the maximum of any child in
3414   * the menu link tree is 5, the relative depth is 3.
3415   *
3416   * @param $item
3417   *   An array representing a menu link item.
3418   *
3419   * @return
3420   *   The relative depth, or zero.
3421   *
3422   */
3423  function menu_link_children_relative_depth($item) {
3424    $query = db_select('menu_links');
3425    $query->addField('menu_links', 'depth');
3426    $query->condition('menu_name', $item['menu_name']);
3427    $query->orderBy('depth', 'DESC');
3428    $query->range(0, 1);
3429  
3430    $i = 1;
3431    $p = 'p1';
3432    while ($i <= MENU_MAX_DEPTH && $item[$p]) {
3433      $query->condition($p, $item[$p]);
3434      $p = 'p' . ++$i;
3435    }
3436  
3437    $max_depth = $query->execute()->fetchField();
3438  
3439    return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
3440  }
3441  
3442  /**
3443   * Updates the children of a menu link that is being moved.
3444   *
3445   * The menu name, parents (p1 - p6), and depth are updated for all children of
3446   * the link, and the has_children status of the previous parent is updated.
3447   */
3448  function _menu_link_move_children($item, $existing_item) {
3449    $query = db_update('menu_links');
3450  
3451    $query->fields(array('menu_name' => $item['menu_name']));
3452  
3453    $p = 'p1';
3454    $expressions = array();
3455    for ($i = 1; $i <= $item['depth']; $p = 'p' . ++$i) {
3456      $expressions[] = array($p, ":p_$i", array(":p_$i" => $item[$p]));
3457    }
3458    $j = $existing_item['depth'] + 1;
3459    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
3460      $expressions[] = array('p' . $i++, 'p' . $j++, array());
3461    }
3462    while ($i <= MENU_MAX_DEPTH) {
3463      $expressions[] = array('p' . $i++, 0, array());
3464    }
3465  
3466    $shift = $item['depth'] - $existing_item['depth'];
3467    if ($shift > 0) {
3468      // The order of expressions must be reversed so the new values don't
3469      // overwrite the old ones before they can be used because "Single-table
3470      // UPDATE assignments are generally evaluated from left to right"
3471      // see: http://dev.mysql.com/doc/refman/5.0/en/update.html
3472      $expressions = array_reverse($expressions);
3473    }
3474    foreach ($expressions as $expression) {
3475      $query->expression($expression[0], $expression[1], $expression[2]);
3476    }
3477  
3478    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
3479    $query->condition('menu_name', $existing_item['menu_name']);
3480    $p = 'p1';
3481    for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p' . ++$i) {
3482      $query->condition($p, $existing_item[$p]);
3483    }
3484  
3485    $query->execute();
3486  
3487    // Check the has_children status of the parent, while excluding this item.
3488    _menu_update_parental_status($existing_item, TRUE);
3489  }
3490  
3491  /**
3492   * Checks and updates the 'has_children' status for the parent of a link.
3493   */
3494  function _menu_update_parental_status($item, $exclude = FALSE) {
3495    // If plid == 0, there is nothing to update.
3496    if ($item['plid']) {
3497      // Check if at least one visible child exists in the table.
3498      $query = db_select('menu_links');
3499      $query->addField('menu_links', 'mlid');
3500      $query->condition('menu_name', $item['menu_name']);
3501      $query->condition('hidden', 0);
3502      $query->condition('plid', $item['plid']);
3503      $query->range(0, 1);
3504      if ($exclude) {
3505        $query->condition('mlid', $item['mlid'], '<>');
3506      }
3507      $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0;
3508      db_update('menu_links')
3509        ->fields(array('has_children' => $parent_has_children))
3510        ->condition('mlid', $item['plid'])
3511        ->execute();
3512    }
3513  }
3514  
3515  /**
3516   * Sets the p1 through p9 values for a menu link being saved.
3517   */
3518  function _menu_link_parents_set(&$item, $parent) {
3519    $i = 1;
3520    while ($i < $item['depth']) {
3521      $p = 'p' . $i++;
3522      $item[$p] = $parent[$p];
3523    }
3524    $p = 'p' . $i++;
3525    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
3526    $item[$p] = $item['mlid'];
3527    while ($i <= MENU_MAX_DEPTH) {
3528      $p = 'p' . $i++;
3529      $item[$p] = 0;
3530    }
3531  }
3532  
3533  /**
3534   * Builds the router table based on the data from hook_menu().
3535   */
3536  function _menu_router_build($callbacks) {
3537    // First pass: separate callbacks from paths, making paths ready for
3538    // matching. Calculate fitness, and fill some default values.
3539    $menu = array();
3540    $masks = array();
3541    foreach ($callbacks as $path => $item) {
3542      $load_functions = array();
3543      $to_arg_functions = array();
3544      $fit = 0;
3545      $move = FALSE;
3546  
3547      $parts = explode('/', $path, MENU_MAX_PARTS);
3548      $number_parts = count($parts);
3549      // We store the highest index of parts here to save some work in the fit
3550      // calculation loop.
3551      $slashes = $number_parts - 1;
3552      // Extract load and to_arg functions.
3553      foreach ($parts as $k => $part) {
3554        $match = FALSE;
3555        // Look for wildcards in the form allowed to be used in PHP functions,
3556        // because we are using these to construct the load function names.
3557        if (preg_match('/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/', $part, $matches)) {
3558          if (empty($matches[1])) {
3559            $match = TRUE;
3560            $load_functions[$k] = NULL;
3561          }
3562          else {
3563            if (function_exists($matches[1] . '_to_arg')) {
3564              $to_arg_functions[$k] = $matches[1] . '_to_arg';
3565              $load_functions[$k] = NULL;
3566              $match = TRUE;
3567            }
3568            if (function_exists($matches[1] . '_load')) {
3569              $function = $matches[1] . '_load';
3570              // Create an array of arguments that will be passed to the _load
3571              // function when this menu path is checked, if 'load arguments'
3572              // exists.
3573              $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function;
3574              $match = TRUE;
3575            }
3576          }
3577        }
3578        if ($match) {
3579          $parts[$k] = '%';
3580        }
3581        else {
3582          $fit |=  1 << ($slashes - $k);
3583        }
3584      }
3585      if ($fit) {
3586        $move = TRUE;
3587      }
3588      else {
3589        // If there is no %, it fits maximally.
3590        $fit = (1 << $number_parts) - 1;
3591      }
3592      $masks[$fit] = 1;
3593      $item['_load_functions'] = $load_functions;
3594      $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
3595      $item += array(
3596        'title' => '',
3597        'weight' => 0,
3598        'type' => MENU_NORMAL_ITEM,
3599        'module' => '',
3600        '_number_parts' => $number_parts,
3601        '_parts' => $parts,
3602        '_fit' => $fit,
3603      );
3604      $item += array(
3605        '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB),
3606        '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK),
3607      );
3608      if ($move) {
3609        $new_path = implode('/', $item['_parts']);
3610        $menu[$new_path] = $item;
3611        $sort[$new_path] = $number_parts;
3612      }
3613      else {
3614        $menu[$path] = $item;
3615        $sort[$path] = $number_parts;
3616      }
3617    }
3618    array_multisort($sort, SORT_NUMERIC, $menu);
3619    // Apply inheritance rules.
3620    foreach ($menu as $path => $v) {
3621      $item = &$menu[$path];
3622      if (!$item['_tab']) {
3623        // Non-tab items.
3624        $item['tab_parent'] = '';
3625        $item['tab_root'] = $path;
3626      }
3627      // If not specified, assign the default tab type for local tasks.
3628      elseif (!isset($item['context'])) {
3629        $item['context'] = MENU_CONTEXT_PAGE;
3630      }
3631      for ($i = $item['_number_parts'] - 1; $i; $i--) {
3632        $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
3633        if (isset($menu[$parent_path])) {
3634  
3635          $parent = &$menu[$parent_path];
3636  
3637          // If we have no menu name, try to inherit it from parent items.
3638          if (!isset($item['menu_name'])) {
3639            // If the parent item of this item does not define a menu name (and no
3640            // previous iteration assigned one already), try to find the menu name
3641            // of the parent item in the currently stored menu links.
3642            if (!isset($parent['menu_name'])) {
3643              $menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path AND module = 'system'", array(':router_path' => $parent_path))->fetchField();
3644              if ($menu_name) {
3645                $parent['menu_name'] = $menu_name;
3646              }
3647            }
3648            // If the parent item defines a menu name, inherit it.
3649            if (!empty($parent['menu_name'])) {
3650              $item['menu_name'] = $parent['menu_name'];
3651            }
3652          }
3653          if (!isset($item['tab_parent'])) {
3654            // Parent stores the parent of the path.
3655            $item['tab_parent'] = $parent_path;
3656          }
3657          if (!isset($item['tab_root']) && !$parent['_tab']) {
3658            $item['tab_root'] = $parent_path;
3659          }
3660          // If an access callback is not found for a default local task we use
3661          // the callback from the parent, since we expect them to be identical.
3662          // In all other cases, the access parameters must be specified.
3663          if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) {
3664            $item['access callback'] = $parent['access callback'];
3665            if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
3666              $item['access arguments'] = $parent['access arguments'];
3667            }
3668          }
3669          // Same for page callbacks.
3670          if (!isset($item['page callback']) && isset($parent['page callback'])) {
3671            $item['page callback'] = $parent['page callback'];
3672            if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
3673              $item['page arguments'] = $parent['page arguments'];
3674            }
3675            if (!isset($item['file path']) && isset($parent['file path'])) {
3676              $item['file path'] = $parent['file path'];
3677            }
3678            if (!isset($item['file']) && isset($parent['file'])) {
3679              $item['file'] = $parent['file'];
3680              if (empty($item['file path']) && isset($item['module']) && isset($parent['module']) && $item['module'] != $parent['module']) {
3681                $item['file path'] = drupal_get_path('module', $parent['module']);
3682              }
3683            }
3684          }
3685          // Same for delivery callbacks.
3686          if (!isset($item['delivery callback']) && isset($parent['delivery callback'])) {
3687            $item['delivery callback'] = $parent['delivery callback'];
3688          }
3689          // Same for theme callbacks.
3690          if (!isset($item['theme callback']) && isset($parent['theme callback'])) {
3691            $item['theme callback'] = $parent['theme callback'];
3692            if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) {
3693              $item['theme arguments'] = $parent['theme arguments'];
3694            }
3695          }
3696          // Same for load arguments: if a loader doesn't have any explict
3697          // arguments, try to find arguments in the parent.
3698          if (!isset($item['load arguments'])) {
3699            foreach ($item['_load_functions'] as $k => $function) {
3700              // This loader doesn't have any explict arguments...
3701              if (!is_array($function)) {
3702                // ... check the parent for a loader at the same position
3703                // using the same function name and defining arguments...
3704                if (isset($parent['_load_functions'][$k]) && is_array($parent['_load_functions'][$k]) && key($parent['_load_functions'][$k]) === $function) {
3705                  // ... and inherit the arguments on the child.
3706                  $item['_load_functions'][$k] = $parent['_load_functions'][$k];
3707                }
3708              }
3709            }
3710          }
3711        }
3712      }
3713      if (!isset($item['access callback']) && isset($item['access arguments'])) {
3714        // Default callback.
3715        $item['access callback'] = 'user_access';
3716      }
3717      if (!isset($item['access callback']) || empty($item['page callback'])) {
3718        $item['access callback'] = 0;
3719      }
3720      if (is_bool($item['access callback'])) {
3721        $item['access callback'] = intval($item['access callback']);
3722      }
3723  
3724      $item['load_functions'] = empty($item['_load_functions']) ? '' : serialize($item['_load_functions']);
3725      $item += array(
3726        'access arguments' => array(),
3727        'access callback' => '',
3728        'page arguments' => array(),
3729        'page callback' => '',
3730        'delivery callback' => '',
3731        'title arguments' => array(),
3732        'title callback' => 't',
3733        'theme arguments' => array(),
3734        'theme callback' => '',
3735        'description' => '',
3736        'position' => '',
3737        'context' => 0,
3738        'tab_parent' => '',
3739        'tab_root' => $path,
3740        'path' => $path,
3741        'file' => '',
3742        'file path' => '',
3743        'include file' => '',
3744      );
3745  
3746      // Calculate out the file to be included for each callback, if any.
3747      if ($item['file']) {
3748        $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
3749        $item['include file'] = $file_path . '/' . $item['file'];
3750      }
3751    }
3752  
3753    // Sort the masks so they are in order of descending fit.
3754    $masks = array_keys($masks);
3755    rsort($masks);
3756  
3757    return array($menu, $masks);
3758  }
3759  
3760  /**
3761   * Saves data from menu_router_build() to the router table.
3762   */
3763  function _menu_router_save($menu, $masks) {
3764    // Delete the existing router since we have some data to replace it.
3765    db_truncate('menu_router')->execute();
3766  
3767    // Prepare insert object.
3768    $insert = db_insert('menu_router')
3769      ->fields(array(
3770        'path',
3771        'load_functions',
3772        'to_arg_functions',
3773        'access_callback',
3774        'access_arguments',
3775        'page_callback',
3776        'page_arguments',
3777        'delivery_callback',
3778        'fit',
3779        'number_parts',
3780        'context',
3781        'tab_parent',
3782        'tab_root',
3783        'title',
3784        'title_callback',
3785        'title_arguments',
3786        'theme_callback',
3787        'theme_arguments',
3788        'type',
3789        'description',
3790        'position',
3791        'weight',
3792        'include_file',
3793      ));
3794  
3795    $num_records = 0;
3796  
3797    foreach ($menu as $path => $item) {
3798      // Fill in insert object values.
3799      $insert->values(array(
3800        'path' => $item['path'],
3801        'load_functions' => $item['load_functions'],
3802        'to_arg_functions' => $item['to_arg_functions'],
3803        'access_callback' => $item['access callback'],
3804        'access_arguments' => serialize($item['access arguments']),
3805        'page_callback' => $item['page callback'],
3806        'page_arguments' => serialize($item['page arguments']),
3807        'delivery_callback' => $item['delivery callback'],
3808        'fit' => $item['_fit'],
3809        'number_parts' => $item['_number_parts'],
3810        'context' => $item['context'],
3811        'tab_parent' => $item['tab_parent'],
3812        'tab_root' => $item['tab_root'],
3813        'title' => $item['title'],
3814        'title_callback' => $item['title callback'],
3815        'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''),
3816        'theme_callback' => $item['theme callback'],
3817        'theme_arguments' => serialize($item['theme arguments']),
3818        'type' => $item['type'],
3819        'description' => $item['description'],
3820        'position' => $item['position'],
3821        'weight' => $item['weight'],
3822        'include_file' => $item['include file'],
3823      ));
3824  
3825      // Execute in batches to avoid the memory overhead of all of those records
3826      // in the query object.
3827      if (++$num_records == 20) {
3828        $insert->execute();
3829        $num_records = 0;
3830      }
3831    }
3832    // Insert any remaining records.
3833    $insert->execute();
3834    // Store the masks.
3835    variable_set('menu_masks', $masks);
3836  
3837    return $menu;
3838  }
3839  
3840  /**
3841   * Checks whether the site is in maintenance mode.
3842   *
3843   * This function will log the current user out and redirect to front page
3844   * if the current user has no 'access site in maintenance mode' permission.
3845   *
3846   * @param $check_only
3847   *   If this is set to TRUE, the function will perform the access checks and
3848   *   return the site offline status, but not log the user out or display any
3849   *   messages.
3850   *
3851   * @return
3852   *   FALSE if the site is not in maintenance mode, the user login page is
3853   *   displayed, or the user has the 'access site in maintenance mode'
3854   *   permission. TRUE for anonymous users not being on the login page when the
3855   *   site is in maintenance mode.
3856   */
3857  function _menu_site_is_offline($check_only = FALSE) {
3858    // Check if site is in maintenance mode.
3859    if (variable_get('maintenance_mode', 0)) {
3860      if (user_access('access site in maintenance mode')) {
3861        // Ensure that the maintenance mode message is displayed only once
3862        // (allowing for page redirects) and specifically suppress its display on
3863        // the maintenance mode settings page.
3864        if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') {
3865          if (user_access('administer site configuration')) {
3866            drupal_set_message(t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE);
3867          }
3868          else {
3869            drupal_set_message(t('Operating in maintenance mode.'), 'status', FALSE);
3870          }
3871        }
3872      }
3873      else {
3874        return TRUE;
3875      }
3876    }
3877    return FALSE;
3878  }
3879  
3880  /**
3881   * @} End of "defgroup menu".
3882   */

title

Description

title

Description

title

Description

title

title

Body