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
|
|