admin_menu.inc 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. <?php
  2. /**
  3. * @file
  4. * Menu builder functions for Administration menu.
  5. */
  6. /**
  7. * Build the full administration menu tree from static and expanded dynamic items.
  8. *
  9. * @param $menu_name
  10. * The menu name to use as base for the tree.
  11. */
  12. function admin_menu_tree($menu_name) {
  13. // Get placeholder expansion arguments from hook_admin_menu_map()
  14. // implementations.
  15. module_load_include('inc', 'admin_menu', 'admin_menu.map');
  16. $expand_map = module_invoke_all('admin_menu_map');
  17. // Allow modules to alter the expansion map.
  18. drupal_alter('admin_menu_map', $expand_map);
  19. $new_map = array();
  20. foreach ($expand_map as $path => $data) {
  21. // Convert named placeholders to anonymous placeholders, since the menu
  22. // system stores paths using anonymous placeholders.
  23. $replacements = array_fill_keys(array_keys($data['arguments'][0]), '%');
  24. $data['parent'] = strtr($data['parent'], $replacements);
  25. $new_map[strtr($path, $replacements)] = $data;
  26. }
  27. $expand_map = $new_map;
  28. unset($new_map);
  29. // Retrieve dynamic menu link tree for the expansion mappings.
  30. // @todo Skip entire processing if initial $expand_map is empty and directly
  31. // return $tree?
  32. if (!empty($expand_map)) {
  33. $tree_dynamic = admin_menu_tree_dynamic($expand_map);
  34. }
  35. else {
  36. $tree_dynamic = array();
  37. }
  38. // Merge local tasks with static menu tree.
  39. $tree = menu_tree_all_data($menu_name);
  40. admin_menu_merge_tree($tree, $tree_dynamic, array());
  41. return $tree;
  42. }
  43. /**
  44. * Load menu link trees for router paths containing dynamic arguments.
  45. *
  46. * @param $expand_map
  47. * An array containing menu router path placeholder expansion argument
  48. * mappings.
  49. *
  50. * @return
  51. * An associative array whose keys are the parent paths of the menu router
  52. * paths given in $expand_map as well as the parent paths of any child link
  53. * deeper down the tree. The parent paths are used in admin_menu_merge_tree()
  54. * to check whether anything needs to be merged.
  55. *
  56. * @see hook_admin_menu_map()
  57. */
  58. function admin_menu_tree_dynamic(array $expand_map) {
  59. $p_columns = array();
  60. for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
  61. $p_columns[] = 'p' . $i;
  62. }
  63. // Fetch p* columns for all router paths to expand.
  64. $router_paths = array_keys($expand_map);
  65. $plids = db_select('menu_links', 'ml')
  66. ->fields('ml', $p_columns)
  67. ->condition('router_path', $router_paths)
  68. ->execute()
  69. ->fetchAll(PDO::FETCH_ASSOC);
  70. // Unlikely, but possible.
  71. if (empty($plids)) {
  72. return array();
  73. }
  74. // Use queried plid columns to query sub-trees for the router paths.
  75. $query = db_select('menu_links', 'ml');
  76. $query->join('menu_router', 'm', 'ml.router_path = m.path');
  77. $query
  78. ->fields('ml')
  79. ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), drupal_schema_fields_sql('menu_links')));
  80. // The retrieved menu link trees have to be ordered by depth, so parents
  81. // always come before their children for the storage logic below.
  82. foreach ($p_columns as $column) {
  83. $query->orderBy($column, 'ASC');
  84. }
  85. $db_or = db_or();
  86. foreach ($plids as $path_plids) {
  87. $db_and = db_and();
  88. // plids with value 0 may be ignored.
  89. foreach (array_filter($path_plids) as $column => $plid) {
  90. $db_and->condition($column, $plid);
  91. }
  92. $db_or->condition($db_and);
  93. }
  94. $query->condition($db_or);
  95. $result = $query
  96. ->execute()
  97. ->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
  98. // Store dynamic links grouped by parent path for later merging and assign
  99. // placeholder expansion arguments.
  100. $tree_dynamic = array();
  101. foreach ($result as $mlid => $link) {
  102. // If contained in $expand_map, then this is a (first) parent, and we need
  103. // to store by the defined 'parent' path for later merging, as well as
  104. // provide the expansion map arguments to apply to the dynamic tree.
  105. if (isset($expand_map[$link['path']])) {
  106. $parent_path = $expand_map[$link['path']]['parent'];
  107. $link['expand_map'] = $expand_map[$link['path']]['arguments'];
  108. }
  109. // Otherwise, just store this link keyed by its parent path; the expand_map
  110. // is automatically derived from parent paths.
  111. else {
  112. $parent_path = $result[$link['plid']]['path'];
  113. }
  114. $tree_dynamic[$parent_path][] = $link;
  115. }
  116. return $tree_dynamic;
  117. }
  118. /**
  119. * Walk through the entire menu tree and merge in expanded dynamic menu links.
  120. *
  121. * @param &$tree
  122. * A menu tree structure as returned by menu_tree_all_data().
  123. * @param $tree_dynamic
  124. * A dynamic menu tree structure as returned by admin_menu_tree_dynamic().
  125. * @param $expand_map
  126. * An array containing menu router path placeholder expansion argument
  127. * mappings.
  128. *
  129. * @see hook_admin_menu_map()
  130. * @see admin_menu_tree_dynamic()
  131. * @see menu_tree_all_data()
  132. */
  133. function admin_menu_merge_tree(array &$tree, array $tree_dynamic, array $expand_map) {
  134. foreach ($tree as $key => $data) {
  135. $path = $data['link']['router_path'];
  136. // Recurse into regular menu tree.
  137. if ($tree[$key]['below']) {
  138. admin_menu_merge_tree($tree[$key]['below'], $tree_dynamic, $expand_map);
  139. }
  140. // Nothing to merge, if this parent path is not in our dynamic tree.
  141. if (!isset($tree_dynamic[$path])) {
  142. continue;
  143. }
  144. // Add expanded dynamic items.
  145. foreach ($tree_dynamic[$path] as $link) {
  146. // If the dynamic item has custom placeholder expansion parameters set,
  147. // use them, otherwise keep current.
  148. if (isset($link['expand_map'])) {
  149. // If there are currently no expansion parameters, we may use the new
  150. // set immediately.
  151. if (empty($expand_map)) {
  152. $current_expand_map = $link['expand_map'];
  153. }
  154. else {
  155. // Otherwise we need to filter out elements that differ from the
  156. // current set, i.e. that are not in the same path.
  157. $current_expand_map = array();
  158. foreach ($expand_map as $arguments) {
  159. foreach ($arguments as $placeholder => $value) {
  160. foreach ($link['expand_map'] as $new_arguments) {
  161. // Skip the new argument if it doesn't contain the current
  162. // replacement placeholders or if their values differ.
  163. if (!isset($new_arguments[$placeholder]) || $new_arguments[$placeholder] != $value) {
  164. continue;
  165. }
  166. $current_expand_map[] = $new_arguments;
  167. }
  168. }
  169. }
  170. }
  171. }
  172. else {
  173. $current_expand_map = $expand_map;
  174. }
  175. // Skip dynamic items without expansion parameters.
  176. if (empty($current_expand_map)) {
  177. continue;
  178. }
  179. // Expand anonymous to named placeholders.
  180. // @see _menu_load_objects()
  181. $path_args = explode('/', $link['path']);
  182. $load_functions = unserialize($link['load_functions']);
  183. if (is_array($load_functions)) {
  184. foreach ($load_functions as $index => $function) {
  185. if ($function) {
  186. if (is_array($function)) {
  187. list($function,) = each($function);
  188. }
  189. // Add the loader function name minus "_load".
  190. $placeholder = '%' . substr($function, 0, -5);
  191. $path_args[$index] = $placeholder;
  192. }
  193. }
  194. }
  195. $path_dynamic = implode('/', $path_args);
  196. // Create new menu items using expansion arguments.
  197. foreach ($current_expand_map as $arguments) {
  198. // Create the cartesian product for all arguments and create new
  199. // menu items for each generated combination thereof.
  200. foreach (admin_menu_expand_args($arguments) as $replacements) {
  201. $newpath = strtr($path_dynamic, $replacements);
  202. // Skip this item, if any placeholder could not be replaced.
  203. // Faster than trying to invoke _menu_translate().
  204. if (strpos($newpath, '%') !== FALSE) {
  205. continue;
  206. }
  207. $map = explode('/', $newpath);
  208. $item = admin_menu_translate($link, $map);
  209. // Skip this item, if the current user does not have access.
  210. if (empty($item)) {
  211. continue;
  212. }
  213. // Build subtree using current replacement arguments.
  214. $new_expand_map = array();
  215. foreach ($replacements as $placeholder => $value) {
  216. $new_expand_map[$placeholder] = array($value);
  217. }
  218. admin_menu_merge_tree($item, $tree_dynamic, array($new_expand_map));
  219. $tree[$key]['below'] += $item;
  220. }
  221. }
  222. }
  223. // Sort new subtree items.
  224. ksort($tree[$key]['below']);
  225. }
  226. }
  227. /**
  228. * Translate an expanded router item into a menu link suitable for rendering.
  229. *
  230. * @param $router_item
  231. * A menu router item.
  232. * @param $map
  233. * A path map with placeholders replaced.
  234. */
  235. function admin_menu_translate($router_item, $map) {
  236. _menu_translate($router_item, $map, TRUE);
  237. // Run through hook_translated_menu_link_alter() to add devel information,
  238. // if configured.
  239. $router_item['menu_name'] = 'management';
  240. // @todo Invoke as usual like _menu_link_translate().
  241. admin_menu_translated_menu_link_alter($router_item, NULL);
  242. if ($router_item['access']) {
  243. // Override mlid to make this item unique; since these items are expanded
  244. // from dynamic items, the mlid is always the same, so each item would
  245. // replace any other.
  246. // @todo Doing this instead leads to plenty of duplicate links below
  247. // admin/structure/menu; likely a hidden recursion problem.
  248. // $router_item['mlid'] = $router_item['href'] . $router_item['mlid'];
  249. $router_item['mlid'] = $router_item['href'];
  250. // Turn menu callbacks into regular menu items to make them visible.
  251. if ($router_item['type'] == MENU_CALLBACK) {
  252. $router_item['type'] = MENU_NORMAL_ITEM;
  253. }
  254. // @see _menu_tree_check_access()
  255. $key = (50000 + $router_item['weight']) . ' ' . $router_item['title'] . ' ' . $router_item['mlid'];
  256. return array($key => array(
  257. 'link' => $router_item,
  258. 'below' => array(),
  259. ));
  260. }
  261. return array();
  262. }
  263. /**
  264. * Create the cartesian product of multiple varying sized argument arrays.
  265. *
  266. * @param $arguments
  267. * A two dimensional array of arguments.
  268. *
  269. * @see hook_admin_menu_map()
  270. */
  271. function admin_menu_expand_args($arguments) {
  272. $replacements = array();
  273. // Initialize line cursors, move out array keys (placeholders) and assign
  274. // numeric keys instead.
  275. $i = 0;
  276. $placeholders = array();
  277. $new_arguments = array();
  278. foreach ($arguments as $placeholder => $values) {
  279. // Skip empty arguments.
  280. if (empty($values)) {
  281. continue;
  282. }
  283. $cursor[$i] = 0;
  284. $placeholders[$i] = $placeholder;
  285. $new_arguments[$i] = $values;
  286. $i++;
  287. }
  288. $arguments = $new_arguments;
  289. unset($new_arguments);
  290. if ($rows = count($arguments)) {
  291. do {
  292. // Collect current argument from each row.
  293. $row = array();
  294. for ($i = 0; $i < $rows; ++$i) {
  295. $row[$placeholders[$i]] = $arguments[$i][$cursor[$i]];
  296. }
  297. $replacements[] = $row;
  298. // Increment cursor position.
  299. $j = $rows - 1;
  300. $cursor[$j]++;
  301. while (!array_key_exists($cursor[$j], $arguments[$j])) {
  302. // No more arguments left: reset cursor, go to next line and increment
  303. // that cursor instead. Repeat until argument found or out of rows.
  304. $cursor[$j] = 0;
  305. if (--$j < 0) {
  306. // We're done.
  307. break 2;
  308. }
  309. $cursor[$j]++;
  310. }
  311. } while (1);
  312. }
  313. return $replacements;
  314. }
  315. /**
  316. * Build the administration menu as renderable menu links.
  317. *
  318. * @param $tree
  319. * A data structure representing the administration menu tree as returned from
  320. * menu_tree_all_data().
  321. *
  322. * @return
  323. * The complete administration menu, suitable for theme_admin_menu_links().
  324. *
  325. * @see theme_admin_menu_links()
  326. * @see admin_menu_menu_alter()
  327. */
  328. function admin_menu_links_menu($tree) {
  329. $links = array();
  330. foreach ($tree as $data) {
  331. // Skip items that are inaccessible, invisible, or link to their parent.
  332. // (MENU_DEFAULT_LOCAL_TASK), and MENU_CALLBACK-alike items that should only
  333. // appear in the breadcrumb.
  334. if (!$data['link']['access'] || $data['link']['type'] & MENU_LINKS_TO_PARENT || $data['link']['type'] == MENU_VISIBLE_IN_BREADCRUMB || $data['link']['hidden'] == 1) {
  335. continue;
  336. }
  337. // Hide 'Administer' and make child links appear on this level.
  338. // @todo Make this configurable.
  339. if ($data['link']['router_path'] == 'admin') {
  340. if ($data['below']) {
  341. $links = array_merge($links, admin_menu_links_menu($data['below']));
  342. }
  343. continue;
  344. }
  345. // Omit alias lookups.
  346. $data['link']['localized_options']['alias'] = TRUE;
  347. // Remove description to prevent mouseover tooltip clashes.
  348. unset($data['link']['localized_options']['attributes']['title']);
  349. // Make action links (typically "Add ...") appear first in dropdowns.
  350. // They might appear first already, but only as long as there is no link
  351. // that comes alphabetically first (e.g., a node type with label "Ad").
  352. if ($data['link']['type'] & MENU_IS_LOCAL_ACTION) {
  353. $data['link']['weight'] -= 1000;
  354. }
  355. $links[$data['link']['href']] = array(
  356. '#title' => $data['link']['title'],
  357. '#href' => $data['link']['href'],
  358. '#options' => $data['link']['localized_options'],
  359. '#weight' => $data['link']['weight'],
  360. );
  361. // Recurse to add any child links.
  362. $children = array();
  363. if ($data['below']) {
  364. $children = admin_menu_links_menu($data['below']);
  365. $links[$data['link']['href']] += $children;
  366. }
  367. // Handle links pointing to category/overview pages.
  368. if ($data['link']['page_callback'] == 'system_admin_menu_block_page' || $data['link']['page_callback'] == 'system_admin_config_page') {
  369. // Apply a marker for others to consume.
  370. $links[$data['link']['href']]['#is_category'] = TRUE;
  371. // Automatically hide empty categories.
  372. // Check for empty children first for performance. Only when non-empty
  373. // (typically 'admin/config'), check whether children are accessible.
  374. if (empty($children) || !element_get_visible_children($children)) {
  375. $links[$data['link']['href']]['#access'] = FALSE;
  376. }
  377. }
  378. }
  379. return $links;
  380. }
  381. /**
  382. * Build icon menu links; mostly containing maintenance helpers.
  383. *
  384. * @see theme_admin_menu_links()
  385. */
  386. function admin_menu_links_icon() {
  387. $destination = drupal_get_destination();
  388. $links = array(
  389. '#theme' => 'admin_menu_links',
  390. '#wrapper_attributes' => array('id' => 'admin-menu-icon'),
  391. '#weight' => -100,
  392. );
  393. $links['icon'] = array(
  394. '#title' => theme('admin_menu_icon'),
  395. '#attributes' => array('class' => array('admin-menu-icon')),
  396. '#href' => '<front>',
  397. '#options' => array(
  398. 'html' => TRUE,
  399. ),
  400. );
  401. // Add link to manually run cron.
  402. $links['icon']['cron'] = array(
  403. '#title' => t('Run cron'),
  404. '#weight' => 50,
  405. '#access' => user_access('administer site configuration'),
  406. '#href' => 'admin/reports/status/run-cron',
  407. );
  408. // Add link to run update.php.
  409. $links['icon']['update'] = array(
  410. '#title' => t('Run updates'),
  411. '#weight' => 50,
  412. // @see update_access_allowed()
  413. '#access' => $GLOBALS['user']->uid == 1 || !empty($GLOBALS['update_free_access']) || user_access('administer software updates'),
  414. '#href' => base_path() . 'update.php',
  415. '#options' => array(
  416. 'external' => TRUE,
  417. ),
  418. );
  419. // Add link to drupal.org.
  420. $links['icon']['drupal.org'] = array(
  421. '#title' => 'Drupal.org',
  422. '#weight' => 100,
  423. '#access' => user_access('display drupal links'),
  424. '#href' => 'http://drupal.org',
  425. );
  426. // Add links to project issue queues.
  427. foreach (module_list(FALSE, TRUE) as $module) {
  428. $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info');
  429. if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) {
  430. continue;
  431. }
  432. $links['icon']['drupal.org'][$info['project']] = array(
  433. '#title' => t('@project issue queue', array('@project' => $info['name'])),
  434. '#weight' => ($info['project'] == 'drupal' ? -10 : 0),
  435. '#href' => 'http://drupal.org/project/issues/' . $info['project'],
  436. '#options' => array(
  437. 'query' => array('version' => (isset($info['core']) ? $info['core'] : 'All')),
  438. ),
  439. );
  440. }
  441. // Add items to flush caches.
  442. $links['icon']['flush-cache'] = array(
  443. '#title' => t('Flush all caches'),
  444. '#weight' => 20,
  445. '#access' => user_access('flush caches'),
  446. '#href' => 'admin_menu/flush-cache',
  447. '#options' => array(
  448. 'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache')),
  449. ),
  450. );
  451. $caches = module_invoke_all('admin_menu_cache_info');
  452. foreach ($caches as $name => $cache) {
  453. $links['icon']['flush-cache'][$name] = array(
  454. '#title' => $cache['title'],
  455. '#href' => 'admin_menu/flush-cache/' . $name,
  456. '#options' => array(
  457. 'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache/' . $name)),
  458. ),
  459. );
  460. }
  461. // Add link to toggle developer modules (performance).
  462. $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL);
  463. $links['icon']['toggle-modules'] = array(
  464. '#title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'),
  465. '#weight' => 88,
  466. '#access' => user_access('administer modules'),
  467. '#href' => 'admin_menu/toggle-modules',
  468. '#options' => array(
  469. 'query' => $destination + array('token' => drupal_get_token('admin_menu/toggle-modules')),
  470. ),
  471. );
  472. // Add Devel module menu links.
  473. if (module_exists('devel')) {
  474. $devel_tree = menu_build_tree('devel');
  475. $devel_links = admin_menu_links_menu($devel_tree);
  476. if (element_get_visible_children($devel_links)) {
  477. $links['icon']['devel'] = array(
  478. '#title' => t('Development'),
  479. '#weight' => 30,
  480. ) + $devel_links;
  481. }
  482. }
  483. return $links;
  484. }
  485. /**
  486. * Builds the account links.
  487. *
  488. * @see theme_admin_menu_links()
  489. */
  490. function admin_menu_links_account() {
  491. $links = array(
  492. '#theme' => 'admin_menu_links',
  493. '#wrapper_attributes' => array('id' => 'admin-menu-account'),
  494. '#weight' => 100,
  495. );
  496. $links['account'] = array(
  497. '#title' => format_username($GLOBALS['user']),
  498. '#weight' => -99,
  499. '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-account')),
  500. '#href' => 'user/' . $GLOBALS['user']->uid,
  501. );
  502. $links['logout'] = array(
  503. '#title' => t('Log out'),
  504. '#weight' => -100,
  505. '#attributes' => array('class' => array('admin-menu-action')),
  506. '#href' => 'user/logout',
  507. );
  508. // Add Devel module switch user links.
  509. $switch_links = module_invoke('devel', 'switch_user_list');
  510. if (!empty($switch_links) && count($switch_links) > 1) {
  511. foreach ($switch_links as $uid => $link) {
  512. $links['account'][$link['title']] = array(
  513. '#title' => $link['title'],
  514. '#description' => $link['attributes']['title'],
  515. '#href' => $link['href'],
  516. '#options' => array(
  517. 'query' => $link['query'],
  518. 'html' => !empty($link['html']),
  519. ),
  520. );
  521. }
  522. }
  523. return $links;
  524. }
  525. /**
  526. * Builds user counter.
  527. *
  528. * @see theme_admin_menu_links()
  529. */
  530. function admin_menu_links_users() {
  531. $links = array(
  532. '#theme' => 'admin_menu_links',
  533. '#wrapper_attributes' => array('id' => 'admin-menu-users'),
  534. '#weight' => 150,
  535. );
  536. // Add link to show current authenticated/anonymous users.
  537. $links['user-counter'] = array(
  538. '#title' => admin_menu_get_user_count(),
  539. '#description' => t('Current anonymous / authenticated users'),
  540. '#weight' => -90,
  541. '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-users')),
  542. '#href' => (user_access('administer users') ? 'admin/people/people' : 'user'),
  543. );
  544. return $links;
  545. }
  546. /**
  547. * Build search widget.
  548. *
  549. * @see theme_admin_menu_links()
  550. */
  551. function admin_menu_links_search() {
  552. $links = array(
  553. '#theme' => 'admin_menu_links',
  554. '#wrapper_attributes' => array('id' => 'admin-menu-search'),
  555. '#weight' => 180,
  556. );
  557. $links['search'] = array(
  558. '#type' => 'textfield',
  559. '#title' => t('Search'),
  560. '#title_display' => 'attribute',
  561. '#attributes' => array(
  562. 'placeholder' => t('Search'),
  563. 'class' => array('admin-menu-search'),
  564. ),
  565. );
  566. return $links;
  567. }
  568. /**
  569. * Form builder function for module settings.
  570. */
  571. function admin_menu_theme_settings() {
  572. $form['admin_menu_margin_top'] = array(
  573. '#type' => 'checkbox',
  574. '#title' => t('Adjust top margin'),
  575. '#default_value' => variable_get('admin_menu_margin_top', 1),
  576. '#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.'),
  577. );
  578. $form['admin_menu_position_fixed'] = array(
  579. '#type' => 'checkbox',
  580. '#title' => t('Keep menu at top of page'),
  581. '#default_value' => variable_get('admin_menu_position_fixed', 1),
  582. '#description' => t('Displays the administration menu always at the top of the browser viewport (even when scrolling the page).'),
  583. );
  584. // @todo Re-confirm this with latest browser versions.
  585. $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>';
  586. $form['advanced'] = array(
  587. '#type' => 'vertical_tabs',
  588. '#title' => t('Advanced settings'),
  589. );
  590. $form['plugins'] = array(
  591. '#type' => 'fieldset',
  592. '#title' => t('Plugins'),
  593. '#group' => 'advanced',
  594. );
  595. $form['plugins']['admin_menu_components'] = array(
  596. '#type' => 'checkboxes',
  597. '#title' => t('Enabled components'),
  598. '#options' => array(
  599. 'admin_menu.icon' => t('Icon menu'),
  600. 'admin_menu.menu' => t('Administration menu'),
  601. 'admin_menu.search' => t('Search bar'),
  602. 'admin_menu.users' => t('User counts'),
  603. 'admin_menu.account' => t('Account links'),
  604. ),
  605. );
  606. $form['plugins']['admin_menu_components']['#default_value'] = array_keys(array_filter(variable_get('admin_menu_components', $form['plugins']['admin_menu_components']['#options'])));
  607. $process = element_info_property('checkboxes', '#process', array());
  608. $form['plugins']['admin_menu_components']['#process'] = array_merge(array('admin_menu_settings_process_components'), $process);
  609. $form['#attached']['js'][] = drupal_get_path('module', 'admin_menu') . '/admin_menu.admin.js';
  610. $form['tweaks'] = array(
  611. '#type' => 'fieldset',
  612. '#title' => t('System tweaks'),
  613. '#group' => 'advanced',
  614. );
  615. $form['tweaks']['admin_menu_tweak_modules'] = array(
  616. '#type' => 'checkbox',
  617. '#title' => t('Collapse module groups on the <a href="!modules-url">%modules</a> page', array(
  618. '%modules' => t('Modules'),
  619. '!modules-url' => url('admin/modules'),
  620. )),
  621. '#default_value' => variable_get('admin_menu_tweak_modules', 0),
  622. );
  623. if (module_exists('util')) {
  624. $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>';
  625. }
  626. $form['tweaks']['admin_menu_tweak_permissions'] = array(
  627. '#type' => 'checkbox',
  628. '#title' => t('Collapse module groups on the <a href="@permissions-url">%permissions</a> page', array(
  629. '%permissions' => t('Permissions'),
  630. '@permissions-url' => url('admin/people/permissions'),
  631. )),
  632. '#default_value' => variable_get('admin_menu_tweak_permissions', 0),
  633. );
  634. $form['tweaks']['admin_menu_tweak_tabs'] = array(
  635. '#type' => 'checkbox',
  636. '#title' => t('Move local tasks into menu'),
  637. '#default_value' => variable_get('admin_menu_tweak_tabs', 0),
  638. '#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>.'),
  639. );
  640. $form['performance'] = array(
  641. '#type' => 'fieldset',
  642. '#title' => t('Performance'),
  643. '#group' => 'advanced',
  644. );
  645. $form['performance']['admin_menu_cache_client'] = array(
  646. '#type' => 'checkbox',
  647. '#title' => t('Cache menu in client-side browser'),
  648. '#default_value' => variable_get('admin_menu_cache_client', 1),
  649. );
  650. // Fetch all available modules manually, since module_list() only returns
  651. // currently enabled modules, which makes this setting pointless if developer
  652. // modules are currently disabled.
  653. $all_modules = array();
  654. $result = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' ORDER BY name ASC");
  655. foreach ($result as $module) {
  656. if (file_exists($module->filename)) {
  657. $info = unserialize($module->info);
  658. $all_modules[$module->name] = $info['name'];
  659. }
  660. }
  661. $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules());
  662. $devel_modules = array_intersect_key($all_modules, array_flip($devel_modules));
  663. $form['performance']['admin_menu_devel_modules_skip'] = array(
  664. '#type' => 'checkboxes',
  665. '#title' => t('Developer modules to keep enabled'),
  666. '#default_value' => variable_get('admin_menu_devel_modules_skip', array()),
  667. '#options' => $devel_modules,
  668. '#access' => !empty($devel_modules),
  669. '#description' => t('The selected modules will not be disabled when the link %disable-developer-modules below the icon in the menu is invoked.', array(
  670. '%disable-developer-modules' => t('Disable developer modules'),
  671. )),
  672. );
  673. return system_settings_form($form);
  674. }
  675. /**
  676. * #process callback for component plugin form element in admin_menu_theme_settings().
  677. */
  678. function admin_menu_settings_process_components($element) {
  679. // Assign 'rel' attributes to all options to achieve a live preview.
  680. // Unfortunately, #states relies on wrapping .form-wrapper classes, so it
  681. // cannot be used here.
  682. foreach ($element['#options'] as $key => $label) {
  683. if (!isset($element[$key]['#attributes']['rel'])) {
  684. $id = preg_replace('/[^a-z]/', '-', $key);
  685. $element[$key]['#attributes']['rel'] = '#' . $id;
  686. }
  687. }
  688. return $element;
  689. }
  690. /**
  691. * Form validation handler for admin_menu_theme_settings().
  692. */
  693. function admin_menu_theme_settings_validate(&$form, &$form_state) {
  694. // Change the configured components to Boolean values.
  695. foreach ($form_state['values']['admin_menu_components'] as $component => &$enabled) {
  696. $enabled = (bool) $enabled;
  697. }
  698. }
  699. /**
  700. * Implementation of hook_form_FORM_ID_alter().
  701. *
  702. * Extends Devel module with Administration menu developer settings.
  703. */
  704. function _admin_menu_form_devel_admin_settings_alter(&$form, $form_state) {
  705. // Shift system_settings_form buttons.
  706. $weight = isset($form['buttons']['#weight']) ? $form['buttons']['#weight'] : 0;
  707. $form['buttons']['#weight'] = $weight + 1;
  708. $form['admin_menu'] = array(
  709. '#type' => 'fieldset',
  710. '#title' => t('Administration menu settings'),
  711. '#collapsible' => TRUE,
  712. '#collapsed' => TRUE,
  713. );
  714. $display_options = array('mid', 'weight', 'pid');
  715. $display_options = array(0 => t('None'), 'mlid' => t('Menu link ID'), 'weight' => t('Weight'), 'plid' => t('Parent link ID'));
  716. $form['admin_menu']['admin_menu_display'] = array(
  717. '#type' => 'radios',
  718. '#title' => t('Display additional data for each menu item'),
  719. '#default_value' => variable_get('admin_menu_display', 0),
  720. '#options' => $display_options,
  721. '#description' => t('Display the selected items next to each menu item link.'),
  722. );
  723. $form['admin_menu']['admin_menu_show_all'] = array(
  724. '#type' => 'checkbox',
  725. '#title' => t('Display all menu items'),
  726. '#default_value' => variable_get('admin_menu_show_all', 0),
  727. '#description' => t('If enabled, all menu items are displayed regardless of your site permissions. <em>Note: Do not enable on a production site.</em>'),
  728. );
  729. }
  730. /**
  731. * Menu callback; Enable/disable developer modules.
  732. *
  733. * This can save up to 150ms on each uncached page request.
  734. */
  735. function admin_menu_toggle_modules() {
  736. if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) {
  737. return MENU_ACCESS_DENIED;
  738. }
  739. $rebuild = FALSE;
  740. $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL);
  741. if (isset($saved_state)) {
  742. // Re-enable modules that were enabled before.
  743. module_enable($saved_state);
  744. variable_del('admin_menu_devel_modules_enabled');
  745. drupal_set_message(t('Enabled these modules: !module-list.', array('!module-list' => implode(', ', $saved_state))));
  746. $rebuild = TRUE;
  747. }
  748. else {
  749. // Allow site admins to override this variable via settings.php.
  750. $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules());
  751. // Store currently enabled modules in a variable.
  752. $devel_modules = array_intersect(module_list(FALSE, FALSE), $devel_modules);
  753. $devel_modules = array_diff($devel_modules, variable_get('admin_menu_devel_modules_skip', array()));
  754. if (!empty($devel_modules)) {
  755. variable_set('admin_menu_devel_modules_enabled', $devel_modules);
  756. // Disable developer modules.
  757. module_disable($devel_modules);
  758. drupal_set_message(t('Disabled these modules: !module-list.', array('!module-list' => implode(', ', $devel_modules))));
  759. $rebuild = TRUE;
  760. }
  761. else {
  762. drupal_set_message(t('No developer modules are enabled.'));
  763. }
  764. }
  765. if ($rebuild) {
  766. // Make sure everything is rebuilt, basically a combination of the calls
  767. // from system_modules() and system_modules_submit().
  768. drupal_theme_rebuild();
  769. menu_rebuild();
  770. cache_clear_all('schema', 'cache');
  771. cache_clear_all();
  772. drupal_clear_css_cache();
  773. drupal_clear_js_cache();
  774. // Synchronize to catch any actions that were added or removed.
  775. actions_synchronize();
  776. // Finally, flush admin_menu's cache.
  777. admin_menu_flush_caches();
  778. }
  779. drupal_goto();
  780. }
  781. /**
  782. * Helper function to return a default list of developer modules.
  783. */
  784. function _admin_menu_developer_modules() {
  785. return array(
  786. 'admin_devel',
  787. 'cache_disable',
  788. 'coder',
  789. 'content_copy',
  790. 'context_ui',
  791. 'debug',
  792. 'delete_all',
  793. 'demo',
  794. 'devel',
  795. 'devel_node_access',
  796. 'devel_themer',
  797. 'field_ui',
  798. 'fontyourface_ui',
  799. 'form_controller',
  800. 'imagecache_ui',
  801. 'journal',
  802. 'l10n_client',
  803. 'l10n_update',
  804. 'macro',
  805. 'rules_admin',
  806. 'stringoverrides',
  807. 'trace',
  808. 'upgrade_status',
  809. 'user_display_ui',
  810. 'util',
  811. 'views_ui',
  812. 'views_theme_wizard',
  813. );
  814. }
  815. /**
  816. * Flush all caches or a specific one.
  817. *
  818. * @param $name
  819. * (optional) Name of cache to flush.
  820. */
  821. function admin_menu_flush_cache($name = NULL) {
  822. if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) {
  823. return MENU_ACCESS_DENIED;
  824. }
  825. if (isset($name)) {
  826. $caches = module_invoke_all('admin_menu_cache_info');
  827. if (!isset($caches[$name])) {
  828. return MENU_NOT_FOUND;
  829. }
  830. }
  831. else {
  832. $caches[$name] = array(
  833. 'title' => t('Every'),
  834. 'callback' => 'drupal_flush_all_caches',
  835. );
  836. }
  837. // Pass the cache to flush forward to the callback.
  838. $function = $caches[$name]['callback'];
  839. $function($name);
  840. drupal_set_message(t('!title cache cleared.', array('!title' => $caches[$name]['title'])));
  841. // The JavaScript injects a destination request parameter pointing to the
  842. // originating page, so the user is redirected back to that page. Without
  843. // destination parameter, the redirect ends on the front page.
  844. drupal_goto();
  845. }
  846. /**
  847. * Implements hook_admin_menu_cache_info().
  848. */
  849. function admin_menu_admin_menu_cache_info() {
  850. $caches['admin_menu'] = array(
  851. 'title' => t('Administration menu'),
  852. 'callback' => '_admin_menu_flush_cache',
  853. );
  854. return $caches;
  855. }
  856. /**
  857. * Implements hook_admin_menu_cache_info() on behalf of System module.
  858. */
  859. function system_admin_menu_cache_info() {
  860. $caches = array(
  861. 'assets' => t('CSS and JavaScript'),
  862. 'cache' => t('Page and else'),
  863. 'menu' => t('Menu'),
  864. 'registry' => t('Class registry'),
  865. 'theme' => t('Theme registry'),
  866. );
  867. foreach ($caches as $name => $cache) {
  868. $caches[$name] = array(
  869. 'title' => $cache,
  870. 'callback' => '_admin_menu_flush_cache',
  871. );
  872. }
  873. return $caches;
  874. }
  875. /**
  876. * Implements hook_admin_menu_cache_info() on behalf of Update module.
  877. */
  878. function update_admin_menu_cache_info() {
  879. $caches['update'] = array(
  880. 'title' => t('Update data'),
  881. 'callback' => '_update_cache_clear',
  882. );
  883. return $caches;
  884. }
  885. /**
  886. * Flush all caches or a specific one.
  887. *
  888. * @param $name
  889. * (optional) Name of cache to flush.
  890. *
  891. * @see system_admin_menu_cache_info()
  892. */
  893. function _admin_menu_flush_cache($name = NULL) {
  894. switch ($name) {
  895. case 'admin_menu':
  896. admin_menu_flush_caches();
  897. break;
  898. case 'menu':
  899. menu_rebuild();
  900. break;
  901. case 'registry':
  902. registry_rebuild();
  903. // Fall-through to clear cache tables, since registry information is
  904. // usually the base for other data that is cached (e.g. SimpleTests).
  905. case 'cache':
  906. // Don't clear cache_form - in-progress form submissions may break.
  907. // Ordered so clearing the page cache will always be the last action.
  908. // @see drupal_flush_all_caches()
  909. $core = array('cache', 'cache_bootstrap', 'cache_filter', 'cache_page');
  910. $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
  911. foreach ($cache_tables as $table) {
  912. cache_clear_all('*', $table, TRUE);
  913. }
  914. break;
  915. case 'assets':
  916. // Change query-strings on css/js files to enforce reload for all users.
  917. _drupal_flush_css_js();
  918. drupal_clear_css_cache();
  919. drupal_clear_js_cache();
  920. // Clear the page cache, since cached HTML pages might link to old CSS and
  921. // JS aggregates.
  922. cache_clear_all('*', 'cache_page', TRUE);
  923. break;
  924. case 'theme':
  925. system_rebuild_theme_data();
  926. drupal_theme_rebuild();
  927. break;
  928. }
  929. }
  930. /**
  931. * Preprocesses variables for theme_admin_menu_icon().
  932. */
  933. function template_preprocess_admin_menu_icon(&$variables) {
  934. // Image source might have been passed in as theme variable.
  935. if (!isset($variables['src'])) {
  936. if (theme_get_setting('toggle_favicon')) {
  937. $variables['src'] = theme_get_setting('favicon');
  938. }
  939. else {
  940. $variables['src'] = base_path() . 'misc/favicon.ico';
  941. }
  942. }
  943. // Strip the protocol without delimiters for transient HTTP/HTTPS support.
  944. // Since the menu is cached on the server-side and client-side, the cached
  945. // version might contain a HTTP link, whereas the actual page is on HTTPS.
  946. // Relative paths will work fine, but theme_get_setting() returns an
  947. // absolute URI.
  948. $variables['src'] = preg_replace('@^https?:@', '', $variables['src']);
  949. $variables['src'] = check_plain($variables['src']);
  950. $variables['alt'] = t('Home');
  951. }
  952. /**
  953. * Renders an icon to display in the administration menu.
  954. *
  955. * @ingroup themeable
  956. */
  957. function theme_admin_menu_icon($variables) {
  958. return '<img class="admin-menu-icon" src="' . $variables['src'] . '" width="16" height="16" alt="' . $variables['alt'] . '" />';
  959. }