[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/ctools/includes/ -> menu.inc (source)

   1  <?php
   2  // $Id: menu.inc,v 1.4.2.1 2010/07/16 00:40:13 merlinofchaos Exp $
   3  
   4  /**
   5   * @file
   6   * General menu helper functions.
   7   *
   8   * The menu system was completely revamped in Drupal 6; as such it is not as
   9   * mature as some other systems and is missing some API functions. This
  10   * file helps smooth some edges out.
  11   */
  12  
  13  /**
  14   * Dynamically add a tab to the current path.
  15   *
  16   * This function allows you to dynamically add tabs to the current path.
  17   * There are several important considerations to this:
  18   *
  19   * - First, Drupal doesn't really allow this. CTools lets this happen by
  20   *   overriding the theme function that displays the tabs. That means that
  21   *   custom themes which do not use CTools functions will not get the new
  22   *   tabs. You can provide instructions to your users about how to deal with
  23   *   this, but you should be prepared for some users not getting the new tabs
  24   *   and not knowing why.
  25   * - Second, if there is only 1 tab, Drupal will not show it. Therefore, if
  26   *   you are only adding one tab, you should find a way to make sure there is
  27   *   already tab, or instead add 2.
  28   * - Third, the caller is responsible for providing access control to these
  29   *   links.
  30   *
  31   * @param $link
  32   *   An array describing this link. It must contain:
  33   *   - 'title': The printed title of the link.
  34   *   - 'href': The path of the link. This is an argument to l() so it has all
  35   *     of those features and limitations.
  36   *   - 'options': Any options that go to l, including query, fragment and html
  37   *     options necessary.
  38   *   - 'weight': The weight to use in ordering the tabs.
  39   *   - 'type': Optional. If set to MENU_DEFAULT_LOCAL_TASK this can be used to
  40   *     add a fake 'default' local task, which is useful if you have to add
  41   *     tabs to a page that has noen.
  42   */
  43  function ctools_menu_add_tab($link = NULL) {
  44    static $links = array();
  45    if (isset($link)) {
  46      $links[$link['href']] = $link;
  47    }
  48  
  49    return $links;
  50  }
  51  
  52  /**
  53   * CTools replacement for menu_get_menu_trail that allows us to take apart
  54   * the menu trail if necessary.
  55   */
  56  function ctools_get_menu_trail($path = NULL) {
  57    $trail   = array();
  58    $trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array(), 'type' => 0);
  59    $item    = menu_get_item($path);
  60  
  61    // Check whether the current item is a local task (displayed as a tab).
  62    if ($item['tab_parent']) {
  63      // The title of a local task is used for the tab, never the page title.
  64      // Thus, replace it with the item corresponding to the root path to get
  65      // the relevant href and title.  For example, the menu item corresponding
  66      // to 'admin' is used when on the 'By module' tab at 'admin/by-module'.
  67      $parts = explode('/', $item['tab_root']);
  68      $args = arg();
  69      // Replace wildcards in the root path using the current path.
  70      foreach ($parts as $index => $part) {
  71        if ($part == '%') {
  72          $parts[$index] = $args[$index];
  73        }
  74      }
  75      // Retrieve the menu item using the root path after wildcard replacement.
  76      $root_item = menu_get_item(implode('/', $parts));
  77      if ($root_item && $root_item['access']) {
  78        $item = $root_item;
  79      }
  80    }
  81  
  82    $tree = ctools_menu_tree_page_data($item, menu_get_active_menu_name());
  83    list($key, $curr) = each($tree);
  84  
  85    while ($curr) {
  86      // Terminate the loop when we find the current path in the active trail.
  87      if ($curr['link']['href'] == $item['href']) {
  88        $trail[] = $curr['link'];
  89        $curr = FALSE;
  90      }
  91      else {
  92        // Add the link if it's in the active trail, then move to the link below.
  93        if ($curr['link']['in_active_trail']) {
  94          $trail[] = $curr['link'];
  95          $tree = $curr['below'] ? $curr['below'] : array();
  96        }
  97        list($key, $curr) = each($tree);
  98      }
  99    }
 100    // Make sure the current page is in the trail (needed for the page title),
 101    // but exclude tabs and the front page.
 102    $last = count($trail) - 1;
 103    if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) {
 104      $trail[] = $item;
 105    }
 106  
 107    return $trail;
 108  }
 109  
 110  /**
 111   * Get the data structure representing a named menu tree, based on the current page.
 112   *
 113   * The tree order is maintained by storing each parent in an individual
 114   * field, see http://drupal.org/node/141866 for more.
 115   *
 116   * @param $menu_name
 117   *   The named menu links to return
 118   *
 119   * @return
 120   *   An array of menu links, in the order they should be rendered. The array
 121   *   is a list of associative arrays -- these have two keys, link and below.
 122   *   link is a menu item, ready for theming as a link. Below represents the
 123   *   submenu below the link if there is one, and it is a subtree that has the
 124   *   same structure described for the top-level array.
 125   */
 126  function ctools_menu_tree_page_data($item, $menu_name = 'navigation') {
 127    static $tree = array();
 128  
 129    // Generate a cache ID (cid) specific for this page.
 130    $cid = 'links:'. $menu_name .':page-cid:'. $item['href'] .':'. (int)$item['access'];
 131  
 132    if (!isset($tree[$cid])) {
 133      // If the static variable doesn't have the data, check {cache_menu}.
 134      $cache = cache_get($cid, 'cache_menu');
 135      if ($cache && isset($cache->data)) {
 136        // If the cache entry exists, it will just be the cid for the actual data.
 137        // This avoids duplication of large amounts of data.
 138        $cache = cache_get($cache->data, 'cache_menu');
 139        if ($cache && isset($cache->data)) {
 140          $data = $cache->data;
 141        }
 142      }
 143      // If the tree data was not in the cache, $data will be NULL.
 144      if (!isset($data)) {
 145        // Build and run the query, and build the tree.
 146        if ($item['access']) {
 147          // Check whether a menu link exists that corresponds to the current path.
 148          $args = array($menu_name, $item['href']);
 149          $placeholders = "'%s'";
 150          if (drupal_is_front_page()) {
 151            $args[] = '<front>';
 152            $placeholders .= ", '%s'";
 153          }
 154          $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));
 155  
 156          if (empty($parents)) {
 157            // If no link exists, we may be on a local task that's not in the links.
 158            // TODO: Handle the case like a local task on a specific node in the menu.
 159            $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']));
 160          }
 161          // We always want all the top-level links with plid == 0.
 162          $parents[] = '0';
 163  
 164          // Use array_values() so that the indices are numeric for array_merge().
 165          $args = $parents = array_unique(array_values($parents));
 166          $placeholders = implode(', ', array_fill(0, count($args), '%d'));
 167          $expanded = variable_get('menu_expanded', array());
 168          // Check whether the current menu has any links set to be expanded.
 169          if (in_array($menu_name, $expanded)) {
 170            // Collect all the links set to be expanded, and then add all of
 171            // their children to the list as well.
 172            do {
 173              $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));
 174              $num_rows = FALSE;
 175              while ($item = db_fetch_array($result)) {
 176                $args[] = $item['mlid'];
 177                $num_rows = TRUE;
 178              }
 179              $placeholders = implode(', ', array_fill(0, count($args), '%d'));
 180            } while ($num_rows);
 181          }
 182          array_unshift($args, $menu_name);
 183        }
 184        else {
 185          // Show only the top-level menu items when access is denied.
 186          $args = array($menu_name, '0');
 187          $placeholders = '%d';
 188          $parents = array();
 189        }
 190        // Select the links from the table, and recursively build the tree. We
 191        // LEFT JOIN since there is no match in {menu_router} for an external
 192        // link.
 193        $data['tree'] = menu_tree_data(db_query("
 194          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.*
 195          FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
 196          WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
 197          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
 198        $data['node_links'] = array();
 199        menu_tree_collect_node_links($data['tree'], $data['node_links']);
 200        // Cache the data, if it is not already in the cache.
 201        $tree_cid = _menu_tree_cid($menu_name, $data);
 202        if (!cache_get($tree_cid, 'cache_menu')) {
 203          cache_set($tree_cid, $data, 'cache_menu');
 204        }
 205        // Cache the cid of the (shared) data using the page-specific cid.
 206        cache_set($cid, $tree_cid, 'cache_menu');
 207      }
 208      // Check access for the current user to each item in the tree.
 209      menu_tree_check_access($data['tree'], $data['node_links']);
 210      $tree[$cid] = $data['tree'];
 211    }
 212    return $tree[$cid];
 213  }
 214  
 215  function ctools_menu_set_trail_parent($path) {
 216    $current = menu_get_active_trail();
 217    $keep = array_pop($current);
 218  
 219    $trail = ctools_get_menu_trail($path);
 220    $trail[] = $keep;
 221  
 222    menu_set_active_trail($trail);
 223  }
 224  
 225  /**
 226   * An alternative to theme_menu_local_tasks to add flexibility to tabs.
 227   *
 228   * The code in theme_menu_local_tasks has no entry points to put hooks.
 229   * Therefore, what CTools does is, if that theme function is not overridden,
 230   * it uses hook_theme_registry_alter() to use its own version. This version
 231   * then allows modules to use ctools_menu_add_local_task() to add a dynamic
 232   * local task to the list.
 233   *
 234   * If a theme *does* override theme_menu_local_tasks, it can still get this
 235   * functionality by using ctools versions of menu_primary_local_tasks() and
 236   * menu_secondary_local_tasks().
 237   */
 238  function ctools_theme_menu_local_tasks() {
 239    $output = '';
 240  
 241    if ($primary = ctools_menu_primary_local_tasks()) {
 242      $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
 243    }
 244    if ($secondary = ctools_menu_secondary_local_tasks()) {
 245      $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
 246    }
 247  
 248    return $output;
 249  }
 250  
 251  /**
 252   * CTools variant of menu_primary_local_tasks().
 253   *
 254   * This can be called by themes which implement their own theme_menu_local_tasks
 255   * in order to get local tasks that include CTools' additions.
 256   */
 257  function ctools_menu_primary_local_tasks() {
 258    return ctools_menu_local_tasks(0);
 259  }
 260  
 261  /**
 262   * CTools variant of menu_secondary_local_tasks().
 263   *
 264   * This can be called by themes which implement their own theme_menu_local_tasks
 265   * in order to get local tasks that include CTools' additions.
 266   */
 267  function ctools_menu_secondary_local_tasks() {
 268    return ctools_menu_local_tasks(1);
 269  }
 270  
 271  /**
 272   * CTools' variant of menu_local_tasks.
 273   *
 274   * This function is a new version of menu_local_tasks that is meant to be more
 275   * flexible and allow for modules to dynamically add items to the local tasks
 276   * using a simple function.
 277   *
 278   * One downside to using this is that the code to build the tabs could be run
 279   * twice on a page if something
 280   */
 281  function ctools_menu_local_tasks($level = 0, $return_root = FALSE) {
 282    static $tabs;
 283    static $root_path;
 284  
 285    if (!isset($tabs)) {
 286      $tabs = array();
 287  
 288      $router_item = menu_get_item();
 289      if (!$router_item || !$router_item['access']) {
 290        return '';
 291      }
 292      // Get all tabs and the root page.
 293      $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' ORDER BY weight, title", $router_item['tab_root']);
 294      $map = arg();
 295      $children = array();
 296      $tasks = array();
 297      $root_path = $router_item['path'];
 298  
 299      while ($item = db_fetch_array($result)) {
 300        _menu_translate($item, $map, TRUE);
 301        if ($item['tab_parent']) {
 302          // All tabs, but not the root page.
 303          $children[$item['tab_parent']][$item['path']] = $item;
 304        }
 305        // Store the translated item for later use.
 306        $tasks[$item['path']] = $item;
 307      }
 308  
 309      // Find all tabs below the current path.
 310      $path = $router_item['path'];
 311  
 312      _ctools_menu_add_dynamic_items($children[$path]);
 313  
 314      // Tab parenting may skip levels, so the number of parts in the path may not
 315      // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
 316      $depth = 1001;
 317      while (isset($children[$path])) {
 318        $tabs_current = '';
 319        $next_path = '';
 320        $count = 0;
 321        foreach ($children[$path] as $item) {
 322          if ($item['access']) {
 323            $count++;
 324            // The default task is always active.
 325            if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
 326              // Find the first parent which is not a default local task.
 327              if (isset($item['tab_parent'])) {
 328                for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
 329                $href = $tasks[$p]['href'];
 330                $next_path = $item['path'];
 331              }
 332              else {
 333                $href = $item['href'];
 334              }
 335              $link = theme('menu_item_link', array('href' => $href) + $item);
 336              $tabs_current .= theme('menu_local_task', $link, TRUE);
 337            }
 338            else {
 339              $link = theme('menu_item_link', $item);
 340              $tabs_current .= theme('menu_local_task', $link);
 341            }
 342          }
 343        }
 344        $path = $next_path;
 345        $tabs[$depth]['count'] = $count;
 346        $tabs[$depth]['output'] = $tabs_current;
 347        $depth++;
 348      }
 349  
 350      // Find all tabs at the same level or above the current one.
 351      $parent = $router_item['tab_parent'];
 352      $path = $router_item['path'];
 353      $current = $router_item;
 354      $depth = 1000;
 355      while (isset($children[$parent])) {
 356        $tabs_current = '';
 357        $next_path = '';
 358        $next_parent = '';
 359        $count = 0;
 360        foreach ($children[$parent] as $item) {
 361          if ($item['access']) {
 362            $count++;
 363            if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
 364              // Find the first parent which is not a default local task.
 365              for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
 366              $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item);
 367              if ($item['path'] == $router_item['path']) {
 368                $root_path = $tasks[$p]['path'];
 369              }
 370            }
 371            else {
 372              $link = theme('menu_item_link', $item);
 373            }
 374            // We check for the active tab.
 375            if ($item['path'] == $path) {
 376              $tabs_current .= theme('menu_local_task', $link, TRUE);
 377              $next_path = $item['tab_parent'];
 378              if (isset($tasks[$next_path])) {
 379                $next_parent = $tasks[$next_path]['tab_parent'];
 380              }
 381            }
 382            else {
 383              $tabs_current .= theme('menu_local_task', $link);
 384            }
 385          }
 386        }
 387        $path = $next_path;
 388        $parent = $next_parent;
 389        $tabs[$depth]['count'] = $count;
 390        $tabs[$depth]['output'] = $tabs_current;
 391        $depth--;
 392      }
 393      // Sort by depth.
 394      ksort($tabs);
 395      // Remove the depth, we are interested only in their relative placement.
 396      $tabs = array_values($tabs);
 397    }
 398  
 399    if ($return_root) {
 400      return $root_path;
 401    }
 402    else {
 403      // We do not display single tabs.
 404      return (isset($tabs[$level]) && $tabs[$level]['count'] > 1) ? $tabs[$level]['output'] : '';
 405    }
 406  }
 407  
 408  /**
 409   * Re-sort menu items after we have modified them.
 410   */
 411  function ctools_menu_sort($a, $b) {
 412    $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0;
 413    $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0;
 414    if ($a_weight == $b_weight) {
 415      $a_title = (is_array($a) && isset($a['title'])) ? $a['title'] : 0;
 416      $b_title = (is_array($b) && isset($b['title'])) ? $b['title'] : 0;
 417      if ($a_title == $b_title) {
 418        return 0;
 419      }
 420  
 421      return ($a_title < $b_title) ? -1 : 1;
 422    }
 423  
 424    return ($a_weight < $b_weight) ? -1 : 1;
 425  }
 426  
 427  /**
 428   * Theme override to use CTools to call help instead of the basic call.
 429   *
 430   * This is used only to try and improve performance; this calls through to
 431   * the same functions that tabs do and executes a complex build. By overriding
 432   * this to use the CTools version, we can prevent this query from being run
 433   * twice on the same page.
 434   */
 435  function ctools_menu_help() {
 436    if ($help = ctools_menu_get_active_help()) {
 437      return '<div class="help">'. $help .'</div>';
 438    }
 439  }
 440  
 441  /**
 442   * CTools' replacement for ctools_menu_get_active_help()
 443   */
 444  function ctools_menu_get_active_help() {
 445    $output = '';
 446    $router_path = ctools_menu_tab_root_path();
 447    // We will always have a path unless we are on a 403 or 404.
 448    if (!$router_path) {
 449      return '';
 450    }
 451  
 452    $arg = drupal_help_arg(arg(NULL));
 453    $empty_arg = drupal_help_arg();
 454  
 455    foreach (module_list() as $name) {
 456      if (module_hook($name, 'help')) {
 457        // Lookup help for this path.
 458        if ($help = module_invoke($name, 'help', $router_path, $arg)) {
 459          $output .= $help ."\n";
 460        }
 461        // Add "more help" link on admin pages if the module provides a
 462        // standalone help page.
 463        if ($arg[0] == "admin" && module_exists('help') && module_invoke($name, 'help', 'admin/help#'. $arg[2], $empty_arg) && $help) {
 464          $output .= theme("more_help_link", url('admin/help/'. $arg[2]));
 465        }
 466      }
 467    }
 468    return $output;
 469  }
 470  
 471  /**
 472   * CTools' replacement for menu_tab_root_path()
 473   */
 474  function ctools_menu_tab_root_path() {
 475    return ctools_menu_local_tasks(0, TRUE);
 476  }
 477  
 478  /**
 479   * Returns the rendered local tasks. The default implementation renders
 480   * them as tabs. Overridden to split the secondary tasks.
 481   *
 482   * @ingroup themeable
 483   */
 484  function ctools_garland_menu_local_tasks() {
 485    return ctools_menu_primary_local_tasks();
 486  }
 487  
 488  function _ctools_menu_add_dynamic_items(&$links) {
 489    // TESTING: add hardcoded values.
 490    if ($additions = ctools_menu_add_tab()) {
 491      foreach ($additions as $addition) {
 492        $links[$addition['href']] = $addition + array(
 493          'access' => TRUE,
 494          'type' => MENU_LOCAL_TASK,
 495        );
 496      }
 497      uasort($links, 'ctools_menu_sort');
 498    }
 499  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7