Commit 83b797df authored by catch's avatar catch

Issue #2095195 by yched, fago, Berdir, amateescu, swentel, pfrenssen, Xano:...

Issue #2095195 by yched, fago, Berdir, amateescu, swentel, pfrenssen, Xano: Remove deprecated field_attach_form_*().
parent 0b6e47e2
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
namespace Drupal\Core\Entity; namespace Drupal\Core\Entity;
use Drupal\Core\Language\Language; use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\entity\Entity\EntityFormDisplay;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
...@@ -15,7 +16,7 @@ ...@@ -15,7 +16,7 @@
* *
* @see \Drupal\Core\ContentEntityBase * @see \Drupal\Core\ContentEntityBase
*/ */
class ContentEntityFormController extends EntityFormController { class ContentEntityFormController extends EntityFormController implements ContentEntityFormControllerInterface {
/** /**
* The entity manager. * The entity manager.
...@@ -47,16 +48,8 @@ public static function create(ContainerInterface $container) { ...@@ -47,16 +48,8 @@ public static function create(ContainerInterface $container) {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function form(array $form, array &$form_state) { public function form(array $form, array &$form_state) {
$entity = $this->entity; $form = parent::form($form, $form_state);
// @todo Exploit the Field API to generate the default widgets for the $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
// entity fields.
if ($entity->getEntityType()->isFieldable()) {
field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
}
// Add a process callback so we can assign weights and hide extra fields.
$form['#process'][] = array($this, 'processForm');
return $form; return $form;
} }
...@@ -66,27 +59,7 @@ public function form(array $form, array &$form_state) { ...@@ -66,27 +59,7 @@ public function form(array $form, array &$form_state) {
public function validate(array $form, array &$form_state) { public function validate(array $form, array &$form_state) {
$this->updateFormLangcode($form_state); $this->updateFormLangcode($form_state);
$entity = $this->buildEntity($form, $form_state); $entity = $this->buildEntity($form, $form_state);
$entity_type = $entity->getEntityTypeId(); $this->getFormDisplay($form_state)->validateFormValues($entity, $form, $form_state);
$entity_langcode = $entity->language()->id;
$violations = array();
foreach ($entity as $field_name => $field) {
$field_violations = $field->validate();
if (count($field_violations)) {
$violations[$field_name] = $field_violations;
}
}
// Map errors back to form elements.
if ($violations) {
foreach ($violations as $field_name => $field_violations) {
$field_state = field_form_get_state($form['#parents'], $field_name, $form_state);
$field_state['constraint_violations'] = $field_violations;
field_form_set_state($form['#parents'], $field_name, $form_state, $field_state);
}
field_invoke_method('flagErrors', _field_invoke_widget_target($form_state['form_display']), $entity, $form, $form_state);
}
// @todo Remove this. // @todo Remove this.
// Execute legacy global validation handlers. // Execute legacy global validation handlers.
...@@ -102,6 +75,10 @@ protected function init(array &$form_state) { ...@@ -102,6 +75,10 @@ protected function init(array &$form_state) {
// language. // language.
$langcode = $this->getFormLangcode($form_state); $langcode = $this->getFormLangcode($form_state);
$this->entity = $this->entity->getTranslation($langcode); $this->entity = $this->entity->getTranslation($langcode);
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
$this->setFormDisplay($form_display, $form_state);
parent::init($form_state); parent::init($form_state);
} }
...@@ -128,37 +105,33 @@ public function isDefaultFormLangcode(array $form_state) { ...@@ -128,37 +105,33 @@ public function isDefaultFormLangcode(array $form_state) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function buildEntity(array $form, array &$form_state) { protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
$entity = clone $this->entity; // First, extract values from widgets.
$entity_type_id = $entity->getEntityTypeId(); $extracted = $this->getFormDisplay($form_state)->extractFormValues($entity, $form, $form_state);
$entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
// Then extract the values of fields that are not rendered through widgets,
// @todo Exploit the Entity Field API to process the submitted field values. // by simply copying from top-level form values. This leaves the fields
// Copy top-level form values that are entity fields but not handled by // that are not being edited within this form untouched.
// field API without changing existing entity fields that are not being foreach ($form_state['values'] as $name => $values) {
// edited by this form. Values of fields handled by field API are copied if ($entity->hasField($name) && !isset($extracted[$name])) {
// by field_attach_extract_form_values() below. $entity->set($name, $values);
$values_excluding_fields = $entity_type->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type_id, $entity->bundle())) : $form_state['values'];
$definitions = $entity->getFieldDefinitions();
foreach ($values_excluding_fields as $key => $value) {
if (isset($definitions[$key])) {
$entity->$key = $value;
} }
} }
}
// Invoke all specified builders for copying form values to entity fields. /**
if (isset($form['#entity_builders'])) { * {@inheritdoc}
foreach ($form['#entity_builders'] as $function) { */
call_user_func_array($function, array($entity_type_id, $entity, &$form, &$form_state)); public function getFormDisplay(array $form_state) {
} return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
} }
// Invoke field API for copying field values. /**
if ($entity_type->isFieldable()) { * {@inheritdoc}
field_attach_extract_form_values($entity, $form, $form_state, array('langcode' => $this->getFormLangcode($form_state))); */
} public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state) {
return $entity; $form_state['form_display'] = $form_display;
return $this;
} }
} }
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityFormControllerInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
/**
* Defines a common interface for content entity form controller classes.
*/
interface ContentEntityFormControllerInterface extends EntityFormControllerInterface {
/**
* Returns the form display.
*
* @param array $form_state
* An associative array containing the current state of the form.
*
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
* The current form display.
*/
public function getFormDisplay(array $form_state);
/**
* Sets the form display.
*
* Sets the form display which will be used for populating form element
* defaults.
*
* @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
* The form display that the current form operates with.
* @param array $form_state
* An associative array containing the current state of the form.
*/
public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state);
}
...@@ -7,9 +7,153 @@ ...@@ -7,9 +7,153 @@
namespace Drupal\Core\Entity\Display; namespace Drupal\Core\Entity\Display;
use Drupal\Core\Entity\ContentEntityInterface;
/** /**
* Provides a common interface for entity form displays. * Provides a common interface for entity form displays.
*/ */
interface EntityFormDisplayInterface extends EntityDisplayInterface { interface EntityFormDisplayInterface extends EntityDisplayInterface {
/**
* Adds field widgets to an entity form.
*
* The form elements for the entity's fields are added by reference as direct
* children in the $form parameter. This parameter can be a full form
* structure (most common case for entity edit forms), or a sub-element of a
* larger form.
*
* By default, submitted field values appear at the top-level of
* $form_state['values']. A different location within $form_state['values']
* can be specified by setting the '#parents' property on the incoming $form
* parameter. Because of name clashes, two instances of the same field cannot
* appear within the same $form element, or within the same '#parents' space.
*
* Sample resulting structure in $form:
* @code
* '#parents' => The location of field values in $form_state['values'],
* '#entity_type' => The name of the entity type,
* '#bundle' => The name of the bundle,
* // One sub-array per field appearing in the entity, keyed by field name.
* // The structure of the array differs slightly depending on whether the
* // widget is 'single-value' (provides the input for one field value,
* // most common case), and will therefore be repeated as many times as
* // needed, or 'multiple-values' (one single widget allows the input of
* // several values, e.g checkboxes, select box...).
* 'field_foo' => array(
* '#access' => TRUE if the current user has 'edit' grants for the field,
* FALSE if not.
* 'widget' => array(
* '#field_name' => The name of the field,
* '#language' => $langcode,
* '#field_parents' => The 'parents' space for the field in the form,
* equal to the #parents property of the $form parameter received by
* this method,
* '#required' => Whether or not the field is required,
* '#title' => The label of the field instance,
* '#description' => The description text for the field instance,
*
* // Only for 'single' widgets:
* '#theme' => 'field_multiple_value_form',
* '#cardinality' => The field cardinality,
* '#cardinality_multiple => TRUE if the field can contain multiple
* items, FALSE otherwise.
* // One sub-array per copy of the widget, keyed by delta.
* 0 => array(
* '#entity_type' => The name of the entity type,
* '#bundle' => The name of the bundle,
* '#field_name' => The name of the field,
* '#field_parents' => The 'parents' space for the field in the form,
* equal to the #parents property of the $form parameter,
* '#title' => The title to be displayed by the widget,
* '#default_value' => The field value for delta 0,
* '#required' => Whether the widget should be marked required,
* '#delta' => 0,
* // The remaining elements in the sub-array depend on the widget.
* '#type' => The type of the widget,
* ...
* ),
* 1 => array(
* ...
* ),
*
* // Only for multiple widgets:
* '#entity_type' => The name of the entity type,
* '#bundle' => $instance['bundle'],
* // The remaining elements in the sub-array depend on the widget.
* '#type' => The type of the widget,
* ...
* ),
* ...
* ),
* )
* @endcode
*
* Additionally, some processing data is placed in $form_state, and can be
* accessed by field_form_get_state() and field_form_set_state().
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param array $form
* The form structure to fill in. This can be a full form structure, or a
* sub-element of a larger form. The #parents property can be set to
* control the location of submitted field values within
* $form_state['values']. If not specified, $form['#parents'] is set to an
* empty array, which results in field values located at the top-level of
* $form_state['values'].
* @param array $form_state
* The form state.
*/
public function buildForm(ContentEntityInterface $entity, array &$form, array &$form_state);
/**
* Validates submitted widget values and sets the corresponding form errors.
*
* There are two levels of validation for fields in forms: widget validation
* and field validation.
* - Widget validation steps are specific to a given widget's own form
* structure and UI metaphors. They are executed during normal form
* validation, usually through Form API's #element_validate property.
* Errors reported at this level are typically those that prevent the
* extraction of proper field values from the submitted form input.
* - If no form / widget errors were reported for the field, field validation
* steps are performed according to the "constraints" specified by the
* field definition. Those are independent of the specific widget being
* used in a given form, and are also performed on REST entity submissions.
*
* This function performs field validation in the context of a form submission.
* It reports field constraint violations as form errors on the correct form
* elements.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param array $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 array $form_state
* The form state.
*/
public function validateFormValues(ContentEntityInterface $entity, array &$form, array &$form_state);
/**
* Extracts field values from the submitted widget values into the entity.
*
* This accounts for drag-and-drop reordering of field values, and filtering
* of empty values.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param array $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 array $form_state
* The form state.
*
* @return array
* An array whose keys and values are the keys of the top-level entries in
* $form_state['values'] that have been processed. The remaining entries, if
* any, do not correspond to widgets and should be extracted manually by
* the caller if needed.
*/
public function extractFormValues(ContentEntityInterface $entity, array &$form, array &$form_state);
} }
...@@ -7,10 +7,8 @@ ...@@ -7,10 +7,8 @@
namespace Drupal\Core\Entity; namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormBase;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\entity\Entity\EntityFormDisplay;
/** /**
* Base class for entity form controllers. * Base class for entity form controllers.
...@@ -122,9 +120,6 @@ protected function init(array &$form_state) { ...@@ -122,9 +120,6 @@ protected function init(array &$form_state) {
// Prepare the entity to be presented in the entity form. // Prepare the entity to be presented in the entity form.
$this->prepareEntity(); $this->prepareEntity();
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
$this->setFormDisplay($form_display, $form_state);
// Invoke the prepare form hooks. // Invoke the prepare form hooks.
$this->prepareInvokeAll('entity_prepare_form', $form_state); $this->prepareInvokeAll('entity_prepare_form', $form_state);
$this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state); $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state);
...@@ -137,13 +132,8 @@ protected function init(array &$form_state) { ...@@ -137,13 +132,8 @@ protected function init(array &$form_state) {
*/ */
public function form(array $form, array &$form_state) { public function form(array $form, array &$form_state) {
$entity = $this->entity; $entity = $this->entity;
// @todo Exploit the Field API to generate the default widgets for the
// entity properties.
if ($entity->getEntityType()->isFieldable()) {
field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
}
// Add a process callback so we can assign weights and hide extra fields. // Add a process callback.
$form['#process'][] = array($this, 'processForm'); $form['#process'][] = array($this, 'processForm');
if (!isset($form['langcode'])) { if (!isset($form['langcode'])) {
...@@ -168,25 +158,6 @@ public function processForm($element, $form_state, $form) { ...@@ -168,25 +158,6 @@ public function processForm($element, $form_state, $form) {
// to the entity object, hence we must restore it. // to the entity object, hence we must restore it.
$this->entity = $form_state['controller']->getEntity(); $this->entity = $form_state['controller']->getEntity();
// Assign the weights configured in the form display.
foreach ($this->getFormDisplay($form_state)->getComponents() as $name => $options) {
if (isset($element[$name])) {
$element[$name]['#weight'] = $options['weight'];
}
}
// Hide or assign weights for extra fields.
$extra_fields = field_info_extra_fields($this->entity->getEntityTypeId(), $this->entity->bundle(), 'form');
foreach ($extra_fields as $extra_field => $info) {
$component = $this->getFormDisplay($form_state)->getComponent($extra_field);
if (!$component) {
$element[$extra_field]['#access'] = FALSE;
}
else {
$element[$extra_field]['#weight'] = $component['weight'];
}
}
return $element; return $element;
} }
...@@ -359,7 +330,7 @@ public function buildEntity(array $form, array &$form_state) { ...@@ -359,7 +330,7 @@ public function buildEntity(array $form, array &$form_state) {
// controller of the current request. // controller of the current request.
$form_state['controller'] = $this; $form_state['controller'] = $this;
$this->copyFormValuesToEntity($entity, $form_state); $this->copyFormValuesToEntity($entity, $form, $form_state);
// Invoke all specified builders for copying form values to entity // Invoke all specified builders for copying form values to entity
// properties. // properties.
...@@ -380,10 +351,12 @@ public function buildEntity(array $form, array &$form_state) { ...@@ -380,10 +351,12 @@ public function buildEntity(array $form, array &$form_state) {
* *
* @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon. * The entity the current form should operate upon.
* @param array $form
* A nested array of form elements comprising the form.
* @param array $form_state * @param array $form_state
* An associative array containing the current state of the form. * An associative array containing the current state of the form.
*/ */
protected function copyFormValuesToEntity(EntityInterface $entity, array $form_state) { protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
// @todo: This relies on a method that only exists for config and content // @todo: This relies on a method that only exists for config and content
// entities, in a different way. Consider moving this logic to a config // entities, in a different way. Consider moving this logic to a config
// entity specific implementation. // entity specific implementation.
...@@ -427,27 +400,12 @@ protected function prepareInvokeAll($hook, array &$form_state) { ...@@ -427,27 +400,12 @@ protected function prepareInvokeAll($hook, array &$form_state) {
if (function_exists($function)) { if (function_exists($function)) {
// Ensure we pass an updated translation object and form display at // Ensure we pass an updated translation object and form display at
// each invocation, since they depend on form state which is alterable. // each invocation, since they depend on form state which is alterable.
$args = array($this->entity, $this->getFormDisplay($form_state), $this->operation, &$form_state); $args = array($this->entity, $this->operation, &$form_state);
call_user_func_array($function, $args); call_user_func_array($function, $args);
} }
} }
} }
/**
* {@inheritdoc}
*/
public function getFormDisplay(array $form_state) {
return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
}
/**
* {@inheritdoc}
*/
public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state) {
$form_state['form_display'] = $form_display;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core\Entity; namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\StringTranslation\TranslationInterface;
...@@ -57,30 +56,6 @@ public function setOperation($operation); ...@@ -57,30 +56,6 @@ public function setOperation($operation);
*/ */
public function getOperation(); public function getOperation();
/**
* Returns the form display.
*
* @param array $form_state
* An associative array containing the current state of the form.
*
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
* The current form display.
*/
public function getFormDisplay(array $form_state);
/**
* Sets the form display.
*
* Sets the form display which will be used for populating form element
* defaults.
*
* @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
* The form display that the current form operates with.
* @param array $form_state
* An associative array containing the current state of the form.
*/
public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state);
/** /**
* Returns the form entity. * Returns the form entity.
* *
......
...@@ -331,15 +331,9 @@ public function defaultValuesFormValidate(array $element, array &$form, array &$ ...@@ -331,15 +331,9 @@ public function defaultValuesFormValidate(array $element, array &$form, array &$
$widget->extractFormValues($this, $element, $form_state); $widget->extractFormValues($this, $element, $form_state);
$violations = $this->validate(); $violations = $this->validate();
// Assign reported errors to the correct form element.
if (count($violations)) { if (count($violations)) {
// Store reported errors in $form_state. $widget->flagErrors($this, $violations, $element, $form_state);
$field_name = $this->getFieldDefinition()->getName();
$field_state = field_form_get_state($element['#parents'], $field_name, $form_state);
$field_state['constraint_violations'] = $violations;
field_form_set_state($element['#parents'], $field_name, $form_state, $field_state);
// Assign reported errors to the correct form element.
$widget->flagErrors($this, $element, $form_state);
} }
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
use Drupal\Component\Utility\SortArray; use Drupal\Component\Utility\SortArray;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/** /**
* Base class for 'Field widget' plugin implementations. * Base class for 'Field widget' plugin implementations.
...@@ -61,7 +62,6 @@ public function form(FieldItemListInterface $items, array &$form, array &$form_s ...@@ -61,7 +62,6 @@ public function form(FieldItemListInterface $items, array &$form, array &$form_s
$field_state = array( $field_state = array(
'items_count' => count($items), 'items_count' => count($items),
'array_parents' => array(), 'array_parents' => array(),
'constraint_violations' => array(),
);