Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.784
diff -u -p -r1.784 comment.module
--- modules/comment/comment.module	13 Oct 2009 05:17:15 -0000	1.784
+++ modules/comment/comment.module	14 Oct 2009 12:38:01 -0000
@@ -844,6 +844,7 @@ function comment_build_content($comment,
     '#markup' => check_markup($comment->comment, $comment->format, '', TRUE),
   );
 
+  field_attach_prepare_view('comment', array($comment->cid => $comment), $build_mode);
   $comment->content += field_attach_view('comment', $comment, $build_mode);
 
   if (empty($comment->in_preview)) {
@@ -937,6 +938,8 @@ function comment_links($comment, $node) 
  *   An array in the format expected by drupal_render().
  */
 function comment_build_multiple($comments, $node, $build_mode = 'full', $weight = 0) {
+  field_attach_prepare_view('comment', $comments, $build_mode);
+
   $build = array(
     '#sorted' => TRUE,
   );
Index: modules/field/field.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.api.php,v
retrieving revision 1.41
diff -u -p -r1.41 field.api.php
--- modules/field/field.api.php	13 Oct 2009 16:38:42 -0000	1.41
+++ modules/field/field.api.php	14 Oct 2009 12:38:01 -0000
@@ -826,6 +826,34 @@ function theme_field_formatter_FORMATTER
 }
 
 /**
+ * Allow formatters to load information for multiple objects.
+ *
+ * This should be used when a formatter needs to load additional information
+ * from the database in order to render a field, for example a reference field
+ * which displays properties of the referenced objects such as name or type.
+ *
+ * @param $obj_type
+ *   The type of $object.
+ * @param $objects
+ *   Array of objects being displayed, keyed by object id.
+ * @param $field
+ *   The field structure for the operation.
+ * @param $instances
+ *   Array of instance structures for $field for each object, keyed by object id.
+ * @param $langcode
+ *   The language the field values are to be shown in. If no language is
+ *   provided the current language is used.
+ * @param $items
+ *   Array of field values for the objects, keyed by object id.
+ * @return
+ *   Changes or additions to field values are done by altering the $items
+ *   parameter by reference.
+ */
+function hook_field_formatter_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $build_mode) {
+
+}
+
+/**
  * @} End of "ingroup field_type"
  */
 
@@ -842,6 +870,41 @@ function theme_field_formatter_FORMATTER
  * See field_attach_form() for details and arguments.
  */
 function hook_field_attach_form($obj_type, $object, &$form, &$form_state, $langcode) {
+  $tids = array();
+
+  // Collect every possible term attached to any of the fieldable entities.
+  foreach ($objects as $id => $object) {
+    foreach ($items[$id] as $delta => $item) {
+      // Force the array key to prevent duplicates.
+      $tids[$item['value']] = $item['value'];
+    }
+  }
+  if ($tids) {
+    $terms = array();
+
+    // Avoid calling taxonomy_term_load_multiple because it could lead to
+    // circular references.
+    $query = db_select('taxonomy_term_data', 't');
+    $query->fields('t');
+    $query->condition('t.tid', $tids, 'IN');
+    $query->addTag('term_access');
+    $terms = $query->execute()->fetchAllAssoc('tid');
+
+    // Iterate through the fieldable entities again to attach the loaded term data.
+    foreach ($objects as $id => $object) {
+      foreach ($items[$id] as $delta => $item) {
+        // Check whether the taxonomy term field instance value could be loaded.
+        if (isset($terms[$item['value']])) {
+          // Replace the instance value with the term data.
+          $items[$id][$delta]['taxonomy_term'] = $terms[$item['value']];
+        }
+        // Otherwise, unset the instance value, since the term does not exist.
+        else {
+          unset($items[$id][$delta]);
+        }
+      }
+    }
+  }
 }
 
 /**
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.52
diff -u -p -r1.52 field.attach.inc
--- modules/field/field.attach.inc	13 Oct 2009 16:38:43 -0000	1.52
+++ modules/field/field.attach.inc	14 Oct 2009 12:38:02 -0000
@@ -1110,6 +1110,13 @@ function field_attach_query_revisions($f
 }
 
 /**
+ * Allow formatters to act on fieldable objects prior to rendering.
+ */
+function field_attach_prepare_view($obj_type, $objects, $build_mode = 'full') {
+  _field_invoke_multiple_default('prepare_view', $obj_type, $objects, $build_mode);
+}
+
+/**
  * Generate and return a structured content array tree suitable for
  * drupal_render() for all of the fields on an object. The format of
  * each field's rendered content depends on the display formatter and
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.19
diff -u -p -r1.19 field.default.inc
--- modules/field/field.default.inc	9 Oct 2009 19:22:55 -0000	1.19
+++ modules/field/field.default.inc	14 Oct 2009 12:38:02 -0000
@@ -54,6 +54,30 @@ function field_default_insert($obj_type,
 }
 
 /**
+ * Invoke hook_field_formatter_prepare_view() on the relavant formatters.
+ */
+function field_default_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $options, $build_mode) {
+  // Group objects, instances and items by formatter module.
+  $modules = array();
+  foreach ($instances as $id => $instance) {
+    $module = $instance['display'][$build_mode]['module'];
+    $modules[] = $module;
+    $grouped_objects[$module][$id] = $objects[$id];
+    $grouped_instances[$module][$id] = $instance;
+    // hook_field_formatter_prepare_view() alters $items by reference.
+    $grouped_items[$module][$id] = &$items[$id];
+  }
+
+  foreach ($modules as $module) {
+    // Invoke hook_field_formatter_prepare_view().
+    $function = $module . '_field_formatter_prepare_view';
+    if (function_exists($function)) {
+      $function($obj_type, $grouped_objects[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $build_mode);
+    }
+  }
+}
+
+/**
  * Default field 'view' operation.
  *
  * @see field_attach_view()
Index: modules/field/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.test,v
retrieving revision 1.58
diff -u -p -r1.58 field.test
--- modules/field/field.test	2 Oct 2009 14:39:43 -0000	1.58
+++ modules/field/field.test	14 Oct 2009 12:38:03 -0000
@@ -733,18 +733,19 @@ class FieldAttachOtherTestCase extends F
   }
 
   /**
-   * Test field_attach_views() and field_attach_preprocess().
+   * Test field_attach_view() and field_atach_prepare_view().
    */
-  function testFieldAttachViewAndPreprocess() {
+  function testFieldAttachView() {
     $entity_type = 'test_entity';
-    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $entity_init = field_test_create_stub_entity();
     $langcode = FIELD_LANGUAGE_NONE;
 
     // Populate values to be displayed.
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $entity->{$this->field_name}[$langcode] = $values;
+    $entity_init->{$this->field_name}[$langcode] = $values;
 
     // Simple formatter, label displayed.
+    $entity = clone($entity_init);
     $formatter_setting = $this->randomName();
     $this->instance['display'] = array(
       'full' => array(
@@ -756,6 +757,7 @@ class FieldAttachOtherTestCase extends F
       ),
     );
     field_update_instance($this->instance);
+    field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
     $entity->content = field_attach_view($entity_type, $entity);
     $output = drupal_render($entity->content);
     $this->content = $output;
@@ -766,14 +768,17 @@ class FieldAttachOtherTestCase extends F
     }
 
     // Label hidden.
+    $entity = clone($entity_init);
     $this->instance['display']['full']['label'] = 'hidden';
     field_update_instance($this->instance);
+    field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
     $entity->content = field_attach_view($entity_type, $entity);
     $output = drupal_render($entity->content);
     $this->content = $output;
     $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
 
     // Field hidden.
+    $entity = clone($entity_init);
     $this->instance['display'] = array(
       'full' => array(
         'label' => 'above',
@@ -781,6 +786,7 @@ class FieldAttachOtherTestCase extends F
       ),
     );
     field_update_instance($this->instance);
+    field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
     $entity->content = field_attach_view($entity_type, $entity);
     $output = drupal_render($entity->content);
     $this->content = $output;
@@ -790,6 +796,7 @@ class FieldAttachOtherTestCase extends F
     }
 
     // Multiple formatter.
+    $entity = clone($entity_init);
     $formatter_setting = $this->randomName();
     $this->instance['display'] = array(
       'full' => array(
@@ -801,6 +808,7 @@ class FieldAttachOtherTestCase extends F
       ),
     );
     field_update_instance($this->instance);
+    field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
     $entity->content = field_attach_view($entity_type, $entity);
     $output = drupal_render($entity->content);
     $display = $formatter_setting;
@@ -810,6 +818,29 @@ class FieldAttachOtherTestCase extends F
     $this->content = $output;
     $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
 
+    // Test a formatter that uses hook_field_formatter_prepare_view()..
+    $entity = clone($entity_init);
+    $formatter_setting = $this->randomName();
+    $this->instance['display'] = array(
+      'full' => array(
+        'label' => 'above',
+        'type' => 'field_test_needs_additional_data',
+        'settings' => array(
+          'test_formatter_setting_additional' => $formatter_setting,
+        )
+      ),
+    );
+    field_update_instance($this->instance);
+    field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
+    $entity->content = field_attach_view($entity_type, $entity);
+    $output = drupal_render($entity->content);
+    $this->content = $output;
+    foreach ($values as $delta => $value) {
+      $this->content = $output;
+      $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1);
+      $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied.");
+    }
+
     // TODO:
     // - check display order with several fields
 
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1145
diff -u -p -r1.1145 node.module
--- modules/node/node.module	11 Oct 2009 03:07:18 -0000	1.1145
+++ modules/node/node.module	14 Oct 2009 12:38:04 -0000
@@ -1238,7 +1238,7 @@ function node_show($node, $message = FAL
   node_tag_new($node->nid);
 
   // For markup consistency with other pages, use node_build_multiple() rather than node_build().
-  return node_build_multiple(array($node), 'full');
+  return node_build_multiple(array($node->nid => $node), 'full');
 }
 
 /**
@@ -2031,6 +2031,7 @@ function node_feed($nids = FALSE, $chann
  *   An array in the format expected by drupal_render().
  */
 function node_build_multiple($nodes, $build_mode = 'teaser', $weight = 0) {
+  field_attach_prepare_view('node', $nodes, $build_mode);
   $build = array();
   foreach ($nodes as $node) {
     $build['nodes'][$node->nid] = node_build($node, $build_mode);
Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.87
diff -u -p -r1.87 node.pages.inc
--- modules/node/node.pages.inc	11 Oct 2009 03:07:18 -0000	1.87
+++ modules/node/node.pages.inc	14 Oct 2009 12:38:04 -0000
@@ -355,6 +355,8 @@ function node_preview($node) {
     }
 
     $node->changed = REQUEST_TIME;
+    $nodes = array($node->nid => $node);
+    field_attach_prepare_view('node', $nodes, 'full');
 
     // Display a preview of the node.
     // Previewing alters $node so it needs to be cloned.
Index: modules/simpletest/tests/field_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v
retrieving revision 1.28
diff -u -p -r1.28 field_test.module
--- modules/simpletest/tests/field_test.module	9 Oct 2009 01:00:03 -0000	1.28
+++ modules/simpletest/tests/field_test.module	14 Oct 2009 12:38:05 -0000
@@ -550,6 +550,13 @@ function field_test_field_formatter_info
         'multiple values' => FIELD_BEHAVIOR_CUSTOM,
       ),
     ),
+    'field_test_needs_additional_data' => array(
+      'label' => t('Tests hook_field_formatter_prepare_view()'),
+      'field types' => array('test_field'),
+      'settings' => array(
+        'test_formatter_setting_additional' => 'dummy test string',
+      ),
+    ),
   );
 }
 
@@ -564,10 +571,31 @@ function field_test_theme() {
     'field_formatter_field_test_multiple' => array(
       'arguments' => array('element' => NULL),
     ),
+    'field_formatter_field_test_needs_additional_data' => array(
+      'arguments' => array('element' => NULL),
+    ),
   );
 }
 
 /**
+ * Implement hook_field_formatter_prepare_view().
+ */
+function field_test_field_formatter_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $build_mode) {
+  foreach ($items as $id => $item) {
+    // To keep the test non-intrusive, only act on the
+    // 'field_test_needs_additional_data' formatter.
+    if ($instances[$id]['display'][$build_mode]['type'] == 'field_test_needs_additional_data') {
+      foreach ($item as $delta => $value) {
+        // Don't add anything on empty values.
+        if ($value) {
+          $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1;
+        }
+      }
+    }
+  }
+}
+
+/**
  * Theme function for 'field_test_default' formatter.
  */
 function theme_field_formatter_field_test_default($variables) {
@@ -596,6 +624,18 @@ function theme_field_formatter_field_tes
 }
 
 /**
+ * Theme function for 'field_test_needs_additional_data' formatter.
+ */
+function theme_field_formatter_field_test_needs_additional_data($variables) {
+  $element = $variables['element'];
+  $value = $element['#item']['value'];
+  $additional = $element['#item']['additional_formatter_value'];
+  $settings = $element['#settings'];
+
+  return $settings['test_formatter_setting_additional'] . '|' . $value . '|' . $additional;
+}
+
+/**
  * Sample function to test default value assignment.
  */
 function field_test_default_value($obj_type, $object, $field, $instance) {
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.516
diff -u -p -r1.516 taxonomy.module
--- modules/taxonomy/taxonomy.module	10 Oct 2009 18:42:52 -0000	1.516
+++ modules/taxonomy/taxonomy.module	14 Oct 2009 12:38:05 -0000
@@ -1259,7 +1259,7 @@ function taxonomy_allowed_values($field)
  * This preloads all taxonomy terms for multiple loaded objects at once and
  * unsets values for invalid terms that do not exist.
  */
-function taxonomy_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
+function taxonomy_field_formatter_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
   $tids = array();
 
   // Collect every possible term attached to any of the fieldable entities.
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.38
diff -u -p -r1.38 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	9 Oct 2009 01:00:06 -0000	1.38
+++ modules/taxonomy/taxonomy.pages.inc	14 Oct 2009 12:38:05 -0000
@@ -30,6 +30,7 @@ function taxonomy_term_page($term) {
   drupal_add_feed(url('taxonomy/term/' . $term->tid . '/feed'), 'RSS - ' . $term->name);
   drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
 
+  field_attach_prepare_view('taxonomy_term', array($term->tid => $term), 'full');
   $build = array();
   $build += field_attach_view('taxonomy_term', $term);
   if (!empty($term->description)) {
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.49
diff -u -p -r1.49 taxonomy.test
--- modules/taxonomy/taxonomy.test	11 Oct 2009 03:07:20 -0000	1.49
+++ modules/taxonomy/taxonomy.test	14 Oct 2009 12:38:05 -0000
@@ -776,6 +776,8 @@ class TaxonomyTermFieldTestCase extends 
 
     // Display the object.
     $entity = field_test_entity_load($id);
+    $entities = array($id => $entity);
+    field_attach_prepare_view($entity_type, $entities, 'full');
     $entity->content = field_attach_view($entity_type, $entity);
     $this->content = drupal_render($entity->content);
     $this->assertText($term->name, t('Term name is displayed'));
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1063
diff -u -p -r1.1063 user.module
--- modules/user/user.module	12 Oct 2009 18:37:30 -0000	1.1063
+++ modules/user/user.module	14 Oct 2009 12:38:06 -0000
@@ -2102,6 +2102,9 @@ function user_build($account) {
 function user_build_content($account) {
   $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);
 
