? .cache
? .settings
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.64
diff -u -p -r1.64 field.attach.inc
--- modules/field/field.attach.inc	13 Dec 2009 12:42:28 -0000	1.64
+++ modules/field/field.attach.inc	19 Dec 2009 23:52:23 -0000
@@ -338,7 +338,7 @@ function _field_invoke_multiple($op, $ob
     if (function_exists($function)) {
       // Iterate over all the field translations.
       foreach ($grouped_items[$field_id] as $langcode => $items) {
-        $results = $function($obj_type, $grouped_objects[$field_id], $field, $grouped_instances[$field_id], $langcode, $grouped_items[$field_id][$langcode], $options, $a, $b);
+        $results = $function($obj_type, $grouped_objects[$field_id], $field, $grouped_instances[$field_id], $langcode, $grouped_items[$field_id][$langcode], $a, $b);
         if (isset($results)) {
           // Collect results by object.
           // For hooks with array results, we merge results together.
@@ -1177,7 +1177,7 @@ function field_attach_view($obj_type, $o
   $output['#pre_render'][] = '_field_extra_weights_pre_render';
   $output['#extra_fields'] = field_extra_fields($bundle);
 
-  // Let other modules make changes after rendering the view.
+  // Let other modules alter the renderable array.
   $context = array(
     'obj_type' => $obj_type,
     'object' => $object,
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.26
diff -u -p -r1.26 field.default.inc
--- modules/field/field.default.inc	13 Dec 2009 12:41:08 -0000	1.26
+++ modules/field/field.default.inc	19 Dec 2009 23:47:29 -0000
@@ -97,13 +97,32 @@ 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) {
+ * Invokes hook_field_formatter_prepare_view() on the relevant formatters.
+ *
+ * @param $obj_type
+ *   The type of $object; e.g. 'node' or 'user'.
+ * @param $objects
+ *   An 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 associated to $items.
+ * @param $items
+ *   Array of field values already loaded for the objects, keyed by object id.
+ * @param $display
+ *   Can be either:
+ *   - the name of a build mode
+ *   - or an array of display settings to use for display, as found in the
+ *   'display' entry of $instance definitions.
+*/
+function field_default_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $display) {
   // Group objects, instances and items by formatter module.
   $modules = array();
   foreach ($instances as $id => $instance) {
-    $display = $instance['display'][$build_mode];
+    $display = is_string($display) ? $instance['display'][$display] : $display;
     if ($display['type'] !== 'hidden') {
       $module = $display['module'];
       $modules[$module] = $module;
@@ -125,16 +144,41 @@ function field_default_prepare_view($obj
 }
 
 /**
- * Default field 'view' operation.
+ * Builds a renderable array for field values..
  *
- * @see field_attach_view()
+ * @param $obj_type
+ *   The type of $object; e.g. 'node' or 'user'.
+ * @param $objects
+ *   An 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 associated to $items.
+ * @param $items
+ *   Array of field values already loaded for the objects, keyed by object id.
+ * @param $display
+ *   Can be either:
+ *   - the name of a build mode;
+ *   - or an array of custom display settings, as found in the 'display' entry
+ *     of $instance definitions.
  */
-function field_default_view($obj_type, $object, $field, $instance, $langcode, $items, $build_mode) {
+function field_default_view($obj_type, $object, $field, $instance, $langcode, $items, $display) {
   list($id, $vid, $bundle) = entity_extract_ids($obj_type, $object);
 
   $addition = array();
 
-  $display = $instance['display'][$build_mode];
+  // Prepare incoming display specifications.
+  if (is_string($display)) {
+    $build_mode = $display;
+    $display = $instance['display'][$build_mode];
+  }
+  else {
+    $build_mode = '_custom_display';
+  }
+
   if ($display['type'] !== 'hidden') {
     // We never want to index fields labels.
     if ($build_mode == 'search_index') {
Index: modules/field/field.info.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v
retrieving revision 1.33
diff -u -p -r1.33 field.info.inc
--- modules/field/field.info.inc	12 Dec 2009 20:16:03 -0000	1.33
+++ modules/field/field.info.inc	19 Dec 2009 23:59:11 -0000
@@ -278,22 +278,10 @@ function _field_info_prepare_instance($i
     $instance['default_value'] = NULL;
   }
 
-  // Fallback to default widget if widget type is not available.
-  if (!field_info_widget_types($instance['widget']['type'])) {
-    $instance['widget']['type'] = $field_type['default_widget'];
-  }
-  // Make sure all expected widget settings are present.
-  $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
+  $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']);
 
   foreach ($instance['display'] as $build_mode => $display) {
-    if ($display['type'] != 'hidden') {
-      // Fallback to default formatter if formatter type is not available.
-      if (!field_info_formatter_types($instance['display'][$build_mode]['type'])) {
-        $instance['display'][$build_mode]['type'] = $field_type['default_formatter'];
-      }
-      // Make sure all expected formatter settings are present.
-      $instance['display'][$build_mode]['settings'] += field_info_formatter_settings($instance['display'][$build_mode]['type']);
-    }
+    $instance['display'][$build_mode] = _field_info_prepare_instance_display($field, $display);
   }
 
   // Fallback to 'full' display settings for unspecified build modes.
@@ -307,6 +295,70 @@ function _field_info_prepare_instance($i
 }
 
 /**
+ * Adapts display specifications to the current run-time context.
+ *
+ * @param $field
+ *   The field structure for the instance.
+ * @param $display
+ *   Display specifications as found in
+ *   $instance['display']['some_build_mode'].
+ */
+function _field_info_prepare_instance_display($field, $display) {
+  $field_type = field_info_field_types($field['type']);
+
+  // Fill in default values.
+  $display += array(
+    'label' => 'above',
+    'type' => $field_type['default_formatter'],
+    'settings' => array(),
+    'weight' => 0,
+  );
+  if ($display['type'] != 'hidden') {
+    $formatter_type = field_info_formatter_types($display['type']);
+    // Fallback to default formatter if formatter type is not available.
+    if (!$formatter_type) {
+      $display['type'] = $field_type['default_formatter'];
+      $formatter_type = field_info_formatter_types($display['type']);
+    }
+    $display['module'] = $formatter_type['module'];
+    // Fill in default settings for the formatter.
+    $display['settings'] += field_info_formatter_settings($display['type']);
+  }
+
+  return $display;
+}
+
+/**
+ * Prepares widget specifications for the current run-time context.
+ *
+ * @param $field
+ *   The field structure for the instance.
+ * @param $widget
+ *   Widget specifications as found in $instance['widget'].
+ */
+function _field_info_prepare_instance_widget($field, $widget) {
+  $field_type = field_info_field_types($field['type']);
+
+  // Fill in default values.
+  $widget += array(
+    'type' => $field_type['default_widget'],
+    'settings' => array(),
+    'weight' => 0,
+  );
+
+  $widget_type = field_info_widget_types($widget['type']);
+  // Fallback to default formatter if formatter type is not available.
+  if (!$widget_type) {
+    $widget['type'] = $field_type['default_widget'];
+    $widget_type = field_info_widget_types($widget['type']);
+  }
+  $widget['module'] = $widget_type['module'];
+  // Fill in default settings for the widget.
+  $widget['settings'] += field_info_widget_settings($widget['type']);
+
+  return $widget;
+}
+
  * Determines the behavior of a widget with respect to an operation.
  *
  * @param $op
Index: modules/field/field.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.module,v
retrieving revision 1.54
diff -u -p -r1.54 field.module
--- modules/field/field.module	13 Dec 2009 12:41:08 -0000	1.54
+++ modules/field/field.module	20 Dec 2009 00:27:23 -0000
@@ -500,7 +500,7 @@ function _field_filter_xss_display_allow
  */
 function field_format($obj_type, $object, $field, $item, $formatter_type = NULL, $formatter_settings = array()) {
   if (!is_array($field)) {
-    $field = field_info_field($field);
+  $field = field_info_field($field);
   }
 
   if (field_access('view', $field, $obj_type, $object)) {
@@ -565,45 +565,99 @@ function field_format($obj_type, $object
 }
 
 /**
- * Return a single field, fully themed with label and multiple values.
+ * Returns a renderable array for the value of a single field in an object.
  *
- * To be used by third-party code (Views, Panels...) that needs to output
- * an isolated field. Do *not* use inside node templates, use
- * render($content[FIELD_NAME]) instead.
- *
- * The field will be displayed using the display options (label display,
- * formatter settings...) specified in the $instance structure for the given
- * build mode: $instance['display'][$build_mode].
+ * The resulting output is a fully themed field with label and multiple values.
  *
+ * This function can be used by third-party modules that needs to output an
+ * isolated field.
+ * - Do not use inside node (or other entities) templates, use
+ *   render($content[FIELD_NAME]) instead.
+ * - Do not use to display all fields in an object, use
+ *   field_attach_prepare_view() and field_attach_view() instead.
+ *
+ * @param $obj_type
+ *   The type of $object; e.g. 'node' or 'user'.
  * @param $object
- *   The object containing the field to display. Must at least contain the id key,
- *   revision key (if applicable), bundle key, and the field data.
- * @param $field
- *   The field structure.
- * @param $instance
- *   The instance structure for $field on $object's bundle.
- * @param $build_mode
- *   Build mode, e.g. 'full', 'teaser'...
+ *   The object containing the field to display. Must at least contain the id
+ *   key and the field data to display.
+ * @param $field_name
+ *   The name of the field to display.
+ * @param $display
+ *   Can be either:
+ *   - The name of a build mode. The field will be displayed according to the
+ *     display settings specified for this build mode in the $instance
+ *     definition for the field in the object's bundle.
+ *     If no display settings are found for the build mode, the settings for
+ *     the 'full' build mode will be used.
+ *   - An array of display settings, as found in the 'display' entry of
+ *     $instance definitions. The following kay/value pairs are allowed:
+ *     - label: (string) Position of the label. The default 'field' theme
+ *       implementation supports the values 'inline', 'above' and 'hidden'.
+ *       Defaults to 'above'.
+ *     - type: (string) The formatter to use.
+ *       Defaults to the 'default_formatter' for the field type, specified in
+ *       hook_field_info(). The default formatter will also be used if the
+ *       requested formatter is not available.
+ *     - settings: (array) Settings specific to the formatter.
+ *       Defaults to the formatter's default settings, specified in
+ *       hook_field_formatter_info().
+ *     - weight: (float) The weight to assign to the renderable element.
+ *       Defaults to 0.
+ * @param $langcode
+ *   (Optional) The language the field values are to be shown in. The site's
+ *   current language fallback logic will be applied no values are available
+ *   for the language. If no language is provided the current language will be
+ *   used.
  * @return
- *   The themed output for the field.
+ *   A renderable array for the field value.
  */
-function field_view_field($obj_type, $object, $field, $instance, $build_mode = 'full') {
-  $output = '';
-  if (isset($object->$field['field_name'])) {
-    $items = $object->$field['field_name'];
-
-    // One-field equivalent to _field_invoke('sanitize').
-    $function = $field['module'] . '_field_sanitize';
-    if (function_exists($function)) {
-      $function($obj_type, $object, $field, $instance, $items);
-      $object->$field['field_name'] = $items;
+function field_view_field($obj_type, $object, $field_name, $display = array(), $langcode = NULL) {
+  $output = array();
+
+  if ($field = field_info_field($field_name)) {
+    if (is_array($display)) {
+      // When using custom display settings, fill in default values.
+      $display = _field_info_prepare_instance_display($field, $display);
+    }
+    else {
+      // When using a build mode, make sure we have settings for it, or fall
+      // back to the 'full' build mode.
+      list(, , $bundle) = entity_extract_ids($obj_type, $object);
+      $instance = field_info_instance($obj_type, $field_name, $bundle);
+      if (!isset($instance['display'][$display])) {
+        $display = 'full';
+      }
     }
 
-    $view = field_default_view($obj_type, $object, $field, $instance, $items, $build_mode);
-    // TODO : what about hook_field_attach_view ?
+    // Hook invocations are done through the _field_invoke() functions in
+    // 'single field' mode, to reuse the language fallback logic.
+    $options = array('field_name' => $field_name, 'language' => field_multilingual_valid_language($langcode, FALSE));
+    $null = NULL;
+    list ($id) = entity_extract_ids($obj_type, $object);
+
+    // First let the field types do their preparation.
+    _field_invoke_multiple('prepare_view', $obj_type, array($id => $object), $display, $null, $options);
+    // Then let the formatters do their own specific massaging.
+    _field_invoke_multiple_default('prepare_view', $obj_type, array($id => $object), $display, $null, $options);
+    // Build the renderable array.
+    $result = _field_invoke_default('view', $obj_type, $object, $display, $null, $options);
+
+    // Let other modules alter the renderable array.
+    $context = array(
+      'obj_type' => $obj_type,
+      'object' => $object,
+      'build_mode' => '_custom',
+      'langcode' => $langcode,
+    );
+    drupal_alter('field_attach_view', $result, $context);
 
-    $output = $view[$field['field_name']];
+    if (isset($result[$field_name])) {
+      $output = $result[$field_name];
+      $output['#attached']['css'][] = drupal_get_path('module', 'field') . '/theme/field.css';
+    }
   }
+
   return $output;
 }
 
Index: modules/field/tests/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/tests/field.test,v
retrieving revision 1.8
diff -u -p -r1.8 field.test
--- modules/field/tests/field.test	13 Dec 2009 12:41:08 -0000	1.8
+++ modules/field/tests/field.test	19 Dec 2009 23:54:36 -0000
@@ -1728,6 +1728,108 @@ class FieldFormTestCase extends FieldTes
   }
 }
 
+class FieldDisplayAPITestCase extends FieldTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Field Display API tests',
+      'description' => 'Test the display API.',
+      'group' => 'Field API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_test');
+
+    // Create a field and instance.
+    $this->field_name = 'test_field';
+    $this->label = $this->randomName();
+    $this->cardinality = 4;
+
+    $this->field = array(
+      'field_name' => $this->field_name,
+      'type' => 'test_field',
+      'cardinality' => $this->cardinality,
+    );
+    $this->instance = array(
+      'field_name' => $this->field_name,
+      'object_type' => 'test_entity',
+      'bundle' => 'test_bundle',
+      'label' => $this->label,
+      'display' => array(
+        'full' => array(
+          'type' => 'field_test_default',
+          'settings' => array(
+            'test_formatter_setting' => $this->randomName(),
+          ),
+        ),
+      ),
+    );
+    field_create_field($this->field);
+    field_create_instance($this->instance);
+
+
+    // Create an entity with values.
+    $this->values = $this->_generateTestFieldValues($this->cardinality);
+    $this->entity = field_test_create_stub_entity();
+    $this->is_new = TRUE;
+    $this->entity->{$this->field_name}[LANGUAGE_NONE] = $this->values;
+    field_test_entity_save($this->entity);
+  }
+
+  /**
+   * Test the field_view_field() function.
+   */
+  function testFieldBuildField() {
+    // No display settings: check that default display settings are used.
+    $output = field_view_field('test_entity', $this->entity, $this->field_name);
+    $this->drupalSetContent(drupal_render($output));
+    $settings = field_info_formatter_settings('field_test_default');
+    $setting = $settings['test_formatter_setting'];
+    $this->assertText($this->label, t('Label was displayed.'));
+    foreach($this->values as $delta => $value) {
+      $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+    }
+
+    // Check that explicit display settings are used.
+    $display = array(
+      'label' => 'hidden',
+      'type' => 'field_test_multiple',
+      'settings' => array(
+        'test_formatter_setting_multiple' => $this->randomName(),
+      ),
+    );
+    $output = field_view_field('test_entity', $this->entity, $this->field_name, $display);
+    $this->drupalSetContent(drupal_render($output));
+    $setting = $display['settings']['test_formatter_setting_multiple'];
+    $this->assertNoText($this->label, t('Label was not displayed.'));
+    $array = array();
+    foreach($this->values as $delta => $value) {
+      $array[] = $delta . ':' . $value['value'];
+    }
+    $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.'));
+
+    // Build mode: check that display settings specified in the instance are
+    // used.
+    $output = field_view_field('test_entity', $this->entity, $this->field_name, 'full');
+    $this->drupalSetContent(drupal_render($output));
+    $setting = $this->instance['display']['full']['settings']['test_formatter_setting'];
+    $this->assertText($this->label, t('Label was displayed.'));
+    foreach($this->values as $delta => $value) {
+      $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+    }
+
+    // Unknown build mode: check that display settings for 'full' build mode
+    // are used.
+    $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_build_mode');
+    $this->drupalSetContent(drupal_render($output));
+    $setting = $this->instance['display']['full']['settings']['test_formatter_setting'];
+    $this->assertText($this->label, t('Label was displayed.'));
+    foreach($this->values as $delta => $value) {
+      $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
+    }
+  }
+}
+
 class FieldCrudTestCase extends FieldTestCase {
   public static function getInfo() {
     return array(
