Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.358
diff -u -p -r1.358 menu.inc
--- includes/menu.inc	1 Nov 2009 23:02:13 -0000	1.358
+++ includes/menu.inc	5 Nov 2009 16:57:47 -0000
@@ -1796,16 +1796,26 @@ function menu_local_tasks($level = 0) {
  * If the path "node/123" is passed to this function, then it will return the
  * links for 'edit' and 'report-as-spam'.
  *
+ * @param $module
+ *   The name of the implementing module. This is used to prefix the key for
+ *   each contextual link, which is transformed into a CSS class during
+ *   rendering by theme_links(). For example, if $module is 'block' and the
+ *   retrieved local task path argument is 'edit', then the resulting CSS class
+ *   will be 'block-edit'.
  * @param $path
- *   The menu router path of the object to retrieve local tasks for, for example
- *   "node/123" or "admin/structure/menu/manage/[menu_name]".
+ *   The static menu router path of the object to retrieve local tasks for, for
+ *   example 'node' or 'admin/structure/block/manage'.
+ * @param $args
+ *   A list of of dynamic path arguments to append to $path to form the fully-
+ *   qualified menu router path, for example array(123) for a certain node or
+ *   array('system', 'navigation') for a certain block.
  *
  * @return
  *   A list of menu router items that are local tasks for the passed in path.
  *
  * @see system_preprocess()
  */
-function menu_contextual_links($parent_path, $args) {
+function menu_contextual_links($module, $parent_path, $args) {
   static $path_empty = array();
 
   $links = array();
@@ -1851,12 +1861,9 @@ function menu_contextual_links($parent_p
     if (!$item['access']) {
       continue;
     }
-    // All contextual links are keyed by the actual "task" path argument. The
-    // menu system does not allow for two local tasks with the same name, and
-    // since the key is also used as CSS class for the link item, which may be
-    // styled as icon, it wouldn't make sense to display the same icon for
-    // different tasks.
-    $links[$key] = $item;
+    // All contextual links are keyed by the actual "task" path argument,
+    // prefixed with the name of the implementing module.
+    $links[$module . '-' . $key] = $item;
   }
 
   // Allow modules to alter contextual links.
Index: includes/update.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/update.inc,v
retrieving revision 1.18
diff -u -p -r1.18 update.inc
--- includes/update.inc	24 Oct 2009 23:19:21 -0000	1.18
+++ includes/update.inc	5 Nov 2009 16:57:47 -0000
@@ -140,6 +140,8 @@ function update_fix_d7_requirements() {
       'not null' => TRUE,
       'default' => 0,
     ));
+    db_drop_index('menu_router', 'tab_parent');
+    db_add_index('menu_router', 'tab_parent', array(array('tab_parent', 64), 'weight', 'title'));
     db_add_field('menu_router', 'theme_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
     db_add_field('menu_router', 'theme_arguments', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
 
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.394
diff -u -p -r1.394 block.module
--- modules/block/block.module	23 Oct 2009 22:24:11 -0000	1.394
+++ modules/block/block.module	5 Nov 2009 16:57:47 -0000
@@ -283,7 +283,7 @@ function _block_get_renderable_array($li
 
     // Add contextual links for this block; skipping the system main block.
     if ($key != 'system_main') {
-      $build[$key]['#contextual_links']['block'] = menu_contextual_links('admin/structure/block/manage', array($block->module, $block->delta));
+      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta));
     }
 
     $build[$key] += array(
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.522
diff -u -p -r1.522 book.module
--- modules/book/book.module	3 Nov 2009 06:47:22 -0000	1.522
+++ modules/book/book.module	5 Nov 2009 16:58:52 -0000
@@ -1087,8 +1087,10 @@ function book_export_traverse($tree, $vi
  *   The HTML generated for the given node.
  */
 function book_node_export(stdClass $node, $children = '') {
-  node_build_content($node, 'print');
-  $node->rendered = drupal_render($node->content);
+  $build = node_build($node, 'print');
+  unset($build['#theme']);
+  // @todo Rendering should happen in the template using render().
+  $node->rendered = drupal_render($build);
 
   return theme('book_node_export_html', array('node' => $node, 'children' => $children));
 }
Index: modules/comment/comment.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.api.php,v
retrieving revision 1.12
diff -u -p -r1.12 comment.api.php
--- modules/comment/comment.api.php	10 Oct 2009 13:37:09 -0000	1.12
+++ modules/comment/comment.api.php	5 Nov 2009 16:57:47 -0000
@@ -73,6 +73,34 @@ function hook_comment_view($comment) {
 }
 
 /**
+ * The comment was built; the module may modify the structured content.
+ *
+ * This hook is called after the content has been assembled in a structured array
+ * and may be used for doing processing which requires that the complete comment
+ * content structure has been built.
+ *
+ * If the module wishes to act on the rendered HTML of the comment rather than the
+ * structured content array, it may use this hook to add a #post_render callback.
+ * Alternatively, it could also implement hook_preprocess_comment(). See
+ * drupal_render() and theme() documentation respectively for details.
+ *
+ * @param $build
+ *   A renderable array representing the comment.
+ *
+ * @see comment_build()
+ */
+function hook_comment_build_alter($build) {
+  // Check for the existence of a field added by another module.
+  if ($build['#build_mode'] == 'full' && isset($build['an_additional_field'])) {
+    // Change its weight.
+    $build['an_additional_field']['#weight'] = -10;
+  }
+
+  // Add a #post_render callback to act on the rendered HTML of the comment.
+  $build['#post_render'][] = 'my_module_comment_post_render';
+}
+
+/**
  * The comment is being published by the moderator.
  *
  * @param $comment
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.800
diff -u -p -r1.800 comment.module
--- modules/comment/comment.module	3 Nov 2009 06:47:22 -0000	1.800
+++ modules/comment/comment.module	5 Nov 2009 16:59:33 -0000
@@ -794,8 +794,6 @@ function comment_build($comment, stdClas
     '#node' => $node,
     '#build_mode' => $build_mode,
   );
-  // Add contextual links for this comment.
-  $build['#contextual_links']['comment'] = menu_contextual_links('comment', array($comment->cid));
 
   $prefix = '';
   $is_threaded = isset($comment->divs) && variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED;
@@ -819,6 +817,9 @@ function comment_build($comment, stdClas
     $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
   }
 
+  // Allow modules to modify the structured comment.
+  drupal_alter('comment_build', $build);
+
   return $build;
 }
 
@@ -844,6 +845,7 @@ function comment_build_content($comment,
     '#markup' => check_markup($comment->comment, $comment->format, '', TRUE),
   );
 
+  // Build fields content.
   field_attach_prepare_view('comment', array($comment->cid => $comment), $build_mode);
   $comment->content += field_attach_view('comment', $comment, $build_mode);
 
@@ -857,9 +859,6 @@ function comment_build_content($comment,
 
   // Allow modules to make their own additions to the comment.
   module_invoke_all('comment_view', $comment, $build_mode);
-
-  // Allow modules to modify the structured comment.
-  drupal_alter('comment_build', $comment, $build_mode);
 }
 
 /**
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.214
diff -u -p -r1.214 menu.module
--- modules/menu/menu.module	1 Nov 2009 12:11:10 -0000	1.214
+++ modules/menu/menu.module	5 Nov 2009 16:57:47 -0000
@@ -427,7 +427,7 @@ function menu_block_view($delta = '') {
   $data['content'] = menu_tree($delta);
   // Add contextual links for this block.
   if (!empty($data['content'])) {
-    $data['content']['#contextual_links']['menu'] = menu_contextual_links('admin/structure/menu/manage', array($delta));
+    $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($delta));
   }
   return $data;
 }
@@ -440,7 +440,7 @@ function menu_block_view_alter(&$data, $
   if ($block->module == 'system' && !empty($data['content'])) {
     $system_menus = menu_list_system_menus();
     if (isset($system_menus[$block->delta])) {
-      $data['content']['#contextual_links']['menu'] = menu_contextual_links('admin/structure/menu/manage', array($block->delta));
+      $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($block->delta));
     }
   }
 }
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.42
diff -u -p -r1.42 node.api.php
--- modules/node/node.api.php	1 Nov 2009 12:11:10 -0000	1.42
+++ modules/node/node.api.php	5 Nov 2009 16:57:47 -0000
@@ -540,9 +540,9 @@ function hook_node_view(stdClass $node, 
 }
 
 /**
- * The node content was built, the module may modify the structured content.
+ * The node content was built; the module may modify the structured content.
  *
- * This hook is called after the content has been assembled in $node->content
+ * This hook is called after the content has been assembled in a structured array
  * and may be used for doing processing which requires that the complete node
  * content structure has been built.
  *
@@ -551,20 +551,19 @@ function hook_node_view(stdClass $node, 
  * Alternatively, it could also implement hook_preprocess_node(). See
  * drupal_render() and theme() documentation respectively for details.
  *
- * @param $node
- *   The node the action is being performed on.
- * @param $build_mode
- *   The $build_mode parameter from node_build().
+ * @param $build
+ *   A renderable array representing the node content.
+ *
+ * @see node_build()
  */
-function hook_node_build_alter(stdClass $node, $build_mode) {
-  // Check for the existence of a field added by another module.
-  if (isset($node->content['an_additional_field'])) {
+function hook_node_build_alter($build) {
+  if ($build['#build_mode'] == 'full' && isset($build['an_additional_field'])) {
     // Change its weight.
-    $node->content['an_additional_field']['#weight'] = -10;
+    $build['an_additional_field']['#weight'] = -10;
   }
 
   // Add a #post_render callback to act on the rendered HTML of the node.
-  $node->content['#post_render'][] = 'my_module_node_post_render';
+  $build['#post_render'][] = 'my_module_node_post_render';
 }
 
 /**
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1163
diff -u -p -r1.1163 node.module
--- modules/node/node.module	5 Nov 2009 16:13:21 -0000	1.1163
+++ modules/node/node.module	5 Nov 2009 17:02:00 -0000
@@ -1148,7 +1148,11 @@ function node_build($node, $build_mode =
     '#build_mode' => $build_mode,
   );
   // Add contextual links for this node.
-  $build['#contextual_links']['node'] = menu_contextual_links('node', array($node->nid));
+  // @todo Make this configurable per build mode.
+  $build['#contextual_links']['node'] = array('node', array($node->nid));
+
+  // Allow modules to modify the structured node.
+  drupal_alter('node_build', $build);
 
   return $build;
 }
@@ -1177,7 +1181,6 @@ function node_build($node, $build_mode =
  *   A node object.
  * @param $build_mode
  *   Build mode, e.g. 'full', 'teaser'...
- *
  */
 function node_build_content(stdClass $node, $build_mode = 'full') {
   // Remove previously built content, if exists.
@@ -1190,6 +1193,9 @@ function node_build_content(stdClass $no
   }
 
   // Build fields content.
+  // @todo field_attach_prepare_view() is only invoked by node_build_multiple(),
+  //   all other entities invoke it _here_.
+  //field_attach_prepare_view('node', array($node->nid => $node), $build_mode);
   $node->content += field_attach_view('node', $node, $build_mode);
 
   // Always display a read more link on teasers because we have no way
@@ -1210,9 +1216,6 @@ function node_build_content(stdClass $no
 
   // Allow modules to make their own additions to the node.
   module_invoke_all('node_view', $node, $build_mode);
-
-  // Allow modules to modify the structured node.
-  drupal_alter('node_build', $node, $build_mode);
 }
 
 /**
@@ -1543,8 +1546,9 @@ function node_search_execute($keys = NUL
   foreach ($find as $item) {
     // Render the node.
     $node = node_load($item->sid);
-    node_build_content($node, 'search_result');
-    $node->rendered = drupal_render($node->content);
+    $build = node_build($node, 'search_result');
+    unset($build['#theme']);
+    $node->rendered = drupal_render($build);
 
     // Fetch comments for snippet.
     $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node);
@@ -2032,16 +2036,17 @@ function node_feed($nids = FALSE, $chann
 
     // The node gets built and modules add to or modify $node->rss_elements
     // and $node->rss_namespaces.
-    node_build_content($node, 'rss');
+    $build = node_build($node, 'rss');
+    unset($build['#theme']);
 
     if (!empty($node->rss_namespaces)) {
       $namespaces = array_merge($namespaces, $node->rss_namespaces);
     }
 
-    if ($item_length != 'title' && !empty($node->content)) {
+    if ($item_length != 'title') {
       // We render node contents and force links to be last.
-      $links = drupal_render($node->content['links']);
-      $item_text .= drupal_render($node->content) . $links;
+      $build['links']['#weight'] = 1000;
+      $item_text .= drupal_render($build);
     }
 
     $items .= format_rss_item($node->title[FIELD_LANGUAGE_NONE][0]['value'], $node->link, $item_text, $node->rss_elements);
@@ -2185,8 +2190,9 @@ function _node_index_node(stdClass $node
   variable_set('node_cron_last', $node->changed);
 
   // Render the node.
-  node_build_content($node, 'search_index');
-  $node->rendered = drupal_render($node->content);
+  $build = node_build($node, 'search_index');
+  unset($build['#theme']);
+  $node->rendered = drupal_render($build);
 
   $text = '<h1>' . check_plain($node->title[FIELD_LANGUAGE_NONE][0]['value']) . '</h1>' . $node->rendered;
 
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.415
diff -u -p -r1.415 system.install
--- modules/system/system.install	24 Oct 2009 23:19:21 -0000	1.415
+++ modules/system/system.install	5 Nov 2009 16:57:47 -0000
@@ -1097,7 +1097,7 @@ function system_schema() {
     ),
     'indexes' => array(
       'fit' => array('fit'),
-      'tab_parent' => array('tab_parent'),
+      'tab_parent' => array(array('tab_parent', 64), 'weight', 'title'),
       'tab_root_weight_title' => array(array('tab_root', 64), 'weight', 'title'),
     ),
     'primary key' => array('path'),
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.836
diff -u -p -r1.836 system.module
--- modules/system/system.module	3 Nov 2009 06:47:23 -0000	1.836
+++ modules/system/system.module	5 Nov 2009 16:57:47 -0000
@@ -217,6 +217,10 @@ function system_permission() {
       'title' => t('Access administration pages'),
       'description' => t('View the administration panel and browse the help system.'),
     ),
+    'access contextual links' => array(
+      'title' => t('Access contextual links'),
+      'description' => t('Use contextual links to perform actions related to elements on a page.'),
+    ),
     'access site in maintenance mode' => array(
       'title' => t('Access site in maintenance mode'),
       'description' => t('Log in when the site is in maintenance mode.'),
@@ -3516,17 +3520,24 @@ function theme_system_settings_form($var
 
 /**
  * Template variable preprocessor for contextual links.
+ *
+ * @see system_build_contextual_links()
  */
 function system_preprocess(&$variables, $hook) {
   static $hooks;
 
+  // Initialize the $contextual_links template variable.
+  $variables['contextual_links'] = array();
+
+  // Nothing to do here if the user is not permitted to access contextual links.
+  if (!user_access('access contextual links')) {
+    return;
+  }
+
   if (!isset($hooks)) {
     $hooks = theme_get_registry();
   }
 
-  // Initialize contextual links template variable.
-  $variables['contextual_links'] = array();
-
   // Determine the primary theme function argument.
   if (isset($hooks[$hook]['variables'])) {
     $keys = array_keys($hooks[$hook]['variables']);
@@ -3539,7 +3550,7 @@ function system_preprocess(&$variables, 
     $element = $variables[$key];
   }
 
-  if (isset($element) && is_array($element) && isset($element['#contextual_links'])) {
+  if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {
     $variables['contextual_links'] = system_build_contextual_links($element);
     if (!empty($variables['contextual_links'])) {
       $variables['classes_array'][] = 'contextual-links-region';
@@ -3551,21 +3562,32 @@ function system_preprocess(&$variables, 
  * Build a renderable array for contextual links.
  *
  * @param $element
- *   A renderable array containing a #contextual_links property.
+ *   A renderable array containing a #contextual_links property, which is a
+ *   keyed array. Each key is the name of the implementing module, and each
+ *   value is an array that forms the function arguments for
+ *   menu_contextual_links(). For example:
+ *   @code
+ *     array('#contextual_links' => array(
+ *       'block' => array('admin/structure/block/manage', array('system', 'navigation')),
+ *       'menu' => array('admin/structure/menu/manage', array('navigation')),
+ *     ))
+ *   @endcode
  *
  * @return
  *   A renderable array representing contextual links.
+ *
+ * @see menu_contextual_links()
  */
 function system_build_contextual_links($element) {
   static $destination;
 
-  // Transform contextual links into parameters suitable for theme_link().
-  $items = call_user_func_array('array_merge_recursive', $element['#contextual_links']);
-  $build = array();
-  if (empty($items)) {
-    return $build;
+  // Retrieve contextual menu links.
+  $items = array();
+  foreach ($element['#contextual_links'] as $module => $args) {
+    $items += menu_contextual_links($module, $args[0], $args[1]);
   }
 
+  // Transform contextual links into parameters suitable for theme_link().
   if (!isset($destination)) {
     $destination = drupal_get_destination();
   }
@@ -3584,6 +3606,7 @@ function system_build_contextual_links($
     $item['localized_options']['query'] += $destination;
     $links[$class] += $item['localized_options'];
   }
+  $build = array();
   if ($links) {
     $build = array(
       '#theme' => 'links',
Index: modules/user/user.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.api.php,v
retrieving revision 1.15
diff -u -p -r1.15 user.api.php
--- modules/user/user.api.php	1 Nov 2009 21:26:44 -0000	1.15
+++ modules/user/user.api.php	5 Nov 2009 16:57:47 -0000
@@ -314,8 +314,10 @@ function hook_user_logout($account) {
  *
  * @param $account
  *   The user object on which the operation is being performed.
+ * @param $build_mode
+ *   Build mode, e.g. 'full'.
  */
-function hook_user_view($account) {
+function hook_user_view($account, $build_mode) {
   if (user_access('create blog content', $account)) {
     $account->content['summary']['blog'] =  array(
       '#type' => 'user_profile_item',
@@ -327,6 +329,34 @@ function hook_user_view($account) {
 }
 
 /**
+ * The user was built; the module may modify the structured content.
+ *
+ * This hook is called after the content has been assembled in a structured array
+ * and may be used for doing processing which requires that the complete user
+ * content structure has been built.
+ *
+ * If the module wishes to act on the rendered HTML of the user rather than the
+ * structured content array, it may use this hook to add a #post_render callback.
+ * Alternatively, it could also implement hook_preprocess_user_profile(). See
+ * drupal_render() and theme() documentation respectively for details.
+ *
+ * @param $build
+ *   A renderable array representing the user.
+ *
+ * @see user_build()
+ */
+function hook_user_build_alter($build) {
+  // Check for the existence of a field added by another module.
+  if (isset($build['an_additional_field'])) {
+    // Change its weight.
+    $build['an_additional_field']['#weight'] = -10;
+  }
+
+  // Add a #post_render callback to act on the rendered HTML of the user.
+  $build['#post_render'][] = 'my_module_user_post_render';
+}
+
+/**
  * Inform other modules that a user role has been added.
  *
  * Modules implementing this hook can act on the user role object when saved to
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1075
diff -u -p -r1.1075 user.module
--- modules/user/user.module	1 Nov 2009 21:26:44 -0000	1.1075
+++ modules/user/user.module	5 Nov 2009 17:02:46 -0000
@@ -2084,13 +2084,15 @@ function _user_cancel($edit, $account, $
  *
  * @param $account
  *   A user object.
+ * @param $build_mode
+ *   Build mode, e.g. 'full'.
  *
  * @return
  *   An array as expected by drupal_render().
  */
-function user_build($account) {
+function user_build($account, $build_mode = 'full') {
   // Retrieve all profile fields and attach to $account->content.
-  user_build_content($account);
+  user_build_content($account, $build_mode);
 
   $build = $account->content;
   // We don't need duplicate rendering info in account->content.
@@ -2099,8 +2101,12 @@ function user_build($account) {
   $build += array(
     '#theme' => 'user_profile',
     '#account' => $account,
+    '#build_mode' => $build_mode,
   );
 
+  // Allow modules to modify the structured user.
+  drupal_alter('user_build', $build);
+
   return $build;
 }
 
@@ -2109,19 +2115,19 @@ function user_build($account) {
  *
  * @param $account
  *   A user object.
- *
+ * @param $build_mode
+ *   Build mode, e.g. 'full'.
  */
-function user_build_content($account) {
+function user_build_content($account, $build_mode = 'full') {
+  // Remove previously built content, if exists.
   $account->content = array();
 
-  $accounts = array($account->uid, $account);
-  field_attach_prepare_view('user', $accounts, 'full');
-
   // Build fields content.
-  $account->content += field_attach_view('user', $account);
+  field_attach_prepare_view('user', array($account->uid, $account), $build_mode);
+  $account->content += field_attach_view('user', $account, $build_mode);
 
   // Populate $account->content with a render() array.
-  module_invoke_all('user_view', $account);
+  module_invoke_all('user_view', $account, $build_mode);
 }
 
 /**
