menu.inc 40.4 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
2
// $Id$
Kjartan's avatar
Kjartan committed
3

4
5
6
7
8
/**
 * @file
 * API for the Drupal menu system.
 */

Dries's avatar
   
Dries committed
9
10
11
/**
 * @defgroup menu Menu system
 * @{
Dries's avatar
   
Dries committed
12
 * Define the navigation menus, and route page requests to code based on URLs.
Dries's avatar
   
Dries committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 *
 * The Drupal menu system drives both the navigation system from a user
 * perspective and the callback system that Drupal uses to respond to URLs
 * passed from the browser. For this reason, a good understanding of the
 * menu system is fundamental to the creation of complex modules.
 *
 * Drupal's menu system follows a simple hierarchy defined by paths.
 * Implementations of hook_menu() define menu items and assign them to
 * paths (which should be unique). The menu system aggregates these items
 * and determines the menu hierarchy from the paths. For example, if the
 * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
 * would form the structure:
 * - a
 *   - a/b
 *     - a/b/c/d
 *     - a/b/h
 * - e
 * - f/g
 * Note that the number of elements in the path does not necessarily
 * determine the depth of the menu item in the tree.
 *
 * When responding to a page request, the menu system looks to see if the
 * path requested by the browser is registered as a menu item with a
 * callback. If not, the system searches up the menu tree for the most
 * complete match with a callback it can find. If the path a/b/i is
 * requested in the tree above, the callback for a/b would be used.
 *
Steven Wittens's avatar
Steven Wittens committed
40
41
42
43
44
45
 * The found callback function is called with any arguments specified
 * in the "callback arguments" attribute of its menu item. The
 * attribute must be an array. After these arguments, any remaining
 * components of the path are appended as further arguments. In this
 * way, the callback for a/b above could respond to a request for
 * a/b/i differently than a request for a/b/j.
Dries's avatar
   
Dries committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 *
 * For an illustration of this process, see page_example.module.
 *
 * Access to the callback functions is also protected by the menu system.
 * The "access" attribute of each menu item is checked as the search for a
 * callback proceeds. If this attribute is TRUE, then access is granted; if
 * FALSE, then access is denied. The first found "access" attribute
 * determines the accessibility of the target. Menu items may omit this
 * attribute to use the value provided by an ancestor item.
 *
 * In the default Drupal interface, you will notice many links rendered as
 * tabs. These are known in the menu system as "local tasks", and they are
 * rendered as tabs by default, though other presentations are possible.
 * Local tasks function just as other menu items in most respects. It is
 * convention that the names of these tasks should be short verbs if
 * possible. In addition, a "default" local task should be provided for
 * each set. When visiting a local task's parent menu item, the default
 * local task will be rendered as if it is selected; this provides for a
 * normal tab user experience. This default task is special in that it
 * links not to its provided path, but to its parent item's path instead.
 * The default task's path is only used to place it appropriately in the
 * menu hierarchy.
Dries's avatar
   
Dries committed
68
69
 */

Dries's avatar
   
Dries committed
70
/**
Dries's avatar
   
Dries committed
71
 * @name Menu flags
Dries's avatar
   
Dries committed
72
 * @{
Dries's avatar
   
Dries committed
73
74
 * Flags for use in the "type" attribute of menu items.
 */
Dries's avatar
   
Dries committed
75

Dries's avatar
   
Dries committed
76
77
78
79
80
81
82
83
define('MENU_IS_ROOT', 0x0001);
define('MENU_VISIBLE_IN_TREE', 0x0002);
define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
define('MENU_VISIBLE_IF_HAS_CHILDREN', 0x0008);
define('MENU_MODIFIABLE_BY_ADMIN', 0x0010);
define('MENU_MODIFIED_BY_ADMIN', 0x0020);
define('MENU_CREATED_BY_ADMIN', 0x0040);
define('MENU_IS_LOCAL_TASK', 0x0080);
Dries's avatar
   
Dries committed
84
define('MENU_EXPANDED', 0x0100);
Dries's avatar
   
Dries committed
85
define('MENU_LINKS_TO_PARENT', 0x0200);
Dries's avatar
   
Dries committed
86

Dries's avatar
   
Dries committed
87
/**
Dries's avatar
   
Dries committed
88
 * @} End of "Menu flags".
Dries's avatar
   
Dries committed
89
90
91
 */

/**
Dries's avatar
   
Dries committed
92
 * @name Menu item types
Dries's avatar
   
Dries committed
93
94
95
96
 * @{
 * Menu item definitions provide one of these constants, which are shortcuts for
 * combinations of the above flags.
 */
Dries's avatar
   
Dries committed
97

Dries's avatar
   
Dries committed
98
99
/**
 * Normal menu items show up in the menu tree and can be moved/hidden by
Dries's avatar
   
Dries committed
100
101
 * the administrator. Use this for most menu items. It is the default value if
 * no menu item type is specified.
Dries's avatar
   
Dries committed
102
103
 */
define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
104

Dries's avatar
   
Dries committed
105
106
/**
 * Item groupings are used for pages like "node/add" that simply list
Dries's avatar
   
Dries committed
107
108
 * subpages to visit. They are distinguished from other pages in that they will
 * disappear from the menu if no subpages exist.
Dries's avatar
   
Dries committed
109
110
 */
define('MENU_ITEM_GROUPING', MENU_VISIBLE_IF_HAS_CHILDREN | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
Dries's avatar
   
Dries committed
111

Dries's avatar
   
Dries committed
112
113
/**
 * Callbacks simply register a path so that the correct function is fired
Dries's avatar
   
Dries committed
114
 * when the URL is accessed. They are not shown in the menu.
Dries's avatar
   
Dries committed
115
116
 */
define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB);
Dries's avatar
   
Dries committed
117

118
/**
Dries's avatar
   
Dries committed
119
120
121
122
 * Dynamic menu items change frequently, and so should not be stored in the
 * database for administrative customization.
 */
define('MENU_DYNAMIC_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
Dries's avatar
   
Dries committed
123

Dries's avatar
   
Dries committed
124
/**
Dries's avatar
   
Dries committed
125
126
 * Modules may "suggest" menu items that the administrator may enable. They act
 * just as callbacks do until enabled, at which time they act like normal items.
Dries's avatar
   
Dries committed
127
128
129
130
 */
define('MENU_SUGGESTED_ITEM', MENU_MODIFIABLE_BY_ADMIN);

/**
Dries's avatar
   
Dries committed
131
132
133
 * Local tasks are rendered as tabs by default. Use this for menu items that
 * describe actions to be performed on their parent item. An example is the path
 * "node/52/edit", which performs the "edit" task on "node/52".
Dries's avatar
   
Dries committed
134
135
136
 */
define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK);

Dries's avatar
   
Dries committed
137
138
139
140
141
142
/**
 * Every set of local tasks should provide one "default" task, that links to the
 * same path as its parent when clicked.
 */
define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT);

Dries's avatar
   
Dries committed
143
/**
Dries's avatar
   
Dries committed
144
145
 * Custom items are those defined by the administrator. Reserved for internal
 * use; do not return from hook_menu() implementations.
Dries's avatar
   
Dries committed
146
147
148
149
 */
define('MENU_CUSTOM_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);

/**
Dries's avatar
   
Dries committed
150
151
 * Custom menus are those defined by the administrator. Reserved for internal
 * use; do not return from hook_menu() implementations.
Dries's avatar
   
Dries committed
152
153
154
155
 */
define('MENU_CUSTOM_MENU', MENU_IS_ROOT | MENU_VISIBLE_IN_TREE | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);

/**
Dries's avatar
   
Dries committed
156
 * @} End of "Menu item types".
Dries's avatar
   
Dries committed
157
158
159
160
161
 */

/**
 * @name Menu status codes
 * @{
Dries's avatar
   
Dries committed
162
163
 * Status codes for menu callbacks.
 */
Dries's avatar
   
Dries committed
164

Dries's avatar
   
Dries committed
165
166
167
define('MENU_FOUND', 1);
define('MENU_NOT_FOUND', 2);
define('MENU_ACCESS_DENIED', 3);
168
define('MENU_SITE_OFFLINE', 4);
Dries's avatar
   
Dries committed
169

Dries's avatar
   
Dries committed
170
/**
Dries's avatar
   
Dries committed
171
 * @} End of "Menu status codes".
Dries's avatar
   
Dries committed
172
 */
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

/**
 * Return the menu data structure.
 *
 * The returned structure contains much information that is useful only
 * internally in the menu system. External modules are likely to need only
 * the ['visible'] element of the returned array. All menu items that are
 * accessible to the current user and not hidden will be present here, so
 * modules and themes can use this structure to build their own representations
 * of the menu.
 *
 * $menu['visible'] will contain an associative array, the keys of which
 * are menu IDs. The values of this array are themselves associative arrays,
 * with the following key-value pairs defined:
 * - 'title' - The displayed title of the menu or menu item. It will already
 *   have been translated by the locale system.
Dries's avatar
   
Dries committed
189
190
 * - 'description' - The description (link title attribute) of the menu item.
 *   It will already have been translated by the locale system.
191
 * - 'path' - The Drupal path to the menu item. A link to a particular item
Dries's avatar
   
Dries committed
192
193
 *   can thus be constructed with
 *   l($item['title'], $item['path'], array('title' => $item['description'])).
194
195
196
197
198
199
200
201
 * - 'children' - A linear list of the menu ID's of this item's children.
 *
 * Menu ID 0 is the "root" of the menu. The children of this item are the
 * menus themselves (they will have no associated path). Menu ID 1 will
 * always be one of these children; it is the default "Navigation" menu.
 */
function menu_get_menu() {
  global $_menu;
Dries's avatar
   
Dries committed
202
  global $user;
203
  global $locale;
204
205

  if (!isset($_menu['items'])) {
Dries's avatar
   
Dries committed
206
207
    // _menu_build() may indirectly call this function, so prevent infinite loops.
    $_menu['items'] = array();
Dries's avatar
   
Dries committed
208

209
    $cid = "menu:$user->uid:$locale";
Dries's avatar
   
Dries committed
210
211
212
213
214
215
216
217
218
219
220
    if ($cached = cache_get($cid)) {
      $_menu = unserialize($cached->data);
    }
    else {
      _menu_build();
      // Cache the menu structure for this user, to expire after one day.
      cache_set($cid, serialize($_menu), time() + (60 * 60 * 24));
    }

    // Make sure items that cannot be cached are added.
    _menu_append_contextual_items();
221
222
223

    // Reset the cached $menu in menu_get_item().
    menu_get_item(NULL, NULL, TRUE);
224
  }
225

Dries's avatar
   
Dries committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  return $_menu;
}

/**
 * Return the local task tree.
 *
 * Unlike the rest of the menu structure, the local task tree cannot be cached
 * nor determined too early in the page request, because the user's current
 * location may be changed by a menu_set_location() call, and the tasks shown
 * (just as the breadcrumb trail) need to reflect the changed location.
 */
function menu_get_local_tasks() {
  global $_menu;

Dries's avatar
   
Dries committed
240
241
242
243
244
245
  // Don't cache the local task tree, as it varies by location and tasks are
  // allowed to be dynamically determined.
  if (!isset($_menu['local tasks'])) {
    // _menu_build_local_tasks() may indirectly call this function, so prevent
    // infinite loops.
    $_menu['local tasks'] = array();
Dries's avatar
   
Dries committed
246
247
248
249
250
    $pid = menu_get_active_nontask_item();
    if (!_menu_build_local_tasks($pid)) {
      // If the build returned FALSE, the tasks need not be displayed.
      $_menu['local tasks'][$pid]['children'] = array();
    }
Dries's avatar
   
Dries committed
251
252
  }

Dries's avatar
   
Dries committed
253
  return $_menu['local tasks'];
Dries's avatar
   
Dries committed
254
}
Dries's avatar
   
Dries committed
255

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/**
 * Retrieves the menu item specified by $mid, or by $path if $mid is not given.
 *
 * @param $mid
 *   The menu ID of the menu item to retrieve.
 * @param $path
 *   The internal path of the menu item to retrieve. Defaults to NULL. Only
 *   used if no item can be found matching $mid.
 * @param $reset
 *   Optional flag that resets the static variable cache of the menu tree, if
 *   set to TRUE. Default is FALSE.
 *
 * @return
 *   The menu item found in the site menu, or an empty array if none could be
 *   found.
 */
function menu_get_item($mid, $path = NULL, $reset = FALSE) {
  static $menu;

  if (!isset($menu) || $reset) {
    $menu = menu_get_menu();
  }

  if (isset($mid)) {
    return $menu['items'][$mid];
  }

  if (isset($path)) {
    return $menu['items'][$menu['path index'][$path]];
  }

  return array();
}

/**
 * Retrieves the menu ID and title of all root menus.
 *
 * @return
 *   Array containing all menus (but not menu items), in the form mid => title.
 */
function menu_get_root_menus() {
  $menu = menu_get_menu();
  $root_menus = array();

  foreach ($menu['items'][0]['children'] as $mid) {
    $root_menus[$mid] = $menu['items'][$mid]['title'];
  }

  return $root_menus;
}

307
/**
Dries's avatar
   
Dries committed
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
 * Change the current menu location of the user.
 *
 * Frequently, modules may want to make a page or node act as if it were
 * in the menu tree somewhere, even though it was not registered in a
 * hook_menu() implementation. If the administrator has rearranged the menu,
 * the newly set location should respect this in the breadcrumb trail and
 * expanded/collapsed status of menu items in the tree. This function
 * allows this behavior.
 *
 * @param $location
 *   An array specifying a complete or partial breadcrumb trail for the
 *   new location, in the same format as the return value of hook_menu().
 *   The last element of this array should be the new location itself.
 *
 * This function will set the new breadcrumb trail to the passed-in value,
 * but if any elements of this trail are visible in the site tree, the
 * trail will be "spliced in" to the existing site navigation at that point.
325
 */
Dries's avatar
   
Dries committed
326
327
328
329
330
function menu_set_location($location) {
  global $_menu;
  $temp_id = min(array_keys($_menu['items'])) - 1;
  $prev_id = 0;

331
332
333
334
  // Don't allow this function to change the actual current path, just the
  // position in the menu tree.
  $location[count($location) - 1]['path'] = $_GET['q'];

Dries's avatar
   
Dries committed
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
  foreach (array_reverse($location) as $item) {
    if (isset($_menu['path index'][$item['path']])) {
      $mid = $_menu['path index'][$item['path']];
      if (isset ($_menu['visible'][$mid])) {
        // Splice in the breadcrumb at this location.
        if ($prev_id) {
          $_menu['items'][$prev_id]['pid'] = $mid;
        }
        $prev_id = 0;
        break;
      }
      else {
        // A hidden item; show it, but only temporarily.
        $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
        if ($prev_id) {
          $_menu['items'][$prev_id]['pid'] = $mid;
        }
        $prev_id = $mid;
      }
    }
    else {
      $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
      if ($prev_id) {
        $_menu['items'][$prev_id]['pid'] = $temp_id;
      }
      $_menu['items'][$temp_id] = $item;
      $_menu['path index'][$item['path']] = $temp_id;
Dries's avatar
   
Dries committed
362

Dries's avatar
   
Dries committed
363
364
365
366
      $prev_id = $temp_id;
      $temp_id--;
    }
  }
Dries's avatar
   
Dries committed
367

Dries's avatar
   
Dries committed
368
369
370
371
372
373
374
375
376
377
378
379
380
  if ($prev_id) {
    // Didn't find a home, so attach this to the main navigation menu.
    $_menu['items'][$prev_id]['pid'] = 1;
  }

  $final_item = array_pop($location);
  menu_set_active_item($final_item['path']);
}

/**
 * Execute the handler associated with the active menu item.
 *
 * This is called early in the page request. The active menu item is at
381
 * this point determined exclusively by the URL. The handler that is called
Dries's avatar
   
Dries committed
382
383
384
385
386
 * here may, as a side effect, change the active menu item so that later
 * menu functions (that display the menus and breadcrumbs, for example)
 * act as if the user were in a different location on the site.
 */
function menu_execute_active_handler() {
387
388
389
390
  if (_menu_site_is_offline()) {
    return MENU_SITE_OFFLINE;
  }

Dries's avatar
   
Dries committed
391
392
393
394
  $menu = menu_get_menu();

  // Determine the menu item containing the callback.
  $path = $_GET['q'];
395
  while ($path && !isset($menu['callbacks'][$path])) {
Dries's avatar
   
Dries committed
396
397
    $path = substr($path, 0, strrpos($path, '/'));
  }
398

399
  if (!isset($menu['callbacks'][$path])) {
Dries's avatar
   
Dries committed
400
    return MENU_NOT_FOUND;
Dries's avatar
   
Dries committed
401
402
  }

403
404
405
406
  if (!function_exists($menu['callbacks'][$path]['callback'])) {
    return MENU_NOT_FOUND;
  }

Dries's avatar
   
Dries committed
407
408
409
410
411
  if (!_menu_item_is_accessible(menu_get_active_item())) {
    return MENU_ACCESS_DENIED;
  }

  // We found one, and are allowed to execute it.
412
413
  $arguments = isset($menu['callbacks'][$path]['callback arguments']) ? $menu['callbacks'][$path]['callback arguments'] : array();
  $arg = substr($_GET['q'], strlen($path) + 1);
Dries's avatar
   
Dries committed
414
  if (strlen($arg)) {
415
    $arguments = array_merge($arguments, explode('/', $arg));
Dries's avatar
   
Dries committed
416
  }
417

418
  return call_user_func_array($menu['callbacks'][$path]['callback'], $arguments);
Dries's avatar
   
Dries committed
419
420
}

Dries's avatar
   
Dries committed
421
/**
422
 * Returns the ID of the active menu item.
Dries's avatar
   
Dries committed
423
424
 */
function menu_get_active_item() {
Dries's avatar
   
Dries committed
425
426
427
  return menu_set_active_item();
}

Dries's avatar
   
Dries committed
428
429
430
/**
 * Sets the path of the active menu item.
 */
Dries's avatar
   
Dries committed
431
function menu_set_active_item($path = NULL) {
432
433
  static $stored_mid;
  $menu = menu_get_menu();
Dries's avatar
   
Dries committed
434

435
436
  if (!isset($stored_mid) || isset($path)) {
    if (!isset($path)) {
437
      $path = $_GET['q'];
Dries's avatar
   
Dries committed
438
439
440
441
    }
    else {
      $_GET['q'] = $path;
    }
Dries's avatar
   
Dries committed
442

443
    while ($path && !isset($menu['path index'][$path])) {
444
      $path = substr($path, 0, strrpos($path, '/'));
Dries's avatar
   
Dries committed
445
    }
446
    $stored_mid = isset($menu['path index'][$path]) ? $menu['path index'][$path] : 0;
Dries's avatar
   
Dries committed
447
448
449
450
451

    // Search for default local tasks to activate instead of this item.
    $continue = TRUE;
    while ($continue) {
      $continue = FALSE;
452
      if (isset($menu['items'][$stored_mid]['children'])) {
Dries's avatar
   
Dries committed
453
454
455
456
457
458
459
460
        foreach ($menu['items'][$stored_mid]['children'] as $cid) {
          if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) {
            $stored_mid = $cid;
            $continue = TRUE;
          }
        }
      }
    }
461
462
463

    // Reset the cached $menu in menu_get_item().
    menu_get_item(NULL, NULL, TRUE);
Dries's avatar
   
Dries committed
464
465
  }

466
  return $stored_mid;
Dries's avatar
   
Dries committed
467
468
}

Dries's avatar
   
Dries committed
469
470
471
472
473
474
475
476
/**
 * Returns the ID of the current menu item or, if the current item is a
 * local task, the menu item to which this task is attached.
 */
function menu_get_active_nontask_item() {
  $mid = menu_get_active_item();

  // Find the first non-task item:
477
478
479
480
481
482
  while ($mid) {
    $item = menu_get_item($mid);

    if (!($item['type'] & MENU_IS_LOCAL_TASK)) {
      return $mid;
    }
Dries's avatar
   
Dries committed
483

484
    $mid = $item['pid'];
Dries's avatar
   
Dries committed
485
486
487
  }
}

Dries's avatar
   
Dries committed
488
/**
Dries's avatar
   
Dries committed
489
490
 * Returns the title of the active menu item.
 */
491
function menu_get_active_title() {
Dries's avatar
   
Dries committed
492
  if ($mid = menu_get_active_nontask_item()) {
493
494
    $item = menu_get_item($mid);
    return $item['title'];
495
496
  }
}
Dries's avatar
   
Dries committed
497

Dries's avatar
   
Dries committed
498
/**
Dries's avatar
   
Dries committed
499
500
 * Returns the help associated with the active menu item.
 */
501
function menu_get_active_help() {
Dries's avatar
   
Dries committed
502
503
  $path = $_GET['q'];
  $output = '';
Dries's avatar
   
Dries committed
504

Dries's avatar
   
Dries committed
505
506
507
508
  if (!_menu_item_is_accessible(menu_get_active_item())) {
    // Don't return help text for areas the user cannot access.
    return;
  }
Dries's avatar
   
Dries committed
509

Dries's avatar
   
Dries committed
510
511
512
513
514
  foreach (module_list() as $name) {
    if (module_hook($name, 'help')) {
      if ($temp = module_invoke($name, 'help', $path)) {
        $output .= $temp . "\n";
      }
515
516
517
518
519
      if (module_hook('help', 'page')) {
        if (substr($path, 0, 6) == "admin/") {
          if (module_invoke($name, 'help', 'admin/help#' . substr($path, 6))) {
            $output .= theme("more_help_link", url('admin/help/' . substr($path, 6)));
          }
Dries's avatar
   
Dries committed
520
521
        }
      }
Dries's avatar
   
Dries committed
522
    }
Dries's avatar
   
Dries committed
523
  }
Dries's avatar
   
Dries committed
524
  return $output;
525
526
}

Dries's avatar
   
Dries committed
527
528
529
530
/**
 * Returns an array of rendered menu items in the active breadcrumb trail.
 */
function menu_get_active_breadcrumb() {
531
  $links[] = l(t('Home'), '<front>');
532

Dries's avatar
   
Dries committed
533
  $trail = _menu_get_active_trail();
534
  foreach ($trail as $mid) {
535
536
    $item = menu_get_item($mid);
    if ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB) {
537
      $links[] = menu_item_link($mid);
538
    }
Dries's avatar
   
Dries committed
539
540
  }

Dries's avatar
   
Dries committed
541
542
543
  // The last item in the trail is the page title; don't display it here.
  array_pop($links);

Dries's avatar
   
Dries committed
544
  return $links;
545
546
}

Dries's avatar
   
Dries committed
547
/**
Dries's avatar
   
Dries committed
548
 * Returns true when the menu item is in the active trail.
Dries's avatar
   
Dries committed
549
 */
Dries's avatar
   
Dries committed
550
function menu_in_active_trail($mid) {
Dries's avatar
   
Dries committed
551
  $trail = _menu_get_active_trail();
Dries's avatar
   
Dries committed
552

Dries's avatar
   
Dries committed
553
  return in_array($mid, $trail);
Dries's avatar
   
Dries committed
554
555
}

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
/**
 * Returns true when the menu item is in the active trail within a
 * specific subsection of the menu tree.
 *
 * @param $mid
 *   The menu item being considered.
 * @param $pid
 *   The root of the subsection of the menu tree in which to look.
 */
function menu_in_active_trail_in_submenu($mid, $pid) {
  $trail = _menu_get_active_trail_in_submenu($pid);

  if (!$trail) {
    return FALSE;
  }

  return in_array($mid, $trail);
}

575
/**
Dries's avatar
   
Dries committed
576
577
578
 * Populate the database representation of the menu.
 *
 * This need only be called at the start of pages that modify the menu.
579
 */
Dries's avatar
   
Dries committed
580
function menu_rebuild() {
Dries's avatar
   
Dries committed
581
  // Clear the page cache, so that changed menus are reflected for anonymous users.
Dries's avatar
   
Dries committed
582
  cache_clear_all();
Dries's avatar
   
Dries committed
583
584
  // Also clear the menu cache.
  cache_clear_all('menu:', TRUE);
Dries's avatar
   
Dries committed
585

586
587
  _menu_build();

Dries's avatar
   
Dries committed
588
589
  if (module_exist('menu')) {
    $menu = menu_get_menu();
Dries's avatar
   
Dries committed
590

591
    // Fill a queue of new menu items which are modifiable.
Dries's avatar
   
Dries committed
592
593
594
    $new_items = array();
    foreach ($menu['items'] as $mid => $item) {
      if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) {
595
        $new_items[$mid] = $item;
596
597
598
      }
    }

599
600
601
602
603
604
605
606
607
608
609
610
611
    // Save the new items updating the pids in each iteration
    while (count($new_items)) {
      foreach($new_items as $mid => $item) {
        // If the item has a valid parent, save it
        if ($item['pid'] >= 0) {
          // The new menu ID gets passed back by reference as $item['mid']
          menu_save_item($item);
          // Fix parent IDs for the children of the menu item just saved
          if ($item['children']) {
            foreach ($item['children'] as $child) {
              if (isset($new_items[$child])) {
                $new_items[$child]['pid'] = $item['mid'];
              }
Dries's avatar
   
Dries committed
612
            }
Dries's avatar
   
Dries committed
613
          }
614
615
          // remove the item
          unset($new_items[$mid]);
Dries's avatar
   
Dries committed
616
        }
617
      }
Dries's avatar
   
Dries committed
618
    }
619
620
    // Rebuild the menu to account for the changes.
    _menu_build();
Dries's avatar
   
Dries committed
621
  }
622
623
624
625

  // Reset the cached $menu in menu_get_item().
  menu_get_item(NULL, NULL, TRUE);

Dries's avatar
   
Dries committed
626
627
}

Dries's avatar
   
Dries committed
628
/**
629
630
631
632
 * Generate the HTML for a menu tree.
 *
 * @param $pid
 *   The parent id of the menu.
Dries's avatar
   
Dries committed
633
634
 *
 * @ingroup themeable
Dries's avatar
   
Dries committed
635
 */
636
637
function theme_menu_tree($pid = 1) {
  if ($tree = menu_tree($pid)) {
638
    return "\n<ul class=\"menu\">\n". $tree ."\n</ul>\n";
639
640
641
642
643
644
645
646
647
648
  }
}

/**
 * Returns a rendered menu tree.
 *
 * @param $pid
 *   The parent id of the menu.
 */
function menu_tree($pid = 1) {
649
650
  $menu = menu_get_menu();
  $output = '';
Dries's avatar
   
Dries committed
651

652
653
  if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
    foreach ($menu['visible'][$pid]['children'] as $mid) {
654
655
656
      $type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL;
      $children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL;
      $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0);
Dries's avatar
   
Dries committed
657
658
659
    }
  }

660
  return $output;
Dries's avatar
   
Dries committed
661
662
}

Dries's avatar
   
Dries committed
663
/**
664
 * Generate the HTML output for a single menu item.
Dries's avatar
   
Dries committed
665
666
 *
 * @param $mid
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
 *   The menu id of the item.
 * @param $children
 *   A string containing any rendered child items of this menu.
 * @param $leaf
 *   A boolean indicating whether this menu item is a leaf.
 *
 * @ingroup themeable
 */
function theme_menu_item($mid, $children = '', $leaf = TRUE) {
  return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid) . $children ."</li>\n";
}

/**
 * Generate the HTML representing a given menu item ID.
 *
 * @param $item
 *   The menu item to render.
684
 * @param $link_item
685
 *   The menu item which should be used to find the correct path.
Dries's avatar
   
Dries committed
686
687
 *
 * @ingroup themeable
Dries's avatar
   
Dries committed
688
 */
689
function theme_menu_item_link($item, $link_item) {
690
  return l($item['title'], $link_item['path'], isset($item['description']) ? array('title' => $item['description']) : array());
691
692
693
694
695
696
697
698
699
}

/**
 * Returns the rendered link to a menu item.
 *
 * @param $mid
 *   The menu item id to render.
 */
function menu_item_link($mid) {
700
701
  $item = menu_get_item($mid);
  $link_item = $item;
Dries's avatar
   
Dries committed
702

703
704
  while ($link_item['type'] & MENU_LINKS_TO_PARENT) {
    $link_item = menu_get_item($link_item['pid']);
Dries's avatar
   
Dries committed
705
706
  }

707
  return theme('menu_item_link', $item, $link_item);
Dries's avatar
   
Dries committed
708
709
710
711
712
}

/**
 * Returns the rendered local tasks. The default implementation renders
 * them as tabs.
Dries's avatar
   
Dries committed
713
714
 *
 * @ingroup themeable
Dries's avatar
   
Dries committed
715
716
 */
function theme_menu_local_tasks() {
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
  $output = '';

  if ($primary = menu_primary_local_tasks()) {
    $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
  }
  if ($secondary = menu_secondary_local_tasks()) {
    $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
  }

  return $output;
}

/**
 * Returns the rendered HTML of the primary local tasks.
 */
function menu_primary_local_tasks() {
Dries's avatar
   
Dries committed
733
  $local_tasks = menu_get_local_tasks();
Dries's avatar
   
Dries committed
734
  $pid = menu_get_active_nontask_item();
Dries's avatar
   
Dries committed
735
  $output = '';
Dries's avatar
   
Dries committed
736

Dries's avatar
   
Dries committed
737
738
  if (count($local_tasks[$pid]['children'])) {
    foreach ($local_tasks[$pid]['children'] as $mid) {
739
      $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE);
Dries's avatar
   
Dries committed
740
    }
741
742
743
744
745
746
747
748
749
750
751
752
  }

  return $output;
}

/**
 * Returns the rendered HTML of the secondary local tasks.
 */
function menu_secondary_local_tasks() {
  $local_tasks = menu_get_local_tasks();
  $pid = menu_get_active_nontask_item();
  $output = '';
Dries's avatar
   
Dries committed
753

754
  if (count($local_tasks[$pid]['children'])) {
Dries's avatar
   
Dries committed
755
    foreach ($local_tasks[$pid]['children'] as $mid) {
Dries's avatar
   
Dries committed
756
      if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) {
Dries's avatar
   
Dries committed
757
        foreach ($local_tasks[$mid]['children'] as $cid) {
758
          $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE);
Dries's avatar
   
Dries committed
759
760
761
762
        }
      }
    }
  }
763

Dries's avatar
   
Dries committed
764
765
766
767
  return $output;
}

/**
Dries's avatar
   
Dries committed
768
 * Generate the HTML representing a given menu item ID as a tab.
Dries's avatar
   
Dries committed
769
770
771
772
773
 *
 * @param $mid
 *   The menu ID to render.
 * @param $active
 *   Whether this tab or a subtab is the active menu item.
774
775
 * @param $primary
 *   Whether this tab is a primary tab or a subtab.
Dries's avatar
   
Dries committed
776
777
 *
 * @ingroup themeable
Dries's avatar
   
Dries committed
778
 */
779
function theme_menu_local_task($mid, $active, $primary) {
Dries's avatar
   
Dries committed
780
  if ($active) {
781
    return '<li class="active">'. menu_item_link($mid) ."</li>\n";
Dries's avatar
   
Dries committed
782
783
  }
  else {
784
    return '<li>'. menu_item_link($mid) ."</li>\n";
Dries's avatar
   
Dries committed
785
786
787
  }
}

788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
/**
 * Returns an array containing the primary links.
 * Can optionally descend from the root of the Primary links menu towards the
 * current node for a specified number of levels and return that submenu.
 * Used to generate a primary/secondary menu from different levels of one menu.
 *
 * @param $start_level
 *   This optional parameter can be used to retrieve a context-sensitive array
 *   of links at $start_level levels deep into the Primary links menu.
 *   The default is to return the top-level links.
 * @param $pid
 *   The parent menu ID from which to search for children. Defaults to the
 *   menu_primary_menu setting.
 * @return An array containing the themed links as the values. The keys of
 *   the array contain some extra encoded information about the results.
 *   The format of the key is {level}-{num}{-active}.
 *   level is the depth within the menu tree of this list.
 *   num is the number within this array, used only to make the key unique.
 *   -active is appended if this element is in the active trail.
 */
function menu_primary_links($start_level = 1, $pid = 0) {
  if (!module_exist('menu')) {
    return NULL;
  }
  if (!$pid) {
    $pid = variable_get('menu_primary_menu', 0);
  }
  if (!$pid) {
    return NULL;
  }

  if ($start_level < 1) {
    $start_level = 1;
  }

  if ($start_level > 1) {
    $trail = _menu_get_active_trail_in_submenu($pid);
    if (!$trail) {
      return NULL;
    }
    else {
      $pid = $trail[$start_level - 1];
    }
  }

  $menu = menu_get_menu();
834
  if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) {
835
836
837
838
839
840
841
842
843
844
845
    $count = 1;
    foreach ($menu['visible'][$pid]['children'] as $cid) {
      $index = "$start_level-$count";
      if (menu_in_active_trail_in_submenu($cid, $pid)) {
        $index .= "-active";
      }
      $links[$index] = menu_item_link($cid);
      $count++;
    }
  }

846
  // Special case - provide link to admin/menu if primary links is empty.
847
  if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0)) {
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
    $links['1-1'] = l(t('edit primary links'),'admin/menu');
  }

  return $links;
}

/**
 * Returns an array containing the secondary links.
 * Secondary links can be either a second level of the Primary links
 * menu or generated from their own menu.
 */
function menu_secondary_links() {
  $msm = variable_get('menu_secondary_menu', 0);
  if ($msm == 0) {
    return NULL;
  }

  if ($msm == variable_get('menu_primary_menu', 0)) {
    return menu_primary_links(2, $msm);
  }

  return menu_primary_links(1, $msm);
}

/**
 * Returns the themed HTML for primary and secondary links.
 * Note that this function is overridden by most core themes because
 * those themes display links in "link | link" format, not from a list.
 * Also note that by default links rendered with this function are
 * displayed with the same CSS as is used for the local tasks.
 * If a theme wishes to render links from a ul it is expected that
 * the theme will provide suitable CSS.
 *
 * @param $links
 *   An array containing links to render.
 * @return
 *   A string containing the themed links.
 *
 * @ingroup themeable
 */
function theme_menu_links($links) {
  if (!count($links)) {
    return '';
  }
892
  $level_tmp = explode('-', key($links));
893
894
895
896
897
898
899
900
901
902
903
904
905
906
  $level = $level_tmp[0];
  $output = "<ul class=\"links-$level\">\n";
  foreach ($links as $index => $link) {
    $output .= '<li';
    if (stristr($index, 'active')) {
      $output .= ' class="active"';
    }
    $output .= ">$link</li>\n";
  }
  $output .= '</ul>';

  return $output;
}

907
/**
Dries's avatar
   
Dries committed
908
 * @} End of "defgroup menu".
909
 */
Dries's avatar
   
Dries committed
910
911

/**
Dries's avatar
   
Dries committed
912
 * Returns an array with the menu items that lead to the current menu item.
Dries's avatar
   
Dries committed
913
 */
Dries's avatar
   
Dries committed
914
915
function _menu_get_active_trail() {
  static $trail;
Dries's avatar
   
Dries committed
916

Dries's avatar
   
Dries committed
917
918
  if (!isset($trail)) {
    $trail = array();
Dries's avatar
   
Dries committed
919

Dries's avatar
   
Dries committed
920
    $mid = menu_get_active_item();
Dries's avatar
   
Dries committed
921

Dries's avatar
   
Dries committed
922
    // Follow the parents up the chain to get the trail.
923
    while ($mid && ($item = menu_get_item($mid))) {
Dries's avatar
   
Dries committed
924
      array_unshift($trail, $mid);
925
      $mid = $item['pid'];
Dries's avatar
   
Dries committed
926
927
    }
  }
Dries's avatar
   
Dries committed
928
929
930
  return $trail;
}

931
932
933
934
935
936
937
938
939
/**
 * Find the active trail through a specific subsection of the menu tree.
 *
 * @param $pid
 *   The root item from which the active trail must descend.
 */
function _menu_get_active_trail_in_submenu($pid) {
  static $trails;

940
  if (!isset($trails)) {
941
942
    // Find all menu items which point to the current node and for each
    // follow the parents up the chain to build an active trail.
943
    $trails = array();
944
945
946
947
948
    $menu = menu_get_menu();
    $path = $_GET['q'];
    $count = 0;
    while ($path && !$count) {
      foreach ($menu['items'] as $key => $item) {
949
        if (isset($item['path']) && $item['path'] == $path) {
950
951
952
953
954
955
          $trails[$count] = array();
          $mid = $key;
          while ($mid && $menu['items'][$mid]) {
            array_unshift($trails[$count], $mid);
            $mid = $menu['items'][$mid]['pid'];
          }
956
          $count ++;
957
958
959
960
961
962
963
        }
      }
      $path = substr($path, 0, strrpos($path, '/'));
    }
  }

  if ($trails) {
964
965
966
    foreach ($trails as $trail) {
      $count_trail = count($trail);
      for ($i = 0; $i < $count_trail; $i++) {
967
        if ($trail[$i] == $pid) {
968
969
970
971
          // Return a trail from $pid down to the current page inclusive.
          for ( ; $i < $count_trail; $i++) {
            $subtrail[] = $trail[$i];
          }
972
973
974
975
976
977
978
979
980
          return $subtrail;
        }
      }
    }
  }

  return NULL;
}

Dries's avatar
   
Dries committed
981
982
983
984
985
986
987
988
989
/**
 * Comparator routine for use in sorting menu items.
 */
function _menu_sort($a, $b) {
  $menu = menu_get_menu();

  $a = &$menu['items'][$a];
  $b = &$menu['items'][$b];

990
991
992
993
994
995
  if ($a['weight'] < $b['weight']) {
    return -1;
  }
  elseif ($a['weight'] > $b['weight']) {
    return 1;
  }
996
997
  elseif (isset($a['title']) && isset($b['title'])) {
    return strnatcasecmp($a['title'], $b['title']);
998
999
1000
1001
  }
  else {
    return 1;
  }
Dries's avatar
   
Dries committed
1002
1003
}

Dries's avatar
   
Dries committed
1004
/**
1005
 * Build the menu by querying both modules and the database.
Dries's avatar
   
Dries committed
1006
 */
Dries's avatar
   
Dries committed
1007
function _menu_build() {
1008
1009
  global $_menu;
  global $user;
Dries's avatar
   
Dries committed
1010

1011
1012
1013
1014
1015
  // Start from a clean slate.
  $_menu = array();

  $_menu['path index'] = array();
  // Set up items array, including default "Navigation" menu.
Dries's avatar
   
Dries committed
1016
  $_menu['items'] = array(
Dries's avatar
   
Dries committed
1017
1018
    0 => array('path' => '', 'title' => '', 'type' => MENU_IS_ROOT),
    1 => array('pid' => 0, 'path' => '', 'title' => t('Navigation'), 'weight' => -50, 'access' => TRUE, 'type' => MENU_IS_ROOT | MENU_VISIBLE_IN_TREE)
Dries's avatar
   
Dries committed
1019
    );
1020
  $_menu['callbacks'] = array();
Dries's avatar
   
Dries committed
1021
1022

  // Build a sequential list of all menu items.
Dries's avatar
   
Dries committed
1023
  $menu_item_list = module_invoke_all('menu', TRUE);
1024
1025
1026
1027

  // Menu items not in the DB get temporary negative IDs.
  $temp_mid = -1;

Dries's avatar
   
Dries committed
1028
  foreach ($menu_item_list as $item) {
1029
    if (!isset($item['path'])) {
Dries's avatar
   
Dries committed
1030
1031
      $item['path'] = '';
    }
1032
    if (!isset($item['type'])) {
Dries's avatar
   
Dries committed
1033
1034
      $item['type'] = MENU_NORMAL_ITEM;
    }
1035
    if (!isset($item['weight'])) {
Dries's avatar
   
Dries committed
1036
1037
      $item['weight'] = 0;
    }
1038
    $mid = $temp_mid;
1039
    if (isset($_menu['path index'][$item['path']])) {
Dries's avatar
   
Dries committed
1040
1041
1042
      // Newer menu items overwrite older ones.
      unset($_menu['items'][$_menu['path index'][$item['path']]]);
    }
1043
1044
1045
1046
1047
1048
1049
1050
    if (isset($item['callback'])) {
      $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']);
      if (isset($item['callback arguments'])) {
        $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments'];
      }
    }
    unset($item['callback']);
    unset($item['callback arguments']);
Dries's avatar
   
Dries committed
1051
1052
    $_menu['items'][$mid] = $item;
    $_menu['path index'][$item['path']] = $mid;
1053
1054

    $temp_mid--;
1055
1056
  }

1057
1058
  // Now fetch items from the DB, reassigning menu IDs as needed.
  if (module_exist('menu')) {
1059
    $result = db_query(db_rewrite_sql('SELECT m.mid, m.* FROM {menu} m ORDER BY m.mid ASC', 'm', 'mid'));
1060
    while ($item = db_fetch_object($result)) {
1061
      // Handle URL aliases if entered in menu administration.
1062
1063
1064
      if (!isset($_menu['path index'][$item->path])) {
        $item->path = drupal_get_normal_path($item->path);
      }
1065
      if (isset($_menu['path index'][$item->path])) {
Dries's avatar
   
Dries committed
1066
        // The path is already declared.
Dries's avatar
   
Dries committed
1067
        $old_mid = $_menu['path index'][$item->path];
Dries's avatar
   
Dries committed
1068
1069
1070
1071
1072
1073
1074
1075
1076
        if ($old_mid < 0) {
          // It had a temporary ID, so use a permanent one.
          $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
          unset($_menu['items'][$old_mid]);
          $_menu['path index'][$item->path] = $item->mid;
        }
        else {
          // It has a permanent ID. Only replace with non-custom menu items.
          if ($item->type & MENU_CREATED_BY_ADMIN) {
1077
            $_menu['items'][$item->mid] = array('path' => $item->path);
Dries's avatar
   
Dries committed
1078
1079
1080
1081
1082
1083
          }
          else {
            // Leave the old item around as a shortcut to this one.
            $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
            $_menu['path index'][$item->path] = $item->mid;
          }
Dries's avatar
   
Dries committed
1084
1085
        }
      }
Dries's avatar
   
Dries committed
1086
1087
1088
      else {
        // The path was not declared, so this is a custom item or an orphaned one.
        if ($item->type & MENU_CREATED_BY_ADMIN) {
1089
          $_menu['items'][$item->mid] = array('path' => $item->path);
Dries's avatar
   
Dries committed
1090
1091
1092
          if (!empty($item->path)) {
            $_menu['path index'][$item->path] = $item->mid;
          }
Dries's avatar
   
Dries committed
1093
        }
Dries's avatar
   
Dries committed
1094
      }
Dries's avatar
   
Dries committed
1095
1096
1097
1098
1099
1100
1101
1102
1103

      // If the administrator has changed the item, reflect the change.
      if ($item->type & MENU_MODIFIED_BY_ADMIN) {
        $_menu['items'][$item->mid]['title'] = $item->title;
        $_menu['items'][$item->mid]['description'] = $item->description;
        $_menu['items'][$item->mid]['pid'] = $item->pid;
        $_menu['items'][$item->mid]['weight'] = $item->weight;
        $_menu['items'][$item->mid]['type'] = $item->type;
      }
1104
1105
1106
    }
  }

Dries's avatar
   
Dries committed
1107
1108
  // Associate parent and child menu items.
  _menu_find_parents($_menu['items']);
Dries's avatar
   
Dries committed
1109

1110
  // Prepare to display trees to the user as required.
Dries's avatar
   
Dries committed
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
  _menu_build_visible_tree();
}

/**
 * Determine whether the given menu item is accessible to the current user.
 *
 * Use this instead of just checking the "access" property of a menu item
 * to properly handle items with fall-through semantics.
 */
function _menu_item_is_accessible($mid) {
  $menu = menu_get_menu();

Dries's avatar
   
Dries committed
1123
  // Follow the path up to find the first "access" attribute.
1124
  $path = isset($menu['items'][$mid]['path']) ? $menu['items'][$mid]['path'] : NULL;
1125
  while ($path && (!isset($menu['path index'][$path]) || !isset($menu['items'][$menu['path index'][$path]]['access']))) {
Dries's avatar
   
Dries committed
1126
1127
    $path = substr($path, 0, strrpos($path, '/'));
  }
Dries's avatar
   
Dries committed
1128
  if (empty($path)) {
1129
1130
1131
1132
    // Items without any access attribute up the chain are denied, unless they
    // were created by the admin. They most likely point to non-Drupal directories
    // or to an external URL and should be allowed.
    return $menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN;
Dries's avatar
   
Dries committed
1133
  }
Dries's avatar
   
Dries committed
1134
  return $menu['items'][$menu['path index'][$path]]['access'];
Dries's avatar
   
Dries committed
1135
1136
}

Dries's avatar
   
Dries committed
1137
/**
1138
1139
1140
1141
 * Find all visible items in the menu tree, for ease in displaying to user.
 *
 * Since this is only for display, we only need title, path, and children
 * for each item.
Dries's avatar
   
Dries committed
1142
 */
Dries's avatar
   
Dries committed
1143
function _menu_build_visible_tree($pid = 0) {
1144
  global $_menu;
Dries's avatar
   
Dries committed
1145

1146
1147
  if (isset($_menu['items'][$pid])) {
    $parent = $_menu['items'][$pid];
1148

1149
    $children = array();
1150
    if (isset($parent['children'])) {
1151
1152
      usort($parent['children'], '_menu_sort');
      foreach ($parent['children'] as $mid) {
Dries's avatar
   
Dries committed
1153
        $children = array_merge($children, _menu_build_visible_tree($mid));
Dries's avatar
   
Dries committed
1154
1155
      }
    }
Dries's avatar
   
Dries committed
1156
1157
1158
    $visible = ($parent['type'] & MENU_VISIBLE_IN_TREE) ||
      ($parent['type'] & MENU_VISIBLE_IF_HAS_CHILDREN && count($children) > 0);
    $allowed = _menu_item_is_accessible($pid);
Dries's avatar
   
Dries committed
1159

Dries's avatar
   
Dries committed
1160
    if (($parent['type'] & MENU_IS_ROOT) || ($visible && $allowed)) {
Dries's avatar
   
Dries committed
1161
      $_menu['visible'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children, 'type' => $parent['type']);
Dries's avatar
   
Dries committed
1162
1163
1164
      foreach ($children as $mid) {
        $_menu['visible'][$mid]['pid'] = $pid;
      }
1165
1166
1167
1168
1169
1170
      return array($pid);
    }
    else {
      return $children;
    }
  }
1171

1172
1173
  return array();
}
1174

Dries's avatar
   
Dries committed
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
/**
 * Account for menu items that are only defined at certain paths, so will not
 * be cached.
 *
 * We don't support the full range of menu item options for these menu items. We
 * don't support MENU_VISIBLE_IF_HAS_CHILDREN, and we require parent items to be
 * declared before their children.
 */
function _menu_append_contextual_items() {
  global $_menu;

  // Build a sequential list of all menu items.
  $menu_item_list = module_invoke_all('menu', FALSE);

  // Menu items not in the DB get temporary negative IDs.
  $temp_mid = min(array_keys($_menu['items'])) - 1;
  $new_items = array();

  foreach ($menu_item_list as $item) {
1194
1195
    if (isset($item['callback'])) {
      $_menu['callbacks'][$item['path']] = array('callback' => $item['callback']);
1196
      if (isset($item['callback arguments'])) {
1197
        $_menu['callbacks'][$item['path']]['callback arguments'] = $item['callback arguments'];
1198
      }
Dries's avatar
   
Dries committed
1199
    }
1200
1201
1202
1203
    unset($item['callback']);
    unset($item['callback arguments']);
    if (!isset($_menu['path index'][$item['path']])) {
      if (!isset($item['path'])) {
Dries's avatar
   
Dries committed
1204
1205
        $item['path'] = '';
      }
1206
      if (!isset($item['type'])) {
Dries's avatar
   
Dries committed
1207
1208
        $item['type'] = MENU_NORMAL_ITEM;
      }
1209
      if (!isset($item['weight'])) {
Dries's avatar
   
Dries committed
1210
1211
1212
1213
1214
1215
1216
        $item['weight'] = 0;
      }
      $_menu['items'][$temp_mid] = $item;
      $_menu['path index'][$item['path']] = $temp_mid;
      $new_items[$temp_mid] = $item;
      $temp_mid--;
    }
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
    else {
      $mid = $_menu['path index'][$item['path']];
      if ($_menu['items'][$mid]['type'] & MENU_CREATED_BY_ADMIN) {
        $_menu['items'][$mid]['access'] = $item['access'];
        $_menu['items'][$mid]['callback'] = $item['callback'];
        if (isset($_menu['items'][$mid]['callback arguments'])) {
          $_menu['items'][$mid]['callback arguments'] = $item['callback arguments'];
        }
      }
    }
Dries's avatar
   
Dries committed
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
  }

  // Establish parent-child relationships.
  _menu_find_parents($new_items);

  // Add new items to the visible tree if necessary.
  foreach ($new_items as $mid => $item) {
    $item = $_menu['items'][$mid];
    if (($item['type'] & MENU_VISIBLE_IN_TREE) && _menu_item_is_accessible($mid)) {
      $pid = $item['pid'];
1237
      while ($pid && !isset($_menu['visible'][$pid])) {
Dries's avatar
   
Dries committed
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
12