Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/CHANGELOG.txt,v retrieving revision 1.33.2.48.2.24 diff -u -p -r1.33.2.48.2.24 CHANGELOG.txt --- CHANGELOG.txt 11 Jun 2009 01:39:24 -0000 1.33.2.48.2.24 +++ CHANGELOG.txt 11 Jun 2009 22:43:41 -0000 @@ -6,6 +6,8 @@ Admin Menu x.x-x.x, xxxx-xx-xx Admin Menu 6.x-3.x, xxxx-xx-xx ------------------------------ +#276751 by sun: Major rewrite. Fixed menu items cannot be moved, altered, or + added as well as various performance issues. by sun: Added variable to allow to disable caching (rewrite). Index: admin_menu.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.inc,v retrieving revision 1.11.2.20.2.11 diff -u -p -r1.11.2.20.2.11 admin_menu.inc --- admin_menu.inc 9 Jun 2009 18:21:22 -0000 1.11.2.20.2.11 +++ admin_menu.inc 11 Jun 2009 23:11:16 -0000 @@ -7,369 +7,144 @@ */ /** - * Rebuild administration menu links. + * Add default features to increase user experience. * - * This is invoked whenever the menu is rebuilt. - * - * @see admin_menu_footer() - * @see admin_menu_flush_caches() - */ -function _admin_menu_rebuild_links() { - // Get the newly rebuilt menu. - $menu = menu_router_build(); - // Add normal and suggested items as links. - $menu_links = array(); - foreach ($menu as $path => $item) { - // Exclude menu callbacks, include items below admin/* and node/add/*. - if ($item['type'] != MENU_CALLBACK && (($item['_parts'][0] == 'admin' && count($item['_parts']) > 1) || (strpos($path, 'node/add') === 0))) { - // @todo Handle local tasks with wildcards. - if (!strpos($path, '%')) { - $item = admin_menu_link_build($item); - $menu_links[$path] = $item; - $sort[$path] = $item['_number_parts']; - } - } - } - // Adjust some menu items for better user experience. - $deleted = admin_menu_adjust_items($menu_links, $sort); - if ($menu_links) { - // Make sure no child comes before its parent. - array_multisort($sort, SORT_NUMERIC, $menu_links); - - foreach ($menu_links as $item) { - admin_menu_link_save($item); - } - } - // Allow modules to add more links. If you want to alter links saved by - // admin_menu instead, use hook_menu_link_alter() and look for - // $item['module'] == 'admin_menu' - $links = module_invoke_all('admin_menu', $deleted); - foreach ($links as $item) { - admin_menu_link_save($item); - } -} - -/** - * Prepare a menu link from basic information formatted for a router item. - */ -function admin_menu_link_build($item) { - $item['module'] = 'admin_menu'; - $item['menu_name'] = 'admin_menu'; - $item += array( - 'link_title' => isset($item['title']) ? $item['title'] : '', - 'link_path' => $item['path'], - 'hidden' => 0, - 'options' => array(), - ); - // Invoke hook_translated_menu_link_alter() for our items. - $item['options']['alter'] = TRUE; - return $item; -} - -/** - * Convenience function that looks up the plid if $item['parent_path'] is set. - */ -function admin_menu_link_save($item) { - $item = admin_menu_link_build($item); - - // Check whether we are able to update an existing item. - $existing_item = db_fetch_array(db_query("SELECT mlid, plid, has_children FROM {menu_links} WHERE link_path = '%s' AND menu_name = '%s'", $item['link_path'], 'admin_menu')); - if ($existing_item) { - $item['mlid'] = $existing_item['mlid']; - $item['plid'] = $existing_item['plid']; - $item['has_children'] = $existing_item['has_children']; - } - - // Look up the parent path for both new and existing links, since the parent - // may change. - if (isset($item['parent_path'])) { - if ($item['parent_path'] == '') { - // means that we want the link at the top level. - $item['plid'] = 0; - } - else { - $plid = db_result(db_query("SELECT mlid from {menu_links} WHERE link_path = '%s' AND menu_name = '%s'", $item['parent_path'], 'admin_menu')); - if ($plid) { - $item['plid'] = $plid; - } - } - } - - menu_link_save($item); -} - -/** - * Implementation of hook_admin_menu(). - * - * @param &$deleted - * Array of links under admin/* that were removed by admin_menu_adjust_items(). - * If one of these links is added back, it should be removed from the array. + * @return + * An array of links to be rendered around the menu tree. */ -function admin_menu_admin_menu(&$deleted) { - $links = array(); - $icon_path = ''; - +function admin_menu_adjust_items() { + // Add icon containing special links. + $links['icon'] = array( + '#title' => theme('admin_menu_icon'), + '#attributes' => array('class' => array('admin-menu-icon')), + '#href' => '', + '#options' => array( + 'html' => TRUE, + ), + ); // Add link to manually run cron. - $links[] = array( - 'title' => 'Run cron', - 'path' => 'admin/reports/status/run-cron', - 'weight' => 50, - 'parent_path' => $icon_path, + $links['icon']['cron'] = array( + '#title' => t('Run cron'), + '#weight' => 50, + '#access' => user_access('administer site configuration'), + '#href' => 'admin/reports/status/run-cron', ); // Add link to run update.php. - $links[] = array( - 'title' => 'Run updates', - 'path' => 'update.php', - 'weight' => 50, - 'parent_path' => $icon_path, - 'options' => array( + $links['icon']['update'] = array( + '#title' => t('Run updates'), + '#weight' => 50, + '#access' => ($GLOBALS['user']->uid == 1 || !empty($GLOBALS['update_free_access'])), + '#href' => base_path() . 'update.php', + '#options' => array( 'external' => TRUE, ), ); - - // Move 'By module' item into Site configuration. - if (isset($deleted['admin/by-module'])) { - $deleted['admin/by-module']['parent_path'] = 'admin/settings'; - $deleted['admin/by-module']['weight'] = -10; - $links[] = $deleted['admin/by-module']; - unset($deleted['admin/by-module']); - } - // Add link to drupal.org. - $links[] = array( - 'title' => 'Drupal.org', - 'path' => 'http://drupal.org', - 'weight' => 100, - 'parent_path' => $icon_path, + $links['icon']['drupal.org'] = array( + '#title' => 'Drupal.org', + '#weight' => 100, + '#access' => user_access('display drupal links'), + '#href' => 'http://drupal.org', ); // Add links to project issue queues. - $links[] = array( - 'title' => 'Drupal issue queue', - 'path' => 'http://drupal.org/project/issues/drupal', - 'weight' => -10, - 'parent_path' => 'http://drupal.org', - ); - $projects = array(); foreach (module_list(FALSE, FALSE, TRUE) as $module) { $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info'); - if (!isset($info['project']) || (isset($info['project']) && ($info['project'] == 'drupal' || isset($projects[$info['project']])))) { + if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) { continue; } - $projects[$info['project']] = 1; - $url = 'http://drupal.org/project/issues/' . $info['project']; - // Filtering project versions via query string is not yet supported. - // @see http://drupal.org/node/97569 - // $url .= !empty($info['version']) ? '/'. $info['version'] : ''; - $links[] = array( - 'title' => check_plain($info['name']) . ' issue queue', - 'path' => $url, - 'parent_path' => 'http://drupal.org', - ); - } - - // Add 'Create ' items to Content management menu. - if (isset($deleted['node/add'])) { - $deleted['node/add']['parent_path'] = 'admin/content'; - $deleted['node/add']['weight'] = 0; - $links[] = $deleted['node/add']; - unset($deleted['node/add']); - } - foreach ($deleted as $path => $item) { - if (strpos($path, 'node/add') !== FALSE) { - $links[] = $deleted[$path]; - unset($deleted[$path]); - } - } - // Make sure longer paths are after shorter ones - ksort($deleted); - foreach (node_get_types('types', NULL, TRUE) as $type) { - $type_url_str = str_replace('_', '-', $type->type); - $type_path = 'admin/content/node-type/' . $type_url_str; - $links[$type_path] = array( - 'title' => 'Edit !content-type', - 'path' => $type_path, - 'parent_path' => 'admin/content/types', - 'options' => array('t' => array('!content-type' => $type->name)), + $links['icon']['drupal.org'][$info['project']] = array( + '#title' => t('@project issue queue', array('@project' => $info['name'])), + '#weight' => ($info['project'] == 'drupal' ? -10 : 0), + '#href' => 'http://drupal.org/project/issues/' . $info['project'], + '#options' => array( + 'query' => 'version=' . (isset($info['core']) ? $info['core'] : 'All'), + ), ); - unset($deleted[$type_path . '/edit']); - // CCK and other modules adding to node types handled here. - foreach ($deleted as $path => $item) { - // Precise test needed to account for multiple content-types having the - // same prefix in their name. - if ($path === $type_path || strpos($path, $type_path . '/') === 0) { - // Logically, parent path can never go shorter than $type_path. - $i = $item['_number_parts'] - 1; - do { - $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); - --$i; - } while (!isset($links[$parent_path]) && $i); - $item['parent_path'] = $parent_path; - $links[$path] = $item; - unset($deleted[$path]); - } - } } - // Add items to flush caches. - $links[] = array( - 'title' => 'Flush all caches', - 'path' => 'admin_menu/flush-cache', - 'options' => array( + $links['icon']['flush-cache'] = array( + '#title' => t('Flush all caches'), + '#weight' => 20, + // @todo Add permission to flush cashes. + '#access' => user_access('administer site configuration'), + '#href' => 'admin_menu/flush-cache', + '#options' => array( 'query' => drupal_get_destination(), ), - 'weight' => 20, - 'parent_path' => $icon_path, ); $caches = array( - 'admin_menu' => 'Administration menu', - 'cache' => 'Cache tables', - 'menu' => 'Menu', - 'requisites' => 'Page requisites', - 'theme' => 'Theme registry', - ); - foreach ($caches as $name => $title) { - $links[] = array( - 'title' => $title, - 'path' => 'admin_menu/flush-cache/' . $name, - 'options' => array( + 'admin_menu' => t('Administration menu'), + 'cache' => t('Cache tables'), + 'menu' => t('Menu'), + 'requisites' => t('Page requisites'), + 'theme' => t('Theme registry'), + ); + foreach ($caches as $arg => $title) { + $links['icon']['flush-cache'][$arg] = array( + '#title' => $title, + '#href' => 'admin_menu/flush-cache/' . $arg, + '#options' => array( 'query' => drupal_get_destination(), ), - 'parent_path' => 'admin_menu/flush-cache', ); } - - // Add devel module links - if (module_exists('devel')) { - // Add variable editor. - $links[] = array( - 'title' => 'Variable editor', - 'path' => 'devel/variable', - 'weight' => 20, - 'parent_path' => $icon_path, - ); - // Add switch_user items. - if ($devel_user_links = module_invoke('devel', 'switch_user_list')) { - foreach ($devel_user_links as $link) { - if (is_array($link)) { - $links[] = array( - 'title' => $link['title'], - 'description' => $link['attributes']['title'], - 'path' => $link['href'], - 'options' => array( - 'query' => $link['query'], - 'html' => !empty($link['html']), - ), - 'parent_path' => 'user/%', - ); - } - // @todo Remove when Devel 6.x-1.16 has been released. - elseif (preg_match('!href="' . base_path() . '([^\?]+)\?([^"]+)" title="([^"]+)">(()?[^<]+()?)!', $link, $match)) { - $links[] = array( - 'title' => $match[4], - 'description' => $match[3], - 'path' => urldecode($match[1]), - 'options' => array( - 'query' => drupal_get_destination(), - 'html' => TRUE, - ), - 'weight' => 20, - 'parent_path' => 'user/%', - ); - } - } - } - } - // Add developer modules toggle link. + // Add link to toggle developer modules (performance). $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); - $links[] = array( - 'title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'), - 'path' => 'admin_menu/toggle-modules', - 'weight' => 88, - 'parent_path' => $icon_path, - 'options' => array( + $links['icon']['toggle-modules'] = array( + '#title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'), + '#weight' => 88, + '#access' => user_access('administer site configuration'), + '#href' => 'admin_menu/toggle-modules', + '#options' => array( 'query' => drupal_get_destination(), ), ); - return $links; -} + // Add link to show current authenticated/anonymous users. + $links['user-counter'] = array( + '#title' => admin_menu_get_user_count(), + '#description' => t('Current anonymous / authenticated users'), + '#weight' => -90, + '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-users')), + '#href' => (user_access('administer users') ? 'admin/user/user' : 'user'), + ); + $links['account'] = array( + '#title' => $GLOBALS['user']->name, + '#weight' => -99, + '#attributes' => array('class' => array('admin-menu-action')), + '#href' => 'user/' . $GLOBALS['user']->uid, + ); + $links['logout'] = array( + '#title' => 'Log out', + '#weight' => -100, + '#attributes' => array('class' => array('admin-menu-action')), + '#href' => 'user/logout', + ); -/** - * Add some hard-coded features for better user experience. - * - * @param $menu_links - * An array containing the complete administration menu structure, passed by - * reference. - * @param $sort - * An array containing the # parts of each link - must be updated if a link - * is added. - * - * @return - * An array of links that were removed from $menu_links. - */ -function admin_menu_adjust_items(&$menu_links, &$sort) { - global $user; - $links = array(); - $deleted = array(); - - // Change or remove items, or add new top-level items. - $deleted['admin/by-module'] = $menu_links['admin/by-module']; - unset($menu_links['admin/by-module'], $sort['admin/by-module']); - $deleted['admin/by-task'] = $menu_links['admin/by-task']; - unset($menu_links['admin/by-task'], $sort['admin/by-task']); - - // Remove certain links to re-position them in admin_menu_admin_menu(). - foreach ($menu_links as $path => $link) { - // Remove links below - // - admin/content/node-type/* - // - node/add* - if (strpos($path, 'admin/content/node-type/') !== FALSE || strpos($path, 'node/add') !== FALSE) { - $deleted[$path] = $link; - unset($menu_links[$path], $sort[$path]); + // Add Devel module links. + if (module_exists('devel')) { + // Add variable editor. + $links['icon']['devel-variables'] = array( + '#title' => t('Variable editor'), + '#weight' => 20, + '#access' => user_access('access devel information'), + '#href' => 'devel/variable', + ); + // Add switch user links. + foreach (module_invoke('devel', 'switch_user_list') as $link) { + $links['account'][$link['title']] = array( + '#title' => $link['title'], + '#description' => $link['attributes']['title'], + '#href' => $link['href'], + '#options' => array( + 'query' => $link['query'], + 'html' => !empty($link['html']), + ), + ); } } - // Add the icon containing special links. - $links[] = array( - 'title' => theme('admin_menu_icon'), - 'path' => '', - 'weight' => -100, - 'parent_path' => '', - 'options' => array('extra class' => 'admin-menu-icon', 'html' => TRUE), - ); - // Add link to show current authenticated/anonymous users - we will add the - // data dynamically in the _alter hook. - $links[] = array( - 'title' => admin_menu_get_user_count(), - 'description' => t('Current anonymous / authenticated users'), - 'path' => 'user', - 'weight' => -90, - 'parent_path' => '', - 'options' => array('extra class' => 'admin-menu-action admin-menu-users'), - ); - $links[] = array( - 'title' => '@username', - 'path' => 'user/%', - 'weight' => -99, - 'parent_path' => '', - // Note: @username is dynamically replaced by default, we just invoke - // replacement by setting the 't' key here. - 'options' => array('extra class' => 'admin-menu-action', 't' => array()), - ); - $links[] = array( - 'title' => 'Log out', - 'path' => 'logout', - 'weight' => -100, - 'parent_path' => '', - 'options' => array('extra class' => 'admin-menu-action'), - ); - foreach ($links as $item) { - $path = $item['path']; - $item = admin_menu_link_build($item); - $menu_links[$path] = $item; - $sort[$path] = 1; - } - return $deleted; + return $links; } /** @@ -411,20 +186,6 @@ function admin_menu_theme_settings() { } /** - * Wipe the menu so it can be rebuilt from scratch. - */ -function admin_menu_wipe() { - // Flush cached menu tree. - db_query("DELETE FROM {menu_links} WHERE menu_name = 'admin_menu'"); - menu_cache_clear('admin_menu'); - variable_set('admin_menu_rebuild_links', TRUE); - // Flush cached output of admin_menu. - cache_clear_all('admin_menu:', 'cache_menu', TRUE); - // Flush client-side cache hashes. - cache_clear_all('*', 'cache_admin_menu', TRUE); -} - -/** * Implementation of hook_form_FORM_ID_alter(). * * Extends Devel module with Administration menu developer settings. @@ -512,7 +273,9 @@ function admin_menu_toggle_modules() { function admin_menu_flush_cache($name = NULL) { switch ($name) { case 'admin_menu': - admin_menu_wipe(); + // Flush cached menu tree. + db_query("DELETE FROM {menu_links} WHERE menu_name = 'admin_menu' AND customized = 0"); + menu_rebuild(); break; case 'cache': @@ -526,7 +289,7 @@ function admin_menu_flush_cache($name = break; case 'menu': - module_invoke('menu', 'rebuild'); + menu_rebuild(); break; case 'requisites': Index: admin_menu.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.install,v retrieving revision 1.4.2.6.2.3 diff -u -p -r1.4.2.6.2.3 admin_menu.install --- admin_menu.install 1 Apr 2009 18:43:00 -0000 1.4.2.6.2.3 +++ admin_menu.install 12 Jun 2009 01:22:50 -0000 @@ -14,6 +14,8 @@ function admin_menu_schema() { * Implementation of hook_install(). */ function admin_menu_install() { + // Create menu. + _admin_menu_install_menu(); // Create cache table. drupal_install_schema('admin_menu'); } @@ -25,23 +27,24 @@ function admin_menu_uninstall() { // Remove cache table. drupal_uninstall_schema('admin_menu'); // Delete menu links. - db_query("DELETE FROM {menu_links} WHERE module = 'admin_menu'"); - menu_cache_clear_all(); + db_query("DELETE FROM {menu_links} WHERE menu_name = 'admin_menu'"); + menu_rebuild(); // Delete variables. variable_del('admin_menu_devel_modules_enabled'); variable_del('admin_menu_margin_top'); variable_del('admin_menu_position_fixed'); - variable_del('admin_menu_rebuild_links'); variable_del('admin_menu_tweak_modules'); variable_del('admin_menu_tweak_tabs'); } /** - * Implementation of hook_enable(). + * Helper function to ensure 'admin_menu' menu. */ -function admin_menu_enable() { - // Rebuild the menu to ensure we do not serve stale data. - variable_set('admin_menu_rebuild_links', TRUE); +function _admin_menu_install_menu() { + if (db_result(db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = 'admin_menu'"))) { + return array('success' => TRUE, 'query' => 'Menu for Administration menu already exists.'); + } + return update_sql("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('admin_menu', 'Administration menu', '')"); } /** @@ -51,7 +54,6 @@ function admin_menu_update_6000() { $ret = array(); // Delete menu links. db_query("DELETE FROM {menu_links} WHERE module = 'admin_menu'"); - variable_set('admin_menu_rebuild_links', TRUE); // Drop the {admin_menu} table in admin_menu_update_6000() on sites that used // one of the later patches in #132524. if (db_table_exists('admin_menu')) { @@ -84,3 +86,26 @@ function admin_menu_update_6300() { return $ret; } +/** + * Add "admin_menu" menu. + */ +function admin_menu_update_6301() { + $ret = array(); + variable_del('admin_menu_rebuild_links'); + // Create menu. + $ret[] = _admin_menu_install_menu(); + // @todo hook_menu_alter() has no effect on customized menu items. During an + // upgrade from earlier versions, it is possible that users altered + // administrative items. Those will be located at the top-level of the menu + // without this (very crude) reset. Suggestions welcome. + db_query("DELETE FROM {menu_links} WHERE router_path LIKE 'admin%%'"); + return $ret; +} + +function admin_menu_form_update_script_selection_form_alter(&$form, $form_state) { + if ($form['start']['admin_menu']['#default_value'] <= 6301) { + drupal_set_message("BACKUP YOUR DATABASE! Administration menu's upgrade path resets and deletes all custom administrative menu items.", 'error'); + } +} + +# UPDATE `test_drupal6`.`system` SET `schema_version` = '6300' WHERE `system`.`filename` = 'sites/all/modules/admin_menu/admin_menu.module' LIMIT 1 ; Index: admin_menu.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.module,v retrieving revision 1.43.2.17.2.10 diff -u -p -r1.43.2.17.2.10 admin_menu.module --- admin_menu.module 11 Jun 2009 01:39:24 -0000 1.43.2.17.2.10 +++ admin_menu.module 12 Jun 2009 01:05:36 -0000 @@ -39,6 +39,9 @@ function admin_menu_perm() { */ function admin_menu_theme() { return array( + 'admin_menu_links' => array( + 'arguments' => array('elements' => array()), + ), 'admin_menu_icon' => array( 'arguments' => array(), 'file' => 'admin_menu.inc', @@ -51,6 +54,7 @@ function admin_menu_theme() { */ function admin_menu_menu() { // AJAX callback. + // @see http://drupal.org/project/js $items['js/admin_menu/cache'] = array( 'page callback' => 'admin_menu_js_cache', 'access arguments' => array('access administration menu'), @@ -82,6 +86,58 @@ function admin_menu_menu() { } /** + * Implementation of hook_menu_alter(). + */ +function admin_menu_menu_alter(&$items) { + // Move all items below admin/* into administration menu. + foreach ($items as $path => $item) { + if (strpos($path, 'admin') === 0) { + if (!isset($item['type'])) { + $items[$path]['menu_name'] = 'admin_menu'; + } + else { + // For any reason, content-type menu items are registered individually + // in Drupal core, but also by CCK. We a) have to copy and re-assign + // them the proper router path and b) turn their parent items from + // MENU_CALLBACK into something visible; otherwise, the menu system + // does not find the parents and relocates child items to the top-level. + if (strpos($path, 'admin/content/node-type/') === 0) { + // Fix router path. + $newpath = strtr($path, array('/node-type/' => '/types/')); + $items[$newpath] = $items[$path]; + // Alter item type and visibility. + $items[$newpath]['type'] &= ~MENU_CALLBACK; + if ($items[$newpath]['type'] === 0) { + $items[$newpath]['type'] |= MENU_NORMAL_ITEM; + } + // Use new item from here, but leave old intact to play nice with + // others. + $path = $newpath; + $item = $items[$newpath]; + } + if ($item['type'] & MENU_IS_LOCAL_TASK) { + // Trick this item into the visible menu tree. + $items[$path]['_visible'] = TRUE; + } + if ($item['type'] & MENU_CALLBACK) { + continue; + } + $items[$path]['menu_name'] = 'admin_menu'; + } + } + } + + // Remove 'admin', so children appear on the top-level. + $items['admin']['_visible'] = FALSE; + // Remove local tasks on 'admin'. + $items['admin/by-task']['_visible'] = FALSE; + $items['admin/by-module']['_visible'] = FALSE; + + // Flush client-side caches. + module_invoke('admin_menu', 'flush_caches'); +} + +/** * Implementation of hook_init(). * * We can't move this into admin_menu_footer(), because PHP-only based themes @@ -169,67 +225,9 @@ function admin_menu_suppress($set = TRUE /** * Implementation of hook_footer(). - * - * @todo Since admin_menu is rebuilt in the same request, we should be able - * to use a helper function instead of a variable to remind us to rebuild - * (variable_set() is slow). */ function admin_menu_footer($main = 0) { - if (!user_access('access administration menu') || admin_menu_suppress(FALSE)) { - return; - } - global $user, $language; - - $cache_server_enabled = variable_get('admin_menu_cache_server', TRUE); - - // Determine whether we need to rebuild. - $rebuild = variable_get('admin_menu_rebuild_links', FALSE); - $cid = 'admin_menu:' . $user->uid . ':' . $language->language; - - // Do nothing at all here if the client supports client-side caching, no - // rebuild is needed, the user has a hash, and is NOT requesting the cache - // update path. Consult the hash cache last, since it requires a DB request. - // @todo Implement a sanity-check to prevent permanent double requests; i.e. - // what if the client-side cache fails for any reason and performs a second - // request on every page? - if (!empty($_COOKIE['has_js']) && !$rebuild && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) { - if (admin_menu_cache_get($cid)) { - return; - } - } - - // Check for the flag indicating that we need to rebuild the menu. - if ($rebuild) { - module_load_include('inc', 'admin_menu'); - _admin_menu_rebuild_links(); - variable_del('admin_menu_rebuild_links'); - } - // Try to load and output administration menu from server-side cache. - elseif ($cache_server_enabled) { - $cache = cache_get($cid, 'cache_menu'); - if ($cache && isset($cache->data)) { - $content = $cache->data; - } - } - - // Rebuild the output. - if (!isset($content)) { - $content = '
'; - $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu')); - $content .= '
'; - - // Cache the menu for this user. - if ($cache_server_enabled) { - cache_set($cid, $content, 'cache_menu'); - } - } - - // Store the new hash for this user. - if (!empty($_COOKIE['has_js'])) { - admin_menu_cache_set($cid, md5($content)); - } - - return $content; + return admin_menu_output(); } /** @@ -279,8 +277,8 @@ function admin_menu_cache_set($cid, $dat * Menu callback; Output administration menu for HTTP caching via AJAX request. */ function admin_menu_js_cache($hash = NULL) { - // Fetch the menu. - $content = admin_menu_footer(); + // Get the rendered menu. + $content = admin_menu_output(); // @todo According to http://www.mnot.net/blog/2006/05/11/browser_caching, // IE will only cache the content when it is compressed. @@ -336,6 +334,132 @@ function admin_menu_get_user_count() { } /** + * Build the administration menu output. + */ +function admin_menu_output() { + if (!user_access('access administration menu') || admin_menu_suppress(FALSE)) { + return; + } + global $user, $language; + + $cache_server_enabled = variable_get('admin_menu_cache_server', TRUE); + $cid = 'admin_menu:' . $user->uid . ':' . $language->language; + + // Do nothing at all here if the client supports client-side caching, the user + // has a hash, and is NOT requesting the cache update path. Consult the hash + // cache last, since it requires a DB request. + // @todo Implement a sanity-check to prevent permanent double requests; i.e. + // what if the client-side cache fails for any reason and performs a second + // request on every page? + if (!empty($_COOKIE['has_js']) && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) { + if (admin_menu_cache_get($cid)) { + return; + } + } + + // Try to load and output administration menu from server-side cache. + if ($cache_server_enabled) { + $cache = cache_get($cid, 'cache_menu'); + if ($cache && isset($cache->data)) { + $content = $cache->data; + } + } + + // Rebuild the output. + if (!isset($content)) { + // @todo Always output container to harden JS-less support. + $content['#prefix'] = '
'; + $content['#suffix'] = '
'; + + // Add menu additions. + module_load_include('inc', 'admin_menu'); + $content['links'] = array( + '#weight' => 0, + '#theme' => 'admin_menu_links', + ); + $content['links'] += admin_menu_adjust_items(); + + // Add administration menu. + // @todo How can we use #theme here? + $content['menu'] = array( + '#weight' => 10, + '#value' => admin_menu_tree_output(menu_tree_all_data('admin_menu')), + ); + + // Allow modules to alter the output. + drupal_alter('admin_menu_output', $content); + $content = drupal_render($content); + + // Cache the menu for this user. + if ($cache_server_enabled) { + cache_set($cid, $content, 'cache_menu'); + } + } + + // Store the new hash for this user. + if (!empty($_COOKIE['has_js'])) { + admin_menu_cache_set($cid, md5($content)); + } + + return $content; +} + +/** + * Render a themed list of links. + * + * @param $elements + * A structured drupal_render()-array of links using the following keys: + * - '#attributes': Optional array of attributes for the list item, processed + * via drupal_attributes(). + * Note that we use an array for 'class'. + * - '#title': Title of the link, passed to l(). + * - '#href': Optional path of the link, passed to l(). When omitted, the + * element's '#title' is rendered without link. + * - '#description': Optional alternative text for the link, passed to l(). + * - '#options': Optional alternative text for the link, passed to l(). + * + * The array key of each child element itself is passed as path for l(). + */ +function theme_admin_menu_links(&$elements) { + $output = ''; + foreach (element_children($elements, TRUE) as $path) { + // Early-return nothing if user does not have access. + if (isset($elements[$path]['#access']) && !$elements[$path]['#access']) { + continue; + } + $elements[$path] += array( + '#attributes' => array(), + '#options' => array(), + ); + // Render children to determine whether this link is expandable. + $children = theme('admin_menu_links', $elements[$path]); + if (!empty($children)) { + $elements[$path]['#attributes']['class'][] = 'expandable'; + } + if (isset($elements[$path]['#attributes']['class'])) { + $elements[$path]['#attributes']['class'] = implode(' ', $elements[$path]['#attributes']['class']); + } + + $output .= ''; + + if (isset($elements[$path]['#href'])) { + $output .= l($elements[$path]['#title'], $elements[$path]['#href'], $elements[$path]['#options']); + } + elseif (!empty($element[$path]['#options']['html'])) { + $output .= $elements[$path]['#title']; + } + else { + $output .= check_plain($elements[$path]['#title']); + } + + $output .= $children; + $output .= ''; + } + // @todo #attributes probably required for UL, but already used for LI. + return $output ? '
    ' . $output . '
' : ''; +} + +/** * Return a rendered menu tree. * * @param $tree @@ -349,6 +473,11 @@ function admin_menu_tree_output($tree) { $output = ''; foreach ($tree as $data) { + // Skip menu callbacks (mostly dynamic items). + if (isset($data['link']['type']) && $data['link']['type'] == MENU_CALLBACK) { + continue; + } + // @todo Obsolete. $extra_class = isset($data['link']['localized_options']['extra class']) ? $data['link']['localized_options']['extra class'] : NULL; $link = admin_menu_item_link($data['link']); @@ -429,40 +558,6 @@ function theme_admin_menu_item($link, $h } /** - * Implementation of hook_form_FORM_ID_alter(). - * - * Extends Devel module with Administration menu developer settings. - */ -function admin_menu_form_devel_admin_settings_alter(&$form, $form_state) { - module_load_include('inc', 'admin_menu'); - _admin_menu_form_devel_admin_settings_alter($form, $form_state); -} - -/** - * Implementation of hook_menu_alter(). - */ -function admin_menu_menu_alter() { - variable_set('admin_menu_rebuild_links', TRUE); -} - -/** - * Implementation of hook_form_FORM_ID_alter(). - */ -function admin_menu_form_system_clean_url_settings_alter(&$form, $form_state) { - $form['#submit'][] = 'admin_menu_system_clean_url_settings_form_submit'; -} - -/** - * Form submit handler to flush client-side cache hashes when clean URLs are toggled. - */ -function admin_menu_system_clean_url_settings_form_submit(&$form, &$form_state) { - // Flush cached output of admin_menu. - cache_clear_all('admin_menu:', 'cache_menu', TRUE); - // Flush client-side cache hashes. - cache_clear_all('*', 'cache_admin_menu', TRUE); -} - -/** * Implementation of hook_translated_menu_link_alter(). * * Here is where we make changes to links that need dynamic information such @@ -489,31 +584,45 @@ function admin_menu_translated_menu_link } } - // We defined 'Run updates' as external link; apply proper base path now. - if ($item['link_path'] == 'update.php') { - $item['title'] = $item['link_title']; - $item['access'] = ($user->uid == 1 || !empty($GLOBALS['update_free_access'])); - $item['href'] = base_path() . $item['href']; - _menu_item_localize($item, $map, TRUE); - return; - } // Don't waste cycles altering items that are not visible if (!$item['access']) { return; } - // Hide links to drupal.org, if user does not have permission to view them. - if ($item['link_path'] == 'http://drupal.org' && !user_access('display drupal links')) { - $item['access'] = FALSE; - return; - } // Add developer information to all links, if enabled. if ($extra = variable_get('admin_menu_display', 0)) { $item['title'] .= ' ' . $extra[0] . ': ' . $item[$extra]; } - // Handle items that need dynamic localization/replacement. - if (isset($item['options']['t'])) { - $item['title'] = t($item['title'], $item['options']['t'] + array('@username' => $user->name)); - } +} + +/** + * Implementation of hook_flush_caches(). + * + * Flush client-side caches. + */ +function admin_menu_flush_caches() { + // Flush cached output of admin_menu. + cache_clear_all('admin_menu:', 'cache_menu', TRUE); + // Flush client-side cache hashes. + cache_clear_all('*', 'cache_admin_menu', TRUE); +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + * + * Form submit handler to flush client-side cache hashes when clean URLs are toggled. + */ +function admin_menu_form_system_clean_url_settings_alter(&$form, $form_state) { + $form['#submit'][] = 'admin_menu_flush_caches'; +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + * + * Extends Devel module with Administration menu developer settings. + */ +function admin_menu_form_devel_admin_settings_alter(&$form, $form_state) { + module_load_include('inc', 'admin_menu'); + _admin_menu_form_devel_admin_settings_alter($form, $form_state); }