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'] == '<root>') {
-      // <root> 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 = '<front>';
-
+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' => '<front>',
+    '#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 <content-type>' 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="([^"]+)">((<em>)?[^<]+(</em>)?)!', $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' => '<front>',
-    'weight' => -100,
-    'parent_path' => '<root>',
-    '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' => '<root>',
-    'options' => array('extra class' => 'admin-menu-action admin-menu-users'),
-  );
-  $links[] = array(
-    'title' => '@username',
-    'path' => 'user/%',
-    'weight' => -99,
-    'parent_path' => '<root>',
-    // 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' => '<root>',
-    '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("<strong>BACKUP YOUR DATABASE!</strong> 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  = '<div id="admin-menu">';
-    $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu'));
-    $content .= '</div>';
-
-    // 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'] = '<div id="admin-menu">';
+    $content['#suffix'] = '</div>';
+
+    // 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 .= '<li' . drupal_attributes($elements[$path]['#attributes']) . '>';
+
+    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 .= '</li>';
+  }
+  // @todo #attributes probably required for UL, but already used for LI.
+  return $output ? '<ul>' . $output . '</ul>' : '';
+}
+
+/**
  * 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);
 }
 
