Commit c63f99d6 authored by webchick's avatar webchick
Browse files

Issue #1785256 by yched, Stalski, zuuperman, sun, tstoeckler, aspilicious,...

Issue #1785256 by yched, Stalski, zuuperman, sun, tstoeckler, aspilicious, EclipseGc, larowlan: Widgets as Plugins.
parent 66b28910
......@@ -689,193 +689,40 @@ function hook_field_is_empty($item, $field) {
* Field API widgets specify how fields are displayed in edit forms. Fields of a
* given @link field_types field type @endlink may be edited using more than one
* widget. In this case, the Field UI module allows the site builder to choose
* which widget to use. Widget types are defined by implementing
* hook_field_widget_info().
* which widget to use.
*
* Widgets are Plugins managed by the
* Drupal\field\Plugin\Type\Widget\WidgetPluginManager class. A widget is
* implemented by providing a class that implements
* Drupal\field\Plugin\Type\Widget\WidgetInterface (in most cases, by
* subclassing Drupal\field\Plugin\Type\Widget\WidgetBase), and provides the
* proper annotation block.
*
* Widgets are @link forms_api_reference.html Form API @endlink
* elements with additional processing capabilities. Widget hooks are typically
* called by the Field Attach API during the creation of the field form
* structure with field_attach_form().
* elements with additional processing capabilities. The methods of the
* WidgetInterface object are typically called by the Field Attach API during
* the creation of the field form structure with field_attach_form().
*
* @see field
* @see field_types
* @see field_formatter
*/
/**
* Expose Field API widget types.
*
* @return
* An array describing the widget types implemented by the module.
* The keys are widget type names. To avoid name clashes, widget type
* names should be prefixed with the name of the module that exposes them.
* The values are arrays describing the widget type, with the following
* key/value pairs:
* - label: The human-readable name of the widget type.
* - description: A short description for the widget type.
* - field types: An array of field types the widget supports.
* - settings: An array whose keys are the names of the settings available
* for the widget type, and whose values are the default values for those
* settings.
* - behaviors: (optional) An array describing behaviors of the widget, with
* the following elements:
* - multiple values: One of the following constants:
* - FIELD_BEHAVIOR_DEFAULT: (default) If the widget allows the input of
* one single field value (most common case). The widget will be
* repeated for each value input.
* - FIELD_BEHAVIOR_CUSTOM: If one single copy of the widget can receive
* several field values. Examples: checkboxes, multiple select,
* comma-separated textfield.
* - default value: One of the following constants:
* - FIELD_BEHAVIOR_DEFAULT: (default) If the widget accepts default
* values.
* - FIELD_BEHAVIOR_NONE: if the widget does not support default values.
* - weight: (optional) An integer to determine the weight of this widget
* relative to other widgets in the Field UI when selecting a widget for a
* given field instance.
*
* @see hook_field_widget_info_alter()
* @see hook_field_widget_form()
* @see hook_field_widget_form_alter()
* @see hook_field_widget_WIDGET_TYPE_form_alter()
* @see hook_field_widget_error()
* @see hook_field_widget_settings_form()
*/
function hook_field_widget_info() {
return array(
'text_textfield' => array(
'label' => t('Text field'),
'field types' => array('text'),
'settings' => array('size' => 60),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
'text_textarea' => array(
'label' => t('Text area (multiple rows)'),
'field types' => array('text_long'),
'settings' => array('rows' => 5),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
'text_textarea_with_summary' => array(
'label' => t('Text area with a summary'),
'field types' => array('text_with_summary'),
'settings' => array('rows' => 9, 'summary_rows' => 3),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
// As an advanced widget, force it to sink to the bottom of the choices.
'weight' => 2,
),
);
}
/**
* Perform alterations on Field API widget types.
*
* @param $info
* Array of informations on widget types exposed by hook_field_widget_info()
* implementations.
* @param array $info
* An array of informations on existing widget types, as collected by the
* annotation discovery mechanism.
*/
function hook_field_widget_info_alter(&$info) {
function hook_field_widget_info_alter(array &$info) {
// Add a setting to a widget type.
$info['text_textfield']['settings'] += array(
'mymodule_additional_setting' => 'default value',
);
// Let a new field type re-use an existing widget.
$info['options_select']['field types'][] = 'my_field_type';
}
/**
* Return the form for a single field widget.
*
* Field widget form elements should be based on the passed-in $element, which
* contains the base form element properties derived from the field
* configuration.
*
* Field API will set the weight, field name and delta values for each form
* element. If there are multiple values for this field, the Field API will
* invoke this hook as many times as needed.
*
* Note that, depending on the context in which the widget is being included
* (regular entity form, field configuration form, advanced search form...),
* the values for $field and $instance might be different from the "official"
* definitions returned by field_info_field() and field_info_instance().
* Examples: mono-value widget even if the field is multi-valued, non-required
* widget even if the field is 'required'...
*
* Therefore, the FAPI element callbacks (such as #process, #element_validate,
* #value_callback...) used by the widget cannot use the field_info_field()
* or field_info_instance() functions to retrieve the $field or $instance
* definitions they should operate on. The field_widget_field() and
* field_widget_instance() functions should be used instead to fetch the
* current working definitions from $form_state, where Field API stores them.
*
* Alternatively, hook_field_widget_form() can extract the needed specific
* properties from $field and $instance and set them as ad-hoc
* $element['#custom'] properties, for later use by its element callbacks.
*
* Other modules may alter the form element provided by this function using
* hook_field_widget_form_alter().
*
* @param $form
* The form structure where widgets are being attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
* @param $field
* The field structure.
* @param $instance
* The field instance.
* @param $langcode
* The language associated with $items.
* @param $items
* Array of default values for this field.
* @param $delta
* The order of this item in the array of subelements (0, 1, 2, etc).
* @param $element
* A form element array containing basic properties for the widget:
* - #entity_type: The name of the entity the field is attached to.
* - #bundle: The name of the field bundle the field is contained in.
* - #field_name: The name of the field.
* - #language: The language the field is being edited in.
* - #field_parents: The 'parents' space for the field in the form. Most
* widgets can simply overlook this property. This identifies the
* location where the field values are placed within
* $form_state['values'], and is used to access processing information
* for the field through the field_form_get_state() and
* field_form_set_state() functions.
* - #columns: A list of field storage columns of the field.
* - #title: The sanitized element label for the field instance, ready for
* output.
* - #description: The sanitized element description for the field instance,
* ready for output.
* - #required: A Boolean indicating whether the element value is required;
* for required multiple value fields, only the first widget's values are
* required.
* - #delta: The order of this item in the array of subelements; see $delta
* above.
*
* @return
* The form elements for a single widget for this field.
*
* @see field_widget_field()
* @see field_widget_instance()
* @see hook_field_widget_form_alter()
* @see hook_field_widget_WIDGET_TYPE_form_alter()
*/
function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$element += array(
'#type' => $instance['widget']['type'],
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
);
return $element;
$info['options_select']['field_types'][] = 'my_field_type';
}
/**
......@@ -956,52 +803,26 @@ function hook_field_widget_WIDGET_TYPE_form_alter(&$element, &$form_state, $cont
* of the hook involves reading from the database, it is highly recommended to
* statically cache the information.
*
* @param $widget
* @param array $widget_properties
* The instance's widget properties.
* @param $context
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - entity: The entity object.
* - bundle: The bundle: e.g., 'page' or 'article'.
* - field: The field that the widget belongs to.
* - instance: The instance of the field.
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see hook_field_widget_properties_ENTITY_TYPE_alter()
*/
function hook_field_widget_properties_alter(&$widget, $context) {
function hook_field_widget_properties_alter(array &$widget_properties, array $context) {
// Change a widget's type according to the time of day.
$field = $context['field'];
if ($context['entity_type'] == 'node' && $field['field_name'] == 'field_foo') {
$time = date('H');
$widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm';
$widget_properties['type'] = $time < 12 ? 'widget_am' : 'widget_pm';
}
}
/**
* Flag a field-level validation error.
*
* @param $element
* An array containing the form element for the widget. The error needs to be
* flagged on the right sub-element, according to the widget's internal
* structure.
* @param $error
* An associative array with the following key-value pairs, as returned by
* hook_field_validate():
* - error: the error code. Complex widgets might need to report different
* errors to different form elements inside the widget.
* - message: the human readable message to be displayed.
* @param $form
* The form structure where field elements are attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
*/
function hook_field_widget_error($element, $error, $form, &$form_state) {
form_error($element, $error['message']);
}
/**
* @} End of "defgroup field_widget".
*/
......@@ -2381,25 +2202,23 @@ function hook_field_extra_fields_display_alter(&$displays, $context) {
* of the hook involves reading from the database, it is highly recommended to
* statically cache the information.
*
* @param $widget
* @param array $widget_properties
* The instance's widget properties.
* @param $context
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - entity: The entity object.
* - bundle: The bundle: e.g., 'page' or 'article'.
* - field: The field that the widget belongs to.
* - instance: The instance of the field.
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see hook_field_widget_properties_alter()
*/
function hook_field_widget_properties_ENTITY_TYPE_alter(&$widget, $context) {
function hook_field_widget_properties_ENTITY_TYPE_alter(array &$widget_properties, array $context) {
// Change a widget's type according to the time of day.
$field = $context['field'];
if ($field['field_name'] == 'field_foo') {
$time = date('H');
$widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm';
$widget_properties['type'] = $time < 12 ? 'widget_am' : 'widget_pm';
}
}
......
......@@ -107,6 +107,95 @@
* the Field API.
*/
/**
* Invoke 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
* 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.
* @param mixed $b
* A parameter for the invoked method. Defaults to NULL.
* @param array $options
* 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.
*/
function field_invoke_method($method, \Closure $target_closure, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) {
// Merge default options.
$default_options = array(
'deleted' => FALSE,
'langcode' => NULL,
);
$options += $default_options;
$entity_type = $entity->entityType();
// Determine the list of instances to iterate on.
$instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);
// Iterate through the instances and collect results.
$return = array();
foreach ($instances as $instance) {
// Let the closure determine the target object on which the method should be
// called.
$target = $target_closure($instance);
if (method_exists($target, $method)) {
$field = field_info_field_by_id($instance['field_id']);
$field_name = $field['field_name'];
// Determine the list of languages to iterate on.
$available_langcodes = field_available_languages($entity_type, $field);
$langcodes = _field_language_suggestion($available_langcodes, $options['langcode'], $field_name);
foreach ($langcodes as $langcode) {
$items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
$result = $target->$method($entity, $langcode, $items, $a, $b);
if (isset($result)) {
// For methods with array results, we merge results together.
// For methods with scalar results, we collect results in an array.
if (is_array($result)) {
$return = array_merge($return, $result);
}
else {
$return[] = $result;
}
}
// Populate $items back in the field values, but avoid replacing missing
// fields with an empty array (those are not equivalent on update).
if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
$entity->{$field_name}[$langcode] = $items;
}
}
}
}
return $return;
}
/**
* Invoke a field hook.
*
......@@ -431,6 +520,20 @@ function _field_invoke_get_instances($entity_type, $bundle, $options) {
return $instances;
}
/**
* Defines a 'target closure' for field_invoke_method().
*
* Used to invoke methods on an instance's widget.
*
* @return Closure
* A 'target closure' for field_invoke_method().
*/
function _field_invoke_widget_target() {
return function ($instance) {
return $instance->getWidget();
};
}
/**
* Add form elements for all fields for an entity to a form structure.
*
......@@ -543,7 +646,7 @@ function field_attach_form($entity_type, EntityInterface $entity, &$form, &$form
// If no language is provided use the default site language.
$options = array('langcode' => field_valid_language($langcode));
$form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options);
$form += (array) field_invoke_method('form', _field_invoke_widget_target(), $entity, $form, $form_state, $options);
// Add custom weight handling.
$form['#pre_render'][] = '_field_extra_fields_pre_render';
......@@ -795,9 +898,6 @@ function field_attach_validate($entity_type, $entity) {
* An associative array containing the current state of the form.
*/
function field_attach_form_validate($entity_type, EntityInterface $entity, $form, &$form_state) {
// Extract field values from submitted values.
_field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state);
// Perform field_level validation.
try {
field_attach_validate($entity_type, $entity);
......@@ -812,7 +912,7 @@ function field_attach_form_validate($entity_type, EntityInterface $entity, $form
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
}
}
_field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state);
field_invoke_method('flagErrors', _field_invoke_widget_target(), $entity, $form, $form_state);
}
}
......@@ -836,9 +936,7 @@ function field_attach_form_validate($entity_type, EntityInterface $entity, $form
*/
function field_attach_submit($entity_type, EntityInterface $entity, $form, &$form_state) {
// Extract field values from submitted values.
_field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state);
_field_invoke_default('submit', $entity_type, $entity, $form, $form_state);
field_invoke_method('submit', _field_invoke_widget_target(), $entity, $form, $form_state);
// Let other modules act on submitting the entity.
// Avoid module_invoke_all() to let $form_state be taken by reference.
......
......@@ -559,6 +559,14 @@ function _field_write_instance($instance, $update = FALSE) {
$field = field_read_field($instance['field_name']);
$field_type = field_info_field_types($field['type']);
// Temporary workaround to allow incoming $instance as arrays or classed
// objects.
// @todo remove once the external APIs have been converted to use
// FieldInstance objects.
if (is_object($instance) && get_class($instance) == 'Drupal\field\FieldInstance') {
$instance = $instance->getArray();
}
// Set defaults.
$instance += array(
'settings' => array(),
......
......@@ -10,39 +10,6 @@
* the corresponding field_attach_[operation]() function.
*/
/**
* Extracts field values from submitted form values.
*
* @param $entity_type
* The type of $entity.
* @param $entity
* The entity for the operation.
* @param $field
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $entity's bundle.
* @param $langcode
* The language associated to $items.
* @param $items
* The field values. This parameter is altered by reference to receive the
* incoming form values.
* @param $form
* The form structure where field elements are attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param $form_state
* The form state.
*/
function field_default_extract_form_values($entity_type, $entity, $field, $instance, $langcode, &$items, $form, &$form_state) {
$path = array_merge($form['#parents'], array($field['field_name'], $langcode));
$key_exists = NULL;
$values = drupal_array_get_nested_value($form_state['values'], $path, $key_exists);
if ($key_exists) {
// Remove the 'value' of the 'add more' button.
unset($values['add_more']);
$items = $values;
}
}
/**
* Generic field validation handler.
*
......@@ -86,13 +53,6 @@ function field_default_validate($entity_type, $entity, $field, $instance, $langc
}
}
function field_default_submit($entity_type, $entity, $field, $instance, $langcode, &$items, $form, &$form_state) {
// Filter out empty values.
$items = _field_filter_items($field, $items);
// Reorder items to account for drag-n-drop reordering.
$items = _field_sort_items($field, $items);
}
/**
* Default field 'insert' operation.
*
......
......@@ -5,290 +5,6 @@
* Field forms management.
*/
/**
* Creates a form element for a field and can populate it with a default value.
*
* If the form element is not associated with an entity (i.e., $entity is NULL)
* field_get_default_value will be called to supply the default value for the
* field. Also allows other modules to alter the form element by implementing
* their own hooks.
*
* @param $entity_type
* The type of entity (for example 'node' or 'user') that the field belongs
* to.
* @param $entity
* The entity object that the field belongs to. This may be NULL if creating a
* form element with a default value.
* @param $field
* An array representing the field whose editing element is being created.
* @param $instance
* An array representing the structure for $field in its current context.
* @param $langcode
* The language associated with the field.
* @param $items
* An array of the field values. When creating a new entity this may be NULL
* or an empty array to use default values.
* @param $form
* An array representing the form that the editing element will be attached
* to.
* @param $form_state
* An array containing the current state of the form.
* @param $get_delta
* Used to get only a specific delta value of a multiple value field.
*
* @return
* The form element array created for this field.
*/
function field_default_form($entity_type, $entity, $field, $instance, $langcode, $items, &$form, &$form_state, $get_delta = NULL) {
// This could be called with no entity, as when a UI module creates a
// dummy form to set default values.
if ($entity) {
$id = $entity->id();
}
$parents = $form['#parents'];
$addition = array();
$field_name = $field['field_name'];
$addition[$field_name] = array();
// Populate widgets with default values when creating a new entity.
if (empty($items) && empty($id)) {
$items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
}
// Let modules alter the widget properties.
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'field' => $field,
'instance' => $instance,
'default' => !$entity,
);
drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $entity_type), $instance['widget'], $context);
// Collect widget elements.
$elements = array();
// Store field information in $form_state.
if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) {
$field_state = array(
'field' => $field,
'instance' => $instance,
'items_count' => count($items),
'array_parents' => array(),
'errors' => array(),
);
field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
}
// If field module handles multiple values for this form element, and we are
// displaying an individual element, process the multiple value form.
if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
// Store the entity in the form.
$form['#entity'] = $entity;
$elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
}
// If the widget is handling multiple values (e.g Options), or if we are
// displaying an individual element, just get a single form element and make
// it the $delta value.
else {
$delta = isset($get_delta) ? $get_delta : 0;