[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/includes/ -> menu.inc (source)

   1  <?php
   2  // $Id: menu.inc,v 1.255.2.38 2010/12/09 11:57:18 goba Exp $
   3  
   4  /**
   5   * @file
   6   * API for the Drupal menu system.
   7   */
   8  
   9  /**
  10   * @defgroup menu Menu system
  11   * @{
  12   * Define the navigation menus, and route page requests to code based on URLs.
  13   *
  14   * The Drupal menu system drives both the navigation system from a user
  15   * perspective and the callback system that Drupal uses to respond to URLs
  16   * passed from the browser. For this reason, a good understanding of the
  17   * menu system is fundamental to the creation of complex modules.
  18   *
  19   * Drupal's menu system follows a simple hierarchy defined by paths.
  20   * Implementations of hook_menu() define menu items and assign them to
  21   * paths (which should be unique). The menu system aggregates these items
  22   * and determines the menu hierarchy from the paths. For example, if the
  23   * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
  24   * would form the structure:
  25   * - a
  26   *   - a/b
  27   *     - a/b/c/d
  28   *     - a/b/h
  29   * - e
  30   * - f/g
  31   * Note that the number of elements in the path does not necessarily
  32   * determine the depth of the menu item in the tree.
  33   *
  34   * When responding to a page request, the menu system looks to see if the
  35   * path requested by the browser is registered as a menu item with a
  36   * callback. If not, the system searches up the menu tree for the most
  37   * complete match with a callback it can find. If the path a/b/i is
  38   * requested in the tree above, the callback for a/b would be used.
  39   *
  40   * The found callback function is called with any arguments specified
  41   * in the "page arguments" attribute of its menu item. The
  42   * attribute must be an array. After these arguments, any remaining
  43   * components of the path are appended as further arguments. In this
  44   * way, the callback for a/b above could respond to a request for
  45   * a/b/i differently than a request for a/b/j.
  46   *
  47   * For an illustration of this process, see page_example.module.
  48   *
  49   * Access to the callback functions is also protected by the menu system.
  50   * The "access callback" with an optional "access arguments" of each menu
  51   * item is called before the page callback proceeds. If this returns TRUE,
  52   * then access is granted; if FALSE, then access is denied. Menu items may
  53   * omit this attribute to use the value provided by an ancestor item.
  54   *
  55   * In the default Drupal interface, you will notice many links rendered as
  56   * tabs. These are known in the menu system as "local tasks", and they are
  57   * rendered as tabs by default, though other presentations are possible.
  58   * Local tasks function just as other menu items in most respects. It is
  59   * convention that the names of these tasks should be short verbs if
  60   * possible. In addition, a "default" local task should be provided for
  61   * each set. When visiting a local task's parent menu item, the default
  62   * local task will be rendered as if it is selected; this provides for a
  63   * normal tab user experience. This default task is special in that it
  64   * links not to its provided path, but to its parent item's path instead.
  65   * The default task's path is only used to place it appropriately in the
  66   * menu hierarchy.
  67   *
  68   * Everything described so far is stored in the menu_router table. The
  69   * menu_links table holds the visible menu links. By default these are
  70   * derived from the same hook_menu definitions, however you are free to
  71   * add more with menu_link_save().
  72   */
  73  
  74  /**
  75   * @name Menu flags
  76   * @{
  77   * Flags for use in the "type" attribute of menu items.
  78   */
  79  
  80  define('MENU_IS_ROOT', 0x0001);
  81  define('MENU_VISIBLE_IN_TREE', 0x0002);
  82  define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
  83  define('MENU_LINKS_TO_PARENT', 0x0008);
  84  define('MENU_MODIFIED_BY_ADMIN', 0x0020);
  85  define('MENU_CREATED_BY_ADMIN', 0x0040);
  86  define('MENU_IS_LOCAL_TASK', 0x0080);
  87  
  88  /**
  89   * @} End of "Menu flags".
  90   */
  91  
  92  /**
  93   * @name Menu item types
  94   * @{
  95   * Menu item definitions provide one of these constants, which are shortcuts for
  96   * combinations of the above flags.
  97   */
  98  
  99  /**
 100   * Normal menu items show up in the menu tree and can be moved/hidden by
 101   * the administrator. Use this for most menu items. It is the default value if
 102   * no menu item type is specified.
 103   */
 104  define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
 105  
 106  /**
 107   * Callbacks simply register a path so that the correct function is fired
 108   * when the URL is accessed. They are not shown in the menu.
 109   */
 110  define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB);
 111  
 112  /**
 113   * Modules may "suggest" menu items that the administrator may enable. They act
 114   * just as callbacks do until enabled, at which time they act like normal items.
 115   * Note for the value: 0x0010 was a flag which is no longer used, but this way
 116   * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate.
 117   */
 118  define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010);
 119  
 120  /**
 121   * Local tasks are rendered as tabs by default. Use this for menu items that
 122   * describe actions to be performed on their parent item. An example is the path
 123   * "node/52/edit", which performs the "edit" task on "node/52".
 124   */
 125  define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK);
 126  
 127  /**
 128   * Every set of local tasks should provide one "default" task, that links to the
 129   * same path as its parent when clicked.
 130   */
 131  define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT);
 132  
 133  /**
 134   * @} End of "Menu item types".
 135   */
 136  
 137  /**
 138   * @name Menu status codes
 139   * @{
 140   * Status codes for menu callbacks.
 141   */
 142  
 143  define('MENU_FOUND', 1);
 144  define('MENU_NOT_FOUND', 2);
 145  define('MENU_ACCESS_DENIED', 3);
 146  define('MENU_SITE_OFFLINE', 4);
 147  
 148  /**
 149   * @} End of "Menu status codes".
 150   */
 151  
 152  /**
 153   * @Name Menu tree parameters
 154   * @{
 155   * Menu tree
 156   */
 157  
 158   /**
 159   * The maximum number of path elements for a menu callback
 160   */
 161  define('MENU_MAX_PARTS', 7);
 162  
 163  
 164  /**
 165   * The maximum depth of a menu links tree - matches the number of p columns.
 166   */
 167  define('MENU_MAX_DEPTH', 9);
 168  
 169  
 170  /**
 171   * @} End of "Menu tree parameters".
 172   */
 173  
 174  /**
 175   * Returns the ancestors (and relevant placeholders) for any given path.
 176   *
 177   * For example, the ancestors of node/12345/edit are:
 178   * - node/12345/edit
 179   * - node/12345/%
 180   * - node/%/edit
 181   * - node/%/%
 182   * - node/12345
 183   * - node/%
 184   * - node
 185   *
 186   * To generate these, we will use binary numbers. Each bit represents a
 187   * part of the path. If the bit is 1, then it represents the original
 188   * value while 0 means wildcard. If the path is node/12/edit/foo
 189   * then the 1011 bitstring represents node/%/edit/foo where % means that
 190   * any argument matches that part.  We limit ourselves to using binary
 191   * numbers that correspond the patterns of wildcards of router items that
 192   * actually exists.  This list of 'masks' is built in menu_rebuild().
 193   *
 194   * @param $parts
 195   *   An array of path parts, for the above example
 196   *   array('node', '12345', 'edit').
 197   * @return
 198   *   An array which contains the ancestors and placeholders. Placeholders
 199   *   simply contain as many '%s' as the ancestors.
 200   */
 201  function menu_get_ancestors($parts) {
 202    $number_parts = count($parts);
 203    $placeholders = array();
 204    $ancestors = array();
 205    $length =  $number_parts - 1;
 206    $end = (1 << $number_parts) - 1;
 207    $masks = variable_get('menu_masks', array());
 208    // Only examine patterns that actually exist as router items (the masks).
 209    foreach ($masks as $i) {
 210      if ($i > $end) {
 211        // Only look at masks that are not longer than the path of interest.
 212        continue;
 213      }
 214      elseif ($i < (1 << $length)) {
 215        // We have exhausted the masks of a given length, so decrease the length.
 216        --$length;
 217      }
 218      $current = '';
 219      for ($j = $length; $j >= 0; $j--) {
 220        if ($i & (1 << $j)) {
 221          $current .= $parts[$length - $j];
 222        }
 223        else {
 224          $current .= '%';
 225        }
 226        if ($j) {
 227          $current .= '/';
 228        }
 229      }
 230      $placeholders[] = "'%s'";
 231      $ancestors[] = $current;
 232    }
 233    return array($ancestors, $placeholders);
 234  }
 235  
 236  /**
 237   * The menu system uses serialized arrays stored in the database for
 238   * arguments. However, often these need to change according to the
 239   * current path. This function unserializes such an array and does the
 240   * necessary change.
 241   *
 242   * Integer values are mapped according to the $map parameter. For
 243   * example, if unserialize($data) is array('view', 1) and $map is
 244   * array('node', '12345') then 'view' will not be changed because
 245   * it is not an integer, but 1 will as it is an integer. As $map[1]
 246   * is '12345', 1 will be replaced with '12345'. So the result will
 247   * be array('node_load', '12345').
 248   *
 249   * @param @data
 250   *   A serialized array.
 251   * @param @map
 252   *   An array of potential replacements.
 253   * @return
 254   *   The $data array unserialized and mapped.
 255   */
 256  function menu_unserialize($data, $map) {
 257    if ($data = unserialize($data)) {
 258      foreach ($data as $k => $v) {
 259        if (is_int($v)) {
 260          $data[$k] = isset($map[$v]) ? $map[$v] : '';
 261        }
 262      }
 263      return $data;
 264    }
 265    else {
 266      return array();
 267    }
 268  }
 269  
 270  
 271  
 272  /**
 273   * Replaces the statically cached item for a given path.
 274   *
 275   * @param $path
 276   *   The path.
 277   * @param $router_item
 278   *   The router item. Usually you take a router entry from menu_get_item and
 279   *   set it back either modified or to a different path. This lets you modify the
 280   *   navigation block, the page title, the breadcrumb and the page help in one
 281   *   call.
 282   */
 283  function menu_set_item($path, $router_item) {
 284    menu_get_item($path, $router_item);
 285  }
 286  
 287  /**
 288   * Get a router item.
 289   *
 290   * @param $path
 291   *   The path, for example node/5. The function will find the corresponding
 292   *   node/% item and return that.
 293   * @param $router_item
 294   *   Internal use only.
 295   * @return
 296   *   The router item, an associate array corresponding to one row in the
 297   *   menu_router table. The value of key map holds the loaded objects. The
 298   *   value of key access is TRUE if the current user can access this page.
 299   *   The values for key title, page_arguments, access_arguments will be
 300   *   filled in based on the database values and the objects loaded.
 301   */
 302  function menu_get_item($path = NULL, $router_item = NULL) {
 303    static $router_items;
 304    if (!isset($path)) {
 305      $path = $_GET['q'];
 306    }
 307    if (isset($router_item)) {
 308      $router_items[$path] = $router_item;
 309    }
 310    if (!isset($router_items[$path])) {
 311      $original_map = arg(NULL, $path);
 312      $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
 313      list($ancestors, $placeholders) = menu_get_ancestors($parts);
 314  
 315      if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
 316        $map = _menu_translate($router_item, $original_map);
 317        if ($map === FALSE) {
 318          $router_items[$path] = FALSE;
 319          return FALSE;
 320        }
 321        if ($router_item['access']) {
 322          $router_item['map'] = $map;
 323          $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
 324        }
 325      }
 326      $router_items[$path] = $router_item;
 327    }
 328    return $router_items[$path];
 329  }
 330  
 331  /**
 332   * Execute the page callback associated with the current path
 333   */
 334  function menu_execute_active_handler($path = NULL) {
 335    if (_menu_site_is_offline()) {
 336      return MENU_SITE_OFFLINE;
 337    }
 338    // Rebuild if we know it's needed, or if the menu masks are missing which
 339    // occurs rarely, likely due to a race condition of multiple rebuilds.
 340    if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
 341      menu_rebuild();
 342    }
 343    if ($router_item = menu_get_item($path)) {
 344      if ($router_item['access']) {
 345        if ($router_item['file']) {
 346          require_once($router_item['file']);
 347        }
 348        return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
 349      }
 350      else {
 351        return MENU_ACCESS_DENIED;
 352      }
 353    }
 354    return MENU_NOT_FOUND;
 355  }
 356  
 357  /**
 358   * Loads objects into the map as defined in the $item['load_functions'].
 359   *
 360   * @param $item
 361   *   A menu router or menu link item
 362   * @param $map
 363   *   An array of path arguments (ex: array('node', '5'))
 364   * @return
 365   *   Returns TRUE for success, FALSE if an object cannot be loaded.
 366   *   Names of object loading functions are placed in $item['load_functions'].
 367   *   Loaded objects are placed in $map[]; keys are the same as keys in the
 368   *   $item['load_functions'] array.
 369   *   $item['access'] is set to FALSE if an object cannot be loaded.
 370   */
 371  function _menu_load_objects(&$item, &$map) {
 372    if ($load_functions = $item['load_functions']) {
 373      // If someone calls this function twice, then unserialize will fail.
 374      if ($load_functions_unserialized = unserialize($load_functions)) {
 375        $load_functions = $load_functions_unserialized;
 376      }
 377      $path_map = $map;
 378      foreach ($load_functions as $index => $function) {
 379        if ($function) {
 380          $value = isset($path_map[$index]) ? $path_map[$index] : '';
 381          if (is_array($function)) {
 382            // Set up arguments for the load function. These were pulled from
 383            // 'load arguments' in the hook_menu() entry, but they need
 384            // some processing. In this case the $function is the key to the
 385            // load_function array, and the value is the list of arguments.
 386            list($function, $args) = each($function);
 387            $load_functions[$index] = $function;
 388  
 389            // Some arguments are placeholders for dynamic items to process.
 390            foreach ($args as $i => $arg) {
 391              if ($arg === '%index') {
 392                // Pass on argument index to the load function, so multiple
 393                // occurances of the same placeholder can be identified.
 394                $args[$i] = $index;
 395              }
 396              if ($arg === '%map') {
 397                // Pass on menu map by reference. The accepting function must
 398                // also declare this as a reference if it wants to modify
 399                // the map.
 400                $args[$i] = &$map;
 401              }
 402              if (is_int($arg)) {
 403                $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : '';
 404              }
 405            }
 406            array_unshift($args, $value);
 407            $return = call_user_func_array($function, $args);
 408          }
 409          else {
 410            $return = $function($value);
 411          }
 412          // If callback returned an error or there is no callback, trigger 404.
 413          if ($return === FALSE) {
 414            $item['access'] = FALSE;
 415            $map = FALSE;
 416            return FALSE;
 417          }
 418          $map[$index] = $return;
 419        }
 420      }
 421      $item['load_functions'] = $load_functions;
 422    }
 423    return TRUE;
 424  }
 425  
 426  /**
 427   * Check access to a menu item using the access callback
 428   *
 429   * @param $item
 430   *   A menu router or menu link item
 431   * @param $map
 432   *   An array of path arguments (ex: array('node', '5'))
 433   * @return
 434   *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
 435   */
 436  function _menu_check_access(&$item, $map) {
 437    // Determine access callback, which will decide whether or not the current
 438    // user has access to this path.
 439    $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
 440    // Check for a TRUE or FALSE value.
 441    if (is_numeric($callback)) {
 442      $item['access'] = (bool)$callback;
 443    }
 444    else {
 445      $arguments = menu_unserialize($item['access_arguments'], $map);
 446      // As call_user_func_array is quite slow and user_access is a very common
 447      // callback, it is worth making a special case for it.
 448      if ($callback == 'user_access') {
 449        $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
 450      }
 451      else {
 452        $item['access'] = call_user_func_array($callback, $arguments);
 453      }
 454    }
 455  }
 456  
 457  /**
 458   * Localize the router item title using t() or another callback.
 459   *
 460   * Translate the title and description to allow storage of English title
 461   * strings in the database, yet display of them in the language required
 462   * by the current user.
 463   *
 464   * @param $item
 465   *   A menu router item or a menu link item.
 466   * @param $map
 467   *   The path as an array with objects already replaced. E.g., for path
 468   *   node/123 $map would be array('node', $node) where $node is the node
 469   *   object for node 123.
 470   * @param $link_translate
 471   *   TRUE if we are translating a menu link item; FALSE if we are
 472   *   translating a menu router item.
 473   * @return
 474   *   No return value.
 475   *   $item['title'] is localized according to $item['title_callback'].
 476   *   If an item's callback is check_plain(), $item['options']['html'] becomes
 477   *   TRUE.
 478   *   $item['description'] is translated using t().
 479   *   When doing link translation and the $item['options']['attributes']['title']
 480   *   (link title attribute) matches the description, it is translated as well.
 481   */
 482  function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
 483    $callback = $item['title_callback'];
 484    $item['localized_options'] = $item['options'];
 485    // If we are translating the title of a menu link, and its title is the same
 486    // as the corresponding router item, then we can use the title information
 487    // from the router. If it's customized, then we need to use the link title
 488    // itself; can't localize.
 489    // If we are translating a router item (tabs, page, breadcrumb), then we
 490    // can always use the information from the router item.
 491    if (!$link_translate || ($item['title'] == $item['link_title'])) {
 492      // t() is a special case. Since it is used very close to all the time,
 493      // we handle it directly instead of using indirect, slower methods.
 494      if ($callback == 't') {
 495        if (empty($item['title_arguments'])) {
 496          $item['title'] = t($item['title']);
 497        }
 498        else {
 499          $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
 500        }
 501      }
 502      elseif ($callback) {
 503        if (empty($item['title_arguments'])) {
 504          $item['title'] = $callback($item['title']);
 505        }
 506        else {
 507          $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
 508        }
 509        // Avoid calling check_plain again on l() function.
 510        if ($callback == 'check_plain') {
 511          $item['localized_options']['html'] = TRUE;
 512        }
 513      }
 514    }
 515    elseif ($link_translate) {
 516      $item['title'] = $item['link_title'];
 517    }
 518  
 519    // Translate description, see the motivation above.
 520    if (!empty($item['description'])) {
 521      $original_description = $item['description'];
 522      $item['description'] = t($item['description']);
 523      if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
 524        $item['localized_options']['attributes']['title'] = $item['description'];
 525      }
 526    }
 527  }
 528  
 529  /**
 530   * Handles dynamic path translation and menu access control.
 531   *
 532   * When a user arrives on a page such as node/5, this function determines
 533   * what "5" corresponds to, by inspecting the page's menu path definition,
 534   * node/%node. This will call node_load(5) to load the corresponding node
 535   * object.
 536   *
 537   * It also works in reverse, to allow the display of tabs and menu items which
 538   * contain these dynamic arguments, translating node/%node to node/5.
 539   *
 540   * Translation of menu item titles and descriptions are done here to
 541   * allow for storage of English strings in the database, and translation
 542   * to the language required to generate the current page
 543   *
 544   * @param $router_item
 545   *   A menu router item
 546   * @param $map
 547   *   An array of path arguments (ex: array('node', '5'))
 548   * @param $to_arg
 549   *   Execute $item['to_arg_functions'] or not. Use only if you want to render a
 550   *   path from the menu table, for example tabs.
 551   * @return
 552   *   Returns the map with objects loaded as defined in the
 553   *   $item['load_functions']. $item['access'] becomes TRUE if the item is
 554   *   accessible, FALSE otherwise. $item['href'] is set according to the map.
 555   *   If an error occurs during calling the load_functions (like trying to load
 556   *   a non existing node) then this function return FALSE.
 557   */
 558  function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
 559    if ($to_arg) {
 560      // Fill in missing path elements, such as the current uid.
 561      _menu_link_map_translate($map, $router_item['to_arg_functions']);
 562    }
 563    // The $path_map saves the pieces of the path as strings, while elements in
 564    // $map may be replaced with loaded objects.
 565    $path_map = $map;
 566    if (!_menu_load_objects($router_item, $map)) {
 567      // An error occurred loading an object.
 568      $router_item['access'] = FALSE;
 569      return FALSE;
 570    }
 571  
 572    // Generate the link path for the page request or local tasks.
 573    $link_map = explode('/', $router_item['path']);
 574    for ($i = 0; $i < $router_item['number_parts']; $i++) {
 575      if ($link_map[$i] == '%') {
 576        $link_map[$i] = $path_map[$i];
 577      }
 578    }
 579    $router_item['href'] = implode('/', $link_map);
 580    $router_item['options'] = array();
 581    _menu_check_access($router_item, $map);
 582    
 583    // For performance, don't localize an item the user can't access.
 584    if ($router_item['access']) {
 585      _menu_item_localize($router_item, $map);
 586    }
 587  
 588    return $map;
 589  }
 590  
 591  /**
 592   * This function translates the path elements in the map using any to_arg
 593   * helper function. These functions take an argument and return an object.
 594   * See http://drupal.org/node/109153 for more information.
 595   *
 596   * @param map
 597   *   An array of path arguments (ex: array('node', '5'))
 598   * @param $to_arg_functions
 599   *   An array of helper function (ex: array(2 => 'menu_tail_to_arg'))
 600   */
 601  function _menu_link_map_translate(&$map, $to_arg_functions) {
 602    if ($to_arg_functions) {
 603      $to_arg_functions = unserialize($to_arg_functions);
 604      foreach ($to_arg_functions as $index => $function) {
 605        // Translate place-holders into real values.
 606        $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index);
 607        if (!empty($map[$index]) || isset($arg)) {
 608          $map[$index] = $arg;
 609        }
 610        else {
 611          unset($map[$index]);
 612        }
 613      }
 614    }
 615  }
 616  
 617  function menu_tail_to_arg($arg, $map, $index) {
 618    return implode('/', array_slice($map, $index));
 619  }
 620  
 621  /**
 622   * This function is similar to _menu_translate() but does link-specific
 623   * preparation such as always calling to_arg functions.
 624   *
 625   * @param $item
 626   *   A menu link
 627   * @return
 628   *   Returns the map of path arguments with objects loaded as defined in the
 629   *   $item['load_functions']:
 630   *   - $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
 631   *   - $item['href'] is generated from link_path, possibly by to_arg functions.
 632   *   - $item['title'] is generated from link_title, and may be localized.
 633   *   - $item['options'] is unserialized; it is also changed within the call
 634   *     here to $item['localized_options'] by _menu_item_localize().
 635   */
 636  function _menu_link_translate(&$item) {
 637    $item['options'] = unserialize($item['options']);
 638    if ($item['external']) {
 639      $item['access'] = 1;
 640      $map = array();
 641      $item['href'] = $item['link_path'];
 642      $item['title'] = $item['link_title'];
 643      $item['localized_options'] = $item['options'];
 644    }
 645    else {
 646      $map = explode('/', $item['link_path']);
 647      _menu_link_map_translate($map, $item['to_arg_functions']);
 648      $item['href'] = implode('/', $map);
 649  
 650      // Note - skip callbacks without real values for their arguments.
 651      if (strpos($item['href'], '%') !== FALSE) {
 652        $item['access'] = FALSE;
 653        return FALSE;
 654      }
 655      // menu_tree_check_access() may set this ahead of time for links to nodes.
 656      if (!isset($item['access'])) {
 657        if (!_menu_load_objects($item, $map)) {
 658          // An error occurred loading an object.
 659          $item['access'] = FALSE;
 660          return FALSE;
 661        }
 662        _menu_check_access($item, $map);
 663      }
 664      // For performance, don't localize a link the user can't access.
 665      if ($item['access']) {
 666        _menu_item_localize($item, $map, TRUE);
 667      }
 668    }
 669  
 670    // Allow other customizations - e.g. adding a page-specific query string to the
 671    // options array. For performance reasons we only invoke this hook if the link
 672    // has the 'alter' flag set in the options array.
 673    if (!empty($item['options']['alter'])) {
 674      drupal_alter('translated_menu_link', $item, $map);
 675    }
 676  
 677    return $map;
 678  }
 679  
 680  /**
 681   * Get a loaded object from a router item.
 682   *
 683   * menu_get_object() will provide you the current node on paths like node/5,
 684   * node/5/revisions/48 etc. menu_get_object('user') will give you the user
 685   * account on user/5 etc. Note - this function should never be called within a
 686   * _to_arg function (like user_current_to_arg()) since this may result in an
 687   * infinite recursion.
 688   *
 689   * @param $type
 690   *   Type of the object. These appear in hook_menu definitons as %type. Core
 691   *   provides aggregator_feed, aggregator_category, contact, filter_format,
 692   *   forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
 693   *   relevant {$type}_load function for more on each. Defaults to node.
 694   * @param $position
 695   *   The expected position for $type object. For node/%node this is 1, for
 696   *   comment/reply/%node this is 2. Defaults to 1.
 697   * @param $path
 698   *   See menu_get_item() for more on this. Defaults to the current path.
 699   */
 700  function menu_get_object($type = 'node', $position = 1, $path = NULL) {
 701    $router_item = menu_get_item($path);
 702    if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type .'_load') {
 703      return $router_item['map'][$position];
 704    }
 705  }
 706  
 707  /**
 708   * Render a menu tree based on the current path.
 709   *
 710   * The tree is expanded based on the current path and dynamic paths are also
 711   * changed according to the defined to_arg functions (for example the 'My account'
 712   * link is changed from user/% to a link with the current user's uid).
 713   *
 714   * @param $menu_name
 715   *   The name of the menu.
 716   * @return
 717   *   The rendered HTML of that menu on the current page.
 718   */
 719  function menu_tree($menu_name = 'navigation') {
 720    static $menu_output = array();
 721  
 722    if (!isset($menu_output[$menu_name])) {
 723      $tree = menu_tree_page_data($menu_name);
 724      $menu_output[$menu_name] = menu_tree_output($tree);
 725    }
 726    return $menu_output[$menu_name];
 727  }
 728  
 729  /**
 730   * Returns a rendered menu tree.
 731   *
 732   * @param $tree
 733   *   A data structure representing the tree as returned from menu_tree_data.
 734   * @return
 735   *   The rendered HTML of that data structure.
 736   */
 737  function menu_tree_output($tree) {
 738    $output = '';
 739    $items = array();
 740  
 741    // Pull out just the menu items we are going to render so that we
 742    // get an accurate count for the first/last classes.
 743    foreach ($tree as $data) {
 744      if (!$data['link']['hidden']) {
 745        $items[] = $data;
 746      }
 747    }
 748  
 749    $num_items = count($items);
 750    foreach ($items as $i => $data) {
 751      $extra_class = array();
 752      if ($i == 0) {
 753        $extra_class[] = 'first';
 754      }
 755      if ($i == $num_items - 1) {
 756        $extra_class[] = 'last';
 757      }
 758      $extra_class = implode(' ', $extra_class);
 759      $link = theme('menu_item_link', $data['link']);
 760      if ($data['below']) {
 761        $output .= theme('menu_item', $link, $data['link']['has_children'], menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class);
 762      }
 763      else {
 764        $output .= theme('menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class);
 765      }
 766    }
 767    return $output ? theme('menu_tree', $output) : '';
 768  }
 769  
 770  /**
 771   * Get the data structure representing a named menu tree.
 772   *
 773   * Since this can be the full tree including hidden items, the data returned
 774   * may be used for generating an an admin interface or a select.
 775   *
 776   * @param $menu_name
 777   *   The named menu links to return
 778   * @param $item
 779   *   A fully loaded menu link, or NULL.  If a link is supplied, only the
 780   *   path to root will be included in the returned tree- as if this link
 781   *   represented the current page in a visible menu.
 782   * @return
 783   *   An tree of menu links in an array, in the order they should be rendered.
 784   */
 785  function menu_tree_all_data($menu_name = 'navigation', $item = NULL) {
 786    static $tree = array();
 787  
 788    // Use $mlid as a flag for whether the data being loaded is for the whole tree.
 789    $mlid = isset($item['mlid']) ? $item['mlid'] : 0;
 790    // Generate a cache ID (cid) specific for this $menu_name and $item.
 791    $cid = 'links:'. $menu_name .':all-cid:'. $mlid;
 792  
 793    if (!isset($tree[$cid])) {
 794      // If the static variable doesn't have the data, check {cache_menu}.
 795      $cache = cache_get($cid, 'cache_menu');
 796      if ($cache && isset($cache->data)) {
 797        // If the cache entry exists, it will just be the cid for the actual data.
 798        // This avoids duplication of large amounts of data.
 799        $cache = cache_get($cache->data, 'cache_menu');
 800        if ($cache && isset($cache->data)) {
 801          $data = $cache->data;
 802        }
 803      }
 804      // If the tree data was not in the cache, $data will be NULL.
 805      if (!isset($data)) {
 806        // Build and run the query, and build the tree.
 807        if ($mlid) {
 808          // The tree is for a single item, so we need to match the values in its
 809          // p columns and 0 (the top level) with the plid values of other links.
 810          $args = array(0);
 811          for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
 812            $args[] = $item["p$i"];
 813          }
 814          $args = array_unique($args);
 815          $placeholders = implode(', ', array_fill(0, count($args), '%d'));
 816          $where = ' AND ml.plid IN ('. $placeholders .')';
 817          $parents = $args;
 818          $parents[] = $item['mlid'];
 819        }
 820        else {
 821          // Get all links in this menu.
 822          $where = '';
 823          $args = array();
 824          $parents = array();
 825        }
 826        array_unshift($args, $menu_name);
 827        // Select the links from the table, and recursively build the tree.  We
 828        // LEFT JOIN since there is no match in {menu_router} for an external
 829        // link.
 830        $data['tree'] = menu_tree_data(db_query("
 831          SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
 832          FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
 833          WHERE ml.menu_name = '%s'". $where ."
 834          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
 835        $data['node_links'] = array();
 836        menu_tree_collect_node_links($data['tree'], $data['node_links']);
 837        // Cache the data, if it is not already in the cache.
 838        $tree_cid = _menu_tree_cid($menu_name, $data);
 839        if (!cache_get($tree_cid, 'cache_menu')) {
 840          cache_set($tree_cid, $data, 'cache_menu');
 841        }
 842        // Cache the cid of the (shared) data using the menu and item-specific cid.
 843        cache_set($cid, $tree_cid, 'cache_menu');
 844      }
 845      // Check access for the current user to each item in the tree.
 846      menu_tree_check_access($data['tree'], $data['node_links']);
 847      $tree[$cid] = $data['tree'];
 848    }
 849  
 850    return $tree[$cid];
 851  }
 852  
 853  /**
 854   * Get the data structure representing a named menu tree, based on the current page.
 855   *
 856   * The tree order is maintained by storing each parent in an individual
 857   * field, see http://drupal.org/node/141866 for more.
 858   *
 859   * @param $menu_name
 860   *   The named menu links to return
 861   * @return
 862   *   An array of menu links, in the order they should be rendered. The array
 863   *   is a list of associative arrays -- these have two keys, link and below.
 864   *   link is a menu item, ready for theming as a link. Below represents the
 865   *   submenu below the link if there is one, and it is a subtree that has the
 866   *   same structure described for the top-level array.
 867   */
 868  function menu_tree_page_data($menu_name = 'navigation') {
 869    static $tree = array();
 870  
 871    // Load the menu item corresponding to the current page.
 872    if ($item = menu_get_item()) {
 873      // Generate a cache ID (cid) specific for this page.
 874      $cid = 'links:'. $menu_name .':page-cid:'. $item['href'] .':'. (int)$item['access'];
 875  
 876      if (!isset($tree[$cid])) {
 877        // If the static variable doesn't have the data, check {cache_menu}.
 878        $cache = cache_get($cid, 'cache_menu');
 879        if ($cache && isset($cache->data)) {
 880          // If the cache entry exists, it will just be the cid for the actual data.
 881          // This avoids duplication of large amounts of data.
 882          $cache = cache_get($cache->data, 'cache_menu');
 883          if ($cache && isset($cache->data)) {
 884            $data = $cache->data;
 885          }
 886        }
 887        // If the tree data was not in the cache, $data will be NULL.
 888        if (!isset($data)) {
 889          // Build and run the query, and build the tree.
 890          if ($item['access']) {
 891            // Check whether a menu link exists that corresponds to the current path.
 892            $args = array($menu_name, $item['href']);
 893            $placeholders = "'%s'";
 894            if (drupal_is_front_page()) {
 895              $args[] = '<front>';
 896              $placeholders .= ", '%s'";
 897            }
 898            $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path IN (". $placeholders .")", $args));
 899  
 900            if (empty($parents)) {
 901              // If no link exists, we may be on a local task that's not in the links.
 902              // TODO: Handle the case like a local task on a specific node in the menu.
 903              $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
 904            }
 905            // We always want all the top-level links with plid == 0.
 906            $parents[] = '0';
 907  
 908            // Use array_values() so that the indices are numeric for array_merge().
 909            $args = $parents = array_unique(array_values($parents));
 910            $placeholders = implode(', ', array_fill(0, count($args), '%d'));
 911            $expanded = variable_get('menu_expanded', array());
 912            // Check whether the current menu has any links set to be expanded.
 913            if (in_array($menu_name, $expanded)) {
 914              // Collect all the links set to be expanded, and then add all of
 915              // their children to the list as well.
 916              do {
 917                $result = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND expanded = 1 AND has_children = 1 AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args));
 918                $num_rows = FALSE;
 919                while ($item = db_fetch_array($result)) {
 920                  $args[] = $item['mlid'];
 921                  $num_rows = TRUE;
 922                }
 923                $placeholders = implode(', ', array_fill(0, count($args), '%d'));
 924              } while ($num_rows);
 925            }
 926            array_unshift($args, $menu_name);
 927          }
 928          else {
 929            // Show only the top-level menu items when access is denied.
 930            $args = array($menu_name, '0');
 931            $placeholders = '%d';
 932            $parents = array();
 933          }
 934          // Select the links from the table, and recursively build the tree. We
 935          // LEFT JOIN since there is no match in {menu_router} for an external
 936          // link.
 937          $data['tree'] = menu_tree_data(db_query("
 938            SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
 939            FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
 940            WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
 941            ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
 942          $data['node_links'] = array();
 943          menu_tree_collect_node_links($data['tree'], $data['node_links']);
 944          // Cache the data, if it is not already in the cache.
 945          $tree_cid = _menu_tree_cid($menu_name, $data);
 946          if (!cache_get($tree_cid, 'cache_menu')) {
 947            cache_set($tree_cid, $data, 'cache_menu');
 948          }
 949          // Cache the cid of the (shared) data using the page-specific cid.
 950          cache_set($cid, $tree_cid, 'cache_menu');
 951        }
 952        // Check access for the current user to each item in the tree.
 953        menu_tree_check_access($data['tree'], $data['node_links']);
 954        $tree[$cid] = $data['tree'];
 955      }
 956      return $tree[$cid];
 957    }
 958  
 959    return array();
 960  }
 961  
 962  /**
 963   * Helper function - compute the real cache ID for menu tree data.
 964   */
 965  function _menu_tree_cid($menu_name, $data) {
 966    return 'links:'. $menu_name .':tree-data:'. md5(serialize($data));
 967  }
 968  
 969  /**
 970   * Recursive helper function - collect node links.
 971   *
 972   * @param $tree
 973   *   The menu tree you wish to collect node links from.
 974   * @param $node_links
 975   *   An array in which to store the collected node links.
 976   */
 977  function menu_tree_collect_node_links(&$tree, &$node_links) {
 978    foreach ($tree as $key => $v) {
 979      if ($tree[$key]['link']['router_path'] == 'node/%') {
 980        $nid = substr($tree[$key]['link']['link_path'], 5);
 981        if (is_numeric($nid)) {
 982          $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
 983          $tree[$key]['link']['access'] = FALSE;
 984        }
 985      }
 986      if ($tree[$key]['below']) {
 987        menu_tree_collect_node_links($tree[$key]['below'], $node_links);
 988      }
 989    }
 990  }
 991  
 992  /**
 993   * Check access and perform other dynamic operations for each link in the tree.
 994   *
 995   * @param $tree
 996   *   The menu tree you wish to operate on.
 997   * @param $node_links
 998   *   A collection of node link references generated from $tree by
 999   *   menu_tree_collect_node_links().
1000   */
1001  function menu_tree_check_access(&$tree, $node_links = array()) {
1002  
1003    if ($node_links) {
1004      // Use db_rewrite_sql to evaluate view access without loading each full node.
1005      $nids = array_keys($node_links);
1006      $placeholders = '%d'. str_repeat(', %d', count($nids) - 1);
1007      $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.status = 1 AND n.nid IN (". $placeholders .")"), $nids);
1008      while ($node = db_fetch_array($result)) {
1009        $nid = $node['nid'];
1010        foreach ($node_links[$nid] as $mlid => $link) {
1011          $node_links[$nid][$mlid]['access'] = TRUE;
1012        }
1013      }
1014    }
1015    _menu_tree_check_access($tree);
1016    return;
1017  }
1018  
1019  /**
1020   * Recursive helper function for menu_tree_check_access()
1021   */
1022  function _menu_tree_check_access(&$tree) {
1023    $new_tree = array();
1024    foreach ($tree as $key => $v) {
1025      $item = &$tree[$key]['link'];
1026      _menu_link_translate($item);
1027      if ($item['access']) {
1028        if ($tree[$key]['below']) {
1029          _menu_tree_check_access($tree[$key]['below']);
1030        }
1031        // The weights are made a uniform 5 digits by adding 50000 as an offset.
1032        // After _menu_link_translate(), $item['title'] has the localized link title.
1033        // Adding the mlid to the end of the index insures that it is unique.
1034        $new_tree[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $tree[$key];
1035      }
1036    }
1037    // Sort siblings in the tree based on the weights and localized titles.
1038    ksort($new_tree);
1039    $tree = $new_tree;
1040  }
1041  
1042  /**
1043   * Build the data representing a menu tree.
1044   *
1045   * @param $result
1046   *   The database result.
1047   * @param $parents
1048   *   An array of the plid values that represent the path from the current page
1049   *   to the root of the menu tree.
1050   * @param $depth
1051   *   The depth of the current menu tree.
1052   * @return
1053   *   See menu_tree_page_data for a description of the data structure.
1054   */
1055  function menu_tree_data($result = NULL, $parents = array(), $depth = 1) {
1056    list(, $tree) = _menu_tree_data($result, $parents, $depth);
1057    return $tree;
1058  }
1059  
1060  /**
1061   * Recursive helper function to build the data representing a menu tree.
1062   *
1063   * The function is a bit complex because the rendering of an item depends on
1064   * the next menu item. So we are always rendering the element previously
1065   * processed not the current one.
1066   */
1067  function _menu_tree_data($result, $parents, $depth, $previous_element = '') {
1068    $remnant = NULL;
1069    $tree = array();
1070    while ($item = db_fetch_array($result)) {
1071      // We need to determine if we're on the path to root so we can later build
1072      // the correct active trail and breadcrumb.
1073      $item['in_active_trail'] = in_array($item['mlid'], $parents);
1074      // The current item is the first in a new submenu.
1075      if ($item['depth'] > $depth) {
1076        // _menu_tree returns an item and the menu tree structure.
1077        list($item, $below) = _menu_tree_data($result, $parents, $item['depth'], $item);
1078        if ($previous_element) {
1079          $tree[$previous_element['mlid']] = array(
1080            'link' => $previous_element,
1081            'below' => $below,
1082          );
1083        }
1084        else {
1085          $tree = $below;
1086        }
1087        // We need to fall back one level.
1088        if (!isset($item) || $item['depth'] < $depth) {
1089          return array($item, $tree);
1090        }
1091        // This will be the link to be output in the next iteration.
1092        $previous_element = $item;
1093      }
1094      // We are at the same depth, so we use the previous element.
1095      elseif ($item['depth'] == $depth) {
1096        if ($previous_element) {
1097          // Only the first time.
1098          $tree[$previous_element['mlid']] = array(
1099            'link' => $previous_element,
1100            'below' => FALSE,
1101          );
1102        }
1103        // This will be the link to be output in the next iteration.
1104        $previous_element = $item;
1105      }
1106      // The submenu ended with the previous item, so pass back the current item.
1107      else {
1108        $remnant = $item;
1109        break;
1110      }
1111    }
1112    if ($previous_element) {
1113      // We have one more link dangling.
1114      $tree[$previous_element['mlid']] = array(
1115        'link' => $previous_element,
1116        'below' => FALSE,
1117      );
1118    }
1119    return array($remnant, $tree);
1120  }
1121  
1122  /**
1123   * Generate the HTML output for a single menu link.
1124   *
1125   * @ingroup themeable
1126   */
1127  function theme_menu_item_link($link) {
1128    if (empty($link['localized_options'])) {
1129      $link['localized_options'] = array();
1130    }
1131  
1132    return l($link['title'], $link['href'], $link['localized_options']);
1133  }
1134  
1135  /**
1136   * Generate the HTML output for a menu tree
1137   *
1138   * @ingroup themeable
1139   */
1140  function theme_menu_tree($tree) {
1141    return '<ul class="menu">'. $tree .'</ul>';
1142  }
1143  
1144  /**
1145   * Generate the HTML output for a menu item and submenu.
1146   *
1147   * @ingroup themeable
1148   */
1149  function theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) {
1150    $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
1151    if (!empty($extra_class)) {
1152      $class .= ' '. $extra_class;
1153    }
1154    if ($in_active_trail) {
1155      $class .= ' active-trail';
1156    }
1157    return '<li class="'. $class .'">'. $link . $menu ."</li>\n";
1158  }
1159  
1160  /**
1161   * Generate the HTML output for a single local task link.
1162   *
1163   * @ingroup themeable
1164   */
1165  function theme_menu_local_task($link, $active = FALSE) {
1166    return '<li '. ($active ? 'class="active" ' : '') .'>'. $link ."</li>\n";
1167  }
1168  
1169  /**
1170   * Generates elements for the $arg array in the help hook.
1171   */
1172  function drupal_help_arg($arg = array()) {
1173    // Note - the number of empty elements should be > MENU_MAX_PARTS.
1174    return $arg + array('', '', '', '', '', '', '', '', '', '', '', '');
1175  }
1176  
1177  /**
1178   * Returns the help associated with the active menu item.
1179   */
1180  function menu_get_active_help() {
1181    $output = '';
1182    $router_path = menu_tab_root_path();
1183    // We will always have a path unless we are on a 403 or 404.
1184    if (!$router_path) {
1185      return '';
1186    }
1187  
1188    $arg = drupal_help_arg(arg(NULL));
1189    $empty_arg = drupal_help_arg();
1190  
1191    foreach (module_list() as $name) {
1192      if (module_hook($name, 'help')) {
1193        // Lookup help for this path.
1194        if ($help = module_invoke($name, 'help', $router_path, $arg)) {
1195          $output .= $help ."\n";
1196        }
1197        // Add "more help" link on admin pages if the module provides a
1198        // standalone help page.
1199        if ($arg[0] == "admin" && module_exists('help') && module_invoke($name, 'help', 'admin/help#'. $arg[2], $empty_arg) && $help) {
1200          $output .= theme("more_help_link", url('admin/help/'. $arg[2]));
1201        }
1202      }
1203    }
1204    return $output;
1205  }
1206  
1207  /**
1208   * Build a list of named menus.
1209   */
1210  function menu_get_names($reset = FALSE) {
1211    static $names;
1212  
1213    if ($reset || empty($names)) {
1214      $names = array();
1215      $result = db_query("SELECT DISTINCT(menu_name) FROM {menu_links} ORDER BY menu_name");
1216      while ($name = db_fetch_array($result)) {
1217        $names[] = $name['menu_name'];
1218      }
1219    }
1220    return $names;
1221  }
1222  
1223  /**
1224   * Return an array containing the names of system-defined (default) menus.
1225   */
1226  function menu_list_system_menus() {
1227    return array('navigation', 'primary-links', 'secondary-links');
1228  }
1229  
1230  /**
1231   * Return an array of links to be rendered as the Primary links.
1232   */
1233  function menu_primary_links() {
1234    return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'));
1235  }
1236  
1237  /**
1238   * Return an array of links to be rendered as the Secondary links.
1239   */
1240  function menu_secondary_links() {
1241  
1242    // If the secondary menu source is set as the primary menu, we display the
1243    // second level of the primary menu.
1244    if (variable_get('menu_secondary_links_source', 'secondary-links') == variable_get('menu_primary_links_source', 'primary-links')) {
1245      return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'), 1);
1246    }
1247    else {
1248      return menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 0);
1249    }
1250  }
1251  
1252  /**
1253   * Return an array of links for a navigation menu.
1254   *
1255   * @param $menu_name
1256   *   The name of the menu.
1257   * @param $level
1258   *   Optional, the depth of the menu to be returned.
1259   * @return
1260   *   An array of links of the specified menu and level.
1261   */
1262  function menu_navigation_links($menu_name, $level = 0) {
1263    // Don't even bother querying the menu table if no menu is specified.
1264    if (empty($menu_name)) {
1265      return array();
1266    }
1267  
1268    // Get the menu hierarchy for the current page.
1269    $tree = menu_tree_page_data($menu_name);
1270  
1271    // Go down the active trail until the right level is reached.
1272    while ($level-- > 0 && $tree) {
1273      // Loop through the current level's items until we find one that is in trail.
1274      while ($item = array_shift($tree)) {
1275        if ($item['link']['in_active_trail']) {
1276          // If the item is in the active trail, we continue in the subtree.
1277          $tree = empty($item['below']) ? array() : $item['below'];
1278          break;
1279        }
1280      }
1281    }
1282  
1283    // Create a single level of links.
1284    $links = array();
1285    foreach ($tree as $item) {
1286      if (!$item['link']['hidden']) {
1287        $class = '';
1288        $l = $item['link']['localized_options'];
1289        $l['href'] = $item['link']['href'];
1290        $l['title'] = $item['link']['title'];
1291        if ($item['link']['in_active_trail']) {
1292          $class = ' active-trail';
1293        }
1294        // Keyed with the unique mlid to generate classes in theme_links().
1295        $links['menu-'. $item['link']['mlid'] . $class] = $l;
1296      }
1297    }
1298    return $links;
1299  }
1300  
1301  /**
1302   * Collects the local tasks (tabs) for a given level.
1303   *
1304   * @param $level
1305   *   The level of tasks you ask for. Primary tasks are 0, secondary are 1.
1306   * @param $return_root
1307   *   Whether to return the root path for the current page.
1308   * @return
1309   *   Themed output corresponding to the tabs of the requested level, or
1310   *   router path if $return_root == TRUE. This router path corresponds to
1311   *   a parent tab, if the current page is a default local task.
1312   */
1313  function menu_local_tasks($level = 0, $return_root = FALSE) {
1314    static $tabs;
1315    static $root_path;
1316  
1317    if (!isset($tabs)) {
1318      $tabs = array();
1319  
1320      $router_item = menu_get_item();
1321      if (!$router_item || !$router_item['access']) {
1322        return '';
1323      }
1324      // Get all tabs and the root page.
1325      $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' ORDER BY weight, title", $router_item['tab_root']);
1326      $map = arg();
1327      $children = array();
1328      $tasks = array();
1329      $root_path = $router_item['path'];
1330  
1331      while ($item = db_fetch_array($result)) {
1332        _menu_translate($item, $map, TRUE);
1333        if ($item['tab_parent']) {
1334          // All tabs, but not the root page.
1335          $children[$item['tab_parent']][$item['path']] = $item;
1336        }
1337        // Store the translated item for later use.
1338        $tasks[$item['path']] = $item;
1339      }
1340  
1341      // Find all tabs below the current path.
1342      $path = $router_item['path'];
1343      // Tab parenting may skip levels, so the number of parts in the path may not
1344      // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
1345      $depth = 1001;
1346      while (isset($children[$path])) {
1347        $tabs_current = '';
1348        $next_path = '';
1349        $count = 0;
1350        foreach ($children[$path] as $item) {
1351          if ($item['access']) {
1352            $count++;
1353            // The default task is always active.
1354            if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
1355              // Find the first parent which is not a default local task.
1356              for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
1357              $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item);
1358              $tabs_current .= theme('menu_local_task', $link, TRUE);
1359              $next_path = $item['path'];
1360            }
1361            else {
1362              $link = theme('menu_item_link', $item);
1363              $tabs_current .= theme('menu_local_task', $link);
1364            }
1365          }
1366        }
1367        $path = $next_path;
1368        $tabs[$depth]['count'] = $count;
1369        $tabs[$depth]['output'] = $tabs_current;
1370        $depth++;
1371      }
1372  
1373      // Find all tabs at the same level or above the current one.
1374      $parent = $router_item['tab_parent'];
1375      $path = $router_item['path'];
1376      $current = $router_item;
1377      $depth = 1000;
1378      while (isset($children[$parent])) {
1379        $tabs_current = '';
1380        $next_path = '';
1381        $next_parent = '';
1382        $count = 0;
1383        foreach ($children[$parent] as $item) {
1384          if ($item['access']) {
1385            $count++;
1386            if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
1387              // Find the first parent which is not a default local task.
1388              for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
1389              $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item);
1390              if ($item['path'] == $router_item['path']) {
1391                $root_path = $tasks[$p]['path'];
1392              }
1393            }
1394            else {
1395              $link = theme('menu_item_link', $item);
1396            }
1397            // We check for the active tab.
1398            if ($item['path'] == $path) {
1399              $tabs_current .= theme('menu_local_task', $link, TRUE);
1400              $next_path = $item['tab_parent'];
1401              if (isset($tasks[$next_path])) {
1402                $next_parent = $tasks[$next_path]['tab_parent'];
1403              }
1404            }
1405            else {
1406              $tabs_current .= theme('menu_local_task', $link);
1407            }
1408          }
1409        }
1410        $path = $next_path;
1411        $parent = $next_parent;
1412        $tabs[$depth]['count'] = $count;
1413        $tabs[$depth]['output'] = $tabs_current;
1414        $depth--;
1415      }
1416      // Sort by depth.
1417      ksort($tabs);
1418      // Remove the depth, we are interested only in their relative placement.
1419      $tabs = array_values($tabs);
1420    }
1421  
1422    if ($return_root) {
1423      return $root_path;
1424    }
1425    else {
1426      // We do not display single tabs.
1427      return (isset($tabs[$level]) && $tabs[$level]['count'] > 1) ? $tabs[$level]['output'] : '';
1428    }
1429  }
1430  
1431  /**
1432   * Returns the rendered local tasks at the top level.
1433   */
1434  function menu_primary_local_tasks() {
1435    return menu_local_tasks(0);
1436  }
1437  
1438  /**
1439   * Returns the rendered local tasks at the second level.
1440   */
1441  function menu_secondary_local_tasks() {
1442    return menu_local_tasks(1);
1443  }
1444  
1445  /**
1446   * Returns the router path, or the path of the parent tab of a default local task.
1447   */
1448  function menu_tab_root_path() {
1449    return menu_local_tasks(0, TRUE);
1450  }
1451  
1452  /**
1453   * Returns the rendered local tasks. The default implementation renders them as tabs.
1454   *
1455   * @ingroup themeable
1456   */
1457  function theme_menu_local_tasks() {
1458    $output = '';
1459  
1460    if ($primary = menu_primary_local_tasks()) {
1461      $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
1462    }
1463    if ($secondary = menu_secondary_local_tasks()) {
1464      $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
1465    }
1466  
1467    return $output;
1468  }
1469  
1470  /**
1471   * Set (or get) the active menu for the current page - determines the active trail.
1472   */
1473  function menu_set_active_menu_name($menu_name = NULL) {
1474    static $active;
1475  
1476    if (isset($menu_name)) {
1477      $active = $menu_name;
1478    }
1479    elseif (!isset($active)) {
1480      $active = 'navigation';
1481    }
1482    return $active;
1483  }
1484  
1485  /**
1486   * Get the active menu for the current page - determines the active trail.
1487   */
1488  function menu_get_active_menu_name() {
1489    return menu_set_active_menu_name();
1490  }
1491  
1492  /**
1493   * Set the active path, which determines which page is loaded.
1494   *
1495   * @param $path
1496   *   A Drupal path - not a path alias.
1497   *
1498   * Note that this may not have the desired effect unless invoked very early
1499   * in the page load, such as during hook_boot, or unless you call
1500   * menu_execute_active_handler() to generate your page output.
1501   */
1502  function menu_set_active_item($path) {
1503    $_GET['q'] = $path;
1504  }
1505  
1506  /**
1507   * Sets or gets the active trail (path to root menu root) of the current page.
1508   *
1509   * @param $new_trail
1510   *   Menu trail to set, or NULL to use previously-set or calculated trail. If
1511   *   supplying a trail, use the same format as the return value (see below).
1512   *
1513   * @return
1514   *   Path to menu root of the current page, as an array of menu link items,
1515   *   starting with the site's home page. Each link item is an associative array
1516   *   with the following components:
1517   *   - title: Title of the item.
1518   *   - href: Drupal path of the item.
1519   *   - localized_options: Options for passing into the l() function.
1520   *   - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to
1521   *     indicate it's not really in the menu (used for the home page item).
1522   *   If $new_trail is supplied, the value is saved in a static variable and
1523   *   returned. If $new_trail is not supplied, and there is a saved value from
1524   *   a previous call, the saved value is returned. If $new_trail is not supplied
1525   *   and there is no saved value, the path to the current page is calculated,
1526   *   saved as the static value, and returned.
1527   */
1528  function menu_set_active_trail($new_trail = NULL) {
1529    static $trail;
1530  
1531    if (isset($new_trail)) {
1532      $trail = $new_trail;
1533    }
1534    elseif (!isset($trail)) {
1535      $trail = array();
1536      $trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array(), 'type' => 0);
1537      $item = menu_get_item();
1538  
1539      // Check whether the current item is a local task (displayed as a tab).
1540      if ($item['tab_parent']) {
1541        // The title of a local task is used for the tab, never the page title.
1542        // Thus, replace it with the item corresponding to the root path to get
1543        // the relevant href and title.  For example, the menu item corresponding
1544        // to 'admin' is used when on the 'By module' tab at 'admin/by-module'.
1545        $parts = explode('/', $item['tab_root']);
1546        $args = arg();
1547        // Replace wildcards in the root path using the current path.
1548        foreach ($parts as $index => $part) {
1549          if ($part == '%') {
1550            $parts[$index] = $args[$index];
1551          }
1552        }
1553        // Retrieve the menu item using the root path after wildcard replacement.
1554        $root_item = menu_get_item(implode('/', $parts));
1555        if ($root_item && $root_item['access']) {
1556          $item = $root_item;
1557        }
1558      }
1559  
1560      $tree = menu_tree_page_data(menu_get_active_menu_name());
1561      list($key, $curr) = each($tree);
1562  
1563      while ($curr) {
1564        // Terminate the loop when we find the current path in the active trail.
1565        if ($curr['link']['href'] == $item['href']) {
1566          $trail[] = $curr['link'];
1567          $curr = FALSE;
1568        }
1569        else {
1570          // Add the link if it's in the active trail, then move to the link below.
1571          if ($curr['link']['in_active_trail']) {
1572            $trail[] = $curr['link'];
1573            $tree = $curr['below'] ? $curr['below'] : array();
1574          }
1575          list($key, $curr) = each($tree);
1576        }
1577      }
1578      // Make sure the current page is in the trail (needed for the page title),
1579      // but exclude tabs and the front page.
1580      $last = count($trail) - 1;
1581      if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) {
1582        $trail[] = $item;
1583      }
1584    }
1585    return $trail;
1586  }
1587  
1588  /**
1589   * Gets the active trail (path to root menu root) of the current page.
1590   *
1591   * See menu_set_active_trail() for details of return value.
1592   */
1593  function menu_get_active_trail() {
1594    return menu_set_active_trail();
1595  }
1596  
1597  /**
1598   * Get the breadcrumb for the current page, as determined by the active trail.
1599   */
1600  function menu_get_active_breadcrumb() {
1601    $breadcrumb = array();
1602  
1603    // No breadcrumb for the front page.
1604    if (drupal_is_front_page()) {
1605      return $breadcrumb;
1606    }
1607  
1608    $item = menu_get_item();
1609    if ($item && $item['access']) {
1610      $active_trail = menu_get_active_trail();
1611  
1612      foreach ($active_trail as $parent) {
1613        $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
1614      }
1615      $end = end($active_trail);
1616  
1617      // Don't show a link to the current page in the breadcrumb trail.
1618      if ($item['href'] == $end['href'] || ($item['type'] == MENU_DEFAULT_LOCAL_TASK && $end['href'] != '<front>')) {
1619        array_pop($breadcrumb);
1620      }
1621    }
1622    return $breadcrumb;
1623  }
1624  
1625  /**
1626   * Get the title of the current page, as determined by the active trail.
1627   */
1628  function menu_get_active_title() {
1629    $active_trail = menu_get_active_trail();
1630  
1631    foreach (array_reverse($active_trail) as $item) {
1632      if (!(bool)($item['type'] & MENU_IS_LOCAL_TASK)) {
1633        return $item['title'];
1634      }
1635    }
1636  }
1637  
1638  /**
1639   * Get a menu link by its mlid, access checked and link translated for rendering.
1640   *
1641   * This function should never be called from within node_load() or any other
1642   * function used as a menu object load function since an infinite recursion may
1643   * occur.
1644   *
1645   * @param $mlid
1646   *   The mlid of the menu item.
1647   * @return
1648   *   A menu link, with $item['access'] filled and link translated for
1649   *   rendering.
1650   */
1651  function menu_link_load($mlid) {
1652    if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
1653      _menu_link_translate($item);
1654      return $item;
1655    }
1656    return FALSE;
1657  }
1658  
1659  /**
1660   * Clears the cached cached data for a single named menu.
1661   */
1662  function menu_cache_clear($menu_name = 'navigation') {
1663    static $cache_cleared = array();
1664  
1665    if (empty($cache_cleared[$menu_name])) {
1666      cache_clear_all('links:'. $menu_name .':', 'cache_menu', TRUE);
1667      $cache_cleared[$menu_name] = 1;
1668    }
1669    elseif ($cache_cleared[$menu_name] == 1) {
1670      register_shutdown_function('cache_clear_all', 'links:'. $menu_name .':', 'cache_menu', TRUE);
1671      $cache_cleared[$menu_name] = 2;
1672    }
1673  }
1674  
1675  /**
1676   * Clears all cached menu data.  This should be called any time broad changes
1677   * might have been made to the router items or menu links.
1678   */
1679  function menu_cache_clear_all() {
1680    cache_clear_all('*', 'cache_menu', TRUE);
1681  }
1682  
1683  /**
1684   * (Re)populate the database tables used by various menu functions.
1685   *
1686   * This function will clear and populate the {menu_router} table, add entries
1687   * to {menu_links} for new router items, then remove stale items from
1688   * {menu_links}. If called from update.php or install.php, it will also
1689   * schedule a call to itself on the first real page load from
1690   * menu_execute_active_handler(), because the maintenance page environment
1691   * is different and leaves stale data in the menu tables.
1692   */
1693  function menu_rebuild() {
1694    if (!lock_acquire('menu_rebuild')) {
1695      // Wait for another request that is already doing this work.
1696      // We choose to block here since otherwise the router item may not 
1697      // be avaiable in menu_execute_active_handler() resulting in a 404.
1698      lock_wait('menu_rebuild');
1699      return FALSE;
1700    }
1701  
1702    $menu = menu_router_build(TRUE);
1703    _menu_navigation_links_rebuild($menu);
1704    // Clear the menu, page and block caches.
1705    menu_cache_clear_all();
1706    _menu_clear_page_cache();
1707    
1708    if (defined('MAINTENANCE_MODE')) {
1709      variable_set('menu_rebuild_needed', TRUE);
1710    }
1711    else {
1712      variable_del('menu_rebuild_needed');
1713    }
1714    lock_release('menu_rebuild');
1715    return TRUE;
1716  }
1717  
1718  /**
1719   * Collect, alter and store the menu definitions.
1720   */
1721  function menu_router_build($reset = FALSE) {
1722    static $menu;
1723  
1724    if (!isset($menu) || $reset) {
1725      // We need to manually call each module so that we can know which module
1726      // a given item came from.
1727      $callbacks = array();
1728      foreach (module_implements('menu') as $module) {
1729        $router_items = call_user_func($module .'_menu');
1730        if (isset($router_items) && is_array($router_items)) {
1731          foreach (array_keys($router_items) as $path) {
1732            $router_items[$path]['module'] = $module;
1733          }
1734          $callbacks = array_merge($callbacks, $router_items);
1735        }
1736      }
1737      // Alter the menu as defined in modules, keys are like user/%user.
1738      drupal_alter('menu', $callbacks);
1739      $menu = _menu_router_build($callbacks);
1740      _menu_router_cache($menu);
1741    }
1742    return $menu;
1743  }
1744  
1745  /**
1746   * Helper function to store the menu router if we have it in memory.
1747   */
1748  function _menu_router_cache($new_menu = NULL) {
1749    static $menu = NULL;
1750  
1751    if (isset($new_menu)) {
1752      $menu = $new_menu;
1753    }
1754    return $menu;
1755  }
1756  
1757  /**
1758   * Builds a link from a router item.
1759   */
1760  function _menu_link_build($item) {
1761    if ($item['type'] == MENU_CALLBACK) {
1762      $item['hidden'] = -1;
1763    }
1764    elseif ($item['type'] == MENU_SUGGESTED_ITEM) {
1765      $item['hidden'] = 1;
1766    }
1767    // Note, we set this as 'system', so that we can be sure to distinguish all
1768    // the menu links generated automatically from entries in {menu_router}.
1769    $item['module'] = 'system';
1770    $item += array(
1771      'menu_name' => 'navigation',
1772      'link_title' => $item['title'],
1773      'link_path' => $item['path'],
1774      'hidden' => 0,
1775      'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
1776    );
1777    return $item;
1778  }
1779  
1780  /**
1781   * Helper function to build menu links for the items in the menu router.
1782   */
1783  function _menu_navigation_links_rebuild($menu) {
1784    // Add normal and suggested items as links.
1785    $menu_links = array();
1786    foreach ($menu as $path => $item) {
1787      if ($item['_visible']) {
1788        $item = _menu_link_build($item);
1789        $menu_links[$path] = $item;
1790        $sort[$path] = $item['_number_parts'];
1791      }
1792    }
1793    if ($menu_links) {
1794      // Make sure no child comes before its parent.
1795      array_multisort($sort, SORT_NUMERIC, $menu_links);
1796  
1797      foreach ($menu_links as $item) {
1798        $existing_item = db_fetch_array(db_query("SELECT mlid, menu_name, plid, customized, has_children, updated FROM {menu_links} WHERE link_path = '%s' AND module = '%s'", $item['link_path'], 'system'));
1799        if ($existing_item) {
1800          $item['mlid'] = $existing_item['mlid'];
1801          // A change in hook_menu may move the link to a different menu
1802          if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) {
1803            $item['menu_name'] = $existing_item['menu_name'];
1804            $item['plid'] = $existing_item['plid'];
1805          }
1806          $item['has_children'] = $existing_item['has_children'];
1807          $item['updated'] = $existing_item['updated'];
1808        }
1809        if (!$existing_item || !$existing_item['customized']) {
1810          menu_link_save($item);
1811        }
1812      }
1813    }
1814    $placeholders = db_placeholders($menu, 'varchar');
1815    $paths = array_keys($menu);
1816    // Updated and customized items whose router paths are gone need new ones.
1817    $result = db_query("SELECT ml.link_path, ml.mlid, ml.router_path, ml.updated FROM {menu_links} ml WHERE ml.updated = 1 OR (router_path NOT IN ($placeholders) AND external = 0 AND customized = 1)", $paths);
1818    while ($item = db_fetch_array($result)) {
1819      $router_path = _menu_find_router_path($item['link_path']);
1820      if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) {
1821        // If the router path and the link path matches, it's surely a working
1822        // item, so we clear the updated flag.
1823        $updated = $item['updated'] && $router_path != $item['link_path'];
1824        db_query("UPDATE {menu_links} SET router_path = '%s', updated = %d WHERE mlid = %d", $router_path, $updated, $item['mlid']);
1825      }
1826    }
1827    // Find any item whose router path does not exist any more.
1828    $result = db_query("SELECT * FROM {menu_links} WHERE router_path NOT IN ($placeholders) AND external = 0 AND updated = 0 AND customized = 0 ORDER BY depth DESC", $paths);
1829    // Remove all such items. Starting from those with the greatest depth will
1830    // minimize the amount of re-parenting done by menu_link_delete().
1831    while ($item = db_fetch_array($result)) {
1832      _menu_delete_item($item, TRUE);
1833    }
1834  }
1835  
1836  /**
1837   * Delete one or several menu links.
1838   *
1839   * @param $mlid
1840   *   A valid menu link mlid or NULL. If NULL, $path is used.
1841   * @param $path
1842   *   The path to the menu items to be deleted. $mlid must be NULL.
1843   */
1844  function menu_link_delete($mlid, $path = NULL) {
1845    if (isset($mlid)) {
1846      _menu_delete_item(db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $mlid)));
1847    }
1848    else {
1849      $result = db_query("SELECT * FROM {menu_links} WHERE link_path = '%s'", $path);
1850      while ($link = db_fetch_array($result)) {
1851        _menu_delete_item($link);
1852      }
1853    }
1854  }
1855  
1856  /**
1857   * Helper function for menu_link_delete; deletes a single menu link.
1858   *
1859   * @param $item
1860   *   Item to be deleted.
1861   * @param $force
1862   *   Forces deletion. Internal use only, setting to TRUE is discouraged.
1863   */
1864  function _menu_delete_item($item, $force = FALSE) {
1865    if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) {
1866      // Children get re-attached to the item's parent.
1867      if ($item['has_children']) {
1868        $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = %d", $item['mlid']);
1869        while ($m = db_fetch_array($result)) {
1870          $child = menu_link_load($m['mlid']);
1871          $child['plid'] = $item['plid'];
1872          menu_link_save($child);
1873        }
1874      }
1875      db_query('DELETE FROM {menu_links} WHERE mlid = %d', $item['mlid']);
1876  
1877      // Update the has_children status of the parent.
1878      _menu_update_parental_status($item);
1879      menu_cache_clear($item['menu_name']);
1880      _menu_clear_page_cache();
1881    }
1882  }
1883  
1884  /**
1885   * Save a menu link.
1886   *
1887   * @param $item
1888   *   An array representing a menu link item. The only mandatory keys are
1889   *   link_path and link_title. Possible keys are:
1890   *   - menu_name: Default is navigation.
1891   *   - weight: Default is 0.
1892   *   - expanded: Whether the item is expanded.
1893   *   - options: An array of options, see l() for more.
1894   *   - mlid: Set to an existing value, or 0 or NULL to insert a new link.
1895   *   - plid: The mlid of the parent.
1896   *   - router_path: The path of the relevant router item.
1897   *
1898   * @return
1899   *   The mlid of the saved menu link, or FALSE if the menu link could not be 
1900   *   saved.
1901   */
1902  function menu_link_save(&$item) {
1903  
1904    // Get the router if it's already in memory. $menu will be NULL, unless this
1905    // is during a menu rebuild
1906    $menu = _menu_router_cache();
1907    drupal_alter('menu_link', $item, $menu);
1908  
1909    // This is the easiest way to handle the unique internal path '<front>',
1910    // since a path marked as external does not need to match a router path.
1911    $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>';
1912    // Load defaults.
1913    $item += array(
1914      'menu_name' => 'navigation',
1915      'weight' => 0,
1916      'link_title' => '',
1917      'hidden' => 0,
1918      'has_children' => 0,
1919      'expanded' => 0,
1920      'options' => array(),
1921      'module' => 'menu',
1922      'customized' => 0,
1923      'updated' => 0,
1924    );
1925    $existing_item = FALSE;
1926    if (isset($item['mlid'])) {
1927      $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
1928    }
1929  
1930    if (isset($item['plid'])) {
1931      $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
1932    }
1933    else {
1934      // Find the parent - it must be unique.
1935      $parent_path = $item['link_path'];
1936      $where = "WHERE link_path = '%s'";
1937      // Only links derived from router items should have module == 'system', and
1938      // we want to find the parent even if it's in a different menu.
1939      if ($item['module'] == 'system') {
1940        $where .= " AND module = '%s'";
1941        $arg2 = 'system';
1942      }
1943      else {
1944        // If not derived from a router item, we respect the specified menu name.
1945        $where .= " AND menu_name = '%s'";
1946        $arg2 = $item['menu_name'];
1947      }
1948      do {
1949        $parent = FALSE;
1950        $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
1951        $result = db_query("SELECT COUNT(*) FROM {menu_links} ". $where, $parent_path, $arg2);
1952        // Only valid if we get a unique result.
1953        if (db_result($result) == 1) {
1954          $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} ". $where, $parent_path, $arg2));
1955        }
1956      } while ($parent === FALSE && $parent_path);
1957    }
1958    if ($parent !== FALSE) {
1959      $item['menu_name'] = $parent['menu_name'];
1960    }
1961    $menu_name = $item['menu_name'];
1962    // Menu callbacks need to be in the links table for breadcrumbs, but can't
1963    // be parents if they are generated directly from a router item.
1964    if (empty($parent['mlid']) || $parent['hidden'] < 0) {
1965      $item['plid'] =  0;
1966    }
1967    else {
1968      $item['plid'] = $parent['mlid'];
1969    }
1970  
1971    if (!$existing_item) {
1972      db_query("INSERT INTO {menu_links} (
1973         menu_name, plid, link_path,
1974        hidden, external, has_children,
1975        expanded, weight,
1976        module, link_title, options,
1977        customized, updated) VALUES (
1978        '%s', %d, '%s',
1979        %d, %d, %d,
1980        %d, %d,
1981        '%s', '%s', '%s', %d, %d)",
1982        $item['menu_name'], $item['plid'], $item['link_path'],
1983        $item['hidden'], $item['_external'], $item['has_children'],
1984        $item['expanded'], $item['weight'],
1985        $item['module'],  $item['link_title'], serialize($item['options']),
1986        $item['customized'], $item['updated']);
1987      $item['mlid'] = db_last_insert_id('menu_links', 'mlid');
1988    }
1989  
1990    if (!$item['plid']) {
1991      $item['p1'] = $item['mlid'];
1992      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
1993        $item["p$i"] = 0;
1994      }
1995      $item['depth'] = 1;
1996    }
1997    else {
1998      // Cannot add beyond the maximum depth.
1999      if ($item['has_children'] && $existing_item) {
2000        $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
2001      }
2002      else {
2003        $limit = MENU_MAX_DEPTH - 1;
2004      }
2005      if ($parent['depth'] > $limit) {
2006        return FALSE;
2007      }
2008      $item['depth'] = $parent['depth'] + 1;
2009      _menu_link_parents_set($item, $parent);
2010    }
2011    // Need to check both plid and menu_name, since plid can be 0 in any menu.
2012    if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
2013      _menu_link_move_children($item, $existing_item);
2014    }
2015    // Find the callback. During the menu update we store empty paths to be
2016    // fixed later, so we skip this.
2017    if (!isset($_SESSION['system_update_6021']) && (empty($item['router_path'])  || !$existing_item || ($existing_item['link_path'] != $item['link_path']))) {
2018      if ($item['_external']) {
2019        $item['router_path'] = '';
2020      }
2021      else {
2022        // Find the router path which will serve this path.
2023        $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
2024        $item['router_path'] = _menu_find_router_path($item['link_path']);
2025      }
2026    }
2027    db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
2028      router_path = '%s', hidden = %d, external = %d, has_children = %d,
2029      expanded = %d, weight = %d, depth = %d,
2030      p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d,
2031      module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
2032      $item['menu_name'], $item['plid'], $item['link_path'],
2033      $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
2034      $item['expanded'], $item['weight'],  $item['depth'],
2035      $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
2036      $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
2037    // Check the has_children status of the parent.
2038    _menu_update_parental_status($item);
2039    menu_cache_clear($menu_name);
2040    if ($existing_item && $menu_name != $existing_item['menu_name']) {
2041      menu_cache_clear($existing_item['menu_name']);
2042    }
2043  
2044    _menu_clear_page_cache();
2045    return $item['mlid'];
2046  }
2047  
2048  /**
2049   * Helper function to clear the page and block caches at most twice per page load.
2050   */
2051  function _menu_clear_page_cache() {
2052    static $cache_cleared = 0;
2053  
2054    // Clear the page and block caches, but at most twice, including at
2055    //  the end of the page load when there are multple links saved or deleted.
2056    if (empty($cache_cleared)) {
2057      cache_clear_all();
2058      // Keep track of which menus have expanded items.
2059      _menu_set_expanded_menus();
2060      $cache_cleared = 1;
2061    }
2062    elseif ($cache_cleared == 1) {
2063      register_shutdown_function('cache_clear_all');
2064      // Keep track of which menus have expanded items.
2065      register_shutdown_function('_menu_set_expanded_menus');
2066      $cache_cleared = 2;
2067    }
2068  }
2069  
2070  /**
2071   * Helper function to update a list of menus with expanded items
2072   */
2073  function _menu_set_expanded_menus() {
2074    $names = array();
2075    $result = db_query("SELECT menu_name FROM {menu_links} WHERE expanded != 0 GROUP BY menu_name");
2076    while ($n = db_fetch_array($result)) {
2077      $names[] = $n['menu_name'];
2078    }
2079    variable_set('menu_expanded', $names);
2080  }
2081  
2082  /**
2083   * Find the router path which will serve this path.
2084   *
2085   * @param $link_path
2086   *  The path for we are looking up its router path.
2087   * @return
2088   *  A path from $menu keys or empty if $link_path points to a nonexisting
2089   *  place.
2090   */
2091  function _menu_find_router_path($link_path) {
2092    // $menu will only have data during a menu rebuild.
2093    $menu = _menu_router_cache();
2094  
2095    $router_path = $link_path;
2096    $parts = explode('/', $link_path, MENU_MAX_PARTS);
2097    list($ancestors, $placeholders) = menu_get_ancestors($parts);
2098  
2099    if (empty($menu)) {
2100      // Not during a menu rebuild, so look up in the database.
2101      $router_path = (string)db_result(db_query_range('SELECT path FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1));
2102    }
2103    elseif (!isset($menu[$router_path])) {
2104      // Add an empty path as a fallback.
2105      $ancestors[] = '';
2106      foreach ($ancestors as $key => $router_path) {
2107        if (isset($menu[$router_path])) {
2108          // Exit the loop leaving $router_path as the first match.
2109          break;
2110        }
2111      }
2112      // If we did not find the path, $router_path will be the empty string
2113      // at the end of $ancestors.
2114    }
2115    return $router_path;
2116  }
2117  
2118  /**
2119   * Insert, update or delete an uncustomized menu link related to a module.
2120   *
2121   * @param $module
2122   *   The name of the module.
2123   * @param $op
2124   *   Operation to perform: insert, update or delete.
2125   * @param $link_path
2126   *   The path this link points to.
2127   * @param $link_title
2128   *   Title of the link to insert or new title to update the link to.
2129   *   Unused for delete.
2130   * @return
2131   *   The insert op returns the mlid of the new item. Others op return NULL.
2132   */
2133  function menu_link_maintain($module, $op, $link_path, $link_title) {
2134    switch ($op) {
2135      case 'insert':
2136        $menu_link = array(
2137          'link_title' => $link_title,
2138          'link_path' => $link_path,
2139          'module' => $module,
2140        );
2141        return menu_link_save($menu_link);
2142        break;
2143      case 'update':
2144        db_query("UPDATE {menu_links} SET link_title = '%s' WHERE link_path = '%s' AND customized = 0 AND module = '%s'", $link_title, $link_path, $module);
2145        $result = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = '%s' AND customized = 0 AND module = '%s'", $link_path, $module);
2146        while ($item = db_fetch_array($result)) {
2147          menu_cache_clear($item['menu_name']);
2148        }
2149        break;
2150      case 'delete':
2151        menu_link_delete(NULL, $link_path);
2152        break;
2153    }
2154  }
2155  
2156  /**
2157   * Find the depth of an item's children relative to its depth.
2158   *
2159   * For example, if the item has a depth of 2, and the maximum of any child in
2160   * the menu link tree is 5, the relative depth is 3.
2161   *
2162   * @param $item
2163   *   An array representing a menu link item.
2164   * @return
2165   *   The relative depth, or zero.
2166   *
2167   */
2168  function menu_link_children_relative_depth($item) {
2169    $i = 1;
2170    $match = '';
2171    $args[] = $item['menu_name'];
2172    $p = 'p1';
2173    while ($i <= MENU_MAX_DEPTH && $item[$p]) {
2174      $match .= " AND $p = %d";
2175      $args[] = $item[$p];
2176      $p = 'p'. ++$i;
2177    }
2178  
2179    $max_depth = db_result(db_query_range("SELECT depth FROM {menu_links} WHERE menu_name = '%s'". $match ." ORDER BY depth DESC", $args, 0, 1));
2180  
2181    return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
2182  }
2183  
2184  /**
2185   * Update the children of a menu link that's being moved.
2186   *
2187   * The menu name, parents (p1 - p6), and depth are updated for all children of
2188   * the link, and the has_children status of the previous parent is updated.
2189   */
2190  function _menu_link_move_children($item, $existing_item) {
2191  
2192    $args[] = $item['menu_name'];
2193    $set[] = "menu_name = '%s'";
2194  
2195    $i = 1;
2196    while ($i <= $item['depth']) {
2197      $p = 'p'. $i++;
2198      $set[] = "$p = %d";
2199      $args[] = $item[$p];
2200    }
2201    $j = $existing_item['depth'] + 1;
2202    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
2203      $set[] = 'p'. $i++ .' = p'. $j++;
2204    }
2205    while ($i <= MENU_MAX_DEPTH) {
2206      $set[] = 'p'. $i++ .' = 0';
2207    }
2208  
2209    $shift = $item['depth'] - $existing_item['depth'];
2210    if ($shift < 0) {
2211      $args[] = -$shift;
2212      $set[] = 'depth = depth - %d';
2213    }
2214    elseif ($shift > 0) {
2215      // The order of $set must be reversed so the new values don't overwrite the
2216      // old ones before they can be used because "Single-table UPDATE
2217      // assignments are generally evaluated from left to right"
2218      // see: http://dev.mysql.com/doc/refman/5.0/en/update.html
2219      $set = array_reverse($set);
2220      $args = array_reverse($args);
2221  
2222      $args[] = $shift;
2223      $set[] = 'depth = depth + %d';
2224    }
2225    $where[] = "menu_name = '%s'";
2226    $args[] = $existing_item['menu_name'];
2227    $p = 'p1';
2228    for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p'. ++$i) {
2229      $where[] = "$p = %d";
2230      $args[] = $existing_item[$p];
2231    }
2232  
2233    db_query("UPDATE {menu_links} SET ". implode(', ', $set) ." WHERE ". implode(' AND ', $where), $args);
2234    // Check the has_children status of the parent, while excluding this item.
2235    _menu_update_parental_status($existing_item, TRUE);
2236  }
2237  
2238  /**
2239   * Check and update the has_children status for the parent of a link.
2240   */
2241  function _menu_update_parental_status($item, $exclude = FALSE) {
2242    // If plid == 0, there is nothing to update.
2243    if ($item['plid']) {
2244      // We may want to exclude the passed link as a possible child.
2245      $where = $exclude ? " AND mlid != %d" : '';
2246      // Check if at least one visible child exists in the table.
2247      $parent_has_children = (bool)db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0". $where, $item['menu_name'], $item['plid'], $item['mlid'], 0, 1));
2248      db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']);
2249    }
2250  }
2251  
2252  /**
2253   * Helper function that sets the p1..p9 values for a menu link being saved.
2254   */
2255  function _menu_link_parents_set(&$item, $parent) {
2256    $i = 1;
2257    while ($i < $item['depth']) {
2258      $p = 'p'. $i++;
2259      $item[$p] = $parent[$p];
2260    }
2261    $p = 'p'. $i++;
2262    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
2263    $item[$p] = $item['mlid'];
2264    while ($i <= MENU_MAX_DEPTH) {
2265      $p = 'p'. $i++;
2266      $item[$p] = 0;
2267    }
2268  }
2269  
2270  /**
2271   * Helper function to build the router table based on the data from hook_menu.
2272   */
2273  function _menu_router_build($callbacks) {
2274    // First pass: separate callbacks from paths, making paths ready for
2275    // matching. Calculate fitness, and fill some default values.
2276    $menu = array();
2277    foreach ($callbacks as $path => $item) {
2278      $load_functions = array();
2279      $to_arg_functions = array();
2280      $fit = 0;
2281      $move = FALSE;
2282  
2283      $parts = explode('/', $path, MENU_MAX_PARTS);
2284      $number_parts = count($parts);
2285      // We store the highest index of parts here to save some work in the fit
2286      // calculation loop.
2287      $slashes = $number_parts - 1;
2288      // Extract load and to_arg functions.
2289      foreach ($parts as $k => $part) {
2290        $match = FALSE;
2291        // Look for wildcards in the form allowed to be used in PHP functions,
2292        // because we are using these to construct the load function names.
2293        // See http://php.net/manual/en/language.functions.php for reference.
2294        if (preg_match('/^%(|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $part, $matches)) {
2295          if (empty($matches[1])) {
2296            $match = TRUE;
2297            $load_functions[$k] = NULL;
2298          }
2299          else {
2300            if (function_exists($matches[1] .'_to_arg')) {
2301              $to_arg_functions[$k] = $matches[1] .'_to_arg';
2302              $load_functions[$k] = NULL;
2303              $match = TRUE;
2304            }
2305            if (function_exists($matches[1] .'_load')) {
2306              $function = $matches[1] .'_load';
2307              // Create an array of arguments that will be passed to the _load
2308              // function when this menu path is checked, if 'load arguments'
2309              // exists.
2310              $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function;
2311              $match = TRUE;
2312            }
2313          }
2314        }
2315        if ($match) {
2316          $parts[$k] = '%';
2317        }
2318        else {
2319          $fit |=  1 << ($slashes - $k);
2320        }
2321      }
2322      if ($fit) {
2323        $move = TRUE;
2324      }
2325      else {
2326        // If there is no %, it fits maximally.
2327        $fit = (1 << $number_parts) - 1;
2328      }
2329      $masks[$fit] = 1;
2330      $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
2331      $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
2332      $item += array(
2333        'title' => '',
2334        'weight' => 0,
2335        'type' => MENU_NORMAL_ITEM,
2336        '_number_parts' => $number_parts,
2337        '_parts' => $parts,
2338        '_fit' => $fit,
2339      );
2340      $item += array(
2341        '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_BREADCRUMB),
2342        '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK),
2343      );
2344      if ($move) {
2345        $new_path = implode('/', $item['_parts']);
2346        $menu[$new_path] = $item;
2347        $sort[$new_path] = $number_parts;
2348      }
2349      else {
2350        $menu[$path] = $item;
2351        $sort[$path] = $number_parts;
2352      }
2353    }
2354    array_multisort($sort, SORT_NUMERIC, $menu);
2355  
2356    if (!$menu) {
2357      // We must have a serious error - there is no data to save.
2358      watchdog('php', 'Menu router rebuild failed - some paths may not work correctly.', array(), WATCHDOG_ERROR);
2359      return array();
2360    }
2361    // Delete the existing router since we have some data to replace it.
2362    db_query('DELETE FROM {menu_router}');
2363    // Apply inheritance rules.
2364    foreach ($menu as $path => $v) {
2365      $item = &$menu[$path];
2366      if (!$item['_tab']) {
2367        // Non-tab items.
2368        $item['tab_parent'] = '';
2369        $item['tab_root'] = $path;
2370      }
2371      for ($i = $item['_number_parts'] - 1; $i; $i--) {
2372        $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
2373        if (isset($menu[$parent_path])) {
2374  
2375          $parent = $menu[$parent_path];
2376  
2377          if (!isset($item['tab_parent'])) {
2378            // Parent stores the parent of the path.
2379            $item['tab_parent'] = $parent_path;
2380          }
2381          if (!isset($item['tab_root']) && !$parent['_tab']) {
2382            $item['tab_root'] = $parent_path;
2383          }
2384          // If an access callback is not found for a default local task we use
2385          // the callback from the parent, since we expect them to be identical.
2386          // In all other cases, the access parameters must be specified.
2387          if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) {
2388            $item['access callback'] = $parent['access callback'];
2389            if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
2390              $item['access arguments'] = $parent['access arguments'];
2391            }
2392          }
2393          // Same for page callbacks.
2394          if (!isset($item['page callback']) && isset($parent['page callback'])) {
2395            $item['page callback'] = $parent['page callback'];
2396            if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
2397              $item['page arguments'] = $parent['page arguments'];
2398            }
2399            if (!isset($item['file']) && isset($parent['file'])) {
2400              $item['file'] = $parent['file'];
2401            }
2402            if (!isset($item['file path']) && isset($parent['file path'])) {
2403              $item['file path'] = $parent['file path'];
2404            }
2405          }
2406        }
2407      }
2408      if (!isset($item['access callback']) && isset($item['access arguments'])) {
2409        // Default callback.
2410        $item['access callback'] = 'user_access';
2411      }
2412      if (!isset($item['access callback']) || empty($item['page callback'])) {
2413        $item['access callback'] = 0;
2414      }
2415      if (is_bool($item['access callback'])) {
2416        $item['access callback'] = intval($item['access callback']);
2417      }
2418  
2419      $item += array(
2420        'access arguments' => array(),
2421        'access callback' => '',
2422        'page arguments' => array(),
2423        'page callback' => '',
2424        'block callback' => '',
2425        'title arguments' => array(),
2426        'title callback' => 't',
2427        'description' => '',
2428        'position' => '',
2429        'tab_parent' => '',
2430        'tab_root' => $path,
2431        'path' => $path,
2432        'file' => '',
2433        'file path' => '',
2434        'include file' => '',
2435        'module' => '',
2436      );
2437  
2438      // Calculate out the file to be included for each callback, if any.
2439      if ($item['file']) {
2440        $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
2441        $item['include file'] = $file_path .'/'. $item['file'];
2442      }
2443  
2444      $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
2445      db_query("INSERT INTO {menu_router}
2446        (path, load_functions, to_arg_functions, access_callback,
2447        access_arguments, page_callback, page_arguments, fit,
2448        number_parts, tab_parent, tab_root,
2449        title, title_callback, title_arguments,
2450        type, block_callback, description, position, weight, file)
2451        VALUES ('%s', '%s', '%s', '%s',
2452        '%s', '%s', '%s', %d,
2453        %d, '%s', '%s',
2454        '%s', '%s', '%s',
2455        %d, '%s', '%s', '%s', %d, '%s')",
2456        $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
2457        serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
2458        $item['_number_parts'], $item['tab_parent'], $item['tab_root'],
2459        $item['title'], $item['title callback'], $title_arguments,
2460        $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
2461    }
2462    // Sort the masks so they are in order of descending fit, and store them.
2463    $masks = array_keys($masks);
2464    rsort($masks);
2465    variable_set('menu_masks', $masks);
2466  
2467    return $menu;
2468  }
2469  
2470  /**
2471   * Returns TRUE if a path is external (e.g. http://example.com).
2472   */
2473  function menu_path_is_external($path) {
2474    $colonpos = strpos($path, ':');
2475    return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
2476  }
2477  
2478  /**
2479   * Checks whether the site is off-line for maintenance.
2480   *
2481   * This function will log the current user out and redirect to front page
2482   * if the current user has no 'administer site configuration' permission.
2483   *
2484   * @return
2485   *   FALSE if the site is not off-line or its the login page or the user has
2486   *     'administer site configuration' permission.
2487   *   TRUE for anonymous users not on the login page if the site is off-line.
2488   */
2489  function _menu_site_is_offline() {
2490    // Check if site is set to off-line mode.
2491    if (variable_get('site_offline', 0)) {
2492      // Check if the user has administration privileges.
2493      if (user_access('administer site configuration')) {
2494        // Ensure that the off-line message is displayed only once [allowing for
2495        // page redirects], and specifically suppress its display on the site
2496        // maintenance page.
2497        if (drupal_get_normal_path($_GET['q']) != 'admin/settings/site-maintenance') {
2498          drupal_set_message(l(t('Operating in off-line mode.'), 'admin/settings/site-maintenance'), 'status', FALSE);
2499        }
2500      }
2501      else {
2502        // Anonymous users get a FALSE at the login prompt, TRUE otherwise.
2503        if (user_is_anonymous()) {
2504          return $_GET['q'] != 'user' && $_GET['q'] != 'user/login';
2505        }
2506        // Logged in users are unprivileged here, so they are logged out.
2507        require_once drupal_get_path('module', 'user') .'/user.pages.inc';
2508        user_logout();
2509      }
2510    }
2511    return FALSE;
2512  }
2513  
2514  /**
2515   * Validates the path of a menu link being created or edited.
2516   *
2517   * @return
2518   *   TRUE if it is a valid path AND the current user has access permission,
2519   *   FALSE otherwise.
2520   */
2521  function menu_valid_path($form_item) {
2522    global $menu_admin;
2523    $item = array();
2524    $path = $form_item['link_path'];
2525    // We indicate that a menu administrator is running the menu access check.
2526    $menu_admin = TRUE;
2527    if ($path == '<front>' || menu_path_is_external($path)) {
2528      $item = array('access' => TRUE);
2529    }
2530    elseif (preg_match('/\/\%/', $path)) {
2531      // Path is dynamic (ie 'user/%'), so check directly against menu_router table.
2532      if ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path))) {
2533        $item['link_path']  = $form_item['link_path'];
2534        $item['link_title'] = $form_item['link_title'];
2535        $item['external']   = FALSE;
2536        $item['options'] = '';
2537        _menu_link_translate($item);
2538      }
2539    }
2540    else {
2541      $item = menu_get_item($path);
2542    }
2543    $menu_admin = FALSE;
2544    return $item && $item['access'];
2545  }
2546  
2547  /**
2548   * @} End of "defgroup menu".
2549   */


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7