Index: admin_menu.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.api.php,v
retrieving revision 1.3
diff -u -p -r1.3 admin_menu.api.php
--- admin_menu.api.php	22 Aug 2009 15:31:11 -0000	1.3
+++ admin_menu.api.php	12 Nov 2009 04:12:44 -0000
@@ -7,6 +7,44 @@
  */
 
 /**
+ * Provide expansion arguments for dynamic menu items, i.e. menu paths
+ * containing one or more %placeholders.
+ *
+ * The map items must be keyed by the dynamic path to expand. Each map item may
+ * have the following properties:
+ *
+ * - "parent": Required. The parent menu path to link the expanded items to.
+ * - "hide": Optional. Used to hide another menu item, usually a superfluous
+ *   "List" item.
+ * - "arguments": Requires. An array of argument sets that will be used in the
+ *   expansion. Each set consists of an array of one or more placeholders with
+ *   an array of possible expansions as value. Upon expansion, each argument
+ *   is combined with every other argument from the set (ie., the cartesian
+ *   product of all arguments is created). The expansion values may be empty,
+ *   that is, you don't need to insert logic to skip map items for which no
+ *   values exist, since admin menu will take care of that.
+ *
+ * admin_menu_admin_menu_expand_map() provides an example of a map item that
+ * uses more than argument set: for each content type a list of type specific
+ * fields needs to be generated as secondary argument.
+ */
+function hook_admin_menu_expand_map() {
+  // Expand content types in Structure >> Content types.
+  // The key denotes the dynamic path to expand to multiple menu items.
+  $map['admin/structure/types/manage/%node_type'] = array(
+    // Link generated items directly to the "Content types" item, and hide the
+    // "List" item.
+    'parent' => 'admin/structure/types',
+    'hide' => 'admin/structure/types/list',
+    // Create expansion arguments for the %node_type placeholder.
+    'arguments' => array(
+      array('%node_type' => array_keys(node_type_get_types()))
+    ),
+  );
+  return $map;
+}
+
+/**
  * Alter content in Administration menu bar before it is rendered.
  *
  * @param $content
Index: admin_menu.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.inc,v
retrieving revision 1.68
diff -u -p -r1.68 admin_menu.inc
--- admin_menu.inc	4 Nov 2009 21:51:19 -0000	1.68
+++ admin_menu.inc	12 Nov 2009 05:29:08 -0000
@@ -7,6 +7,328 @@
  */
 
 /**
+ * Build the full admin menu tree from static and expanded dynamic items.
+ *
+ * @param $menu_name
+ *   Menu name to use as base for the tree.
+ */
+function admin_menu_tree($menu_name) {
+  // Get placeholder expansion arguments from hook_admin_menu_expand_map()
+  // implementations.
+  module_load_include('inc', 'admin_menu', 'admin_menu.map');
+  $expand_map = module_invoke_all('admin_menu_expand_map');
+  drupal_alter('admin_menu_expand_map', $expand_map);
+
+  $new_map = array();
+  $hidden = array();
+  foreach ($expand_map as $path => $data) {
+    // Convert named placeholders, since the menu system stores paths using
+    // anonymous placeholders.
+    $replacements = array_fill_keys(array_keys($data['arguments'][0]), '%');
+    $data['parent'] = strtr($data['parent'], $replacements);
+    $new_map[strtr($path, $replacements)] = $data;
+
+    // Collect paths to hide.
+    if (isset($data['hide'])) {
+      $hidden[strtr($data['hide'], $replacements)] = 1;
+    }
+  }
+  $expand_map = $new_map;
+  unset($new_map);
+
+  // Get static and dynamic local tasks.
+  $local_tasks = admin_menu_local_tasks($menu_name, $expand_map);
+
+  // Merge local tasks with static menu tree.
+  $tree = menu_tree_all_data($menu_name);
+  admin_menu_merge_tree($tree, $local_tasks, array(), $hidden);
+
+  return $tree;
+}
+
+/**
+ * Walk through the entire menu tree and merge in local tasks.
+ *
+ * @param &$tree
+ *   A menu tree structure as returned by menu_tree_all_data().
+ * @param $local_tasks
+ *   A local tasks structure as resturned by admin_menu_local_tasks().
+ * @param $expand_map
+ *   Placeholder expansion arguments.
+ * @see menu_tree_all_data()
+ * @see admin_menu_local_tasks()
+ */
+function admin_menu_merge_tree(array &$tree, array $local_tasks, array $expand_map = array(), array $hidden = array()) {
+  foreach ($tree as $key => $data) {
+    $path = $data['link']['router_path'];
+
+    if ($tree[$key]['below']) {
+      // Recurse into static submenu tree items.
+      admin_menu_merge_tree($tree[$key]['below'], $local_tasks, $expand_map, $hidden);
+    }
+    elseif (isset($hidden[$path]) && !isset($local_tasks[$path])) {
+      // Hide item if it has no local tasks. This is done to prevent hiding
+      // items that have local tasks from different modules assigned, and only
+      // one is requesting to hide the item.
+      $tree[$key]['link']['access'] = FALSE;
+      continue;
+    }
+
+    // Check if there is a local task that has this item's path as parent.
+    if (isset($local_tasks[$path])) {
+      foreach ($local_tasks[$path] as $router_item) {
+        // If the local task has custom placeholder expansion arguments set,
+        // merge them with parent arguments.
+        if (isset($router_item['expand_map'])) {
+          $expand_map = array_merge($expand_map, $router_item['expand_map']);
+        }
+
+        // Set up path arguments map, depends on whether the item is dynamic
+        // (contains placeholders) or not.
+        if (strpos($router_item['path'], '%') === FALSE) {
+          // Build static item and subtree.
+          $map = explode('/', $router_item['path']);
+          $item = admin_menu_translate($router_item, $map);
+          admin_menu_merge_tree($item, $local_tasks, $expand_map, $hidden);
+          $tree[$key]['below'] += $item;
+        }
+        else {
+          // Drop dynamic items without expansion parameters.
+          if (empty($expand_map)) {
+            continue;
+          }
+
+          // Expand anonymous to named placeholders.
+          // @see _menu_load_objects()
+          // @todo explode() => implode() => explode() required for strtr()
+          $active = '';
+          $path = explode('/', $router_item['path']);
+          $load_functions = unserialize($router_item['load_functions']);
+          foreach ($load_functions as $index => $function) {
+            if ($function) {
+              if (is_array($function)) {
+                list($function,) = each($function);
+              }
+              // Add the loader function name minus "_load".
+              $placeholder = '%' . substr($function, 0, -5);
+              $path[$index] = $placeholder;
+            }
+          }
+          $path = implode('/', $path);
+
+          // Create new menu items using expansion arguments.
+          foreach ($expand_map as $arguments) {
+            // Create the cartesian product for all arguments and create new
+            // menu items for each generated combination thereof.
+            foreach (admin_menu_expand_args($arguments) as $replacements) {
+              $map = explode('/', strtr($path, $replacements));
+              $item = admin_menu_translate($router_item, $map);
+              // Build subtree using current replacement arguments.
+              // @todo Avoid rebuilding this for each item.
+              $current_map = array();
+              foreach ($replacements as $placeholder => $value) {
+                $current_map[$placeholder] = array($value);
+              }
+              admin_menu_merge_tree($item, $local_tasks, array($current_map), $hidden);
+              $tree[$key]['below'] += $item;
+            }
+          }
+        }
+      }
+      // Sort new subtree items.
+      ksort($tree[$key]['below']);
+    }
+  }
+}
+
+/**
+ * Create a menu item suitable for rendering.
+ *
+ * @param $router_item
+ *   A menu router item.
+ * @param $map
+ *   A path map with placeholders replaced.
+ */
+function admin_menu_translate($router_item, $map) {
+  _menu_translate($router_item, $map, TRUE);
+
+  // Run through hook_translated_menu_link_alter() to add devel information,
+  // if configured.
+  $router_item['menu_name'] = 'management';
+  admin_menu_translated_menu_link_alter($router_item, NULL);
+
+  if ($router_item['access']) {
+    // Turn local task menu callbacks into regular menu items, otherwise they
+    // won't be visible.
+    if ($router_item['type'] == MENU_CALLBACK) {
+      $router_item['type'] = MENU_NORMAL_ITEM;
+    }
+    // Fill in pseudo menu link values for rendering later.
+    $router_item['router_path'] = $router_item['path'];
+    $router_item['mlid'] = $router_item['href'];
+    // @todo Strip potential HTML from titles?
+    $router_item['title'] = strip_tags($router_item['title']);
+    return array((50000 + $router_item['weight']) . ' ' . $router_item['title'] . ' ' . $router_item['mlid'] => array(
+      'link' => $router_item,
+      'below' => array(),
+    ));
+  }
+
+  return array();
+}
+
+/**
+ * Create the cartesian product of multiple varying sized argument arrays.
+ *
+ * @param $arguments
+ *   A two dimensional array of arguments.
+ * @see hook_admin_menu_expand_map()
+ */
+function admin_menu_expand_args($arguments) {
+  $replacements = array();
+
+  // Initialize line cursors, move out array keys (placeholders) and assign
+  // numeric keys instead.
+  $i = 0;
+  $placeholders = array();
+  $new_arguments = array();
+  foreach ($arguments as $placeholder => $values) {
+    // Skip empty arguments.
+    if (empty($values)) {
+      continue;
+    }
+    $cursor[$i] = 0;
+    $placeholders[$i] = $placeholder;
+    $new_arguments[$i] = $values;
+    $i++;
+  }
+  $arguments = $new_arguments;
+  unset($new_arguments);
+
+  if ($rows = count($arguments)) {
+    do {
+      // Collect current argument from each row.
+      $row = array();
+      for ($i = 0; $i < $rows; ++$i) {
+        $row[$placeholders[$i]] = $arguments[$i][$cursor[$i]];
+      }
+      $replacements[] = $row;
+
+      // Increment cursor position.
+      $j = $rows - 1;
+      $cursor[$j]++;
+      while (!array_key_exists($cursor[$j], $arguments[$j])) {
+        // No more arguments left: reset cursor, go to next line and increment
+        // that cursor instead. Repeat until argument found or out of rows.
+        $cursor[$j] = 0;
+        if (--$j < 0) {
+          // We're done.
+          break 2;
+        }
+        $cursor[$j]++;
+      }
+    } while (1);
+  }
+
+  return $replacements;
+}
+
+/**
+ * Load all local tasks for a given menu name.
+ *
+ * @param $menu_name
+ *   A menu name to load local tasks for.
+ * @param $expand_map
+ *   Placeholder expansion arguments.
+ * @see hook_admin_menu_expand_map()
+ */
+function admin_menu_local_tasks($menu_name, array $expand_map) {
+  $local_tasks = array();
+
+  // Fetch all root level items.
+  $parents = db_select('menu_links')
+    ->fields('menu_links', array('router_path'))
+    ->condition('menu_name', $menu_name)
+    ->condition('depth', 1)
+    ->condition('hidden', 0)
+    ->condition('external', 0)
+    ->execute()->fetchAll(PDO::FETCH_ASSOC);
+
+  if ($parents) {
+    foreach ($parents as $parent) {
+      // Fetch all local tasks that are chil elements of the parent item.
+      $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC))
+        ->fields('menu_router')
+        ->where("LEFT(tab_root, " . (strlen($parent['router_path']) + 1) . ") = :router_path", array(':router_path' => $parent['router_path'] . '/'))
+        ->condition('type', MENU_LOCAL_TASK | MENU_LOCAL_ACTION, '&')
+        ->condition('context', MENU_CONTEXT_INLINE, '<>')
+        ->orderBy('fit')
+        ->execute();
+
+      foreach ($result as $local_task) {
+        // Store local tasks grouped by parent path for later merging.
+        $parent_path = $local_task['tab_parent'];
+        // Load any missing parent menu items.
+        admin_menu_build_ancestry($local_tasks, $parent_path, $expand_map);
+        // Assign placeholder expansion arguments.
+        if (isset($expand_map[$local_task['path']])) {
+          $local_task['expand_map'] = $expand_map[$local_task['path']]['arguments'];
+        }
+        $local_tasks[$parent_path][] = $local_task;
+      }
+    }
+  }
+
+  return $local_tasks;
+}
+
+/**
+ * Add missing dynamic parent menu items to complete the menu tree ancestry.
+ *
+ * @param &$local_tasks
+ *   Static and dynamic local tasks.
+ * @param $path
+ *   Router path to validate.
+ * @param $expand_map
+ *   Placeholder expansion arguments.
+ * @see admin_menu_local_tasks()
+ * @see hook_admin_menu_expand_map()
+ */
+function admin_menu_build_ancestry(array &$local_tasks, $path, array $expand_map) {
+  if (!isset($local_tasks[$path])) {
+    $local_tasks[$path] = array();
+
+    // Add the parent items if they are dynamic and therefore missing in the
+    // tree structure built by menu_tree_all_data().
+    if (isset($expand_map[$path]) && ($router_item = admin_menu_get_item($path))) {
+      $parent_path = $expand_map[$path]['parent'];
+      if (!isset($local_tasks[$parent_path])) {
+        // Enter recursion to create the parent's parent item if it is also
+        // missing.
+        admin_menu_build_ancestry($local_tasks, $parent_path, $expand_map);
+      }
+      // Assign placeholder expansion arguments.
+      $router_item['expand_map'] = $expand_map[$path]['arguments'];
+      $local_tasks[$parent_path][] = $router_item;
+    }
+  }
+}
+
+/**
+ * Load a single menu router item.
+ *
+ * @see menu_get_item()
+ */
+function admin_menu_get_item($path) {
+  return db_select('menu_router')
+    ->fields('menu_router')
+    ->condition('path', $path)
+    ->range(0, 1)
+    ->addTag('menu_get_item')
+    ->execute()->fetchAssoc();
+}
+
+/**
  * Build the administration menu as renderable menu links.
  *
  * @param $tree
@@ -22,8 +344,8 @@
 function admin_menu_links_menu($tree) {
   $links = array();
   foreach ($tree as $data) {
-    // Skip menu callbacks (mostly dynamic items).
-    if (isset($data['link']['type']) && $data['link']['type'] == MENU_CALLBACK) {
+    // Skip invisible items.
+    if (!$data['link']['access'] || $data['link']['type'] == MENU_CALLBACK) {
       continue;
     }
     // Hide 'Administer' and make child links appear on this level.
Index: admin_menu.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.install,v
retrieving revision 1.22
diff -u -p -r1.22 admin_menu.install
--- admin_menu.install	9 Sep 2009 22:51:55 -0000	1.22
+++ admin_menu.install	12 Nov 2009 04:44:35 -0000
@@ -93,3 +93,16 @@ function admin_menu_update_7302() {
   return $ret;
 }
 
+/**
+ * Remove local tasks from {menu_links} table.
+ */
+function admin_menu_update_7303() {
+  $paths = db_query('SELECT path FROM {menu_router} WHERE path LIKE :prefix AND type & :type', array(
+    ':prefix' => 'admin/%',
+    ':type' => MENU_IS_LOCAL_TASK,
+  ))->fetchCol();
+  db_delete('menu_links')
+    ->condition('router_path', $paths, 'IN')
+    ->execute();
+}
+
Index: admin_menu.map.inc
===================================================================
RCS file: admin_menu.map.inc
diff -N admin_menu.map.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ admin_menu.map.inc	12 Nov 2009 05:28:04 -0000
@@ -0,0 +1,108 @@
+<?php
+// $Id: admin_menu.inc,v 1.68 2009/11/04 21:51:19 sun Exp $
+
+/**
+ * @file
+ * Implements hook_admin_menu_expand_map() on behalf of other modules.
+ */
+
+/**
+ * Implements hook_admin_menu_expand_map() on behalf of Filter module.
+ */
+function filter_admin_menu_expand_map() {
+  // Configuration >> Content authoring >> Text formats
+  $map['admin/config/content/formats/%filter_format'] = array(
+    'parent' => 'admin/config/content/formats',
+    'hide' => 'admin/config/content/formats/list',
+    'arguments' => array(
+      array('%filter_format' => array_keys(filter_formats())),
+    ),
+  );
+  return $map;
+}
+
+/**
+ * Implements hook_admin_menu_expand_map() on behalf of Menu module.
+ */
+function menu_admin_menu_expand_map() {
+  // Structure >> Menus
+  $map['admin/structure/menu/manage/%menu'] = array(
+    'parent' => 'admin/structure/menu',
+    'hide' => 'admin/structure/menu/list',
+    'arguments' => array(
+      array('%menu' => array_keys(menu_get_menus())),
+    ),
+  );
+  return $map;
+}
+
+/**
+ * Implements hook_admin_menu_expand_map() on behalf of Node module.
+ */
+function node_admin_menu_expand_map() {
+  // Structure >> Content types
+  $map['admin/structure/types/manage/%node_type'] = array(
+    'parent' => 'admin/structure/types',
+    'hide' => 'admin/structure/types/list',
+    'arguments' => array(
+      array('%node_type' => array_keys(node_type_get_types())),
+    ),
+  );
+  return $map;
+}
+
+/**
+ * Implements hook_admin_menu_expand_map() on behalf of Field UI module.
+ */
+function field_ui_admin_menu_expand_map() {
+  foreach (entity_get_info() as $obj_type => $info) {
+    foreach ($info['bundles'] as $bundle_name => $bundle_info) {
+      if (isset($bundle_info['admin'])) {
+        switch ($obj_type) {
+          case 'node':
+            // Provide replacement sets for content types, note that the
+            // fields (%field_ui_menu) are different for each content type.
+            $arguments = array();
+            foreach (array_keys($info['bundles']) as $node_type) {
+              $fields = array();
+              foreach (field_info_instances($obj_type, $node_type) as $field) {
+                $fields[] = $field['field_name'];
+              }
+              $arguments[] = array(
+                '%node_type' => array($node_type),
+                '%field_ui_menu' => $fields,
+              );
+            }
+            break;
+
+          case 'user':
+            $arguments = array(array('%field_ui_menu' => array_keys(field_info_fields('user'))));
+            break;
+        }
+        $path = $bundle_info['admin']['path'];
+        $map["$path/fields/%field_ui_menu"] = array(
+          'parent' => "$path/fields",
+          'arguments' => $arguments,
+        );
+      }
+    }
+  }
+  return $map;
+}
+
+/**
+ * Implements hook_admin_menu_expand_map() on behalf of Views UI module.
+ */
+function views_ui_admin_menu_expand_map() {
+  // Structure >> Views
+  // @todo Requires patch to views_ui.
+  $map['admin/structure/views/edit/%views_ui_cache'] = array(
+    'parent' => 'admin/structure/views',
+    'hide' => 'admin/structure/views/list',
+    'arguments' => array(
+      array('%views_ui_cache' => array_keys(views_get_all_views())),
+    ),
+  );
+  return $map;
+}
+
Index: admin_menu.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.module,v
retrieving revision 1.104
diff -u -p -r1.104 admin_menu.module
--- admin_menu.module	4 Nov 2009 21:51:19 -0000	1.104
+++ admin_menu.module	12 Nov 2009 04:18:46 -0000
@@ -106,75 +106,11 @@ function admin_menu_menu() {
  * Implementation of hook_menu_alter().
  */
 function admin_menu_menu_alter(&$items) {
-  foreach ($items as $path => $item) {
-    if (!strncmp($path, 'admin/', 6) && isset($item['type'])) {
-      // To avoid namespace clashes, content-type menu items are registered
-      // on a separate path. Copy these items with the proper router path and
-      // make them visible.
-      // @todo Is there another possibility to re-assign these items to their
-      // true parent (admin/structure/types) during menu rebuild to avoid the
-      // (possibly) clashing copying?
-      if (strpos($path, 'admin/structure/node-type/') === 0) {
-        // Copy item with fixed router path.
-        $newpath = str_replace('/node-type/', '/types/', $path);
-        $items[$newpath] = $item;
-        // Use new item from here, but leave old intact to play nice with
-        // others.
-        $path = $newpath;
-        // Turn MENU_CALLBACK items into visible menu items.
-        if ($item['type'] == MENU_CALLBACK) {
-          $items[$path]['type'] = MENU_NORMAL_ITEM;
-        }
-      }
-      // Trick local tasks and actions into the visible menu tree.
-      if ($item['type'] & MENU_IS_LOCAL_TASK) {
-        $items[$path]['_visible'] = TRUE;
-      }
-    }
-  }
-
-  // Remove local tasks on 'admin'.
-  $items['admin/by-task']['_visible'] = FALSE;
-  $items['admin/by-module']['_visible'] = FALSE;
-  // Move 'Add new content' to the start of the menu.
-  $items['node/add']['weight'] = -15;
-
   // Flush client-side caches whenever the menu is rebuilt.
   admin_menu_flush_caches();
 }
 
 /**
- * Copy a menu router item to another location.
- *
- * Adjusts numeric path argument references for various menu router item
- * properties.
- *
- * @param $item
- *   The existing menu router item.
- * @param $old_path
- *   The existing path of $item, f.e. 'node/add'.
- * @param $new_path
- *   The path to copy the menu router item to, f.e. 'admin/node/add'.
- * @return
- *   The menu router item with adjusted path argument references.
- */
-function admin_menu_copy_item($item, $old_path, $new_path) {
-  $offset = count(explode('/', $new_path)) - count(explode('/', $old_path));
-  if ($offset != 0) {
-    foreach (array('page arguments', 'access arguments', 'load arguments', 'title arguments') as $args) {
-      if (!empty($item[$args])) {
-        foreach ($item[$args] as $key => $value) {
-          if (is_numeric($value)) {
-            $item[$args][$key] = $value + $offset;
-          }
-        }
-      }
-    }
-  }
-  return $item;
-}
-
-/**
  * Implementation of hook_init().
  *
  * We can't move this into admin_menu_footer(), because PHP-only based themes
@@ -440,7 +376,7 @@ function admin_menu_output() {
     module_load_include('inc', 'admin_menu');
 
     // Add administration menu.
-    $content['menu'] = admin_menu_links_menu(menu_tree_all_data('management'));
+    $content['menu'] = admin_menu_links_menu(admin_menu_tree('management'));
     $content['menu']['#theme'] = 'admin_menu_links';
     // Ensure the menu tree is rendered between the icon and user links.
     $content['menu']['#weight'] = 0;
