Projet

Général

Profil

Paste
Télécharger (30,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / admin_menu / admin_menu.module @ 13755f8d

1
<?php
2

    
3
/**
4
 * @file
5
 * Render an administrative menu as a dropdown menu at the top of the window.
6
 */
7

    
8
/**
9
 * Implements hook_help().
10
 */
11
function admin_menu_help($path, $arg) {
12
  switch ($path) {
13
    case 'admin/config/administration/admin_menu':
14
      return '<p>' . t('The administration menu module provides a dropdown menu arranged for one- or two-click access to most administrative tasks and other common destinations (to users with the proper permissions). Use the settings below to customize the appearance of the menu.') . '</p>';
15

    
16
    case 'admin/help#admin_menu':
17
      $output = '';
18
      $output .= '<p>' . t('The administration menu module provides a dropdown menu arranged for one- or two-click access to most administrative tasks and other common destinations (to users with the proper permissions). Administration menu also displays the number of anonymous and authenticated users, and allows modules to add their own custom menu items. Integration with the menu varies from module to module; the contributed module <a href="@drupal">Devel</a>, for instance, makes strong use of the administration menu module to provide quick access to development tools.', array('@drupal' => 'http://drupal.org/project/devel')) . '</p>';
19
      $output .= '<p>' . t('The administration menu <a href="@settings">settings page</a> allows you to modify some elements of the menu\'s behavior and appearance. Since the appearance of the menu is dependent on your site theme, substantial customizations require modifications to your site\'s theme and CSS files. See the advanced module README.txt file for more information on theme and CSS customizations.', array('@settings' => url('admin/config/administration/admin_menu'))) . '</p>';
20
      $output .= '<p>' . t('The menu items displayed in the administration menu depend upon the actual permissions of the viewer. First, the administration menu is only displayed to users in roles with the <em>Access administration menu</em> (admin_menu module) permission. Second, a user must be a member of a role with the <em>Access administration pages</em> (system module) permission to view administrative links. And, third, only currently permitted links are displayed; for example, if a user is not a member of a role with the permissions <em>Administer permissions</em> (user module) and <em>Administer users</em> (user module), the <em>User management</em> menu item is not displayed.') . '</p>';
21
      return $output;
22
  }
23
}
24

    
25
/**
26
 * Implements hook_permission().
27
 */
28
function admin_menu_permission() {
29
  return array(
30
    'access administration menu' => array(
31
      'title' => t('Access administration menu'),
32
      'description' => t('Display the administration menu at the top of each page.'),
33
    ),
34
    'flush caches' => array(
35
      'title' => t('Flush caches'),
36
      'description' => t('Access links to flush caches in the administration menu.'),
37
    ),
38
    'display drupal links' => array(
39
      'title' => t('Display Drupal links'),
40
      'description' => t('Provide Drupal.org links in the administration menu.'),
41
    ),
42
  );
43
}
44

    
45
/**
46
 * Implements hook_theme().
47
 */
48
function admin_menu_theme() {
49
  return array(
50
    'admin_menu_links' => array(
51
      'render element' => 'elements',
52
    ),
53
    'admin_menu_icon' => array(
54
      'variables' => array('src' => NULL, 'alt' => NULL),
55
      'file' => 'admin_menu.inc',
56
    ),
57
  );
58
}
59

    
60
/**
61
 * Implements hook_menu().
62
 */
63
function admin_menu_menu() {
64
  // AJAX callback.
65
  // @see http://drupal.org/project/js
66
  $items['js/admin_menu/cache'] = array(
67
    'page callback' => 'admin_menu_js_cache',
68
    'delivery callback' => 'admin_menu_deliver',
69
    'access arguments' => array('access administration menu'),
70
    'type' => MENU_CALLBACK,
71
  );
72
  // Module settings.
73
  $items['admin/config/administration'] = array(
74
    'title' => 'Administration',
75
    'description' => 'Administration tools.',
76
    'page callback' => 'system_admin_menu_block_page',
77
    'access arguments' => array('access administration pages'),
78
    'file' => 'system.admin.inc',
79
    'file path' => drupal_get_path('module', 'system'),
80
  );
81
  $items['admin/config/administration/admin_menu'] = array(
82
    'title' => 'Administration menu',
83
    'description' => 'Adjust administration menu settings.',
84
    'page callback' => 'drupal_get_form',
85
    'page arguments' => array('admin_menu_theme_settings'),
86
    'access arguments' => array('administer site configuration'),
87
    'file' => 'admin_menu.inc',
88
  );
89
  // Menu link callbacks.
90
  $items['admin_menu/flush-cache'] = array(
91
    'page callback' => 'admin_menu_flush_cache',
92
    'access arguments' => array('flush caches'),
93
    'type' => MENU_CALLBACK,
94
    'file' => 'admin_menu.inc',
95
  );
96
  return $items;
97
}
98

    
99
/**
100
 * Implements hook_menu_alter().
101
 */
102
function admin_menu_menu_alter(&$items) {
103
  // Flush client-side caches whenever the menu is rebuilt.
104
  admin_menu_flush_caches();
105
}
106

    
107
/**
108
 * Implements hook_menu_link_insert().
109
 */
110
function admin_menu_menu_link_insert($link) {
111
  // Flush all of our caches to pick up the link.
112
  admin_menu_flush_caches();
113
}
114

    
115
/**
116
 * Implements hook_menu_link_update().
117
 */
118
function admin_menu_menu_link_update($link) {
119
  // Flush all of our caches to pick up the link.
120
  admin_menu_flush_caches();
121
}
122

    
123
/**
124
 * Implements hook_menu_link_delete().
125
 */
126
function admin_menu_menu_link_delete($link) {
127
  // Flush all of our caches to pick up the link.
128
  admin_menu_flush_caches();
129
}
130

    
131
/**
132
 * Implements hook_system_info_alter().
133
 *
134
 * Indicate that the 'page_bottom' region (in which the administration menu
135
 * is displayed) is an overlay supplemental region that should be refreshed
136
 * whenever its content is updated.
137
 *
138
 * @see toolbar_system_info_alter()
139
 */
140
function admin_menu_system_info_alter(&$info, $file, $type) {
141
  if ($type == 'theme') {
142
    $info['overlay_supplemental_regions'][] = 'page_bottom';
143
  }
144
}
145

    
146
/**
147
 * Implements hook_page_build().
148
 */
149
function admin_menu_page_build(&$page) {
150
  if (!user_access('access administration menu') || admin_menu_suppress(FALSE)) {
151
    return;
152
  }
153
  // Performance: Skip this entirely for AJAX requests.
154
  if (strpos($_GET['q'], 'js/') === 0) {
155
    return;
156
  }
157
  global $user, $language;
158
  $path = drupal_get_path('module', 'admin_menu');
159

    
160
  $page['page_bottom']['admin_menu'] = array(
161
    '#attached' => array(),
162
  );
163
  $attached = &$page['page_bottom']['admin_menu']['#attached'];
164
  $options = array('every_page' => TRUE);
165

    
166
  $attached['css'][$path . '/admin_menu.css'] = $options;
167
  if ($user->uid == 1) {
168
    $attached['css'][$path . '/admin_menu.uid1.css'] = $options;
169
  }
170
  // Previous versions used the 'defer' attribute to increase browser rendering
171
  // performance. At least starting with Firefox 3.6, deferred .js files are
172
  // loaded, but Drupal.behaviors are not contained in the DOM when drupal.js
173
  // executes Drupal.attachBehaviors().
174
  $attached['js'][$path . '/admin_menu.js'] = $options;
175

    
176
  // Destination query strings are applied via JS.
177
  $settings['destination'] = drupal_http_build_query(drupal_get_destination());
178

    
179
  // Determine whether we need to show all components and disable all caches.
180
  $complete = FALSE;
181
  if (current_path() == 'admin/config/administration/admin_menu' && $_SERVER['REQUEST_METHOD'] == 'GET') {
182
    $complete = TRUE;
183
  }
184

    
185
  // If the client supports JavaScript and we have a cached menu for the current
186
  // user, only output the hash for the client-side HTTP cache callback URL.
187
  $cid = 'admin_menu:' . $user->uid . ':' . session_id() . ':' . $language->language;
188
  if (!$complete && !empty($_COOKIE['has_js']) && ($hash = admin_menu_cache_get($cid))) {
189
    $settings['hash'] = $hash;
190
    // The base path to use for cache requests depends on whether clean URLs
191
    // are enabled, whether Drupal runs in a sub-directory, and on the language
192
    // system configuration. url() already provides us the proper path and also
193
    // invokes potentially existing custom_url_rewrite() functions, which may
194
    // add further required components to the URL to provide context. Due to
195
    // those components, and since url('') returns only base_path() when clean
196
    // URLs are disabled, we need to use a replacement token as path.  Yuck.
197
    $settings['basePath'] = url('admin_menu');
198
  }
199
  // Otherwise, add the full menu to the page.
200
  else {
201
    $page['page_bottom']['admin_menu']['#markup'] = admin_menu_output($complete);
202
  }
203

    
204
  $replacements = module_invoke_all('admin_menu_replacements', $complete);
205
  if (!empty($replacements)) {
206
    $settings['replacements'] = $replacements;
207
  }
208

    
209
  if ($setting = variable_get('admin_menu_margin_top', 1)) {
210
    $settings['margin_top'] = $setting;
211
    // @todo Drupal.behaviors.adminMenuMarginTop is obsolete, but
212
    //   hook_page_build() does not allow to set a CSS class on the body yet.
213
    // @see http://drupal.org/node/1473548, http://drupal.org/node/1194528
214
    //$page['#attributes']['class'][] = 'admin-menu';
215
  }
216
  if ($setting = variable_get('admin_menu_position_fixed', 1)) {
217
    $settings['position_fixed'] = $setting;
218

    
219
    // In fixed positioning, supply a callback function for tableheader.js to
220
    // allow it to determine the top viewport offset.
221
    // @see admin_menu.js, toolbar.js
222
    $attached['js'][] = array(
223
      'data' => array('tableHeaderOffset' => 'Drupal.admin.height'),
224
      'type' => 'setting',
225
    );
226
  }
227
  if ($setting = variable_get('admin_menu_tweak_tabs', 0)) {
228
    $settings['tweak_tabs'] = $setting;
229
  }
230
  if ($_GET['q'] == 'admin/modules' || strpos($_GET['q'], 'admin/modules/list') === 0) {
231
    $settings['tweak_modules'] = variable_get('admin_menu_tweak_modules', 0);
232
  }
233
  if ($_GET['q'] == 'admin/people/permissions' || $_GET['q'] == 'admin/people/permissions/list') {
234
    $settings['tweak_permissions'] = variable_get('admin_menu_tweak_permissions', 0);
235
  }
236

    
237
  $attached['js'][] = array(
238
    'data' => array('admin_menu' => $settings),
239
    'type' => 'setting',
240
  );
241
}
242

    
243
/**
244
 * Suppress display of administration menu.
245
 *
246
 * This function should be called from within another module's page callback
247
 * (preferably using module_invoke()) when the menu should not be displayed.
248
 * This is useful for modules that implement popup pages or other special
249
 * pages where the menu would be distracting or break the layout.
250
 *
251
 * @param $set
252
 *   Defaults to TRUE. If called before hook_footer(), the menu will not be
253
 *   displayed. If FALSE is passed, the suppression state is returned.
254
 */
255
function admin_menu_suppress($set = TRUE) {
256
  static $suppress = FALSE;
257
  // drupal_add_js() must only be invoked once.
258
  if (!empty($set) && $suppress === FALSE) {
259
    $suppress = TRUE;
260
    drupal_add_js(array('admin_menu' => array('suppress' => 1)), 'setting');
261
  }
262
  return $suppress;
263
}
264

    
265
/**
266
 * Implements hook_js().
267
 */
268
function admin_menu_js() {
269
  return array(
270
    'cache' => array(
271
      'callback' => 'admin_menu_js_cache',
272
      'includes' => array('common', 'theme', 'unicode'),
273
      'dependencies' => array('devel', 'filter', 'user'),
274
    ),
275
  );
276
}
277

    
278
/**
279
 * Retrieve a client-side cache hash from cache.
280
 *
281
 * The hash cache is consulted more than once per request; we therefore cache
282
 * the results statically to avoid multiple database requests.
283
 *
284
 * This should only be used for client-side cache hashes. Use cache_menu for
285
 * administration menu content.
286
 *
287
 * @param $cid
288
 *   The cache ID of the data to retrieve.
289
 */
290
function admin_menu_cache_get($cid) {
291
  $cache = &drupal_static(__FUNCTION__, array());
292

    
293
  if (!variable_get('admin_menu_cache_client', TRUE)) {
294
    return FALSE;
295
  }
296
  if (!array_key_exists($cid, $cache)) {
297
    $cache[$cid] = cache_get($cid, 'cache_admin_menu');
298
    if ($cache[$cid] && isset($cache[$cid]->data)) {
299
      $cache[$cid] = $cache[$cid]->data;
300
    }
301
  }
302

    
303
  return $cache[$cid];
304
}
305

    
306
/**
307
 * Store a client-side cache hash in persistent cache.
308
 *
309
 * This should only be used for client-side cache hashes. Use cache_menu for
310
 * administration menu content.
311
 *
312
 * @param $cid
313
 *   The cache ID of the data to retrieve.
314
 */
315
function admin_menu_cache_set($cid, $data) {
316
  if (variable_get('admin_menu_cache_client', TRUE)) {
317
    cache_set($cid, $data, 'cache_admin_menu');
318
  }
319
}
320

    
321
/**
322
 * Menu callback; Output administration menu for HTTP caching via AJAX request.
323
 *
324
 * @see admin_menu_deliver()
325
 */
326
function admin_menu_js_cache() {
327
  global $conf;
328

    
329
  // Suppress Devel module.
330
  $GLOBALS['devel_shutdown'] = FALSE;
331

    
332
  // Enforce page caching.
333
  $conf['cache'] = 1;
334
  drupal_page_is_cacheable(TRUE);
335

    
336
  // If we have a cache, serve it.
337
  // @see _drupal_bootstrap_page_cache()
338
  $cache = drupal_page_get_cache();
339
  if (is_object($cache)) {
340
    header('X-Drupal-Cache: HIT');
341
    // Restore the metadata cached with the page.
342
    $_GET['q'] = $cache->data['path'];
343
    date_default_timezone_set(drupal_get_user_timezone());
344

    
345
    drupal_serve_page_from_cache($cache);
346

    
347
    // We are done.
348
    exit;
349
  }
350

    
351
  // Otherwise, create a new page response (that will be cached).
352
  header('X-Drupal-Cache: MISS');
353

    
354
  // The Expires HTTP header is the heart of the client-side HTTP caching. The
355
  // additional server-side page cache only takes effect when the client
356
  // accesses the callback URL again (e.g., after clearing the browser cache or
357
  // when force-reloading a Drupal page).
358
  $max_age = 3600 * 24 * 365;
359
  drupal_add_http_header('Expires', gmdate(DATE_RFC1123, REQUEST_TIME + $max_age));
360
  drupal_add_http_header('Cache-Control', 'private, max-age=' . $max_age);
361

    
362
  // Retrieve and return the rendered menu.
363
  return admin_menu_output();
364
}
365

    
366
/**
367
 * Delivery callback for client-side HTTP caching.
368
 *
369
 * @see admin_menu_js_cache()
370
 */
371
function admin_menu_deliver($page_callback_result) {
372
  drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
373

    
374
  // Send appropriate language header for browsers.
375
  global $language;
376
  drupal_add_http_header('Content-Language', $language->language);
377

    
378
  // The page callback is always admin_menu_js_cache(), which always returns a
379
  // string, and is only accessed when the user actually has access to it.
380
  // Therefore, we do not care for the other possible page callback results.
381
  print $page_callback_result;
382

    
383
  // Perform end-of-request tasks. The page cache is created here.
384
  drupal_page_footer();
385
}
386

    
387
/**
388
 * Implements hook_admin_menu_replacements().
389
 */
390
function admin_menu_admin_menu_replacements($complete) {
391
  $items = array();
392
  // If the complete menu is output, then it is uncached and will contain the
393
  // current counts already.
394
  if (!$complete) {
395
    // Check whether the users count component is enabled.
396
    $components = variable_get('admin_menu_components', array());
397
    if (!empty($components['admin_menu.users']) && ($user_count = admin_menu_get_user_count())) {
398
      // Replace the counters in the cached menu output with current counts.
399
      $items['.admin-menu-users a'] = $user_count;
400
    }
401
  }
402
  return $items;
403
}
404

    
405
/**
406
 * Return count of online anonymous/authenticated users.
407
 *
408
 * @see user_block(), user.module
409
 */
410
function admin_menu_get_user_count() {
411
  $interval   = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
412
  $count_anon = admin_menu_session_count($interval, TRUE);
413
  $count_auth = admin_menu_session_count($interval, FALSE);
414

    
415
  return t('@count-anon / @count-auth', array('@count-anon' => $count_anon, '@count-auth' => $count_auth));
416
}
417

    
418
/**
419
 * Counts how many users are active on the site.
420
 *
421
 * Counts how many users have sessions which have been active since the
422
 * specified time. Can count either anonymous sessions or authenticated
423
 * sessions.
424
 *
425
 * @param $timestamp
426
 *   A Unix timestamp. Users who have been active since this time will be
427
 *   counted. The default is 0, which counts all existing sessions.
428
 * @param $anonymous
429
 *   TRUE counts only anonymous users. FALSE counts only authenticated users.
430
 *
431
 * @return
432
 *   The number of users with sessions.
433
 *
434
 * @todo There are mostly no anonymous sessions anymore. Split this into a
435
 *   separate module providing proper user statistics.
436
 */
437
function admin_menu_session_count($timestamp = 0, $anonymous = TRUE) {
438
  $query = db_select('sessions');
439
  $query->addExpression('COUNT(sid)', 'count');
440
  $query->condition('timestamp', $timestamp, '>=');
441
  $query->condition('uid', 0, $anonymous ? '=' : '>');
442
  return $query->execute()->fetchField();
443
}
444

    
445
/**
446
 * Build the administration menu output.
447
 *
448
 * @param bool $complete
449
 *   (optional) Whether to build to the complete menu including all components
450
 *   and ignore the cache. Defaults to FALSE. Internally used for the settings
451
 *   page.
452
 */
453
function admin_menu_output($complete = FALSE) {
454
  global $user, $language;
455

    
456
  $cache_server_enabled = !$complete && variable_get('admin_menu_cache_server', TRUE);
457
  $cid = 'admin_menu:' . $user->uid . ':' . session_id() . ':' . $language->language;
458

    
459
  // Try to load and output administration menu from server-side cache.
460
  // @todo Duplicates the page cache? Page cache ID contains the hash that is
461
  //   generated at the bottom of this function, which is based on $content,
462
  //   but logically identical to the $cid. Investigate whether not only the
463
  //   cache_menu but also the cache_admin_menu could be dropped; the
464
  //   client-side HTTP cache hash check could be based on a cid lookup in
465
  //   cache_page instead? (i.e., one cache to rule them all) However,
466
  //   cache_page is cleared very often.
467
  if ($cache_server_enabled) {
468
    $cache = cache_get($cid, 'cache_menu');
469
    if ($cache && isset($cache->data)) {
470
      $content = $cache->data;
471
    }
472
  }
473

    
474
  // Rebuild the output.
475
  if (!isset($content)) {
476
    // Retrieve enabled components to display and make them available for others.
477
    $components = variable_get('admin_menu_components', array());
478
    $components += array(
479
      'admin_menu.menu' => TRUE,
480
      'admin_menu.icon' => TRUE,
481
      'admin_menu.account' => TRUE,
482
    );
483
    $content['#components'] = $components;
484
    $content['#complete'] = $complete;
485

    
486
    // Add site name as CSS class for development/staging theming purposes. We
487
    // leverage the cookie domain instead of HTTP_HOST to account for many (but
488
    // not all) multi-domain setups (e.g. language-based sub-domains).
489
    $classes = 'admin-menu-site' . drupal_strtolower(preg_replace('/[^a-zA-Z0-9-]/', '-', $GLOBALS['cookie_domain']));
490
    // Displace overlay.
491
    // @see Drupal.overlay.create
492
    // @see toolbar_preprocess_toolbar()
493
    if (module_exists('overlay')) {
494
      $classes .= ' overlay-displace-top';
495
    }
496
    // @todo Always output container to harden JS-less support.
497
    $content['#prefix'] = '<div id="admin-menu" class="' . $classes . '"><div id="admin-menu-wrapper">';
498
    $content['#suffix'] = '</div></div>';
499

    
500
    // Load menu builder functions.
501
    module_load_include('inc', 'admin_menu');
502

    
503
    // @todo Move the below callbacks into hook_admin_menu_build()
504
    //   implementations (and $module.admin_menu.inc).
505

    
506
    // Add administration menu.
507
    if (!empty($components['admin_menu.menu']) || $complete) {
508
      $content['menu'] = admin_menu_links_menu(admin_menu_tree('management'));
509
      $content['menu']['#theme'] = 'admin_menu_links';
510
      $content['menu']['#wrapper_attributes']['id'] = 'admin-menu-menu';
511
      // Ensure the menu tree is rendered between the icon and user links.
512
      $content['menu']['#weight'] = 0;
513
    }
514

    
515
    // Add menu additions.
516
    if (!empty($components['admin_menu.icon']) || $complete) {
517
      $content['icon'] = admin_menu_links_icon();
518
    }
519
    if (!empty($components['admin_menu.account']) || $complete) {
520
      $content['account'] = admin_menu_links_account();
521
    }
522
    if (!empty($components['admin_menu.users']) || $complete) {
523
      $content['users'] = admin_menu_links_users();
524
    }
525
    if (!empty($components['admin_menu.search']) || $complete) {
526
      $content['search'] = admin_menu_links_search();
527
    }
528

    
529
    // Allow modules to enhance the menu.
530
    // Uses '_output' suffix for consistency with the alter hook (see below).
531
    foreach (module_implements('admin_menu_output_build') as $module) {
532
      $function = $module . '_admin_menu_output_build';
533
      $function($content);
534
    }
535

    
536
    // Allow modules to alter the output.
537
    // The '_output' suffix is required to prevent hook implementation function
538
    // name clashes with the contributed Admin module.
539
    drupal_alter('admin_menu_output', $content);
540

    
541
    $content = drupal_render($content);
542

    
543
    // Cache the menu for this user.
544
    if ($cache_server_enabled) {
545
      cache_set($cid, $content, 'cache_menu');
546
    }
547
  }
548

    
549
  // Store the new hash for this user.
550
  if (!empty($_COOKIE['has_js']) && !$complete) {
551
    admin_menu_cache_set($cid, md5($content));
552
  }
553

    
554
  return $content;
555
}
556

    
557
/**
558
 * Implements hook_admin_menu_output_build().
559
 */
560
function admin_menu_admin_menu_output_build(&$content) {
561
  if (!isset($content['menu'])) {
562
    return;
563
  }
564

    
565
  // Unassign weights for categories below Configuration.
566
  // An alphabetical order is more natural for a dropdown menu.
567
  if (isset($content['menu']['admin/config'])) {
568
    foreach (element_children($content['menu']['admin/config']) as $key) {
569
      $content['menu']['admin/config'][$key]['#weight_original'] = $content['menu']['admin/config'][$key]['#weight'];
570
      unset($content['menu']['admin/config'][$key]['#weight']);
571
    }
572
  }
573

    
574
  // Retrieve the "Add content" link tree.
575
  $link = db_query("SELECT * FROM {menu_links} WHERE router_path = 'node/add' AND module = 'system'")->fetchAssoc();
576
  $conditions = array();
577
  for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
578
    if (!empty($link["p$i"])) {
579
      $conditions["p$i"] = $link["p$i"];
580
    }
581
  }
582
  $tree = menu_build_tree($link['menu_name'], array(
583
    'conditions' => $conditions,
584
    'min_depth' => $link['depth'],
585
  ));
586
  $links = admin_menu_links_menu($tree);
587
  if (!empty($links)) {
588
    // If the user has access to the top-level "Content" category, insert the
589
    // "Add content" link tree there.
590
    if (isset($content['menu']['admin/content'])) {
591
      $content['menu']['admin/content'] += $links;
592
    }
593
    // Otherwise make insert "Add content" as top-level category.
594
    else {
595
      $key = key($links);
596
      $links[$key]['#weight'] = -100;
597
      $content['menu'] += $links;
598
    }
599
  }
600
}
601

    
602
/**
603
 * Implements hook_admin_menu_output_alter().
604
 */
605
function admin_menu_admin_menu_output_alter(&$content) {
606
  foreach ($content['menu'] as $key => $link) {
607
    // Move local tasks on 'admin' into icon menu.
608
    if ($key == 'admin/tasks' || $key == 'admin/index') {
609
      $content['icon']['icon'][$key] = $link;
610
      unset($content['menu'][$key]);
611
    }
612
  }
613
}
614

    
615
/**
616
 * Render a themed list of links.
617
 *
618
 * @param $variables
619
 *   - elements: A renderable array of links using the following keys:
620
 *     - #attributes: Optional array of attributes for the list item, processed
621
 *       via drupal_attributes().
622
 *     - #title: Title of the link, passed to l().
623
 *     - #href: Optional path of the link, passed to l(). When omitted, the
624
 *       element's '#title' is rendered without link.
625
 *     - #description: Optional alternative text for the link, passed to l().
626
 *     - #options: Optional alternative text for the link, passed to l().
627
 *     The array key of each child element itself is passed as path for l().
628
 */
629
function theme_admin_menu_links($variables) {
630
  $destination = &drupal_static('admin_menu_destination');
631
  $elements = $variables['elements'];
632

    
633
  if (!isset($destination)) {
634
    $destination = drupal_get_destination();
635
    $destination = $destination['destination'];
636
  }
637

    
638
  // The majority of items in the menu are sorted already, but since modules
639
  // may add or change arbitrary items anywhere, there is no way around sorting
640
  // everything again. element_sort() is not sufficient here, as it
641
  // intentionally retains the order of elements having the same #weight,
642
  // whereas menu links are supposed to be ordered by #weight and #title.
643
  uasort($elements, 'admin_menu_element_sort');
644
  $elements['#sorted'] = TRUE;
645

    
646
  $output = '';
647
  foreach (element_children($elements) as $path) {
648
    // Early-return nothing if user does not have access.
649
    if (isset($elements[$path]['#access']) && !$elements[$path]['#access']) {
650
      continue;
651
    }
652
    $elements[$path] += array(
653
      '#attributes' => array(),
654
      '#options' => array(),
655
    );
656
    // Render children to determine whether this link is expandable.
657
    if (isset($elements[$path]['#type']) || isset($elements[$path]['#theme']) || isset($elements[$path]['#pre_render'])) {
658
      $elements[$path]['#children'] = drupal_render($elements[$path]);
659
    }
660
    else {
661
      $elements[$path]['#children'] = theme('admin_menu_links', array('elements' => $elements[$path]));
662
      if (!empty($elements[$path]['#children'])) {
663
        $elements[$path]['#attributes']['class'][] = 'expandable';
664
      }
665
      if (isset($elements[$path]['#attributes']['class'])) {
666
        $elements[$path]['#attributes']['class'] = $elements[$path]['#attributes']['class'];
667
      }
668
    }
669

    
670
    $link = '';
671
    // Handle menu links.
672
    if (isset($elements[$path]['#href'])) {
673
      // Strip destination query string from href attribute and apply a CSS class
674
      // for our JavaScript behavior instead.
675
      if (isset($elements[$path]['#options']['query']['destination']) && $elements[$path]['#options']['query']['destination'] == $destination) {
676
        unset($elements[$path]['#options']['query']['destination']);
677
        $elements[$path]['#options']['attributes']['class'][] = 'admin-menu-destination';
678
      }
679

    
680
      $link = l($elements[$path]['#title'], $elements[$path]['#href'], $elements[$path]['#options']);
681
    }
682
    // Handle plain text items, but do not interfere with menu additions.
683
    elseif (!isset($elements[$path]['#type']) && isset($elements[$path]['#title'])) {
684
      if (!empty($elements[$path]['#options']['html'])) {
685
        $title = $elements[$path]['#title'];
686
      }
687
      else {
688
        $title = check_plain($elements[$path]['#title']);
689
      }
690
      $attributes = '';
691
      if (isset($elements[$path]['#options']['attributes'])) {
692
        $attributes = drupal_attributes($elements[$path]['#options']['attributes']);
693
      }
694
      $link = '<span' . $attributes . '>' . $title . '</span>';
695
    }
696

    
697
    $output .= '<li' . drupal_attributes($elements[$path]['#attributes']) . '>';
698
    $output .= $link . $elements[$path]['#children'];
699
    $output .= '</li>';
700
  }
701
  // @todo #attributes probably required for UL, but already used for LI.
702
  // @todo Use $element['#children'] here instead.
703
  if ($output) {
704
    $elements['#wrapper_attributes']['class'][] = 'dropdown';
705
    $attributes = drupal_attributes($elements['#wrapper_attributes']);
706
    $output = "\n" . '<ul' . $attributes . '>' . $output . '</ul>';
707
  }
708
  return $output;
709
}
710

    
711
/**
712
 * Function used by uasort to sort structured arrays by #weight AND #title.
713
 */
714
function admin_menu_element_sort($a, $b) {
715
  // @see element_sort()
716
  $a_weight = isset($a['#weight']) ? $a['#weight'] : 0;
717
  $b_weight = isset($b['#weight']) ? $b['#weight'] : 0;
718
  if ($a_weight == $b_weight) {
719
    // @see element_sort_by_title()
720
    $a_title = isset($a['#title']) ? $a['#title'] : '';
721
    $b_title = isset($b['#title']) ? $b['#title'] : '';
722
    return strnatcasecmp($a_title, $b_title);
723
  }
724
  return ($a_weight < $b_weight) ? -1 : 1;
725
}
726

    
727
/**
728
 * Implements hook_translated_menu_link_alter().
729
 *
730
 * Here is where we make changes to links that need dynamic information such
731
 * as the current page path or the number of users.
732
 */
733
function admin_menu_translated_menu_link_alter(&$item, $map) {
734
  global $user, $base_url;
735
  static $access_all;
736

    
737
  if ($item['menu_name'] != 'admin_menu') {
738
    return;
739
  }
740

    
741
  // Check whether additional development output is enabled.
742
  if (!isset($access_all)) {
743
    $access_all = variable_get('admin_menu_show_all', 0) && module_exists('devel');
744
  }
745
  // Prepare links that would not be displayed normally.
746
  if ($access_all && !$item['access']) {
747
    $item['access'] = TRUE;
748
    // Prepare for http://drupal.org/node/266596
749
    if (!isset($item['localized_options'])) {
750
      _menu_item_localize($item, $map, TRUE);
751
    }
752
  }
753

    
754
  // Don't waste cycles altering items that are not visible
755
  if (!$item['access']) {
756
    return;
757
  }
758

    
759
  // Add developer information to all links, if enabled.
760
  if ($extra = variable_get('admin_menu_display', 0)) {
761
    $item['title'] .= ' ' . $extra[0] . ': ' . $item[$extra];
762
  }
763
}
764

    
765
/**
766
 * Implements hook_flush_caches().
767
 *
768
 * Flushes client-side caches.
769
 *
770
 * @param int $uid
771
 *   (optional) A user ID to limit the cache flush to.
772
 */
773
function admin_menu_flush_caches($uid = NULL) {
774
  // A call to menu_rebuild() will trigger potentially thousands of calls into
775
  // menu_link_save(), for which admin_menu has to implement the corresponding
776
  // CRUD hooks, in order to take up any menu link changes, since any menu link
777
  // change could affect the admin menu (which essentially is an aggregate) and
778
  // since there is no other way to get notified about stale caches. The cache
779
  // only needs to be flushed once though, so we prevent a ton of needless
780
  // subsequent calls with this static.
781
  // @see http://drupal.org/node/918538
782
  $was_flushed = &drupal_static(__FUNCTION__, array());
783
  // $uid can be NULL. PHP automatically converts that into '' (empty string),
784
  // which is different to uid 0 (zero).
785
  if (isset($was_flushed[$uid])) {
786
    return;
787
  }
788
  $was_flushed[$uid] = TRUE;
789

    
790
  $cid = 'admin_menu:';
791
  if (isset($uid)) {
792
    $cid .= $uid . ':';
793
  }
794
  // Flush cached output of admin_menu.
795
  cache_clear_all($cid, 'cache_menu', TRUE);
796
  // Flush client-side cache hashes.
797
  drupal_static_reset('admin_menu_cache_get');
798
  // db_table_exists() required for SimpleTest.
799
  if (db_table_exists('cache_admin_menu')) {
800
    cache_clear_all(isset($uid) ? $cid : '*', 'cache_admin_menu', TRUE);
801
  }
802
}
803

    
804
/**
805
 * Implements hook_form_alter().
806
 */
807
function admin_menu_form_alter(&$form, &$form_state, $form_id) {
808
  $global_flush_ids = array(
809
    'admin_menu_theme_settings' => 1,
810
    // Update links for clean/non-clean URLs.
811
    'system_clean_url_settings' => 1,
812
    // Incorporate changed user permissions.
813
    'user_admin_permissions' => 1,
814
    // Removing a role potentially means less permissions.
815
    'user_admin_role_delete_confirm' => 1,
816
    // User name and roles may be changed on the user account form.
817
    'user_profile_form' => 1,
818
  );
819
  if (isset($global_flush_ids[$form_id])) {
820
    $form['#submit'][] = 'admin_menu_form_alter_flush_cache_submit';
821

    
822
    // Optionally limit the cache flush to a certain user ID.
823
    $form_state['admin_menu_uid'] = NULL;
824
    if ($form_id == 'user_profile_form') {
825
      $form_state['admin_menu_uid'] = $form_state['user']->uid;
826
    }
827
  }
828

    
829
  // UX: Add a confirmation to the permissions form to ask the user whether to
830
  // auto-enable the 'access administration menu' permission along with
831
  // 'access administration pages'.
832
  if ($form_id == 'user_admin_permissions') {
833
    $form['#attached']['js'][] = drupal_get_path('module', 'admin_menu') . '/admin_menu.admin.js';
834
  }
835
}
836

    
837
/**
838
 * Form submission handler to flush Administration menu caches.
839
 */
840
function admin_menu_form_alter_flush_cache_submit($form, &$form_state) {
841
  admin_menu_flush_caches($form_state['admin_menu_uid']);
842
}
843

    
844
/**
845
 * Implements hook_form_FORM_ID_alter().
846
 *
847
 * Extends Devel module with Administration menu developer settings.
848
 */
849
function admin_menu_form_devel_admin_settings_alter(&$form, &$form_state) {
850
  form_load_include($form_state, 'inc', 'admin_menu');
851
  _admin_menu_form_devel_admin_settings_alter($form, $form_state);
852
}