Commit b149df70 authored by webchick's avatar webchick

Issue #1785748 by yched, swentel, Stalski: Field formatters as plugins.

parent ca64740b
This diff is collapsed.
This diff is collapsed.
......@@ -86,127 +86,6 @@ function field_default_insert($entity_type, $entity, $field, $instance, $langcod
}
}
/**
* Invokes hook_field_formatter_prepare_view() on the relevant formatters.
*
* @param $entity_type
* The type of $entity; e.g. 'node' or 'user'.
* @param $entities
* An array of entities being displayed, keyed by entity ID.
* @param $field
* The field structure for the operation.
* @param $instances
* Array of instance structures for $field for each entity, keyed by entity
* ID.
* @param $langcode
* The language associated with $items.
* @param $items
* Array of field values already loaded for the entities, keyed by entity id.
* @param $display
* Can be either:
* - The name of a view mode
* - An array of display settings to use for display, as found in the
* 'display' entry of $instance definitions.
*/
function field_default_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $display) {
// Group entities, instances and items by formatter module.
$modules = array();
foreach ($instances as $id => $instance) {
if (is_string($display)) {
$view_mode = $display;
$instance_display = field_get_display($instance, $view_mode, $entities[$id]);
}
else {
$instance_display = $display;
}
if ($instance_display['type'] !== 'hidden') {
$module = $instance_display['module'];
$modules[$module] = $module;
$grouped_entities[$module][$id] = $entities[$id];
$grouped_instances[$module][$id] = $instance;
$grouped_displays[$module][$id] = $instance_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($entity_type, $grouped_entities[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]);
}
}
}
/**
* Builds a renderable array for one field on one entity instance.
*
* @param $entity_type
* The type of $entity; e.g. 'node' or 'user'.
* @param $entity
* A single object of type $entity_type.
* @param $field
* The field structure for the operation.
* @param $instance
* An array containing each field on $entity's bundle.
* @param $langcode
* The language associated to $items.
* @param $items
* Array of field values already loaded for the entities, keyed by entity id.
* @param $display
* Can be either:
* - the name of a view mode;
* - or an array of custom display settings, as found in the 'display' entry
* of $instance definitions.
*/
function field_default_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$addition = array();
// Prepare incoming display specifications.
if (is_string($display)) {
$view_mode = $display;
$display = field_get_display($instance, $view_mode, $entity);
}
else {
$view_mode = '_custom_display';
}
if ($display['type'] !== 'hidden') {
// Calling the formatter function through module_invoke() can have a
// performance impact on pages with many fields and values.
$function = $display['module'] . '_field_formatter_view';
if (function_exists($function)) {
$elements = $function($entity_type, $entity, $field, $instance, $langcode, $items, $display);
if ($elements) {
$info = array(
'#theme' => 'field',
'#weight' => $display['weight'],
'#title' => $instance['label'],
'#access' => field_access('view', $field, $entity_type, $entity),
'#label_display' => $display['label'],
'#view_mode' => $view_mode,
'#language' => $langcode,
'#field_name' => $field['field_name'],
'#field_type' => $field['type'],
'#field_translatable' => $field['translatable'],
'#entity_type' => $entity_type,
'#bundle' => $entity->bundle(),
'#object' => $entity,
'#items' => $items,
'#formatter' => $display['type']
);
$addition[$field['field_name']] = array_merge($info, $elements);
}
}
}
return $addition;
}
/**
* Copies source field values into the entity to be prepared.
*
......
......@@ -73,22 +73,10 @@ function field_info_cache_clear() {
* instance_settings, default_widget, default_formatter, and behaviors
* from hook_field_info(), as well as module, giving the module that exposes
* the field type.
* - 'widget types': Array of hook_field_widget_info() results, keyed by
* widget_type. Each element has the following components: label, field
* types, settings, weight, and behaviors from hook_field_widget_info(),
* as well as module, giving the module that exposes the widget type.
* - 'formatter types': Array of hook_field_formatter_info() results, keyed by
* formatter_type. Each element has the following components: label, field
* types, and behaviors from hook_field_formatter_info(), as well as
* module, giving the module that exposes the formatter type.
* - 'storage types': Array of hook_field_storage_info() results, keyed by
* storage type names. Each element has the following components: label,
* description, and settings from hook_field_storage_info(), as well as
* module, giving the module that exposes the storage type.
* - 'fieldable types': Array of hook_entity_info() results, keyed by
* entity_type. Each element has the following components: name, id key,
* revision key, bundle key, cacheable, and bundles from hook_entity_info(),
* as well as module, giving the module that exposes the entity type.
*
* @see _field_info_collate_types_reset()
*/
......@@ -114,7 +102,6 @@ function _field_info_collate_types() {
else {
$info = array(
'field types' => array(),
'formatter types' => array(),
'storage types' => array(),
);
......@@ -133,20 +120,6 @@ function _field_info_collate_types() {
}
drupal_alter('field_info', $info['field types']);
// Populate formatter types.
foreach (module_implements('field_formatter_info') as $module) {
$formatter_types = (array) module_invoke($module, 'field_formatter_info');
foreach ($formatter_types as $name => $formatter_info) {
// Provide defaults.
$formatter_info += array(
'settings' => array(),
);
$info['formatter types'][$name] = $formatter_info;
$info['formatter types'][$name]['module'] = $module;
}
}
drupal_alter('field_formatter_info', $info['formatter types']);
// Populate storage types.
foreach (module_implements('field_storage_info') as $module) {
$storage_types = (array) module_invoke($module, 'field_storage_info');
......@@ -259,25 +232,21 @@ function field_info_widget_types($widget_type = NULL) {
/**
* Returns information about field formatters from hook_field_formatter_info().
*
* @param $formatter_type
* @param string $formatter_type
* (optional) A formatter type name. If omitted, all formatter types will be
* returned.
*
* @return
* Either a single formatter type description, as provided by
* hook_field_formatter_info(), or an array of all existing formatter types,
* keyed by formatter type name.
* @return array
* Either a single formatter type description, as provided by class
* annotations, or an array of all existing formatter types, keyed by
* formatter type name.
*/
function field_info_formatter_types($formatter_type = NULL) {
$info = _field_info_collate_types();
$formatter_types = $info['formatter types'];
if ($formatter_type) {
if (isset($formatter_types[$formatter_type])) {
return $formatter_types[$formatter_type];
}
return field_get_plugin_manager('formatter')->getDefinition($formatter_type);
}
else {
return $formatter_types;
return field_get_plugin_manager('formatter')->getDefinitions();
}
}
......
......@@ -561,6 +561,7 @@ function field_get_plugin_manager($plugin_type) {
$classes = array(
'widget' => 'Drupal\field\Plugin\Type\Widget\WidgetPluginManager',
'formatter' => 'Drupal\field\Plugin\Type\Formatter\FormatterPluginManager',
);
if (isset($classes[$plugin_type])) {
......@@ -753,39 +754,6 @@ function field_view_mode_settings($entity_type, $bundle) {
return $cache[$entity_type][$bundle];
}
/**
* Returns the display settings to use for an instance in a given view mode.
*
* @param $instance
* The field instance being displayed.
* @param $view_mode
* The view mode.
* @param $entity
* The entity being displayed.
*
* @return
* The display settings to be used when displaying the field values.
*/
function field_get_display($instance, $view_mode, $entity) {
// Check whether the view mode uses custom display settings or the 'default'
// mode.
$view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
$actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default');
$display = $instance['display'][$actual_mode];
// Let modules alter the display settings.
$context = array(
'entity_type' => $instance['entity_type'],
'field' => field_info_field($instance['field_name']),
'instance' => $instance,
'entity' => $entity,
'view_mode' => $view_mode,
);
drupal_alter(array('field_display', 'field_display_' . $instance['entity_type']), $display, $context);
return $display;
}
/**
* Returns the display settings to use for pseudo-fields in a given view mode.
*
......@@ -998,46 +966,48 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display =
function field_view_field($entity_type, $entity, $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.
$cache = _field_info_field_cache();
$display = $cache->prepareInstanceDisplay($display, $field["type"]);
}
// Hook invocations are done through the _field_invoke() functions in
// 'single field' mode, to reuse the language fallback logic.
// Determine the actual language code to display for the field, given the
// language codes available in the field data.
$display_langcode = field_language($entity_type, $entity, $field_name, $langcode);
$options = array('field_name' => $field_name, 'langcode' => $display_langcode);
$null = NULL;
// Invoke prepare_view steps if needed.
if (empty($entity->_field_view_prepared)) {
$id = $entity->id();
// First let the field types do their preparation.
_field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
// Then let the formatters do their own specific massaging.
_field_invoke_multiple_default('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
if ($instance = field_info_instance($entity_type, $field_name, $entity->bundle())) {
if (is_array($display) && empty($display['type'])) {
$field = field_info_field($instance['field_name']);
$field_type_info = field_info_field_types($field['type']);
$display['type'] = $field_type_info['default_formatter'];
}
if ($formatter = $instance->getFormatter($display)) {
$display_langcode = field_language($entity_type, $entity, $field_name, $langcode);
$items = $entity->{$field_name}[$display_langcode];
// Invoke prepare_view steps if needed.
if (empty($entity->_field_view_prepared)) {
$id = $entity->id();
// First let the field types do their preparation.
// @todo invoke hook_field_prepare_view() directly ?
$options = array('field_name' => $field_name, 'langcode' => $display_langcode);
$null = NULL;
_field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
// Then let the formatters do their own specific massaging.
$items_multi = array($id => $entity->{$field_name}[$display_langcode]);
$formatter->prepareView(array($id => $entity), $display_langcode, $items_multi);
$items = $items_multi[$id];
}
// Build the renderable array.
$result = _field_invoke_default('view', $entity_type, $entity, $display, $null, $options);
// Build the renderable array.
$result = $formatter->view($entity, $display_langcode, $items);
// Invoke hook_field_attach_view_alter() to let other modules alter the
// renderable array, as in a full field_attach_view() execution.
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'view_mode' => '_custom',
'display' => $display,
);
drupal_alter('field_attach_view', $result, $context);
// Invoke hook_field_attach_view_alter() to let other modules alter the
// renderable array, as in a full field_attach_view() execution.
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'view_mode' => '_custom',
'display' => $display,
);
drupal_alter('field_attach_view', $result, $context);
if (isset($result[$field_name])) {
$output = $result[$field_name];
if (isset($result[$field_name])) {
$output = $result[$field_name];
}
}
}
......
......@@ -514,68 +514,9 @@ public function prepareInstance($instance, $field_type) {
$instance['default_value'] = NULL;
}
// Prepare display settings.
foreach ($instance['display'] as $view_mode => $display) {
$instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
}
// Fall back to 'hidden' for view modes configured to use custom display
// settings, and for which the instance has no explicit settings.
$entity_info = entity_get_info($instance['entity_type']);
$view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
$view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
foreach ($view_modes as $view_mode) {
if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
if (!isset($instance['display'][$view_mode])) {
$instance['display'][$view_mode] = array(
'type' => 'hidden',
'label' => 'above',
'settings' => array(),
'weight' => 0,
);
}
}
}
return $instance;
}
/**
* Adapts display specifications to the current run-time context.
*
* @param $display
* Display specifications as found in $instance['display']['a_view_mode'].
* @param $field_type
* The field type.
*
* @return
* The display properties completed for the current runtime context.
*/
public function prepareInstanceDisplay($display, $field_type) {
$field_type_info = field_info_field_types($field_type);
// Fill in default values.
$display += array(
'label' => 'above',
'type' => $field_type_info['default_formatter'],
'settings' => array(),
'weight' => 0,
);
if ($display['type'] != 'hidden') {
$formatter_type_info = field_info_formatter_types($display['type']);
// Fall back to default formatter if formatter type is not available.
if (!$formatter_type_info) {
$display['type'] = $field_type_info['default_formatter'];
$formatter_type_info = field_info_formatter_types($display['type']);
}
$display['module'] = $formatter_type_info['module'];
// Fill in default settings for the formatter.
$display['settings'] += field_info_formatter_settings($display['type']);
}
return $display;
}
/**
* Prepares 'extra fields' for the current run-time context.
*
......
......@@ -26,6 +26,13 @@ class FieldInstance implements \ArrayAccess {
*/
protected $widget;
/**
* The formatter objects used for this instance, keyed by view mode.
*
* @var array
*/
protected $formatters;
/**
* Constructs a FieldInstance object.
*
......@@ -67,6 +74,88 @@ public function getWidget() {
return $this->widget;
}
/**
* Returns a Formatter plugin for the instance.
*
* @param mixed $display_properties
* Can be either:
* - The name of a view mode.
* - An array of display properties, as found in the 'display' entry of
* $instance definitions.
*
* @return Drupal\field\Plugin\Type\Formatter\FormatterInterface|null
* The Formatter plugin to be used for the instance, or NULL if the field
* is hidden.
*/
public function getFormatter($display_properties) {
if (is_string($display_properties)) {
// A view mode was provided. Switch to 'default' if the view mode is not
// configured to use dedicated settings.
$view_mode = $display_properties;
$view_mode_settings = field_view_mode_settings($this->definition['entity_type'], $this->definition['bundle']);
$actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default');
if (isset($this->formatters[$actual_mode])) {
return $this->formatters[$actual_mode];
}
// Switch to 'hidden' if the instance has no properties for the view
// mode.
if (isset($this->definition['display'][$actual_mode])) {
$display_properties = $this->definition['display'][$actual_mode];
}
else {
$display_properties = array(
'type' => 'hidden',
'settings' => array(),
'label' => 'above',
'weight' => 0,
);
}
// Let modules alter the widget properties.
$context = array(
'entity_type' => $this->definition['entity_type'],
'bundle' => $this->definition['bundle'],
'field' => field_info_field($this->definition['field_name']),
'instance' => $this,
'view_mode' => $view_mode,
);
drupal_alter(array('field_display', 'field_display_' . $this->definition['entity_type']), $display_properties, $context);
}
else {
// Arbitrary display settings. Make sure defaults are present.
$display_properties += array(
'settings' => array(),
'label' => 'above',
'weight' => 0,
);
$view_mode = '_custom_display';
}
if (!empty($display_properties['type']) && $display_properties['type'] != 'hidden') {
$options = array(
'instance' => $this,
'type' => $display_properties['type'],
'settings' => $display_properties['settings'],
'label' => $display_properties['label'],
'weight' => $display_properties['weight'],
'view_mode' => $view_mode,
);
$formatter = field_get_plugin_manager('formatter')->getInstance($options);
}
else {
$formatter = NULL;
}
// Persist the object if we were not passed custom display settings.
if (isset($actual_mode)) {
$this->formatters[$actual_mode] = $formatter;
}
return $formatter;
}
/**
* Implements ArrayAccess::offsetExists().
*/
......@@ -91,11 +180,14 @@ public function offsetSet($offset, $value) {
}
$this->definition[$offset] = $value;
// If the widget properties changed, the widget plugin needs to be
// re-instanciated.
// If the widget or formatter properties changed, the corrsponding plugins
// need to be re-instanciated.
if ($offset == 'widget') {
unset($this->widget);
}
if ($offset == 'display') {
unset($this->formatters);
}
}
/**
......@@ -104,11 +196,14 @@ public function offsetSet($offset, $value) {
public function offsetUnset($offset) {
unset($this->definition[$offset]);
// If the widget properties changed, the widget plugin needs to be
// re-instanciated.
// If the widget or formatter properties changed, the corrsponding plugins
// need to be re-instanciated.
if ($offset == 'widget') {
unset($this->widget);
}
if ($offset == 'display') {
unset($this->formatters);
}
}
/**
......
<?php
/**
* @file
* Definition of Drupal\field\Plugin\Type\Formatter\FormatterBase.
*/
namespace Drupal\field\Plugin\Type\Formatter;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Plugin\PluginSettingsBase;
use Drupal\field\FieldInstance;
/**
* Base class for 'Field formatter' plugin implementations.
*/
abstract class FormatterBase extends PluginSettingsBase implements FormatterInterface {
/**
* The field definition.
*
* @var array
*/
protected $field;
/**
* The field instance definition.
*
* @var Drupal\field\FieldInstance
*/
protected $instance;
/**
* The formatter settings.
*
* @var array
*/
protected $settings;
/**
* The formatter weight.
*
* @var int
*/
protected $weight;
/**
* The label display setting.
*
* @var string
*/
protected $label;
/**
* The view mode.
*
* @var string
*/
protected $viewMode;
/**
* Constructs a FormatterBase object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
* The Discovery class that holds access to the formatter implementation
* definition.
* @param Drupal\field\FieldInstance $instance
* The field instance to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param int $weight
* The formatter weight.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
*/
public function __construct($plugin_id, DiscoveryInterface $discovery, $instance, array $settings, $weight, $label, $view_mode) {
parent::__construct(array(), $plugin_id, $discovery);
$this->instance = $instance;
$this->field = field_info_field($instance['field_name']);
$this->settings = $settings;
$this->weight = $weight;
$this->label = $label;
$this->viewMode = $view_mode;
}
/**
* Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::view().
*/
public function view(EntityInterface $entity, $langcode, array $items) {
$field = $this->field;
$instance = $this->instance;
$addition = array();
$elements = $this->viewElements($entity, $langcode, $items);
if ($elements) {
$entity_type = $entity->entityType();
$info = array(
'#theme' => 'field',
'#weight' => $this->weight,
'#title' => $instance['label'],
'#access' => field_access($field, 'view', $entity_type, $entity),
'#label_display' => $this->label,
'#view_mode' => $this->viewMode,
'#language' => $langcode,
'#field_name' => $field['field_name'],
'#field_type' => $field['type'],
'#field_translatable' => $field['translatable'],
'#entity_type' => $entity_type,
'#bundle' => $entity->bundle(),
'#object' => $entity,
'#items' => $items,
'#formatter' => $this->getPluginId(),
);
$addition[$field['field_name']] = array_merge($info, $elements);
}
return $addition;
}
/**
* Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
*/
public function settingsForm(array $form, array &$form_state) {
return array();
}
/**
* Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary().
*/
public function settingsSummary() {
return '';
}
/**
* Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView().
*/
public function prepareView(array $entities, $langcode, array &$items) { }
}