diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-12-20 21:08:26 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-12-20 21:08:26 +0000 |
commit | e9f18147482f6a3e4b9e177d84e9deebd4410778 (patch) | |
tree | 8a769a32591e495d6976756cdb54c86899d1de64 | |
parent | 8874a4e2f7299d3068ea6c0346999fc9d2f7d2ce (diff) | |
download | brdo-e9f18147482f6a3e4b9e177d84e9deebd4410778.tar.gz brdo-e9f18147482f6a3e4b9e177d84e9deebd4410778.tar.bz2 |
- Patch #612894 by yched: display field improvements.
-rw-r--r-- | modules/field/field.attach.inc | 4 | ||||
-rw-r--r-- | modules/field/field.default.inc | 60 | ||||
-rw-r--r-- | modules/field/field.info.inc | 80 | ||||
-rw-r--r-- | modules/field/field.module | 114 | ||||
-rw-r--r-- | modules/field/tests/field.test | 101 |
5 files changed, 307 insertions, 52 deletions
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 48eab2dec..d87ac3a1a 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -338,7 +338,7 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL 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, $object, $build_mode = 'full', $langcode = $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, diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index 7305b2db9..4e7dca6cb 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.inc @@ -97,13 +97,32 @@ function field_default_insert($obj_type, $object, $field, $instance, $langcode, } /** - * 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_type, $objects, $field, $instances, $la } /** - * 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') { diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index e4df3a391..4921fdaee 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -278,22 +278,10 @@ function _field_info_prepare_instance($instance, $field) { $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($instance, $field) { } /** + * 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 diff --git a/modules/field/field.module b/modules/field/field.module index d89b76fa3..f0dc6bb8e 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -565,45 +565,103 @@ function field_format($obj_type, $object, $field, $item, $formatter_type = NULL, } /** - * 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 resulting output is a fully themed field with label and multiple values. * - * 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]. + * This function can be used by third-party modules that need 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. * + * The function takes care of invoking the prepare_view steps. It also respects + * field access permissions. + * + * @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']; +function field_view_field($obj_type, $object, $field_name, $display = array(), $langcode = NULL) { + $output = array(); - // 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; + 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); + + // Invoke hook_field_attach_view_alter() to tet other modules alter the + // renderable array, as in a full field_attach_view() execution. + $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; } diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 1bbb36744..b148f31c8 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -1728,6 +1728,107 @@ class FieldFormTestCase extends FieldTestCase { } } +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( |