Commit ef2c2400 authored by Dries's avatar Dries
Browse files

- Patch #652834 by yched, effulgentsia: changed Field formatters as render...

- Patch #652834 by yched, effulgentsia: changed Field formatters as render arrays to increase performance (and to clean-up the code).
parent 47182dfb
......@@ -688,30 +688,14 @@ function hook_field_widget_error($element, $error) {
/**
* Expose Field API formatter types.
*
* Formatters are mainly theme functions that handle the output of individual
* field values. These theme calls are typically triggered during the execution
* of drupal_render() on the render structure built by field_attach_view().
*
* The name of the theme hook invoked when displaying the values is derived
* from formatter type names, using the pattern field_formatter_FORMATTER_NAME.
* field.module takes care of exposing the corresponding theme functions
* through hook_theme(). Specifically, field.module defines the theme
* hook:
*
* @code
* 'field_formatter_FORMATTER_NAME' => array(
* 'render_element' => 'element',
* )
* @code
*
* If a formatter requires a different theme hook definition,
* implement hook_theme_registry_alter().
* Formatters handle the display of field values. Formatter hooks are typically
* called by the Field Attach API field_attach_prepare_view() and
* field_attach_view() functions.
*
* @see hook_field_formatter_info().
* @see hook_field_formatter_info_alter().
* @see theme_field_formatter_FORMATTER_NAME().
* @see hook_theme().
* @see hook_theme_registry_alter().
* @see hook_field_formatter().
* @see hook_field_formatter_prepare_view().
*
* @return
* An array describing the formatter types implemented by the module.
......@@ -733,8 +717,8 @@ function hook_field_widget_error($element, $error) {
* field value (most common case). The formatter theme will be invoked
* iteratively on each of the field valies.
* FIELD_BEHAVIOR_CUSTOM if one single invocation of the formatter theme
* takes care of displays all the field values. Examples: points on
* a generated graph picture, a Google map, a single link to a popup...
* takes care of displaying all the field values. Examples: points on a
* generated graph picture, a Google map, a single link to a popup...
*/
function hook_field_formatter_info() {
return array(
......@@ -799,61 +783,7 @@ function hook_field_formatter_info_alter(&$info) {
}
/**
* Theme function for a field formatter.
*
* This is an example of a 'single' formatter, displaying one single field
* value (the hook_field_formatter_info() entry uses
* 'multiple values' = FIELD_BEHAVIOR_DEFAULT).
*
* @param $variables
* An associative array containing:
* - element: A render structure sub-array, containing the following keys:
* - #item: The field value being displayed.
* - #delta: The index of the value being displayed within the object's
* values for the field.
* - #field_name: The name of the field being displayed.
* - #bundle: The bundle of the object being displayed.
* - #object: The object being displayed.
* - #object_type: The type of the object being displayed.
* - #formatter: The name of the formatter being used.
* - #settings: The array of formatter settings.
*/
function theme_field_formatter_FORMATTER_SINGLE($variables) {
// This relies on a 'safe' element being prepared in hook_field_sanitize().
return $variables['element']['#item']['safe'];
}
/**
* Theme function for a field formatter.
*
* This is an example of a 'single' formatter, displaying all the field values
* (the hook_field_formatter_info() entry uses
* 'multiple values' = FIELD_BEHAVIOR_CUSTOM).
*
* @param $variables
* An associative array containing:
* - element: A render structure sub-array, containing the following keys:
* - #field_name: The name of the field being displayed.
* - #bundle: The bundle of the object being displayed.
* - #object: The object being displayed.
* - #object_type: The type of the object being displayed.
* - #formatter: The name of the formatter being used.
* - #settings: The array of formatter settings.
* - numeric indexes: the field values being displayed.
*/
function theme_field_formatter_FORMATTER_MULTIPLE($variables) {
$element = $variables['element'];
$items = array();
foreach (element_children($element) as $key) {
$items[$key] = $key .':'. $element[$key]['#item']['value'];
}
$output = implode('|', $items);
return $output;
}
/**
* Allow formatters to load information for multiple objects.
* Allow formatters to load information for field values being displayed.
*
* 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
......@@ -866,18 +796,77 @@ function theme_field_formatter_FORMATTER_MULTIPLE($variables) {
* @param $field
* The field structure for the operation.
* @param $instances
* Array of instance structures for $field for each object, keyed by object id.
* 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.
* @param $displays
* Array of display settings to use for each object display, 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) {
function hook_field_formatter_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $displays) {
}
/**
* Builds a renderable array for a field value.
*
* @param $obj_type
* The type of $object.
* @param $object
* The object being displayed.
* @param $field
* The field structure.
* @param $instance
* The field instance.
* @param $langcode
* The language associated to $items.
* @param $display
* The display settings to use, as found in the 'display' entry of instance
* definitions.
* @param $items
* Array of values for this field.
* @param $delta
*
* @return
*/
function hook_field_formatter($obj_type, $object, $field, $instance, $langcode, $display, $items, $delta) {
$settings = $display['settings'];
switch ($display['type']) {
case 'field_test_default':
// Sample code for a 'single' formatter, displaying one single field
// value (the hook_field_formatter_info() entry uses
// 'multiple values' = FIELD_BEHAVIOR_DEFAULT).
$item = $items[$delta];
$result = array(
'#markup' => $item['value'],
);
break;
case 'field_test_multiple':
// Sample code for a 'multiple' formatter, displaying all the field
// values (the hook_field_formatter_info() entry uses
// 'multiple values' = FIELD_BEHAVIOR_CUSTOM).
$array = array();
foreach ($items as $delta => $item) {
$array[] = $delta .':'. $item['value'];
}
$result = array(
'#markup' => implode('|', $array),
);
break;
}
return array(
'#markup' => $output,
);
}
/**
......
......@@ -1113,10 +1113,39 @@ function field_attach_prepare_view($obj_type, $objects, $build_mode = 'full') {
}
/**
* 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
* its settings.
* Returns a renderable array for the fields on an object.
*
* Each field is displayed according to the display options specified in the
* $instance definition for the given $build_mode.
*
* Sample structure:
* @code
* array(
* 'field_foo' => array(
* '#theme' => 'field',
* '#title' => the label of the field instance,
* '#label_display' => the label display mode,
* '#object' => the fieldable object being displayed,
* '#object_type' => the type of the object being displayed,
* '#language' => the language of the field values being displayed,
* '#build_mode' => the build mode,
* '#field_name' => the name of the field,
* '#field_type' => the type of the field,
* '#formatter' => the name of the formatter,
* '#items' => the field values being displayed,
* // The element's children are the formatted values returned by
* // hook_field_formatter(), keyed by delta.
* 0 => array(
* // Renderable array for value 0, or for all values if the formatter
* // is 'multiple-values'.
* ),
* 1 => array(
* // Renderable array for value 1 (only for 'single-value' formatters).
* ),
* // ...
* ),
* );
* @endcode
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
......@@ -1128,56 +1157,7 @@ function field_attach_prepare_view($obj_type, $objects, $build_mode = 'full') {
* The language the field values are to be shown in. If no language is
* provided the current language is used.
* @return
* A structured content array tree for drupal_render().
* Sample structure:
* @code
* array(
* 'field_foo' => array(
* // The structure of the array differs slightly depending on whether
* // the formatter is 'single-value' (displays one single field value,
* // most common case) or 'multiple-values' (displays all the field's
* // values, e.g. points on a graph or a map).
* '#theme' => 'field',
* '#title' => the label of the field instance,
* '#label_display' => the label display mode,
* '#object' => the fieldable object being displayed,
* '#object_type' => the type of the object being displayed,
* '#language' => the language of the field values being displayed,
* '#build_mode' => the build mode,
* '#field_name' => the name of the field,
* '#formatter_single' => boolean indicating whether the formatter is single or
* multiple,
* 'items' => array(
* // One sub-array per field value, keyed by delta.
* 0 => array(
* '#item' => the field value for delta 0,
*
* // Only for 'single-value' formatters:
* '#theme' => the formatter's theme function,
* '#formatter' => name of the formatter,
* '#settings' => array of formatter settings,
* '#object' => the fieldable object being displayed,
* '#object_type' => the type of the object being displayed,
* '#field_name' => the name of the field,
* '#bundle' => the object's bundle,
* '#delta' => 0,
* ),
* 1 => array(
* ...
* ),
*
* // Only for 'multiple-values' formatters:
* '#theme' => the formatter's theme function,
* '#formatter' => name of the formatter,
* '#settings' => array of formatter settings,
* '#object' => the fieldable object being displayed,
* '#object_type' => the type of the object being displayed,
* '#field_name' => the name of the field,
* '#bundle' => the object's bundle,
* ),
* ),
* );
* @endcode
* A renderable array for the field values.
*/
function field_attach_view($obj_type, $object, $build_mode = 'full', $langcode = NULL) {
// If no language is provided use the current UI language.
......
......@@ -60,19 +60,23 @@ function field_default_prepare_view($obj_type, $objects, $field, $instances, $la
// Group objects, instances and items by formatter module.
$modules = array();
foreach ($instances as $id => $instance) {
$module = $instance['display'][$build_mode]['module'];
$modules[$module] = $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];
$display = $instance['display'][$build_mode];
if ($display['type'] !== 'hidden') {
$module = $display['module'];
$modules[$module] = $module;
$grouped_objects[$module][$id] = $objects[$id];
$grouped_instances[$module][$id] = $instance;
$grouped_displays[$module][$id] = $display;
// 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);
$function($obj_type, $grouped_objects[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]);
}
}
}
......@@ -89,57 +93,38 @@ function field_default_view($obj_type, $object, $field, $instance, $langcode, $i
$display = $instance['display'][$build_mode];
if ($display['type'] !== 'hidden') {
$theme = 'field_formatter_' . $display['type'];
$single = (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT);
$label_display = $display['label'];
if ($build_mode == 'search_index') {
$label_display = 'hidden';
$display['label'] = 'hidden';
}
$info = array(
'#field_name' => $field['field_name'],
'#object_type' => $obj_type,
'#bundle' => $bundle,
'#object' => $object,
);
$element = $info + array(
$element = array(
'#theme' => 'field',
'#weight' => $display['weight'],
'#title' => check_plain(t($instance['label'])),
'#title' => t($instance['label']),
'#access' => field_access('view', $field, $obj_type, $object),
'#label_display' => $label_display,
'#label_display' => $display['label'],
'#build_mode' => $build_mode,
'#language' => $langcode,
'#formatter_single' => $single,
'items' => array(),
);
// Fill-in items.
foreach ($items as $delta => $item) {
$element['items'][$delta] = array(
'#item' => $item,
'#weight' => $delta,
);
}
// Append formatter information either on each item ('single-value' formatter)
// or at the upper 'items' level ('multiple-value' formatter)
$format_info = $info + array(
'#theme' => $theme,
'#field_name' => $field['field_name'],
'#field_type' => $field['type'],
'#object_type' => $obj_type,
'#bundle' => $bundle,
'#object' => $object,
'#items' => $items,
'#formatter' => $display['type'],
'#settings' => $display['settings'],
);
if ($single) {
// Calling the formatter function through module_invoke() can have a
// performance impact on pages with many fields and values.
$formatter_function = $display['module'] . '_field_formatter';
if (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT) {
foreach ($items as $delta => $item) {
$element['items'][$delta] += $format_info;
$element['items'][$delta]['#item']['#delta'] = $delta;
$element[$delta] = $formatter_function($obj_type, $object, $field, $instance, $langcode, $display, $items, $delta);
$element[$delta]['#weight'] = $delta;
}
}
else {
$element['items'] += $format_info;
$element[0] = $formatter_function($obj_type, $object, $field, $instance, $langcode, $display, $items, 0);
}
$addition = array($field['field_name'] => $element);
......
......@@ -181,15 +181,7 @@ function field_theme() {
'render element' => 'element',
),
);
$field_formatters = field_info_formatter_types(NULL);
foreach ($field_formatters as $key => $field_formatter) {
$items['field_formatter_' . $key] = array(
'render element' => 'element',
);
if (isset($field_formatter['theme'])) {
$items['field_formatter_' . $key] += $field_formatter['theme'];
}
}
return $items;
}
......@@ -686,24 +678,18 @@ function field_extract_bundle($obj_type, $bundle) {
*/
function template_preprocess_field(&$variables) {
$element = $variables['element'];
list(, , $bundle) = entity_extract_ids($element['#object_type'], $element['#object']);
$instance = field_info_instance($element['#object_type'], $element['#field_name'], $bundle);
$instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
$field = field_info_field($element['#field_name']);
// @todo Convert to using drupal_html_class() after benchmarking the impact of
// doing so.
$field_type_css = strtr($field['type'], '_', '-');
$field_name_css = strtr($field['field_name'], '_', '-');
// Prepare an $items variable that the template can simply loop on.
if ($element['#formatter_single']) {
// Filter out non-children properties that might have been added if the
// renderable array has gone through form_builder().
$items = array_intersect_key($element['items'], array_flip(element_children($element['items'])));
}
else {
// If the formatter is multiple, the template sees only one 'item', which
// will include all values.
$items = array($element['items']);
}
// Filter out non-children properties that might have been added if the
// renderable array has gone through form_builder().
$items = array_intersect_key($element, array_flip(element_children($element)));
$additions = array(
'object' => $element['#object'],
......@@ -715,7 +701,7 @@ function template_preprocess_field(&$variables) {
'field_name' => $field['field_name'],
'field_type_css' => $field_type_css,
'field_name_css' => $field_name_css,
'label' => $element['#title'],
'label' => check_plain($element['#title']),
'label_display' => $element['#label_display'],
'label_hidden' => $element['#label_display'] == 'hidden',
'field_language' => $element['#language'],
......@@ -728,8 +714,8 @@ function template_preprocess_field(&$variables) {
'template_files' => array(
'field',
'field-' . $element['#field_name'],
'field-' . $bundle,
'field-' . $element['#field_name'] . '-' . $bundle,
'field-' . $element['#bundle'],
'field-' . $element['#field_name'] . '-' . $element['#bundle'],
),
);
$variables = array_merge($variables, $additions);
......
......@@ -298,22 +298,29 @@ function list_field_formatter_info() {
}
/**
* Theme function for 'default' list field formatter.
* Implements hook_field_formatter().
*/
function theme_field_formatter_list_default($variables) {
$element = $variables['element'];
$field = field_info_field($element['#field_name']);
if (($allowed_values = list_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
return $allowed_values[$element['#item']['value']];
function list_field_formatter($object_type, $object, $field, $instance, $langcode, $display, $items, $delta) {
$value = $items[$delta]['value'];
switch ($display['type']) {
case 'list_default':
$allowed_values = list_allowed_values($field);
if (isset($allowed_values[$value])) {
$output = $allowed_values[$value];
}
else {
// If no match was found in allowed values, fall back to the key.
$output = $value;
}
break;
case 'list_key':
$output = $value;
break;
}
// If no match was found in allowed values, fall back to the key.
return $element['#item']['safe'];
}
/**
* Theme function for 'key' list field formatter.
*/
function theme_field_formatter_list_key($variables) {
$element = $variables['element'];
return $element['#item']['safe'];
return array(
'#markup' => $output,
);
}
......@@ -6,15 +6,6 @@
* Defines numeric field types.
*/
/**
* Implements hook_theme_alter().
*/
function number_theme_registry_alter(&$theme_registry) {
// The number_integer and number_decimal formatters use the same function.
$theme_registry['field_formatter_number_default']['function'] = 'theme_field_formatter_number';
$theme_registry['field_formatter_number_decimal']['function'] = 'theme_field_formatter_number';
}
/**
* Implements hook_field_info().
*/
......@@ -25,7 +16,7 @@ function number_field_info() {
'description' => t('This field stores a number in the database as an integer.'),
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
'default_widget' => 'number',
'default_formatter' => 'number_default',
'default_formatter' => 'number_integer',
),
'number_decimal' => array(
'label' => t('Decimal'),
......@@ -221,7 +212,7 @@ function number_field_is_empty($item, $field) {
*/
function number_field_formatter_info() {
return array(
'number_default' => array(
'number_integer' => array(
'label' => t('default'),
'field types' => array('number_integer'),
'settings' => array(
......@@ -249,39 +240,36 @@ function number_field_formatter_info() {
}
/**
* Theme function for 'unformatted' number field formatter.
*/
function theme_field_formatter_number_unformatted($variables) {
$element = $variables['element'];
return $element['#item']['value'];
}
/**
* Proxy theme function for number field formatters.
* Implements hook_field_formatter().
*/
function theme_field_formatter_number($variables) {
$element = $variables['element'];
$field = field_info_field($element['#field_name']);
$instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
$value = $element['#item']['value'];
$settings = $element['#settings'];
$formatter_type = $element['#formatter'];
if (empty($value) && $value !== '0') {
return '';
}
$output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
function number_field_formatter($object_type, $object, $field, $instance, $langcode, $display, $items, $delta) {
$value = $items[$delta]['value'];
switch ($display['type']) {
case 'number_integer':
case 'number_decimal':
$settings = $display['settings'];
if (empty($value) && $value !== '0') {
return '';
}
$output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
if ($settings['prefix_suffix']) {
$prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
$suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
$prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
$suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
$output = $prefix . $output . $suffix;
}
break;
if ($settings['prefix_suffix']) {
$prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
$suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
$prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
$suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
$output = $prefix . $output . $suffix;
case 'number_unformatted':
$output = $value;
break;
}
return $output;
return array(
'#markup' => $output,
);
}
/**
......
......@@ -191,49 +191,12 @@ function text_field_validate($obj_type, $object, $field, $instance, $langcode, $