Index: modules/field/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.test,v
retrieving revision 1.36
diff -u -r1.36 field.test
--- modules/field/field.test	2 Aug 2009 11:24:21 -0000	1.36
+++ modules/field/field.test	2 Aug 2009 22:57:22 -0000
@@ -448,15 +448,19 @@
     $this->assertEqual($result, $expected, t('FIELD_QUERY_RETURN_IDS result format returns the expect result'));
   }
 
-  function testFieldAttachViewAndPreprocess() {
+  /**
+   * Test field_attach_view() and field_atach_prepare_view().
+   */
+  function testFieldAttachView() {
     $entity_type = 'test_entity';
-    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $entity_init = field_test_create_stub_entity();
 
     // Populate values to be displayed.
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $entity->{$this->field_name} = $values;
+    $entity_init->{$this->field_name} = $values;
 
     // Simple formatter, label displayed.
+    $entity = clone($entity_init);
     $formatter_setting = $this->randomName();
     $this->instance['display'] = array(
       'full' => array(
@@ -468,6 +472,7 @@
       ),
     );
     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;
@@ -478,14 +483,17 @@
     }
 
     // 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',
@@ -493,6 +501,7 @@
       ),
     );
     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;
@@ -502,6 +511,7 @@
     }
 
     // Multiple formatter.
+    $entity = clone($entity_init);
     $formatter_setting = $this->randomName();
     $this->instance['display'] = array(
       'full' => array(
@@ -513,6 +523,7 @@
       ),
     );
     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;
@@ -522,6 +533,29 @@
     $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/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.33
diff -u -r1.33 field.attach.inc
--- modules/field/field.attach.inc	16 Jul 2009 10:30:12 -0000	1.33
+++ modules/field/field.attach.inc	2 Aug 2009 22:57:21 -0000
@@ -866,6 +866,13 @@
 }
 
 /**
+ * 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.12
diff -u -r1.12 field.default.inc
--- modules/field/field.default.inc	2 Aug 2009 11:24:21 -0000	1.12
+++ modules/field/field.default.inc	2 Aug 2009 22:57:21 -0000
@@ -52,6 +52,35 @@
 }
 
 /**
+ * Invoke hook_field_formatter_prepare_view() on the relavant formatters.
+ */
+function field_default_prepare_view($obj_type, $objects, $field, $instances, &$items, $build_mode) {
+  // Group objects, instances and items by formatter module.
+  $modules = array();
+  foreach ($instances as $id => $instance) {
+    // Determine the right formatter to use for the build mode.
+    $display = isset($instance['display'][$build_mode]) ? $instance['display'][$build_mode] : $instance['display']['full'];
+    // Back up to the default formatter if needed.
+    $instance['display'][$build_mode] = _field_get_formatter($display, $field);;
+
+    $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 (drupal_function_exists($function)) {
+      $function($obj_type, $grouped_objects[$module], $field, $grouped_instances[$module], $grouped_items[$module], $build_mode);
+    }
+  }
+}
+
+/**
  * The 'view' operation constructs the $object in a way that you can use
  * drupal_render() to display the formatted output for an individual field.
  * i.e. print drupal_render($object->content['field_foo']);
Index: modules/field/field.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.api.php,v
retrieving revision 1.24
diff -u -r1.24 field.api.php
--- modules/field/field.api.php	1 Aug 2009 06:03:12 -0000	1.24
+++ modules/field/field.api.php	2 Aug 2009 22:57:20 -0000
@@ -811,6 +811,30 @@
 }
 
 /**
+ * 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 $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, &$items, $build_mode) {
+}
+
+/**
  * @} End of "ingroup field_type"
  */
 
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.31
diff -u -r1.31 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	8 Jul 2009 07:18:08 -0000	1.31
+++ modules/taxonomy/taxonomy.pages.inc	2 Aug 2009 22:57:23 -0000
@@ -30,6 +30,7 @@
   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)) {
@@ -109,8 +110,8 @@
       ->condition('t.vid', $vid)
       // Select rows that either match by term or synonym name.
       ->condition(db_or()
-	    ->where("LOWER(t.name) LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
-	    ->where("LOWER(ts.name) LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
+      ->where("LOWER(t.name) LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
+      ->where("LOWER(ts.name) LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
       )
       ->range(0, 10)
       ->execute()
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1090
diff -u -r1.1090 node.module
--- modules/node/node.module	31 Jul 2009 19:01:02 -0000	1.1090
+++ modules/node/node.module	2 Aug 2009 22:57:23 -0000
@@ -1185,7 +1185,7 @@
   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');
 }
 
 /**
@@ -1958,6 +1958,7 @@
  *   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/simpletest/tests/field_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v
retrieving revision 1.13
diff -u -r1.13 field_test.module
--- modules/simpletest/tests/field_test.module	10 Jul 2009 05:58:13 -0000	1.13
+++ modules/simpletest/tests/field_test.module	2 Aug 2009 22:57:23 -0000
@@ -518,12 +518,9 @@
       'settings' => array(
         'test_formatter_setting' => 'dummy test string',
       ),
-      'behaviors' => array(
-        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
-      ),
     ),
     'field_test_multiple' => array(
-      'label' => t('Default'),
+      'label' => t('Tests multiple value display'),
       'field types' => array('test_field'),
       'settings' => array(
         'test_formatter_setting_multiple' => 'dummy test string',
@@ -532,6 +529,13 @@
         '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',
+      ),
+    ),
   );
 }
 
@@ -546,10 +550,31 @@
     '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, &$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($element) {
@@ -574,6 +599,17 @@
 }
 
 /**
+ * Theme function for 'field_test_needs_additional_data' formatter.
+ */
+function theme_field_formatter_field_test_needs_additional_data($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/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1015
diff -u -r1.1015 user.module
--- modules/user/user.module	31 Jul 2009 19:01:03 -0000	1.1015
+++ modules/user/user.module	2 Aug 2009 22:57:25 -0000
@@ -1997,11 +1997,10 @@
  *
  * @param $account
  *   A user object.
- *
- * @return
- *   A structured array containing the individual elements of the profile.
  */
-function user_build_content(&$account) {
+function user_build_content($account) {
+  $accounts = array($account->uid, $account);
+  field_attach_prepare_view('user', $accounts, 'full');
   $edit = NULL;
   $account->content = array();
 
@@ -2013,8 +2012,6 @@
 
   // Allow modules to modify the fully-built profile.
   drupal_alter('profile', $account);
-
-  return $account->content;
 }
 
 /**
