| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |