Projet

Général

Profil

Paste
Télécharger (34,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / admin_menu / admin_menu.inc @ 74f6bef0

1
<?php
2

    
3
/**
4
 * @file
5
 * Menu builder functions for Administration menu.
6
 */
7

    
8
/**
9
 * Build the full administration menu tree from static and expanded dynamic items.
10
 *
11
 * @param $menu_name
12
 *   The menu name to use as base for the tree.
13
 */
14
function admin_menu_tree($menu_name) {
15
  // Get placeholder expansion arguments from hook_admin_menu_map()
16
  // implementations.
17
  module_load_include('inc', 'admin_menu', 'admin_menu.map');
18
  $expand_map = module_invoke_all('admin_menu_map');
19
  // Allow modules to alter the expansion map.
20
  drupal_alter('admin_menu_map', $expand_map);
21

    
22
  $new_map = array();
23
  foreach ($expand_map as $path => $data) {
24
    // Convert named placeholders to anonymous placeholders, since the menu
25
    // system stores paths using anonymous placeholders.
26
    $replacements = array_fill_keys(array_keys($data['arguments'][0]), '%');
27
    $data['parent'] = strtr($data['parent'], $replacements);
28
    $new_map[strtr($path, $replacements)] = $data;
29
  }
30
  $expand_map = $new_map;
31
  unset($new_map);
32

    
33
  // Retrieve dynamic menu link tree for the expansion mappings.
34
  // @todo Skip entire processing if initial $expand_map is empty and directly
35
  //   return $tree?
36
  if (!empty($expand_map)) {
37
    $tree_dynamic = admin_menu_tree_dynamic($expand_map);
38
  }
39
  else {
40
    $tree_dynamic = array();
41
  }
42

    
43
  // Merge local tasks with static menu tree.
44
  $tree = menu_tree_all_data($menu_name);
45
  admin_menu_merge_tree($tree, $tree_dynamic, array());
46

    
47
  return $tree;
48
}
49

    
50
/**
51
 * Load menu link trees for router paths containing dynamic arguments.
52
 *
53
 * @param $expand_map
54
 *   An array containing menu router path placeholder expansion argument
55
 *   mappings.
56
 *
57
 * @return
58
 *   An associative array whose keys are the parent paths of the menu router
59
 *   paths given in $expand_map as well as the parent paths of any child link
60
 *   deeper down the tree. The parent paths are used in admin_menu_merge_tree()
61
 *   to check whether anything needs to be merged.
62
 *
63
 * @see hook_admin_menu_map()
64
 */
65
function admin_menu_tree_dynamic(array $expand_map) {
66
  $p_columns = array();
67
  for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
68
    $p_columns[] = 'p' . $i;
69
  }
70

    
71
  // Fetch p* columns for all router paths to expand.
72
  $router_paths = array_keys($expand_map);
73
  $plids = db_select('menu_links', 'ml')
74
    ->fields('ml', $p_columns)
75
    ->condition('router_path', $router_paths)
76
    ->execute()
77
    ->fetchAll(PDO::FETCH_ASSOC);
78

    
79
  // Unlikely, but possible.
80
  if (empty($plids)) {
81
    return array();
82
  }
83

    
84
  // Use queried plid columns to query sub-trees for the router paths.
85
  $query = db_select('menu_links', 'ml');
86
  $query->join('menu_router', 'm', 'ml.router_path = m.path');
87
  $query
88
    ->fields('ml')
89
    ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), drupal_schema_fields_sql('menu_links')));
90

    
91
  // The retrieved menu link trees have to be ordered by depth, so parents
92
  // always come before their children for the storage logic below.
93
  foreach ($p_columns as $column) {
94
    $query->orderBy($column, 'ASC');
95
  }
96

    
97
  $db_or = db_or();
98
  foreach ($plids as $path_plids) {
99
    $db_and = db_and();
100
    // plids with value 0 may be ignored.
101
    foreach (array_filter($path_plids) as $column => $plid) {
102
      $db_and->condition($column, $plid);
103
    }
104
    $db_or->condition($db_and);
105
  }
106
  $query->condition($db_or);
107
  $result = $query
108
    ->execute()
109
    ->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
110

    
111
  // Store dynamic links grouped by parent path for later merging and assign
112
  // placeholder expansion arguments.
113
  $tree_dynamic = array();
114
  foreach ($result as $mlid => $link) {
115
    // If contained in $expand_map, then this is a (first) parent, and we need
116
    // to store by the defined 'parent' path for later merging, as well as
117
    // provide the expansion map arguments to apply to the dynamic tree.
118
    if (isset($expand_map[$link['path']])) {
119
      $parent_path = $expand_map[$link['path']]['parent'];
120
      $link['expand_map'] = $expand_map[$link['path']]['arguments'];
121
    }
122
    // Otherwise, just store this link keyed by its parent path; the expand_map
123
    // is automatically derived from parent paths.
124
    else {
125
      $parent_path = $result[$link['plid']]['path'];
126
    }
127

    
128
    $tree_dynamic[$parent_path][] = $link;
129
  }
130

    
131
  return $tree_dynamic;
132
}
133

    
134
/**
135
 * Walk through the entire menu tree and merge in expanded dynamic menu links.
136
 *
137
 * @param &$tree
138
 *   A menu tree structure as returned by menu_tree_all_data().
139
 * @param $tree_dynamic
140
 *   A dynamic menu tree structure as returned by admin_menu_tree_dynamic().
141
 * @param $expand_map
142
 *   An array containing menu router path placeholder expansion argument
143
 *   mappings.
144
 *
145
 * @see hook_admin_menu_map()
146
 * @see admin_menu_tree_dynamic()
147
 * @see menu_tree_all_data()
148
 */
149
function admin_menu_merge_tree(array &$tree, array $tree_dynamic, array $expand_map) {
150
  foreach ($tree as $key => $data) {
151
    $path = $data['link']['router_path'];
152

    
153
    // Recurse into regular menu tree.
154
    if ($tree[$key]['below']) {
155
      admin_menu_merge_tree($tree[$key]['below'], $tree_dynamic, $expand_map);
156
    }
157
    // Nothing to merge, if this parent path is not in our dynamic tree.
158
    if (!isset($tree_dynamic[$path])) {
159
      continue;
160
    }
161

    
162
    // Add expanded dynamic items.
163
    foreach ($tree_dynamic[$path] as $link) {
164
      // If the dynamic item has custom placeholder expansion parameters set,
165
      // use them, otherwise keep current.
166
      if (isset($link['expand_map'])) {
167
        // If there are currently no expansion parameters, we may use the new
168
        // set immediately.
169
        if (empty($expand_map)) {
170
          $current_expand_map = $link['expand_map'];
171
        }
172
        else {
173
          // Otherwise we need to filter out elements that differ from the
174
          // current set, i.e. that are not in the same path.
175
          $current_expand_map = array();
176
          foreach ($expand_map as $arguments) {
177
            foreach ($arguments as $placeholder => $value) {
178
              foreach ($link['expand_map'] as $new_arguments) {
179
                // Skip the new argument if it doesn't contain the current
180
                // replacement placeholders or if their values differ.
181
                if (!isset($new_arguments[$placeholder]) || $new_arguments[$placeholder] != $value) {
182
                  continue;
183
                }
184
                $current_expand_map[] = $new_arguments;
185
              }
186
            }
187
          }
188
        }
189
      }
190
      else {
191
        $current_expand_map = $expand_map;
192
      }
193

    
194
      // Skip dynamic items without expansion parameters.
195
      if (empty($current_expand_map)) {
196
        continue;
197
      }
198

    
199
      // Expand anonymous to named placeholders.
200
      // @see _menu_load_objects()
201
      $path_args = explode('/', $link['path']);
202
      $load_functions = unserialize($link['load_functions']);
203
      if (is_array($load_functions)) {
204
        foreach ($load_functions as $index => $function) {
205
          if ($function) {
206
            if (is_array($function)) {
207
              list($function,) = each($function);
208
            }
209
            // Add the loader function name minus "_load".
210
            $placeholder = '%' . substr($function, 0, -5);
211
            $path_args[$index] = $placeholder;
212
          }
213
        }
214
      }
215
      $path_dynamic = implode('/', $path_args);
216

    
217
      // Create new menu items using expansion arguments.
218
      foreach ($current_expand_map as $arguments) {
219
        // Create the cartesian product for all arguments and create new
220
        // menu items for each generated combination thereof.
221
        foreach (admin_menu_expand_args($arguments) as $replacements) {
222
          $newpath = strtr($path_dynamic, $replacements);
223
          // Skip this item, if any placeholder could not be replaced.
224
          // Faster than trying to invoke _menu_translate().
225
          if (strpos($newpath, '%') !== FALSE) {
226
            continue;
227
          }
228
          $map = explode('/', $newpath);
229
          $item = admin_menu_translate($link, $map);
230
          // Skip this item, if the current user does not have access.
231
          if (empty($item)) {
232
            continue;
233
          }
234
          // Build subtree using current replacement arguments.
235
          $new_expand_map = array();
236
          foreach ($replacements as $placeholder => $value) {
237
            $new_expand_map[$placeholder] = array($value);
238
          }
239
          admin_menu_merge_tree($item, $tree_dynamic, array($new_expand_map));
240
          $tree[$key]['below'] += $item;
241
        }
242
      }
243
    }
244
    // Sort new subtree items.
245
    ksort($tree[$key]['below']);
246
  }
247
}
248

    
249
/**
250
 * Translate an expanded router item into a menu link suitable for rendering.
251
 *
252
 * @param $router_item
253
 *   A menu router item.
254
 * @param $map
255
 *   A path map with placeholders replaced.
256
 */
257
function admin_menu_translate($router_item, $map) {
258
  _menu_translate($router_item, $map, TRUE);
259

    
260
  // Run through hook_translated_menu_link_alter() to add devel information,
261
  // if configured.
262
  $router_item['menu_name'] = 'management';
263
  // @todo Invoke as usual like _menu_link_translate().
264
  admin_menu_translated_menu_link_alter($router_item, NULL);
265

    
266
  if ($router_item['access']) {
267
    // Override mlid to make this item unique; since these items are expanded
268
    // from dynamic items, the mlid is always the same, so each item would
269
    // replace any other.
270
    // @todo Doing this instead leads to plenty of duplicate links below
271
    //   admin/structure/menu; likely a hidden recursion problem.
272
    // $router_item['mlid'] = $router_item['href'] . $router_item['mlid'];
273
    $router_item['mlid'] = $router_item['href'];
274
    // Turn menu callbacks into regular menu items to make them visible.
275
    if ($router_item['type'] == MENU_CALLBACK) {
276
      $router_item['type'] = MENU_NORMAL_ITEM;
277
    }
278

    
279
    // @see _menu_tree_check_access()
280
    $key = (50000 + $router_item['weight']) . ' ' . $router_item['title'] . ' ' . $router_item['mlid'];
281
    return array($key => array(
282
      'link' => $router_item,
283
      'below' => array(),
284
    ));
285
  }
286

    
287
  return array();
288
}
289

    
290
/**
291
 * Create the cartesian product of multiple varying sized argument arrays.
292
 *
293
 * @param $arguments
294
 *   A two dimensional array of arguments.
295
 *
296
 * @see hook_admin_menu_map()
297
 */
298
function admin_menu_expand_args($arguments) {
299
  $replacements = array();
300

    
301
  // Initialize line cursors, move out array keys (placeholders) and assign
302
  // numeric keys instead.
303
  $i = 0;
304
  $placeholders = array();
305
  $new_arguments = array();
306
  foreach ($arguments as $placeholder => $values) {
307
    // Skip empty arguments.
308
    if (empty($values)) {
309
      continue;
310
    }
311
    $cursor[$i] = 0;
312
    $placeholders[$i] = $placeholder;
313
    $new_arguments[$i] = $values;
314
    $i++;
315
  }
316
  $arguments = $new_arguments;
317
  unset($new_arguments);
318

    
319
  if ($rows = count($arguments)) {
320
    do {
321
      // Collect current argument from each row.
322
      $row = array();
323
      for ($i = 0; $i < $rows; ++$i) {
324
        $row[$placeholders[$i]] = $arguments[$i][$cursor[$i]];
325
      }
326
      $replacements[] = $row;
327

    
328
      // Increment cursor position.
329
      $j = $rows - 1;
330
      $cursor[$j]++;
331
      while (!array_key_exists($cursor[$j], $arguments[$j])) {
332
        // No more arguments left: reset cursor, go to next line and increment
333
        // that cursor instead. Repeat until argument found or out of rows.
334
        $cursor[$j] = 0;
335
        if (--$j < 0) {
336
          // We're done.
337
          break 2;
338
        }
339
        $cursor[$j]++;
340
      }
341
    } while (1);
342
  }
343

    
344
  return $replacements;
345
}
346

    
347
/**
348
 * Build the administration menu as renderable menu links.
349
 *
350
 * @param $tree
351
 *   A data structure representing the administration menu tree as returned from
352
 *   menu_tree_all_data().
353
 *
354
 * @return
355
 *   The complete administration menu, suitable for theme_admin_menu_links().
356
 *
357
 * @see theme_admin_menu_links()
358
 * @see admin_menu_menu_alter()
359
 */
360
function admin_menu_links_menu($tree) {
361
  $links = array();
362
  foreach ($tree as $data) {
363
    // Skip items that are inaccessible, invisible, or link to their parent.
364
    // (MENU_DEFAULT_LOCAL_TASK), and MENU_CALLBACK-alike items that should only
365
    // appear in the breadcrumb.
366
    if (!$data['link']['access'] || $data['link']['type'] & MENU_LINKS_TO_PARENT || $data['link']['type'] == MENU_VISIBLE_IN_BREADCRUMB || $data['link']['hidden'] == 1) {
367
      continue;
368
    }
369
    // Hide 'Administer' and make child links appear on this level.
370
    // @todo Make this configurable.
371
    if ($data['link']['router_path'] == 'admin') {
372
      if ($data['below']) {
373
        $links = array_merge($links, admin_menu_links_menu($data['below']));
374
      }
375
      continue;
376
    }
377
    // Omit alias lookups.
378
    $data['link']['localized_options']['alias'] = TRUE;
379
    // Remove description to prevent mouseover tooltip clashes.
380
    unset($data['link']['localized_options']['attributes']['title']);
381

    
382
    // Make action links (typically "Add ...") appear first in dropdowns.
383
    // They might appear first already, but only as long as there is no link
384
    // that comes alphabetically first (e.g., a node type with label "Ad").
385
    if ($data['link']['type'] & MENU_IS_LOCAL_ACTION) {
386
      $data['link']['weight'] -= 1000;
387
    }
388

    
389
    $links[$data['link']['href']] = array(
390
      '#title' => $data['link']['title'],
391
      '#href' => $data['link']['href'],
392
      '#options' => $data['link']['localized_options'],
393
      '#weight' => $data['link']['weight'],
394
    );
395

    
396
    // Recurse to add any child links.
397
    $children = array();
398
    if ($data['below']) {
399
      $children = admin_menu_links_menu($data['below']);
400
      $links[$data['link']['href']] += $children;
401
    }
402

    
403
    // Handle links pointing to category/overview pages.
404
    if ($data['link']['page_callback'] == 'system_admin_menu_block_page' || $data['link']['page_callback'] == 'system_admin_config_page') {
405
      // Apply a marker for others to consume.
406
      $links[$data['link']['href']]['#is_category'] = TRUE;
407
      // Automatically hide empty categories.
408
      // Check for empty children first for performance. Only when non-empty
409
      // (typically 'admin/config'), check whether children are accessible.
410
      if (empty($children) || !element_get_visible_children($children)) {
411
        $links[$data['link']['href']]['#access'] = FALSE;
412
      }
413
    }
414
  }
415
  return $links;
416
}
417

    
418
/**
419
 * Build icon menu links; mostly containing maintenance helpers.
420
 *
421
 * @see theme_admin_menu_links()
422
 */
423
function admin_menu_links_icon() {
424
  $destination = drupal_get_destination();
425

    
426
  $links = array(
427
    '#theme' => 'admin_menu_links',
428
    '#wrapper_attributes' => array('id' => 'admin-menu-icon'),
429
    '#weight' => -100,
430
  );
431
  $links['icon'] = array(
432
    '#title' => theme('admin_menu_icon'),
433
    '#attributes' => array('class' => array('admin-menu-icon')),
434
    '#href' => '<front>',
435
    '#options' => array(
436
      'html' => TRUE,
437
    ),
438
  );
439
  // Add link to manually run cron.
440
  $links['icon']['cron'] = array(
441
    '#title' => t('Run cron'),
442
    '#weight' => 50,
443
    '#access' => user_access('administer site configuration'),
444
    '#href' => 'admin/reports/status/run-cron',
445
  );
446
  // Add link to run update.php.
447
  $links['icon']['update'] = array(
448
    '#title' => t('Run updates'),
449
    '#weight' => 50,
450
    // @see update_access_allowed()
451
    '#access' => $GLOBALS['user']->uid == 1 || !empty($GLOBALS['update_free_access']) || user_access('administer software updates'),
452
    '#href' => base_path() . 'update.php',
453
    '#options' => array(
454
      'external' => TRUE,
455
    ),
456
  );
457
  // Add link to drupal.org.
458
  $links['icon']['drupal.org'] = array(
459
    '#title' => 'Drupal.org',
460
    '#weight' => 100,
461
    '#access' => user_access('display drupal links'),
462
    '#href' => 'http://drupal.org',
463
  );
464
  // Add links to project issue queues.
465
  foreach (module_list(FALSE, TRUE) as $module) {
466
    $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info');
467
    if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) {
468
      continue;
469
    }
470
    $links['icon']['drupal.org'][$info['project']] = array(
471
      '#title' => t('@project issue queue', array('@project' => $info['name'])),
472
      '#weight' => ($info['project'] == 'drupal' ? -10 : 0),
473
      '#href' => 'http://drupal.org/project/issues/' . $info['project'],
474
      '#options' => array(
475
        'query' => array('version' => (isset($info['core']) ? $info['core'] : 'All')),
476
      ),
477
    );
478
  }
479
  // Add items to flush caches.
480
  $links['icon']['flush-cache'] = array(
481
    '#title' => t('Flush all caches'),
482
    '#weight' => 20,
483
    '#access' => user_access('flush caches'),
484
    '#href' => 'admin_menu/flush-cache',
485
    '#options' => array(
486
      'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache')),
487
    ),
488
  );
489
  $caches = module_invoke_all('admin_menu_cache_info');
490
  foreach ($caches as $name => $cache) {
491
    $links['icon']['flush-cache'][$name] = array(
492
      '#title' => $cache['title'],
493
      '#href' => 'admin_menu/flush-cache/' . $name,
494
      '#options' => array(
495
        'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache/' . $name)),
496
      ),
497
    );
498
  }
499

    
500
  // Add link to toggle developer modules (performance).
501
  $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL);
502
  $links['icon']['toggle-modules'] = array(
503
    '#title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'),
504
    '#weight' => 88,
505
    '#access' => user_access('administer modules'),
506
    '#href' => 'admin_menu/toggle-modules',
507
    '#options' => array(
508
      'query' => $destination + array('token' => drupal_get_token('admin_menu/toggle-modules')),
509
    ),
510
  );
511

    
512
  // Add Devel module menu links.
513
  if (module_exists('devel')) {
514
    $devel_tree = menu_build_tree('devel');
515
    $devel_links = admin_menu_links_menu($devel_tree);
516
    if (element_get_visible_children($devel_links)) {
517
      $links['icon']['devel'] = array(
518
        '#title' => t('Development'),
519
        '#weight' => 30,
520
      ) + $devel_links;
521
    }
522
  }
523

    
524
  return $links;
525
}
526

    
527
/**
528
 * Builds the account links.
529
 *
530
 * @see theme_admin_menu_links()
531
 */
532
function admin_menu_links_account() {
533
  $links = array(
534
    '#theme' => 'admin_menu_links',
535
    '#wrapper_attributes' => array('id' => 'admin-menu-account'),
536
    '#weight' => 100,
537
  );
538
  $links['account'] = array(
539
    '#title' => format_username($GLOBALS['user']),
540
    '#weight' => -99,
541
    '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-account')),
542
    '#href' => 'user/' . $GLOBALS['user']->uid,
543
  );
544
  $links['logout'] = array(
545
    '#title' => t('Log out'),
546
    '#weight' => -100,
547
    '#attributes' => array('class' => array('admin-menu-action')),
548
    '#href' => 'user/logout',
549
  );
550
  // Add Devel module switch user links.
551
  $switch_links = module_invoke('devel', 'switch_user_list');
552
  if (!empty($switch_links) && count($switch_links) > 1) {
553
    foreach ($switch_links as $uid => $link) {
554
      $links['account'][$link['title']] = array(
555
        '#title' => $link['title'],
556
        '#description' => $link['attributes']['title'],
557
        '#href' => $link['href'],
558
        '#options' => array(
559
          'query' => $link['query'],
560
          'html' => !empty($link['html']),
561
        ),
562
      );
563
    }
564
  }
565
  return $links;
566
}
567

    
568
/**
569
 * Builds user counter.
570
 *
571
 * @see theme_admin_menu_links()
572
 */
573
function admin_menu_links_users() {
574
  $links = array(
575
    '#theme' => 'admin_menu_links',
576
    '#wrapper_attributes' => array('id' => 'admin-menu-users'),
577
    '#weight' => 150,
578
  );
579
  // Add link to show current authenticated/anonymous users.
580
  $links['user-counter'] = array(
581
    '#title' => admin_menu_get_user_count(),
582
    '#description' => t('Current anonymous / authenticated users'),
583
    '#weight' => -90,
584
    '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-users')),
585
    '#href' => (user_access('administer users') ? 'admin/people/people' : 'user'),
586
  );
587
  return $links;
588
}
589

    
590
/**
591
 * Build search widget.
592
 *
593
 * @see theme_admin_menu_links()
594
 */
595
function admin_menu_links_search() {
596
  $links = array(
597
    '#theme' => 'admin_menu_links',
598
    '#wrapper_attributes' => array('id' => 'admin-menu-search'),
599
    '#weight' => 180,
600
  );
601
  $links['search'] = array(
602
    '#type' => 'textfield',
603
    '#title' => t('Search'),
604
    '#title_display' => 'attribute',
605
    '#attributes' => array(
606
      'placeholder' => t('Search'),
607
      'class' => array('admin-menu-search'),
608
    ),
609
  );
610
  return $links;
611
}
612

    
613
/**
614
 * Form builder function for module settings.
615
 */
616
function admin_menu_theme_settings() {
617
  $form['admin_menu_margin_top'] = array(
618
    '#type' => 'checkbox',
619
    '#title' => t('Adjust top margin'),
620
    '#default_value' => variable_get('admin_menu_margin_top', 1),
621
    '#description' => t('Shifts the site output down by approximately 20 pixels from the top of the viewport. If disabled, absolute- or fixed-positioned page elements may be covered by the administration menu.'),
622
  );
623
  $form['admin_menu_position_fixed'] = array(
624
    '#type' => 'checkbox',
625
    '#title' => t('Keep menu at top of page'),
626
    '#default_value' => variable_get('admin_menu_position_fixed', 1),
627
    '#description' => t('Displays the administration menu always at the top of the browser viewport (even when scrolling the page).'),
628
  );
629
  // @todo Re-confirm this with latest browser versions.
630
  $form['admin_menu_position_fixed']['#description'] .= '<br /><strong>' . t('In some browsers, this setting may result in a malformed page, an invisible cursor, non-selectable elements in forms, or other issues.') . '</strong>';
631

    
632
  $form['advanced'] = array(
633
    '#type' => 'vertical_tabs',
634
    '#title' => t('Advanced settings'),
635
  );
636

    
637
  $form['plugins'] = array(
638
    '#type' => 'fieldset',
639
    '#title' => t('Plugins'),
640
    '#group' => 'advanced',
641
  );
642
  $form['plugins']['admin_menu_components'] = array(
643
    '#type' => 'checkboxes',
644
    '#title' => t('Enabled components'),
645
    '#options' => array(
646
      'admin_menu.icon' => t('Icon menu'),
647
      'admin_menu.menu' => t('Administration menu'),
648
      'admin_menu.search' => t('Search bar'),
649
      'admin_menu.users' => t('User counts'),
650
      'admin_menu.account' => t('Account links'),
651
    ),
652
  );
653
  $form['plugins']['admin_menu_components']['#default_value'] = array_keys(array_filter(variable_get('admin_menu_components', $form['plugins']['admin_menu_components']['#options'])));
654

    
655
  $process = element_info_property('checkboxes', '#process', array());
656
  $form['plugins']['admin_menu_components']['#process'] = array_merge(array('admin_menu_settings_process_components'), $process);
657
  $form['#attached']['js'][] = drupal_get_path('module', 'admin_menu') . '/admin_menu.admin.js';
658

    
659
  $form['tweaks'] = array(
660
    '#type' => 'fieldset',
661
    '#title' => t('System tweaks'),
662
    '#group' => 'advanced',
663
  );
664
  $form['tweaks']['admin_menu_tweak_modules'] = array(
665
    '#type' => 'checkbox',
666
    '#title' => t('Collapse module groups on the <a href="!modules-url">%modules</a> page', array(
667
      '%modules' => t('Modules'),
668
      '!modules-url' => url('admin/modules'),
669
    )),
670
    '#default_value' => variable_get('admin_menu_tweak_modules', 0),
671
  );
672
  if (module_exists('util')) {
673
    $form['tweaks']['admin_menu_tweak_modules']['#description'] .= '<br /><strong>' . t('If the Utility module was installed for this purpose, it can be safely disabled and uninstalled.') . '</strong>';
674
  }
675
  $form['tweaks']['admin_menu_tweak_permissions'] = array(
676
    '#type' => 'checkbox',
677
    '#title' => t('Collapse module groups on the <a href="@permissions-url">%permissions</a> page', array(
678
      '%permissions' => t('Permissions'),
679
      '@permissions-url' => url('admin/people/permissions'),
680
    )),
681
    '#default_value' => variable_get('admin_menu_tweak_permissions', 0),
682
  );
683
  $form['tweaks']['admin_menu_tweak_tabs'] = array(
684
    '#type' => 'checkbox',
685
    '#title' => t('Move local tasks into menu'),
686
    '#default_value' => variable_get('admin_menu_tweak_tabs', 0),
687
    '#description' => t('Moves the tabs on all pages into the administration menu. Only possible for themes using the CSS classes <code>tabs primary</code> and <code>tabs secondary</code>.'),
688
  );
689

    
690
  $form['performance'] = array(
691
    '#type' => 'fieldset',
692
    '#title' => t('Performance'),
693
    '#group' => 'advanced',
694
  );
695
  $form['performance']['admin_menu_cache_client'] = array(
696
    '#type' => 'checkbox',
697
    '#title' => t('Cache menu in client-side browser'),
698
    '#default_value' => variable_get('admin_menu_cache_client', 1),
699
  );
700
  // Fetch all available modules manually, since module_list() only returns
701
  // currently enabled modules, which makes this setting pointless if developer
702
  // modules are currently disabled.
703
  $all_modules = array();
704
  $result = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' ORDER BY name ASC");
705
  foreach ($result as $module) {
706
    if (file_exists($module->filename)) {
707
      $info = unserialize($module->info);
708
      $all_modules[$module->name] = $info['name'];
709
    }
710
  }
711
  $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules());
712
  $devel_modules = array_intersect_key($all_modules, array_flip($devel_modules));
713
  $form['performance']['admin_menu_devel_modules_skip'] = array(
714
    '#type' => 'checkboxes',
715
    '#title' => t('Developer modules to keep enabled'),
716
    '#default_value' => variable_get('admin_menu_devel_modules_skip', array()),
717
    '#options' => $devel_modules,
718
    '#access' => !empty($devel_modules),
719
    '#description' => t('The selected modules will not be disabled when the link %disable-developer-modules below the icon in the menu is invoked.', array(
720
      '%disable-developer-modules' => t('Disable developer modules'),
721
    )),
722
  );
723

    
724
  return system_settings_form($form);
725
}
726

    
727
/**
728
 * #process callback for component plugin form element in admin_menu_theme_settings().
729
 */
730
function admin_menu_settings_process_components($element) {
731
  // Assign 'rel' attributes to all options to achieve a live preview.
732
  // Unfortunately, #states relies on wrapping .form-wrapper classes, so it
733
  // cannot be used here.
734
  foreach ($element['#options'] as $key => $label) {
735
    if (!isset($element[$key]['#attributes']['rel'])) {
736
      $id = preg_replace('/[^a-z]/', '-', $key);
737
      $element[$key]['#attributes']['rel'] = '#' . $id;
738
    }
739
  }
740
  return $element;
741
}
742

    
743
/**
744
 * Form validation handler for admin_menu_theme_settings().
745
 */
746
function admin_menu_theme_settings_validate(&$form, &$form_state) {
747
  // Change the configured components to Boolean values.
748
  foreach ($form_state['values']['admin_menu_components'] as $component => &$enabled) {
749
    $enabled = (bool) $enabled;
750
  }
751
}
752

    
753
/**
754
 * Implementation of hook_form_FORM_ID_alter().
755
 *
756
 * Extends Devel module with Administration menu developer settings.
757
 */
758
function _admin_menu_form_devel_admin_settings_alter(&$form, $form_state) {
759
  // Shift system_settings_form buttons.
760
  $weight = isset($form['buttons']['#weight']) ? $form['buttons']['#weight'] : 0;
761
  $form['buttons']['#weight'] = $weight + 1;
762

    
763
  $form['admin_menu'] = array(
764
    '#type' => 'fieldset',
765
    '#title' => t('Administration menu settings'),
766
    '#collapsible' => TRUE,
767
    '#collapsed' => TRUE,
768
  );
769
  $display_options = array('mid', 'weight', 'pid');
770
  $display_options = array(0 => t('None'), 'mlid' => t('Menu link ID'), 'weight' => t('Weight'), 'plid' => t('Parent link ID'));
771
  $form['admin_menu']['admin_menu_display'] = array(
772
    '#type' => 'radios',
773
    '#title' => t('Display additional data for each menu item'),
774
    '#default_value' => variable_get('admin_menu_display', 0),
775
    '#options' => $display_options,
776
    '#description' => t('Display the selected items next to each menu item link.'),
777
  );
778
  $form['admin_menu']['admin_menu_show_all'] = array(
779
    '#type' => 'checkbox',
780
    '#title' => t('Display all menu items'),
781
    '#default_value' => variable_get('admin_menu_show_all', 0),
782
    '#description' => t('If enabled, all menu items are displayed regardless of your site permissions. <em>Note: Do not enable on a production site.</em>'),
783
  );
784
}
785

    
786
/**
787
 * Menu callback; Enable/disable developer modules.
788
 *
789
 * This can save up to 150ms on each uncached page request.
790
 */
791
function admin_menu_toggle_modules() {
792
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) {
793
    return MENU_ACCESS_DENIED;
794
  }
795

    
796
  $rebuild = FALSE;
797
  $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL);
798
  if (isset($saved_state)) {
799
    // Re-enable modules that were enabled before.
800
    module_enable($saved_state);
801
    variable_del('admin_menu_devel_modules_enabled');
802
    drupal_set_message(t('Enabled these modules: !module-list.', array('!module-list' => implode(', ', $saved_state))));
803
    $rebuild = TRUE;
804
  }
805
  else {
806
    // Allow site admins to override this variable via settings.php.
807
    $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules());
808
    // Store currently enabled modules in a variable.
809
    $devel_modules = array_intersect(module_list(FALSE, FALSE), $devel_modules);
810
    $devel_modules = array_diff($devel_modules, variable_get('admin_menu_devel_modules_skip', array()));
811
    if (!empty($devel_modules)) {
812
      variable_set('admin_menu_devel_modules_enabled', $devel_modules);
813
      // Disable developer modules.
814
      module_disable($devel_modules);
815
      drupal_set_message(t('Disabled these modules: !module-list.', array('!module-list' => implode(', ', $devel_modules))));
816
      $rebuild = TRUE;
817
    }
818
    else {
819
      drupal_set_message(t('No developer modules are enabled.'));
820
    }
821
  }
822
  if ($rebuild) {
823
    // Make sure everything is rebuilt, basically a combination of the calls
824
    // from system_modules() and system_modules_submit().
825
    drupal_theme_rebuild();
826
    menu_rebuild();
827
    cache_clear_all('schema', 'cache');
828
    cache_clear_all();
829
    drupal_clear_css_cache();
830
    drupal_clear_js_cache();
831
    // Synchronize to catch any actions that were added or removed.
832
    actions_synchronize();
833
    // Finally, flush admin_menu's cache.
834
    admin_menu_flush_caches();
835
  }
836
  drupal_goto();
837
}
838

    
839
/**
840
 * Helper function to return a default list of developer modules.
841
 */
842
function _admin_menu_developer_modules() {
843
  return array(
844
    'admin_devel',
845
    'cache_disable',
846
    'coder',
847
    'content_copy',
848
    'context_ui',
849
    'debug',
850
    'delete_all',
851
    'demo',
852
    'devel',
853
    'devel_node_access',
854
    'devel_themer',
855
    'field_ui',
856
    'fontyourface_ui',
857
    'form_controller',
858
    'imagecache_ui',
859
    'journal',
860
    'l10n_client',
861
    'l10n_update',
862
    'macro',
863
    'rules_admin',
864
    'stringoverrides',
865
    'trace',
866
    'upgrade_status',
867
    'user_display_ui',
868
    'util',
869
    'views_ui',
870
    'views_theme_wizard',
871
  );
872
}
873

    
874
/**
875
 * Flush all caches or a specific one.
876
 *
877
 * @param $name
878
 *   (optional) Name of cache to flush.
879
 */
880
function admin_menu_flush_cache($name = NULL) {
881
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) {
882
    return MENU_ACCESS_DENIED;
883
  }
884
  if (isset($name)) {
885
    $caches = module_invoke_all('admin_menu_cache_info');
886
    if (!isset($caches[$name])) {
887
      return MENU_NOT_FOUND;
888
    }
889
  }
890
  else {
891
    $caches[$name] = array(
892
      'title' => t('Every'),
893
      'callback' => 'drupal_flush_all_caches',
894
    );
895
  }
896
  // Pass the cache to flush forward to the callback.
897
  $function = $caches[$name]['callback'];
898
  $function($name);
899

    
900
  drupal_set_message(t('!title cache cleared.', array('!title' => $caches[$name]['title'])));
901

    
902
  // The JavaScript injects a destination request parameter pointing to the
903
  // originating page, so the user is redirected back to that page. Without
904
  // destination parameter, the redirect ends on the front page.
905
  drupal_goto();
906
}
907

    
908
/**
909
 * Implements hook_admin_menu_cache_info().
910
 */
911
function admin_menu_admin_menu_cache_info() {
912
  $caches['admin_menu'] = array(
913
    'title' => t('Administration menu'),
914
    'callback' => '_admin_menu_flush_cache',
915
  );
916
  return $caches;
917
}
918

    
919
/**
920
 * Implements hook_admin_menu_cache_info() on behalf of System module.
921
 */
922
function system_admin_menu_cache_info() {
923
  $caches = array(
924
    'assets' => t('CSS and JavaScript'),
925
    'cache' => t('Page and else'),
926
    'menu' => t('Menu'),
927
    'registry' => t('Class registry'),
928
    'theme' => t('Theme registry'),
929
  );
930
  foreach ($caches as $name => $cache) {
931
    $caches[$name] = array(
932
      'title' => $cache,
933
      'callback' => '_admin_menu_flush_cache',
934
    );
935
  }
936
  return $caches;
937
}
938

    
939
/**
940
 * Implements hook_admin_menu_cache_info() on behalf of Update module.
941
 */
942
function update_admin_menu_cache_info() {
943
  $caches['update'] = array(
944
    'title' => t('Update data'),
945
    'callback' => '_update_cache_clear',
946
  );
947
  return $caches;
948
}
949

    
950
/**
951
 * Flush all caches or a specific one.
952
 *
953
 * @param $name
954
 *   (optional) Name of cache to flush.
955
 *
956
 * @see system_admin_menu_cache_info()
957
 */
958
function _admin_menu_flush_cache($name = NULL) {
959
  switch ($name) {
960
    case 'admin_menu':
961
      admin_menu_flush_caches();
962
      break;
963

    
964
    case 'menu':
965
      menu_rebuild();
966
      break;
967

    
968
    case 'registry':
969
      registry_rebuild();
970
      // Fall-through to clear cache tables, since registry information is
971
      // usually the base for other data that is cached (e.g. SimpleTests).
972
    case 'cache':
973
      // Don't clear cache_form - in-progress form submissions may break.
974
      // Ordered so clearing the page cache will always be the last action.
975
      // @see drupal_flush_all_caches()
976
      $core = array('cache', 'cache_bootstrap', 'cache_filter', 'cache_page');
977
      $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
978
      foreach ($cache_tables as $table) {
979
        cache_clear_all('*', $table, TRUE);
980
      }
981
      break;
982

    
983
    case 'assets':
984
      // Change query-strings on css/js files to enforce reload for all users.
985
      _drupal_flush_css_js();
986

    
987
      drupal_clear_css_cache();
988
      drupal_clear_js_cache();
989

    
990
      // Clear the page cache, since cached HTML pages might link to old CSS and
991
      // JS aggregates.
992
      cache_clear_all('*', 'cache_page', TRUE);
993
      break;
994

    
995
    case 'theme':
996
      system_rebuild_theme_data();
997
      drupal_theme_rebuild();
998
      break;
999
  }
1000
}
1001

    
1002
/**
1003
 * Preprocesses variables for theme_admin_menu_icon().
1004
 */
1005
function template_preprocess_admin_menu_icon(&$variables) {
1006
  // Image source might have been passed in as theme variable.
1007
  if (!isset($variables['src'])) {
1008
    if (theme_get_setting('toggle_favicon')) {
1009
      $variables['src'] = theme_get_setting('favicon');
1010
    }
1011
    else {
1012
      $variables['src'] = base_path() . 'misc/favicon.ico';
1013
    }
1014
  }
1015
  // Strip the protocol without delimiters for transient HTTP/HTTPS support.
1016
  // Since the menu is cached on the server-side and client-side, the cached
1017
  // version might contain a HTTP link, whereas the actual page is on HTTPS.
1018
  // Relative paths will work fine, but theme_get_setting() returns an
1019
  // absolute URI.
1020
  $variables['src'] = preg_replace('@^https?:@', '', $variables['src']);
1021
  $variables['src'] = check_plain($variables['src']);
1022
  $variables['alt'] = t('Home');
1023
}
1024

    
1025
/**
1026
 * Renders an icon to display in the administration menu.
1027
 *
1028
 * @ingroup themeable
1029
 */
1030
function theme_admin_menu_icon($variables) {
1031
  return '<img class="admin-menu-icon" src="' . $variables['src'] . '" width="16" height="16" alt="' . $variables['alt'] . '" />';
1032
}
1033