### Eclipse Workspace Patch 1.0
#P drupal_test_7
Index: modules/field/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.test,v
retrieving revision 1.30
diff -u -r1.30 field.test
--- modules/field/field.test	7 Jul 2009 09:28:07 -0000	1.30
+++ modules/field/field.test	10 Jul 2009 12:51:06 -0000
@@ -400,15 +400,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(
@@ -420,6 +424,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;
@@ -430,14 +435,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',
@@ -445,6 +453,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;
@@ -454,6 +463,7 @@
     }
 
     // Multiple formatter.
+    $entity = clone($entity_init);
     $formatter_setting = $this->randomName();
     $this->instance['display'] = array(
       'full' => array(
@@ -465,6 +475,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;
@@ -474,6 +485,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.info.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v
retrieving revision 1.8
diff -u -r1.8 field.info.inc
--- modules/field/field.info.inc	10 Jul 2009 05:58:13 -0000	1.8
+++ modules/field/field.info.inc	10 Jul 2009 12:51:05 -0000
@@ -30,6 +30,7 @@
       $field_type = field_info_field_types($field['type']);
       $display['type'] = $field_type['default_formatter'];
       $formatter_type = field_info_formatter_types($display['type']);
+      $display['module'] = $formatter_type['module'];
     }
     $function = $formatter_type['module'] . '_field_formatter_settings';
     if (drupal_function_exists($function)) {
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.30
diff -u -r1.30 field.attach.inc
--- modules/field/field.attach.inc	10 Jul 2009 05:58:13 -0000	1.30
+++ modules/field/field.attach.inc	10 Jul 2009 12:51:05 -0000
@@ -858,6 +858,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.9
diff -u -r1.9 field.default.inc
--- modules/field/field.default.inc	24 Jun 2009 18:16:38 -0000	1.9
+++ modules/field/field.default.inc	10 Jul 2009 12:51:05 -0000
@@ -58,6 +58,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.18
diff -u -r1.18 field.api.php
--- modules/field/field.api.php	10 Jul 2009 05:58:13 -0000	1.18
+++ modules/field/field.api.php	10 Jul 2009 12:51:05 -0000
@@ -550,6 +550,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	10 Jul 2009 12:51:08 -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.1082
diff -u -r1.1082 node.module
--- modules/node/node.module	10 Jul 2009 05:58:13 -0000	1.1082
+++ modules/node/node.module	10 Jul 2009 12:51:08 -0000
@@ -1193,7 +1193,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');
 }
 
 /**
@@ -1966,6 +1966,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	10 Jul 2009 12:51:08 -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.1010
diff -u -r1.1010 user.module
--- modules/user/user.module	10 Jul 2009 05:58:13 -0000	1.1010
+++ modules/user/user.module	10 Jul 2009 12:51:09 -0000
@@ -1589,7 +1589,7 @@
  * authentication fails. Distributed authentication modules are welcome
  * to use hook_form_alter() to change this series in order to
  * authenticate against their user database instead of the local users
- * table. If a distributed authentication module is successful, it 
+ * table. If a distributed authentication module is successful, it
  * should set $form_state['uid'] to a user ID.
  *
  * We use three validators instead of one since external authentication
@@ -1617,7 +1617,7 @@
 
 /**
  * A validate handler on the login form. Check supplied username/password
- * against local users table. If successful, $form_state['uid'] 
+ * against local users table. If successful, $form_state['uid']
  * is set to the matching user ID.
  */
 function user_login_authenticate_validate($form, &$form_state) {
@@ -1651,10 +1651,10 @@
       // Allow alternate password hashing schemes.
       require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
       if (user_check_password($password, $account)) {
-        
+
         // Successful authentication. Set a flag for user_login_final_validate().
         $form_state['uid'] = $account->uid;
-        
+
         // Update user to new password scheme if needed.
         if (user_needs_new_hash($account)) {
           $new_hash = user_hash_password($password);
@@ -1696,15 +1696,15 @@
 }
 
 /**
- * Submit handler for the login form. Load $user object and perform standard login 
- * tasks. The user is then redirected to the My Account page. Setting the 
+ * Submit handler for the login form. Load $user object and perform standard login
+ * tasks. The user is then redirected to the My Account page. Setting the
  * destination in the query string overrides the redirect.
  */
 function user_login_submit($form, &$form_state) {
   global $user;
   $user = user_load($form_state['uid']);
   user_login_finalize();
-  
+
   $form_state['redirect'] = 'user/' . $user->uid;
 }
 
@@ -1732,7 +1732,7 @@
     }
     user_set_authmaps($account, array("authname_$module" => $name));
   }
-  
+
   // Log user in.
   $form_state['uid'] = $account->uid;
   user_login_submit(array(), $form_state);
@@ -2004,7 +2004,9 @@
  * @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();
 
@@ -2016,8 +2018,6 @@
 
   // Allow modules to modify the fully-built profile.
   drupal_alter('profile', $account);
-
-  return $account->content;
 }
 
 /**
@@ -2779,7 +2779,7 @@
       drupal_set_message(t('</p><p> Your password is <strong>%pass</strong>. You may change your password below.</p>', array('%pass' => $pass)));
     }
 
-    $form_state['values'] += $merge_data; 
+    $form_state['values'] += $merge_data;
     user_authenticate(array_merge($form_state));
 
     $form_state['redirect'] = 'user/1/edit';
