From b149df70af393eecd8b66839d53dba9a2efadadf Mon Sep 17 00:00:00 2001 From: webchick Date: Sat, 13 Oct 2012 23:04:15 -0700 Subject: [PATCH] Issue #1785748 by yched, swentel, Stalski: Field formatters as plugins. --- core/modules/field/field.api.php | 249 ++---------------- core/modules/field/field.attach.inc | 184 +++++++++++-- core/modules/field/field.default.inc | 121 --------- core/modules/field/field.info.inc | 45 +--- core/modules/field/field.module | 102 +++---- .../field/lib/Drupal/field/FieldInfo.php | 59 ----- .../field/lib/Drupal/field/FieldInstance.php | 103 +++++++- .../Plugin/Type/Formatter/FormatterBase.php | 147 +++++++++++ .../Type/Formatter/FormatterFactory.php | 24 ++ .../Type/Formatter/FormatterInterface.php | 108 ++++++++ .../FormatterLegacyDiscoveryDecorator.php | 41 +++ .../Type/Formatter/FormatterPluginManager.php | 93 +++++++ .../Plugin/Type/LegacyDiscoveryDecorator.php | 45 ++-- .../Widget/WidgetLegacyDiscoveryDecorator.php | 44 ++++ .../Type/Widget/WidgetPluginManager.php | 4 +- .../field/formatter/LegacyFormatter.php | 122 +++++++++ .../lib/Drupal/field/Tests/FieldInfoTest.php | 23 +- .../formatter/DefaultNumberFormatter.php | 98 +++++++ .../formatter/NumberDecimalFormatter.php | 73 +++++ .../formatter/NumberIntegerFormatter.php | 45 ++++ .../formatter/NumberUnformattedFormatter.php | 44 ++++ .../field/modules/number/number.module | 139 ---------- .../field/formatter/TextDefaultFormatter.php | 45 ++++ .../field/formatter/TextPlainFormatter.php | 44 ++++ .../TextSummaryOrTrimmedFormatter.php | 28 ++ .../field/formatter/TextTrimmedFormatter.php | 82 ++++++ core/modules/field/modules/text/text.module | 120 --------- .../modules/field_test/field_test.field.inc | 138 ---------- .../modules/field_test/field_test.module | 3 +- .../formatter/TestFieldDefaultFormatter.php | 66 +++++ .../formatter/TestFieldMultipleFormatter.php | 70 +++++ .../TestFieldPrepareViewFormatter.php | 80 ++++++ core/modules/field_ui/field_ui.admin.inc | 182 ++++++------- core/modules/field_ui/field_ui.api.php | 93 +------ 34 files changed, 1734 insertions(+), 1130 deletions(-) create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterFactory.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterLegacyDiscoveryDecorator.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php create mode 100644 core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php create mode 100644 core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/DefaultNumberFormatter.php create mode 100644 core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberDecimalFormatter.php create mode 100644 core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberIntegerFormatter.php create mode 100644 core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberUnformattedFormatter.php create mode 100644 core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php create mode 100644 core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php create mode 100644 core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php create mode 100644 core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php create mode 100644 core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php create mode 100644 core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php create mode 100644 core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index b05f4d901ca..22a9ed72b14 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -806,8 +806,8 @@ function hook_field_widget_WIDGET_TYPE_form_alter(&$element, &$form_state, $cont * The instance's widget properties. * @param array $context * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - bundle: The bundle: e.g., 'page' or 'article'. + * - entity_type: The entity type, e.g., 'node' or 'user'. + * - bundle: The bundle, e.g., 'page' or 'article'. * - field: The field that the widget belongs to. * - instance: The instance of the field. * @@ -836,78 +836,28 @@ function hook_field_widget_properties_alter(array &$widget_properties, array $co * which the field is attached is displayed. Fields of a given * @link field_types field type @endlink may be displayed using more than one * formatter. In this case, the Field UI module allows the site builder to - * choose which formatter to use. Field formatters are defined by implementing - * hook_field_formatter_info(). + * choose which formatter to use. + * + * Formatters are Plugins managed by the + * Drupal\field\Plugin\Type\Formatter\FormatterPluginManager class. A formatter + * is implemented by providing a class that implements + * Drupal\field\Plugin\Type\Formatter\FormatterInterface (in most cases, by + * subclassing Drupal\field\Plugin\Type\Formatter\FormatterBase), and provides + * the proper annotation block. * * @see field * @see field_types * @see field_widget */ -/** - * Expose Field API formatter types. - * - * 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. - * - * @return - * An array describing the formatter types implemented by the module. The keys - * are formatter type names. To avoid name clashes, formatter type names - * should be prefixed with the name of the module that exposes them. The - * values are arrays describing the formatter type, with the following - * key/value pairs: - * - label: The human-readable name of the formatter type. - * - description: A short description of the formatter type. - * - field types: An array of field types the formatter supports. - * - settings: An array whose keys are the names of the settings available to - * the formatter type, and whose values are the default values for those - * settings. - * - * @see hook_field_formatter_info_alter() - * @see hook_field_formatter_view() - * @see hook_field_formatter_prepare_view() - */ -function hook_field_formatter_info() { - return array( - 'text_default' => array( - 'label' => t('Default'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - 'text_plain' => array( - 'label' => t('Plain text'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The text_trimmed formatter displays the trimmed version of the - // full element of the field. It is intended to be used with text - // and text_long fields. It also works with text_with_summary - // fields though the text_summary_or_trimmed formatter makes more - // sense for that field type. - 'text_trimmed' => array( - 'label' => t('Trimmed'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The 'summary or trimmed' field formatter for text_with_summary - // fields displays returns the summary element of the field or, if - // the summary is empty, the trimmed version of the full element - // of the field. - 'text_summary_or_trimmed' => array( - 'label' => t('Summary or trimmed'), - 'field types' => array('text_with_summary'), - ), - ); -} - /** * Perform alterations on Field API formatter types. * - * @param $info - * An array of information on formatter types exposed by - * hook_field_formatter_info() implementations. + * @param array $info + * An array of informations on existing formatter types, as collected by the + * annotation discovery mechanism. */ -function hook_field_formatter_info_alter(&$info) { +function hook_field_formatter_info_alter(array &$info) { // Add a setting to a formatter type. $info['text_default']['settings'] += array( 'mymodule_additional_setting' => 'default value', @@ -917,149 +867,6 @@ function hook_field_formatter_info_alter(&$info) { $info['text_default']['field types'][] = 'my_field_type'; } -/** - * 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 - * that displays properties of the referenced entities such as name or type. - * - * This hook is called after the field type's own hook_field_prepare_view(). - * - * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity ID. - * For performance reasons, information for all available entities should be - * loaded in a single query where possible. - * - * Changes or additions to field values are done by alterings the $items - * parameter by reference. - * - * @param $entity_type - * The type of $entity. - * @param $entities - * 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 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 entities, keyed by entity ID. - * @param $displays - * Array of display settings to use for each entity, keyed by entity ID. - */ -function hook_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { - $tids = array(); - - // Collect every possible term attached to any of the fieldable entities. - foreach ($entities as $id => $entity) { - foreach ($items[$id] as $delta => $item) { - // Force the array key to prevent duplicates. - $tids[$item['tid']] = $item['tid']; - } - } - - if ($tids) { - $terms = taxonomy_term_load_multiple($tids); - - // Iterate through the fieldable entities again to attach the loaded term - // data. - foreach ($entities as $id => $entity) { - $rekey = FALSE; - - foreach ($items[$id] as $delta => $item) { - // Check whether the taxonomy term field instance value could be loaded. - if (isset($terms[$item['tid']])) { - // Replace the instance value with the term data. - $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; - } - // Otherwise, unset the instance value, since the term does not exist. - else { - unset($items[$id][$delta]); - $rekey = TRUE; - } - } - - if ($rekey) { - // Rekey the items array. - $items[$id] = array_values($items[$id]); - } - } - } -} - -/** - * Build a renderable array for a field value. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity being displayed. - * @param $field - * The field structure. - * @param $instance - * The field instance. - * @param $langcode - * The language associated with $items. - * @param $items - * Array of values for this field. - * @param $display - * The display settings to use, as found in the 'display' entry of instance - * definitions. The array notably contains the following keys and values: - * - type: The name of the formatter to use. - * - settings: The array of formatter settings. - * - * @return - * A renderable array for $items, as an array of child elements keyed by - * numeric indexes starting from 0. - */ -function hook_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'sample_field_formatter_simple': - // Common case: each value is displayed individually in a sub-element - // keyed by delta. The field.tpl.php template specifies the markup - // wrapping each value. - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['some_setting'] . $item['value']); - } - break; - - case 'sample_field_formatter_themeable': - // More elaborate formatters can defer to a theme function for easier - // customization. - foreach ($items as $delta => $item) { - $element[$delta] = array( - '#theme' => 'mymodule_theme_sample_field_formatter_themeable', - '#data' => $item['value'], - '#some_setting' => $settings['some_setting'], - ); - } - break; - - case 'sample_field_formatter_combined': - // Some formatters might need to display all values within a single piece - // of markup. - $rows = array(); - foreach ($items as $delta => $item) { - $rows[] = array($delta, $item['value']); - } - $element[0] = array( - '#theme' => 'table', - '#header' => array(t('Delta'), t('Value')), - '#rows' => $rows, - ); - break; - } - - return $element; -} - /** * @} End of "defgroup field_formatter". */ @@ -2145,27 +1952,27 @@ function hook_field_info_max_weight($entity_type, $bundle, $context) { * hook involves reading from the database, it is highly recommended to * statically cache the information. * - * @param $display + * @param array $display_properties * The display settings that will be used to display the field values, as * found in the 'display' key of $instance definitions. - * @param $context + * @param array $context * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. + * - entity_type: The entity type, e.g., 'node' or 'user'. + * - bundle: The bundle, e.g., 'page' or 'article'. * - field: The field being rendered. * - instance: The instance being rendered. - * - entity: The entity being rendered. * - view_mode: The view mode, e.g. 'full', 'teaser'... * * @see hook_field_display_ENTITY_TYPE_alter() */ -function hook_field_display_alter(&$display, $context) { +function hook_field_display_alter(array &$display_properties, array $context) { // Leave field labels out of the search index. // Note: The check against $context['entity_type'] == 'node' could be avoided // by using hook_field_display_node_alter() instead of // hook_field_display_alter(), resulting in less function calls when // rendering non-node entities. if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { - $display['label'] = 'hidden'; + $display_properties['label'] = 'hidden'; } } @@ -2180,23 +1987,23 @@ function hook_field_display_alter(&$display, $context) { * hook involves reading from the database, it is highly recommended to * statically cache the information. * - * @param $display + * @param array $display_properties * The display settings that will be used to display the field values, as * found in the 'display' key of $instance definitions. - * @param $context + * @param array $context * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. + * - entity_type: The entity type, e.g., 'node' or 'user'. + * - bundle: The bundle, e.g., 'page' or 'article'. * - field: The field being rendered. * - instance: The instance being rendered. - * - entity: The entity being rendered. * - view_mode: The view mode, e.g. 'full', 'teaser'... * * @see hook_field_display_alter() */ -function hook_field_display_ENTITY_TYPE_alter(&$display, $context) { +function hook_field_display_ENTITY_TYPE_alter(array &$display_properties, array $context) { // Leave field labels out of the search index. if ($context['view_mode'] == 'search_index') { - $display['label'] = 'hidden'; + $display_properties['label'] = 'hidden'; } } @@ -2238,8 +2045,8 @@ function hook_field_extra_fields_display_alter(&$displays, $context) { * The instance's widget properties. * @param array $context * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - bundle: The bundle: e.g., 'page' or 'article'. + * - entity_type: The entity type, e.g., 'node' or 'user'. + * - bundle: The bundle, e.g., 'page' or 'article'. * - field: The field that the widget belongs to. * - instance: The instance of the field. * diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 2278826bc90..d26754678c9 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -107,24 +107,25 @@ const FIELD_STORAGE_INSERT = 'insert'; */ /** - * Invoke a method on all the fields of a given entity. + * Invokes a method on all the fields of a given entity. * * @todo Remove _field_invoke() and friends when field types and formatters are * turned into plugins. * * @param string $method * The name of the method to invoke. - * @param Closure $target - * A closure that receives an $instance object and returns the object on + * @param callable $target_function + * A function that receives an $instance object and returns the object on * which the method should be invoked. * @param Drupal\Core\Entity\EntityInterface $entity * The fully formed $entity_type entity. * @param mixed $a - * A parameter for the invoked method. Defaults to NULL. + * (optional) A parameter for the invoked method. Defaults to NULL. * @param mixed $b - * A parameter for the invoked method. Defaults to NULL. + * (optional) A parameter for the invoked method. Defaults to NULL. * @param array $options - * An associative array of additional options, with the following keys: + * (optional) An associative array of additional options, with the following + * keys: * - field_name: The name of the field whose operation should be invoked. By * default, the operation is invoked on all the fields in the entity's * bundle. NOTE: This option is not compatible with the 'deleted' option; @@ -138,8 +139,11 @@ const FIELD_STORAGE_INSERT = 'insert'; * - langcode: A language code or an array of language codes keyed by field * name. It will be used to narrow down to a single value the available * languages to act on. + * + * @return array + * An array of returned values. */ -function field_invoke_method($method, \Closure $target_closure, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) { +function field_invoke_method($method, $target_function, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) { // Merge default options. $default_options = array( 'deleted' => FALSE, @@ -155,9 +159,9 @@ function field_invoke_method($method, \Closure $target_closure, EntityInterface $return = array(); foreach ($instances as $instance) { - // Let the closure determine the target object on which the method should be + // Let the function determine the target object on which the method should be // called. - $target = $target_closure($instance); + $target = call_user_func($target_function, $instance); if (method_exists($target, $method)) { $field = field_info_field_by_id($instance['field_id']); @@ -195,6 +199,136 @@ function field_invoke_method($method, \Closure $target_closure, EntityInterface return $return; } +/** + * Invokes a method across fields on multiple entities. + * + * @param string $method + * The name of the method to invoke. + * @param callable $target_function + * A function that receives an $instance object and returns the object on + * which the method should be invoked. + * @param array $entities + * An array of entities, keyed by entity ID. + * @param mixed $a + * (optional) A parameter for the invoked method. Defaults to NULL. + * @param mixed $b + * (optional) A parameter for the invoked method. Defaults to NULL. + * @param $options + * (optional) An associative array of additional options, with the following + * keys: + * - field_name: The name of the field whose operation should be invoked. By + * default, the operation is invoked on all the fields in the entity's + * bundle. NOTE: This option is not compatible with the 'deleted' option; + * the 'field_id' option should be used instead. + * - field_id: The ID of the field whose operation should be invoked. By + * default, the operation is invoked on all the fields in the entity's' + * bundles. + * - deleted: If TRUE, the function will operate on deleted fields as well + * as non-deleted fields. If unset or FALSE, only non-deleted fields are + * operated on. + * - langcode: A language code or an array of language codes keyed by field + * name. It will be used to narrow down to a single value the available + * languages to act on. + * + * @return array + * An array of returned values keyed by entity ID. + * + * @see field_invoke_method() + */ +function field_invoke_method_multiple($method, $target_function, array $entities, &$a = NULL, &$b = NULL, array $options = array()) { + // Merge default options. + $default_options = array( + 'deleted' => FALSE, + 'langcode' => NULL, + ); + $options += $default_options; + + $instances = array(); + $grouped_entities = array(); + $grouped_items = array(); + $grouped_targets = array(); + $return = array(); + + // Go through the entities and collect the instances on which the method + // should be called. + foreach ($entities as $entity) { + $id = $entity->id(); + $entity_type = $entity->entityType(); + + // Determine the list of instances to iterate on. + $entity_instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options); + + foreach ($entity_instances as $instance) { + $instance_id = $instance['id']; + $field_name = $instance['field_name']; + + // Let the closure determine the target object on which the method should + // be called. + if (empty($grouped_targets[$instance_id])) { + $grouped_targets[$instance_id] = call_user_func($target_function, $instance); + } + + if (method_exists($grouped_targets[$instance_id], $method)) { + // Add the instance to the list of instances to invoke the hook on. + if (!isset($instances[$instance_id])) { + $instances[$instance_id] = $instance; + } + + // Unless a language code suggestion is provided we iterate on all the + // available language codes. + $field = field_info_field_by_id($instance['field_id']); + $available_langcodes = field_available_languages($entity_type, $field); + $langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode']; + $langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name); + foreach ($langcodes as $langcode) { + // Group the entities and items corresponding to the current field. + $grouped_entities[$instance_id][$langcode][$id] = $entities[$id]; + $grouped_items[$instance_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); + } + } + } + // Initialize the return value for each entity. + $return[$id] = array(); + } + + // For each instance, invoke the method and collect results. + foreach ($instances as $instance_id => $instance) { + $field_name = $instance['field_name']; + + // Iterate over all the field translations. + foreach ($grouped_items[$instance_id] as $langcode => &$items) { + $entities = $grouped_entities[$instance_id][$langcode]; + $results = $grouped_targets[$instance_id]->$method($entities, $langcode, $items, $a, $b); + + if (isset($results)) { + // Collect results by entity. + // For hooks with array results, we merge results together. + // For hooks with scalar results, we collect results in an array. + foreach ($results as $id => $result) { + if (is_array($result)) { + $return[$id] = array_merge($return[$id], $result); + } + else { + $return[$id][] = $result; + } + } + } + } + + // Populate field values back in the entities, but avoid replacing missing + // fields with an empty array (those are not equivalent on update). + foreach ($grouped_entities[$instance_id] as $langcode => $entities) { + foreach ($entities as $id => $entity) { + if ($grouped_items[$instance_id][$langcode][$id] !== array() || isset($entity->{$field_name}[$langcode])) { + $entity->{$field_name}[$langcode] = $grouped_items[$instance_id][$langcode][$id]; + } + } + } + } + + return $return; +} + /** * Invoke a field hook. * @@ -563,12 +697,12 @@ function _field_invoke_get_instances($entity_type, $bundle, $options) { } /** - * Defines a 'target closure' for field_invoke_method(). + * Defines a 'target function' for field_invoke_method(). * * Used to invoke methods on an instance's widget. * - * @return Closure - * A 'target closure' for field_invoke_method(). + * @return callable $target_function + * A 'target function' for field_invoke_method(). */ function _field_invoke_widget_target() { return function ($instance) { @@ -576,6 +710,26 @@ function _field_invoke_widget_target() { }; } +/** + * Defines a 'target function' for field_invoke_method(). + * + * Used to invoke methods on an instance's formatter. + * + * @param mixed $display + * Can be either: + * - The name of a view mode. + * - An array of display properties, as found in + * $instance['display'][$view_mode]. + * + * @return callable $target_function + * A 'target function' for field_invoke_method(). + */ +function _field_invoke_formatter_target($display) { + return function ($instance) use ($display) { + return $instance->getFormatter($display); + }; +} + /** * Adds form elements for all fields for an entity to a form structure. * @@ -1223,9 +1377,7 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod // First let the field types do their preparation. _field_invoke_multiple('prepare_view', $entity_type, $prepare, $null, $null, $options); // Then let the formatters do their own specific massaging. - // field_default_prepare_view() takes care of dispatching to the correct - // formatters according to the display settings for the view mode. - _field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode, $null, $options); + field_invoke_method_multiple('prepareView', _field_invoke_formatter_target($view_mode), $prepare, $view_mode, $null, $options); } /** @@ -1281,7 +1433,7 @@ function field_attach_view($entity_type, EntityInterface $entity, $view_mode, $l // Invoke field_default_view(). $null = NULL; - $output = _field_invoke_default('view', $entity_type, $entity, $view_mode, $null, $options); + $output = field_invoke_method('view', _field_invoke_formatter_target($view_mode), $entity, $view_mode, $null, $options); // Add custom weight handling. $output['#pre_render'][] = '_field_extra_fields_pre_render'; diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc index 961c59be03e..d37d0ac16d0 100644 --- a/core/modules/field/field.default.inc +++ b/core/modules/field/field.default.inc @@ -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. * diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc index 62bf171c65a..323a4faff64 100644 --- a/core/modules/field/field.info.inc +++ b/core/modules/field/field.info.inc @@ -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(); } } diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 1c3c4fbd580..7fac3d24afa 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -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"]); + 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]; - // 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(); - // 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); - // 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); - } + // 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]; + } } } diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php index 02f3b120c89..accbe171a8f 100644 --- a/core/modules/field/lib/Drupal/field/FieldInfo.php +++ b/core/modules/field/lib/Drupal/field/FieldInfo.php @@ -514,68 +514,9 @@ class FieldInfo { $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. * diff --git a/core/modules/field/lib/Drupal/field/FieldInstance.php b/core/modules/field/lib/Drupal/field/FieldInstance.php index 30da0728709..71144dd595a 100644 --- a/core/modules/field/lib/Drupal/field/FieldInstance.php +++ b/core/modules/field/lib/Drupal/field/FieldInstance.php @@ -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 @@ class FieldInstance implements \ArrayAccess { 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 @@ class FieldInstance implements \ArrayAccess { } $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 @@ class FieldInstance implements \ArrayAccess { 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); + } } /** diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php new file mode 100644 index 00000000000..5a23fa4b260 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php @@ -0,0 +1,147 @@ +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) { } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterFactory.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterFactory.php new file mode 100644 index 00000000000..8abf0f1134b --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterFactory.php @@ -0,0 +1,24 @@ +getPluginClass($plugin_id); + return new $plugin_class($plugin_id, $this->discovery, $configuration['instance'], $configuration['settings'], $configuration['weight'], $configuration['label'], $configuration['view_mode']); + } +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php new file mode 100644 index 00000000000..36693b0a2ec --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php @@ -0,0 +1,108 @@ + array(), + 'default_value' => TRUE, + ); + + /** + * The cache bin used for plugin definitions. + * + * @var string + */ + protected $cache_bin = 'field'; + + /** + * The cache id used for plugin definitions. + * + * @var string + */ + protected $cache_id = 'field_formatter_types'; + + /** + * Constructs a FormatterPluginManager object. + */ + public function __construct() { + $this->baseDiscovery = new AlterDecorator(new FormatterLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'formatter')), 'field_formatter_info'); + $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); + + $this->factory = new FormatterFactory($this); + } + + /** + * Clears cached definitions. + * + * @todo Remove when http://drupal.org/node/1764232 is fixed. + */ + public function clearDefinitions() { + // Clear 'static' data by creating a new object. + $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); + cache($this->cache_bin)->delete($this->cache_id); + } + + /** + * Overrides PluginManagerBase::getInstance(). + */ + public function getInstance(array $options) { + $instance = $options['instance']; + $type = $options['type']; + + $definition = $this->getDefinition($type); + $field = field_info_field($instance['field_name']); + + // Switch back to default formatter if either: + // - $type_info doesn't exist (the widget type is unknown), + // - the field type is not allowed for the widget. + if (!isset($definition['class']) || !in_array($field['type'], $definition['field_types'])) { + // Grab the default widget for the field type. + $field_type_definition = field_info_field_types($field['type']); + $type = $field_type_definition['default_formatter']; + } + + $configuration = array( + 'instance' => $instance, + 'settings' => $options['settings'], + 'weight' => $options['weight'], + 'label' => $options['label'], + 'view_mode' => $options['view_mode'], + ); + return $this->createInstance($type, $configuration); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php index 4ef023e8e8d..43aad820e8b 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php @@ -11,14 +11,19 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Core\Plugin\Discovery\HookDiscovery; /** - * Custom decorator to add legacy widgets. + * Custom decorator to add legacy plugins. * - * Legacy widgets are discovered through the old hook_field_widget_info() hook, - * and handled by the Drupal\field\Plugin\field\widget\LegacyWidget class. - * - * @todo Remove once all core widgets have been converted. + * Legacy plugins are discovered through + * Drupal\Core\Plugin\Discovery\HookDiscovery, and handled by a legacy class. */ -class LegacyDiscoveryDecorator implements DiscoveryInterface { +abstract class LegacyDiscoveryDecorator implements DiscoveryInterface { + + /** + * The name of the hook for Drupal\Core\Plugin\Discovery\HookDiscovery. + * + * @var string + */ + protected $hook; /** * The decorated discovery object. @@ -52,28 +57,32 @@ class LegacyDiscoveryDecorator implements DiscoveryInterface { public function getDefinitions() { $definitions = $this->decorated->getDefinitions(); - $legacy_discovery = new HookDiscovery('field_widget_info'); + $legacy_discovery = new HookDiscovery($this->hook); if ($legacy_definitions = $legacy_discovery->getDefinitions()) { - foreach ($legacy_definitions as $plugin_id => &$definition) { - $definition['class'] = '\Drupal\field\Plugin\field\widget\LegacyWidget'; + foreach ($legacy_definitions as $plugin_id => $definition) { + $this->processDefinition($definition); - // Transform properties for which the format has changed. - if (isset($definition['field types'])) { - $definition['field_types'] = $definition['field types']; - unset($definition['field types']); - } - if (isset($definition['behaviors']['multiple values'])) { - $definition['multiple_values'] = $definition['behaviors']['multiple values']; - unset($definition['behaviors']['multiple values']); - } if (isset($definition['behaviors']['default value'])) { $definition['default_value'] = $definition['behaviors']['default value']; unset($definition['behaviors']['default value']); } + $definitions[$plugin_id] = $definition; } } return $definitions; } + /** + * Massages a legacy plugin definition. + * + * @var array $definition + * A plugin definition, as discovered by + * Drupal\Core\Plugin\Discovery\HookDiscovery. + * + * @return array + * The massaged plugin definition. + */ + abstract public function processDefinition(array &$definition); + } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php new file mode 100644 index 00000000000..48dee26a68a --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php @@ -0,0 +1,44 @@ +baseDiscovery = new LegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget')); + $this->baseDiscovery = new WidgetLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget')); $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); $this->factory = new WidgetFactory($this); diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php b/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php new file mode 100644 index 00000000000..4f2c4f5d0d6 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php @@ -0,0 +1,122 @@ +getDefinition(); + $function = $definition['module'] . '_field_formatter_settings_form'; + + // hook_field_formatter_settings_form() implementations read display + // properties directly from $instance. Put the actual properties we use + // here. + $instance = clone $this->instance; + $instance['display'][$this->viewMode] = array( + 'type' => $this->getPluginId(), + 'settings' => $this->getSettings(), + 'weight' => $this->weight, + 'label' => $this->label, + ); + + if (function_exists($function)) { + return $function($this->field, $instance, $this->viewMode, $form, $form_state); + } + return array(); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary(). + */ + public function settingsSummary() { + $definition = $this->getDefinition(); + $function = $definition['module'] . '_field_formatter_settings_summary'; + + // hook_field_formatter_settings_summary() implementations read display + // properties directly from $instance. Put the actual properties we use + // here. + $instance = clone $this->instance; + $instance['display'][$this->viewMode] = array( + 'type' => $this->getPluginId(), + 'settings' => $this->getSettings(), + 'weight' => $this->weight, + 'label' => $this->label, + ); + + if (function_exists($function)) { + return $function($this->field, $instance, $this->viewMode); + } + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView(). + */ + public function prepareView(array $entities, $langcode, array &$items) { + $definition = $this->getDefinition(); + $function = $definition['module'] . '_field_formatter_prepare_view'; + if (function_exists($function)) { + // Grab the entity type from the first entity. + $entity = current($entities); + $entity_type = $entity->entityType(); + + // hook_field_formatter_prepare_view() received an array of display properties, + // for each entity (the same hook could end up being called for different formatters, + // since one hook implementation could provide several formatters). + $display = array( + 'type' => $this->getPluginId(), + 'settings' => $this->getSettings(), + 'weight' => $this->weight, + 'label' => $this->label, + ); + $displays = array(); + foreach ($entities as $entity) { + $displays[$entity->id()] = $display; + } + + $function($entity_type, $entities, $this->field, $this->instance, $langcode, $items, $displays); + } + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $definition = $this->getDefinition(); + $function = $definition['module'] . '_field_formatter_view'; + if (function_exists($function)) { + // hook_field_formatter_view() received an array of display properties, + $display = array( + 'type' => $this->getPluginId(), + 'settings' => $this->getSettings(), + 'weight' => $this->weight, + 'label' => $this->label, + ); + + return $function($entity->entityType(), $entity, $this->field, $this->instance, $langcode, $items, $display); + } + } + +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php index d5e35692deb..dc4397fa981 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php @@ -43,15 +43,6 @@ class FieldInfoTest extends FieldTestBase { $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears")); } - $formatter_info = field_test_field_formatter_info(); - $info = field_info_formatter_types(); - foreach ($formatter_info as $f_key => $formatter) { - foreach ($formatter as $key => $val) { - $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val")); - } - $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears")); - } - $storage_info = field_test_field_storage_info(); $info = field_info_storage_types(); foreach ($storage_info as $s_key => $storage) { @@ -205,10 +196,10 @@ class FieldInfoTest extends FieldTestBase { $this->assertIdentical($widget->getSettings(), $widget_type['settings'] , 'All expected widget settings are present.'); // Check that display settings are set for the 'default' mode. - $display = $instance['display']['default']; - $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); - $formatter_type = field_info_formatter_types($display['type']); - $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); + $formatter = $instance->getFormatter('default'); + $this->assertIdentical($formatter->getPluginId(), $field_type['default_formatter'], "Formatter is set for the 'default' view mode"); + $formatter_type = $formatter->getDefinition(); + $this->assertIdentical($formatter->getSettings(), $formatter_type['settings'] , "Formatter settings are set for the 'default' view mode"); } @@ -329,9 +320,9 @@ class FieldInfoTest extends FieldTestBase { $this->assertIdentical(field_info_widget_settings($type), $info['settings'], "field_info_widget_settings returns {$type}'s widget settings"); } - $info = field_test_field_formatter_info(); - foreach ($info as $type => $data) { - $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); + foreach (array('field_test_default', 'field_test_multiple', 'field_test_with_prepare_view') as $type) { + $info = field_info_formatter_types($type); + $this->assertIdentical(field_info_formatter_settings($type), $info['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); } } } diff --git a/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/DefaultNumberFormatter.php b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/DefaultNumberFormatter.php new file mode 100644 index 00000000000..29e4dbf2ccf --- /dev/null +++ b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/DefaultNumberFormatter.php @@ -0,0 +1,98 @@ + t(''), + '.' => t('Decimal point'), + ',' => t('Comma'), + ' ' => t('Space'), + chr(8201) => t('Thin space'), + "'" => t('Apostrophe'), + ); + $elements['thousand_separator'] = array( + '#type' => 'select', + '#title' => t('Thousand marker'), + '#options' => $options, + '#default_value' => $this->getSetting('thousand_separator'), + '#weight' => 0, + ); + + $elements['prefix_suffix'] = array( + '#type' => 'checkbox', + '#title' => t('Display prefix and suffix.'), + '#default_value' => $this->getSetting('prefix_suffix'), + '#weight' => 10, + ); + + return $elements; + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm(). + */ + public function settingsSummary() { + $summary = array(); + + $summary[] = $this->numberFormat(1234.1234567890); + if ($this->getSetting('prefix_suffix')) { + $summary[] = t('Display with prefix and suffix.'); + } + + return implode('
', $summary); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $elements = array(); + + foreach ($items as $delta => $item) { + $output = $this->numberFormat($item['value']); + + // Account for prefix and suffix. + if ($this->getSetting('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($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0]; + $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0]; + $output = $prefix . $output . $suffix; + } + + $elements[$delta] = array('#markup' => $output); + } + + return $elements; + } + + /** + * Formats a number. + * + * @param mixed $number + * The numeric value. + * + * @return string + * The formatted number. + */ + abstract protected function numberFormat($number); +} diff --git a/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberDecimalFormatter.php b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberDecimalFormatter.php new file mode 100644 index 00000000000..ac03e0e00e7 --- /dev/null +++ b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberDecimalFormatter.php @@ -0,0 +1,73 @@ + 'select', + '#title' => t('Decimal marker'), + '#options' => array('.' => t('Decimal point'), ',' => t('Comma')), + '#default_value' => $this->getSetting('decimal_separator'), + 'weight' => 5, + ); + $elements['scale'] = array( + '#type' => 'select', + '#title' => t('Scale'), + '#options' => drupal_map_assoc(range(0, 10)), + '#default_value' => $this->getSetting('scale'), + '#description' => t('The number of digits to the right of the decimal.'), + 'weight' => 6, + ); + + return $elements; + } + + /** + * Overrides Drupal\number\Plugin\field\formatter\DefaultNumberFormatter::numberFormat(). + */ + protected function numberFormat($number) { + return number_format($number, $this->getSetting('scale'), $this->getSetting('decimal_separator'), $this->getSetting('thousand_separator')); + } + +} diff --git a/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberIntegerFormatter.php b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberIntegerFormatter.php new file mode 100644 index 00000000000..3979de3490b --- /dev/null +++ b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberIntegerFormatter.php @@ -0,0 +1,45 @@ +getSetting('thousand_separator')); + } + +} diff --git a/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberUnformattedFormatter.php b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberUnformattedFormatter.php new file mode 100644 index 00000000000..30e1f18f7b5 --- /dev/null +++ b/core/modules/field/modules/number/lib/Drupal/number/Plugin/field/formatter/NumberUnformattedFormatter.php @@ -0,0 +1,44 @@ + $item) { + $elements[$delta] = array('#markup' => $item['value']); + } + + return $elements; + } + +} diff --git a/core/modules/field/modules/number/number.module b/core/modules/field/modules/number/number.module index 6eb729a481c..85d87fdb3ba 100644 --- a/core/modules/field/modules/number/number.module +++ b/core/modules/field/modules/number/number.module @@ -165,142 +165,3 @@ function number_field_is_empty($item, $field) { } return FALSE; } - -/** - * Implements hook_field_formatter_info(). - */ -function number_field_formatter_info() { - return array( - // The 'Default' formatter is different for integer fields on the one hand, - // and for decimal and float fields on the other hand, in order to be able - // to use different default values for the settings. - 'number_integer' => array( - 'label' => t('Default'), - 'field types' => array('number_integer'), - 'settings' => array( - 'thousand_separator' => '', - // The 'decimal_separator' and 'scale' settings are not configurable - // through the UI, and will therefore keep their default values. They - // are only present so that the 'number_integer' and 'number_decimal' - // formatters can use the same code. - 'decimal_separator' => '.', - 'scale' => 0, - 'prefix_suffix' => TRUE, - ), - ), - 'number_decimal' => array( - 'label' => t('Default'), - 'field types' => array('number_decimal', 'number_float'), - 'settings' => array( - 'thousand_separator' => '', - 'decimal_separator' => '.', - 'scale' => 2, - 'prefix_suffix' => TRUE, - ), - ), - 'number_unformatted' => array( - 'label' => t('Unformatted'), - 'field types' => array('number_integer', 'number_decimal', 'number_float'), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function number_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { - $options = array( - '' => t(''), - '.' => t('Decimal point'), - ',' => t('Comma'), - ' ' => t('Space'), - chr(8201) => t('Thin space'), - "'" => t('Apostrophe'), - ); - $element['thousand_separator'] = array( - '#type' => 'select', - '#title' => t('Thousand marker'), - '#options' => $options, - '#default_value' => $settings['thousand_separator'], - ); - - if ($display['type'] == 'number_decimal') { - $element['decimal_separator'] = array( - '#type' => 'select', - '#title' => t('Decimal marker'), - '#options' => array('.' => t('Decimal point'), ',' => t('Comma')), - '#default_value' => $settings['decimal_separator'], - ); - $element['scale'] = array( - '#type' => 'select', - '#title' => t('Scale'), - '#options' => drupal_map_assoc(range(0, 10)), - '#default_value' => $settings['scale'], - '#description' => t('The number of digits to the right of the decimal.'), - ); - } - - $element['prefix_suffix'] = array( - '#type' => 'checkbox', - '#title' => t('Display prefix and suffix.'), - '#default_value' => $settings['prefix_suffix'], - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function number_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = array(); - if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { - $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']); - if ($settings['prefix_suffix']) { - $summary[] = t('Display with prefix and suffix.'); - } - } - - return implode('
', $summary); -} - -/** - * Implements hook_field_formatter_view(). - */ -function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'number_integer': - case 'number_decimal': - foreach ($items as $delta => $item) { - $output = number_format($item['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($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0]; - $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0]; - $output = $prefix . $output . $suffix; - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'number_unformatted': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $item['value']); - } - break; - } - - return $element; -} diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php new file mode 100644 index 00000000000..6b34ba94779 --- /dev/null +++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php @@ -0,0 +1,45 @@ + $item) { + $output = _text_sanitize($this->instance, $langcode, $item, 'value'); + $elements[$delta] = array('#markup' => $output); + } + + return $elements; + } + +} diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php new file mode 100644 index 00000000000..0f7b615da15 --- /dev/null +++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php @@ -0,0 +1,44 @@ + $item) { + $elements[$delta] = array('#markup' => strip_tags($item['value'])); + } + + return $elements; + } + +} diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php new file mode 100644 index 00000000000..11f0c14542d --- /dev/null +++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php @@ -0,0 +1,28 @@ + t('Trim length'), + '#type' => 'number', + '#default_value' => $this->getSetting('trim_length'), + '#min' => 1, + '#required' => TRUE, + ); + return $element; + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary(). + */ + public function settingsSummary() { + return t('Trim length: @trim_length', array( + '@trim_length' => $this->getSetting('trim_length'), + )); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $elements = array(); + + foreach ($items as $delta => $item) { + if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item['summary'])) { + $output = _text_sanitize($this->instance, $langcode, $item, 'summary'); + } + else { + $output = _text_sanitize($this->instance, $langcode, $item, 'value'); + $output = text_summary($output, $this->instance['settings']['text_processing'] ? $item['format'] : NULL, $this->getSetting('trim_length')); + } + $elements[$delta] = array('#markup' => $output); + } + + return $elements; + } + +} diff --git a/core/modules/field/modules/text/text.module b/core/modules/field/modules/text/text.module index f362fb76b11..efb00792cf8 100644 --- a/core/modules/field/modules/text/text.module +++ b/core/modules/field/modules/text/text.module @@ -150,8 +150,6 @@ function text_field_validate($entity_type, $entity, $field, $instance, $langcode * Where possible, the function generates the sanitized version of each field * early so that it is cached in the field cache. This avoids the need to look * up the field in the filter cache separately. - * - * @see text_field_formatter_view() */ function text_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) { foreach ($entities as $id => $entity) { @@ -178,124 +176,6 @@ function text_field_is_empty($item, $field) { return FALSE; } -/** - * Implements hook_field_formatter_info(). - */ -function text_field_formatter_info() { - return array( - 'text_default' => array( - 'label' => t('Default'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - 'text_plain' => array( - 'label' => t('Plain text'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The text_trimmed formatter displays the trimmed version of the - // full element of the field. It is intended to be used with text - // and text_long fields. It also works with text_with_summary - // fields though the text_summary_or_trimmed formatter makes more - // sense for that field type. - 'text_trimmed' => array( - 'label' => t('Trimmed'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - 'settings' => array('trim_length' => 600), - ), - - // The 'summary or trimmed' field formatter for text_with_summary - // fields displays returns the summary element of the field or, if - // the summary is empty, the trimmed version of the full element - // of the field. - 'text_summary_or_trimmed' => array( - 'label' => t('Summary or trimmed'), - 'field types' => array('text_with_summary'), - 'settings' => array('trim_length' => 600), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function text_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $element = array(); - - if (strpos($display['type'], '_trimmed') !== FALSE) { - $element['trim_length'] = array( - '#title' => t('Trim length'), - '#type' => 'number', - '#default_value' => $settings['trim_length'], - '#min' => 1, - '#required' => TRUE, - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function text_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = ''; - - if (strpos($display['type'], '_trimmed') !== FALSE) { - $summary = t('Trim length: @trim_length', array( - '@trim_length' => $settings['trim_length'], - )); - } - - return $summary; -} - -/** - * Implements hook_field_formatter_view(). - */ -function text_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - - switch ($display['type']) { - case 'text_default': - case 'text_trimmed': - foreach ($items as $delta => $item) { - $output = _text_sanitize($instance, $langcode, $item, 'value'); - if ($display['type'] == 'text_trimmed') { - $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']); - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'text_summary_or_trimmed': - foreach ($items as $delta => $item) { - if (!empty($item['summary'])) { - $output = _text_sanitize($instance, $langcode, $item, 'summary'); - } - else { - $output = _text_sanitize($instance, $langcode, $item, 'value'); - $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']); - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'text_plain': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => strip_tags($item['value'])); - } - break; - } - - return $element; -} - /** * Sanitizes the 'value' or 'summary' data of a text value. * diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc index b2ed35ccbbd..44d22c19021 100644 --- a/core/modules/field/tests/modules/field_test/field_test.field.inc +++ b/core/modules/field/tests/modules/field_test/field_test.field.inc @@ -174,144 +174,6 @@ function field_test_widget_multiple_validate($element, &$form_state) { form_set_value($element, $items, $form_state); } -/** - * Implements hook_field_formatter_info(). - */ -function field_test_field_formatter_info() { - return array( - 'field_test_default' => array( - 'label' => t('Default'), - 'description' => t('Default formatter'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting' => 'dummy test string', - ), - ), - 'field_test_multiple' => array( - 'label' => t('Multiple'), - 'description' => t('Multiple formatter'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting_multiple' => 'dummy test string', - ), - ), - 'field_test_with_prepare_view' => array( - 'label' => t('Tests hook_field_formatter_prepare_view()'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting_additional' => 'dummy test string', - ), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function field_test_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $element = array(); - - // The name of the setting depends on the formatter type. - $map = array( - 'field_test_default' => 'test_formatter_setting', - 'field_test_multiple' => 'test_formatter_setting_multiple', - 'field_test_with_prepare_view' => 'test_formatter_setting_additional', - ); - - if (isset($map[$display['type']])) { - $name = $map[$display['type']]; - - $element[$name] = array( - '#title' => t('Setting'), - '#type' => 'textfield', - '#size' => 20, - '#default_value' => $settings[$name], - '#required' => TRUE, - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function field_test_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = ''; - - // The name of the setting depends on the formatter type. - $map = array( - 'field_test_default' => 'test_formatter_setting', - 'field_test_multiple' => 'test_formatter_setting_multiple', - 'field_test_with_prepare_view' => 'test_formatter_setting_additional', - ); - - if (isset($map[$display['type']])) { - $name = $map[$display['type']]; - $summary = t('@setting: @value', array('@setting' => $name, '@value' => $settings[$name])); - } - - return $summary; -} - -/** - * Implements hook_field_formatter_prepare_view(). - */ -function field_test_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { - foreach ($items as $id => $item) { - // To keep the test non-intrusive, only act on the - // 'field_test_with_prepare_view' formatter. - if ($displays[$id]['type'] == 'field_test_with_prepare_view') { - foreach ($item as $delta => $value) { - // Don't add anything on empty values. - if ($value) { - $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1; - } - } - } - } -} - -/** - * Implements hook_field_formatter_view(). - */ -function field_test_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'field_test_default': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['test_formatter_setting'] . '|' . $item['value']); - } - break; - - case 'field_test_with_prepare_view': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['test_formatter_setting_additional'] . '|' . $item['value'] . '|' . $item['additional_formatter_value']); - } - break; - - case 'field_test_multiple': - if (!empty($items)) { - $array = array(); - foreach ($items as $delta => $item) { - $array[] = $delta . ':' . $item['value']; - } - $element[0] = array('#markup' => $settings['test_formatter_setting_multiple'] . '|' . implode('|', $array)); - } - break; - } - - return $element; -} - /** * Sample 'default value' callback. */ diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module index c2ddebb9c44..8c25b1d3e61 100644 --- a/core/modules/field/tests/modules/field_test/field_test.module +++ b/core/modules/field/tests/modules/field_test/field_test.module @@ -285,11 +285,10 @@ function field_test_query_efq_table_prefixing_test_alter(&$query) { * Implements hook_field_formatter_settings_form_alter(). */ function field_test_field_formatter_settings_form_alter(&$element, &$form_state, $context) { - $settings = $context['instance']['display'][$context['view_mode']]['settings']; $element['field_test_formatter_settings_form_alter'] = array( '#type' => 'textfield', '#title' => t('Formatter settings form alter'), - '#default_value' => isset($settings['field_test_formatter_settings_form_alter']) ? $settings['field_test_formatter_settings_form_alter'] : '', + '#default_value' => $context['formatter']->getSetting('field_test_formatter_settings_form_alter'), ); } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php new file mode 100644 index 00000000000..e1782a0e546 --- /dev/null +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php @@ -0,0 +1,66 @@ + t('Setting'), + '#type' => 'textfield', + '#size' => 20, + '#default_value' => $this->getSetting('test_formatter_setting'), + '#required' => TRUE, + ); + return $element; + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm(). + */ + public function settingsSummary() { + return t('@setting: @value', array('@setting' => 'test_formatter_setting', '@value' => $this->getSetting('test_formatter_setting'))); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $elements = array(); + + foreach ($items as $delta => $item) { + $elements[$delta] = array('#markup' => $this->getSetting('test_formatter_setting') . '|' . $item['value']); + } + + return $elements; + } +} diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php new file mode 100644 index 00000000000..6be0e28fccf --- /dev/null +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php @@ -0,0 +1,70 @@ + t('Setting'), + '#type' => 'textfield', + '#size' => 20, + '#default_value' => $this->getSetting('test_formatter_setting_multiple'), + '#required' => TRUE, + ); + return $element; + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm(). + */ + public function settingsSummary() { + return t('@setting: @value', array('@setting' => 'test_formatter_setting_multiple', '@value' => $this->getSetting('test_formatter_setting_multiple'))); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $elements = array(); + + if (!empty($items)) { + $array = array(); + foreach ($items as $delta => $item) { + $array[] = $delta . ':' . $item['value']; + } + $elements[0] = array('#markup' => $this->getSetting('test_formatter_setting_multiple') . '|' . implode('|', $array)); + } + + return $elements; + } +} diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php new file mode 100644 index 00000000000..34316c793af --- /dev/null +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php @@ -0,0 +1,80 @@ + t('Setting'), + '#type' => 'textfield', + '#size' => 20, + '#default_value' => $this->getSetting('test_formatter_setting_additional'), + '#required' => TRUE, + ); + return $element; + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm(). + */ + public function settingsSummary() { + return t('@setting: @value', array('@setting' => 'test_formatter_setting_additional', '@value' => $this->getSetting('test_formatter_setting_additional'))); + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView(). + */ + public function prepareView(array $entities, $langcode, array &$items) { + foreach ($items as $id => $item) { + foreach ($item as $delta => $value) { + // Don't add anything on empty values. + if ($value) { + $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1; + } + } + } + } + + /** + * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements(). + */ + public function viewElements(EntityInterface $entity, $langcode, array $items) { + $elements = array(); + + foreach ($items as $delta => $item) { + $elements[$delta] = array('#markup' => $this->getSetting('test_formatter_setting_additional') . '|' . $item['value'] . '|' . $item['additional_formatter_value']); + } + + return $elements; + } +} diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc index 57f5132ab71..bd605e54224 100644 --- a/core/modules/field_ui/field_ui.admin.inc +++ b/core/modules/field_ui/field_ui.admin.inc @@ -960,7 +960,20 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund // Field rows. foreach ($instances as $name => $instance) { $field = field_info_field($instance['field_name']); - $display = $instance['display'][$view_mode]; + + // Provide default settings if needed. + if (isset($instance['display'][$view_mode])) { + $display = $instance['display'][$view_mode]; + } + else { + $display = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + } + $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'field', @@ -1025,23 +1038,12 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund // Check the currently selected formatter, and merge persisted values for // formatter settings. if (isset($form_state['values']['fields'][$name]['type'])) { - $formatter_type = $form_state['values']['fields'][$name]['type']; - } - else { - $formatter_type = $display['type']; + $display['type'] = $form_state['values']['fields'][$name]['type']; } if (isset($form_state['formatter_settings'][$name])) { - $settings = $form_state['formatter_settings'][$name]; + $display['settings'] = $form_state['formatter_settings'][$name]; } - else { - $settings = $display['settings']; - } - $settings += field_info_formatter_settings($formatter_type); - - $instance['display'][$view_mode]['type'] = $formatter_type; - $formatter = field_info_formatter_types($formatter_type); - $instance['display'][$view_mode]['module'] = $formatter['module']; - $instance['display'][$view_mode]['settings'] = $settings; + $formatter = $instance->getFormatter($display); // Base button element for the various formatter settings actions. $base_button = array( @@ -1059,86 +1061,89 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund // settings form and submit buttons. $table[$name]['format']['settings_edit_form'] = array(); - $settings_form = array(); - $function = $formatter['module'] . '_field_formatter_settings_form'; - if (function_exists($function)) { - $settings_form = $function($field, $instance, $view_mode, $form, $form_state); - } + if ($formatter) { + $formatter_type_info = $formatter->getDefinition(); - // Allow other modules to alter the formatter settings form. - $context = array( - 'module' => $formatter['module'], - 'formatter' => $formatter, - 'field' => $field, - 'instance' => $instance, - 'view_mode' => $view_mode, - 'form' => $form, - ); - drupal_alter('field_formatter_settings_form', $settings_form, $form_state, $context); - - if ($settings_form) { - $table[$name]['format']['#cell_attributes'] = array('colspan' => 3); - $table[$name]['format']['settings_edit_form'] = array( - '#type' => 'container', - '#attributes' => array('class' => array('field-formatter-settings-edit-form')), - '#parents' => array('fields', $name, 'settings_edit_form'), - 'label' => array( - '#markup' => t('Format settings:') . ' ' . $formatter['label'] . '', - ), - 'settings' => $settings_form, - 'actions' => array( - '#type' => 'actions', - 'save_settings' => $base_button + array( - '#type' => 'submit', - '#name' => $name . '_formatter_settings_update', - '#value' => t('Update'), - '#op' => 'update', - ), - 'cancel_settings' => $base_button + array( - '#type' => 'submit', - '#name' => $name . '_formatter_settings_cancel', - '#value' => t('Cancel'), - '#op' => 'cancel', - // Do not check errors for the 'Cancel' button, but make sure we - // get the value of the 'formatter type' select. - '#limit_validation_errors' => array(array('fields', $name, 'type')), - ), - ), + // Generate the settings form and allow other modules to alter it. + $settings_form = $formatter->settingsForm($form, $form_state); + $context = array( + 'formatter' => $formatter, + 'field' => $field, + 'instance' => $instance, + 'view_mode' => $view_mode, + 'form' => $form, ); - $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; + drupal_alter('field_formatter_settings_form', $settings_form, $form_state, $context); + + if ($settings_form) { + $table[$name]['format']['#cell_attributes'] = array('colspan' => 3); + $table[$name]['format']['settings_edit_form'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('field-formatter-settings-edit-form')), + '#parents' => array('fields', $name, 'settings_edit_form'), + 'label' => array( + '#markup' => t('Format settings:') . ' ' . $formatter_type_info['label'] . '', + ), + 'settings' => $settings_form, + 'actions' => array( + '#type' => 'actions', + 'save_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_update', + '#value' => t('Update'), + '#op' => 'update', + ), + 'cancel_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_cancel', + '#value' => t('Cancel'), + '#op' => 'cancel', + // Do not check errors for the 'Cancel' button, but make sure we + // get the value of the 'formatter type' select. + '#limit_validation_errors' => array(array('fields', $name, 'type')), + ), + ), + ); + $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; + } } } else { - // Display a summary of the current formatter settings. - $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode); - - // Allow other modules to alter the summary. - $context = array( - 'field' => $field, - 'instance' => $instance, - 'view_mode' => $view_mode, - ); - drupal_alter('field_formatter_settings_summary', $summary, $context); - $table[$name]['settings_summary'] = array(); $table[$name]['settings_edit'] = array(); - if ($summary) { - $table[$name]['settings_summary'] = array( - '#markup' => '
' . $summary . '
', - '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), - ); - $table[$name]['settings_edit'] = $base_button + array( - '#type' => 'image_button', - '#name' => $name . '_formatter_settings_edit', - '#src' => 'core/misc/configure-dark.png', - '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), - '#op' => 'edit', - // Do not check errors for the 'Edit' button, but make sure we get - // the value of the 'formatter type' select. - '#limit_validation_errors' => array(array('fields', $name, 'type')), - '#prefix' => '
', - '#suffix' => '
', + + if ($formatter) { + // Display a summary of the current formatter settings, and (if the + // summary is not empty) a button to edit them. + $summary = $formatter->settingsSummary(); + + // Allow other modules to alter the summary. + $context = array( + 'formatter' => $formatter, + 'field' => $field, + 'instance' => $instance, + 'view_mode' => $view_mode, ); + drupal_alter('field_formatter_settings_summary', $summary, $context); + + if ($summary) { + $table[$name]['settings_summary'] = array( + '#markup' => '
' . $summary . '
', + '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), + ); + $table[$name]['settings_edit'] = $base_button + array( + '#type' => 'image_button', + '#name' => $name . '_formatter_settings_edit', + '#src' => 'core/misc/configure-dark.png', + '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), + '#op' => 'edit', + // Do not check errors for the 'Edit' button, but make sure we get + // the value of the 'formatter type' select. + '#limit_validation_errors' => array(array('fields', $name, 'type')), + '#prefix' => '
', + '#suffix' => '
', + ); + } } } } @@ -1265,7 +1270,6 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund return $form; } - /** * Form submission handler for buttons in field_ui_display_overview_form(). */ @@ -1540,7 +1544,7 @@ function field_ui_formatter_options($field_type = NULL) { $field_types = field_info_field_types(); $options = array(); foreach (field_info_formatter_types() as $name => $formatter) { - foreach ($formatter['field types'] as $formatter_field_type) { + foreach ($formatter['field_types'] as $formatter_field_type) { // Check that the field type exists. if (isset($field_types[$formatter_field_type])) { $options[$formatter_field_type][$name] = $formatter['label']; diff --git a/core/modules/field_ui/field_ui.api.php b/core/modules/field_ui/field_ui.api.php index 60388f13194..cb553622240 100644 --- a/core/modules/field_ui/field_ui.api.php +++ b/core/modules/field_ui/field_ui.api.php @@ -90,43 +90,6 @@ function hook_field_instance_settings_form($field, $instance) { return $form; } -/** - * Specify the form elements for a formatter's settings. - * - * @param $field - * The field structure being configured. - * @param $instance - * The instance structure being configured. - * @param $view_mode - * The view mode being configured. - * @param $form - * The (entire) configuration form array, which will usually have no use here. - * @param $form_state - * The form state of the (entire) configuration form. - * - * @return - * The form elements for the formatter settings. - */ -function hook_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $element = array(); - - if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') { - $element['trim_length'] = array( - '#title' => t('Length'), - '#type' => 'number', - '#default_value' => $settings['trim_length'], - '#min' => 1, - '#required' => TRUE, - ); - } - - return $element; - -} - /** * Alter the formatter settings form. * @@ -136,21 +99,19 @@ function hook_field_formatter_settings_form($field, $instance, $view_mode, $form * The form state of the (entire) configuration form. * @param $context * An associative array with the following elements: - * - 'module': The module that contains the definition of this formatter. - * - 'formatter': The formatter type description array. - * - 'field': The field structure being configured. - * - 'instance': The instance structure being configured. - * - 'view_mode': The view mode being configured. - * - 'form': The (entire) configuration form array. + * - formatter: The formatter object. + * - field: The field structure being configured. + * - instance: The instance structure being configured. + * - view_mode: The view mode being configured. + * - form: The (entire) configuration form array. */ function hook_field_formatter_settings_form_alter(&$element, &$form_state, $context) { - // Add a mysetting checkbox to the settings form for foo_field fields. + // Add a 'mysetting' checkbox to the settings form for 'foo_field' fields. if ($context['field']['type'] == 'foo_field') { - $display = $context['instance']['display'][$context['view_mode']]; $element['mysetting'] = array( '#type' => 'checkbox', '#title' => t('My setting'), - '#default_value' => $display['settings']['mysetting'], + '#default_value' => $context['formatter']->getSetting('mysetting'), ); } } @@ -162,51 +123,21 @@ function hook_field_formatter_settings_form_alter(&$element, &$form_state, $cont * The summary as returned by hook_field_formatter_settings_summary(). * @param $context * An associative array with the following elements: - * - 'field': The field structure being configured. - * - 'instance': The instance structure being configured. - * - 'view_mode': The view mode being configured. + * - formatter: The formatter object. + * - field: The field structure being configured. + * - instance: The instance structure being configured. + * - view_mode: The view mode being configured. */ function hook_field_formatter_settings_summary_alter(&$summary, $context) { // Append a message to the summary when an instance of foo_field has // mysetting set to TRUE for the current view mode. if ($context['field']['type'] == 'foo_field') { - $display = $context['instance']['display'][$context['view_mode']]; - if ($display['settings']['mysetting']) { + if ($context['formatter']->getSetting('mysetting')) { $summary .= '
' . t('My setting enabled.'); } } } -/** - * Return a short summary for the current formatter settings of an instance. - * - * If an empty result is returned, the formatter is assumed to have no - * configurable settings, and no UI will be provided to display a settings - * form. - * - * @param $field - * The field structure. - * @param $instance - * The instance structure. - * @param $view_mode - * The view mode for which a settings summary is requested. - * - * @return - * A string containing a short summary of the formatter settings. - */ -function hook_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = ''; - - if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') { - $summary = t('Length: @chars chars', array('@chars' => $settings['trim_length'])); - } - - return $summary; -} - /** * @} End of "addtogroup field_types". */