diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php index 0383533..e3a4d7e 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php @@ -7,7 +7,8 @@ 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; /** @@ -15,7 +16,7 @@ * * @see \Drupal\Core\ContentEntityBase */ -class ContentEntityFormController extends EntityFormController { +class ContentEntityFormController extends EntityFormController implements ContentEntityFormControllerInterface { /** * The entity manager. @@ -47,12 +48,7 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function form(array $form, array &$form_state) { - $entity = $this->entity; - // @todo Exploit the Field API to generate the default widgets for the - // entity fields. - if ($entity->getEntityType()->isFieldable()) { - field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state)); - } + $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state); // Add a process callback so we can assign weights and hide extra fields. $form['#process'][] = array($this, 'processForm'); @@ -63,31 +59,40 @@ public function form(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function validate(array $form, array &$form_state) { - $this->updateFormLangcode($form_state); - $entity = $this->buildEntity($form, $form_state); - $entity_type = $entity->getEntityTypeId(); - $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; + public function processForm($element, $form_state, $form) { + parent::processForm($element, $form_state, $form); + + $form_display = $this->getFormDisplay($form_state); + + // Assign the weights configured in the form display. + // @todo: Once https://drupal.org/node/1875974 provides the missing API, + // only do it for 'extra fields', since other components have been taken + // care of in EntityViewDisplay::buildMultiple(). + foreach ($form_display->getComponents() as $name => $options) { + if (isset($element[$name])) { + $element[$name]['#weight'] = $options['weight']; } } - // 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); + // Hide extra fields. + $extra_fields = field_info_extra_fields($this->entity->getEntityTypeId(), $this->entity->bundle(), 'form'); + foreach ($extra_fields as $extra_field => $info) { + if (!$form_display->getComponent($extra_field)) { + $element[$extra_field]['#access'] = FALSE; } - - field_invoke_method('flagErrors', _field_invoke_widget_target($form_state['form_display']), $entity, $form, $form_state); } + return $element; + } + + /** + * {@inheritdoc} + */ + public function validate(array $form, array &$form_state) { + $this->updateFormLangcode($form_state); + $entity = $this->buildEntity($form, $form_state); + $this->getFormDisplay($form_state)->validateFormValues($entity, $form, $form_state); + // @todo Remove this. // Execute legacy global validation handlers. unset($form_state['validate_handlers']); @@ -102,6 +107,10 @@ protected function init(array &$form_state) { // language. $langcode = $this->getFormLangcode($form_state); $this->entity = $this->entity->getTranslation($langcode); + + $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation()); + $this->setFormDisplay($form_display, $form_state); + parent::init($form_state); } @@ -130,35 +139,42 @@ public function isDefaultFormLangcode(array $form_state) { */ public function buildEntity(array $form, array &$form_state) { $entity = clone $this->entity; - $entity_type_id = $entity->getEntityTypeId(); - $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); - - // @todo Exploit the Entity Field API to process the submitted field values. - // Copy top-level form values that are entity fields but not handled by - // field API without changing existing entity fields that are not being - // edited by this form. Values of fields handled by field API are copied - // by field_attach_extract_form_values() below. - $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; + + // First, extract values from widgets. + $extracted = $this->getFormDisplay($form_state)->extractFormValues($entity, $form, $form_state); + + // Then extract the values of fields that are not rendered through widgets, + // by simply copying from top-level form values. This leaves the fields + // that are not being edited within this form untouched. + foreach ($form_state['values'] as $name => $values) { + if ($entity->hasField($name) && !isset($extracted[$name])) { + $entity->$name = $values; } } // Invoke all specified builders for copying form values to entity fields. if (isset($form['#entity_builders'])) { foreach ($form['#entity_builders'] as $function) { - call_user_func_array($function, array($entity_type_id, $entity, &$form, &$form_state)); + call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state)); } } - // Invoke field API for copying field values. - if ($entity_type->isFieldable()) { - field_attach_extract_form_values($entity, $form, $form_state, array('langcode' => $this->getFormLangcode($form_state))); - } return $entity; } + /** + * {@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; + } + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php new file mode 100644 index 0000000..77235d9 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php @@ -0,0 +1,41 @@ + 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); + } diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 58d9035..cbe920d 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\entity\Entity\EntityFormDisplay; @@ -122,9 +121,6 @@ protected function init(array &$form_state) { // Prepare the entity to be presented in the entity form. $this->prepareEntity(); - $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation()); - $this->setFormDisplay($form_display, $form_state); - // Invoke the prepare form hooks. $this->prepareInvokeAll('entity_prepare_form', $form_state); $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state); @@ -137,11 +133,6 @@ protected function init(array &$form_state) { */ public function form(array $form, array &$form_state) { $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. $form['#process'][] = array($this, 'processForm'); @@ -168,25 +159,6 @@ public function processForm($element, $form_state, $form) { // to the entity object, hence we must restore it. $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; } @@ -427,7 +399,7 @@ protected function prepareInvokeAll($hook, array &$form_state) { if (function_exists($function)) { // Ensure we pass an updated translation object and form display at // 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); } } @@ -436,21 +408,6 @@ protected function prepareInvokeAll($hook, array &$form_state) { /** * {@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} - */ public function getOperation() { return $this->operation; } diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php index e63679b..5e0881d 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\StringTranslation\TranslationInterface; @@ -58,30 +57,6 @@ public function setOperation($operation); 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. * * The form entity which has been used for populating form element defaults. diff --git a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php b/core/lib/Drupal/Core/Field/ConfigFieldItemList.php index 35b7098..5050df6 100644 --- a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php +++ b/core/lib/Drupal/Core/Field/ConfigFieldItemList.php @@ -56,15 +56,9 @@ public function defaultValuesFormValidate(array $element, array &$form, array &$ $widget->extractFormValues($this, $element, $form_state); $violations = $this->validate(); + // Assign reported errors to the correct form element. if (count($violations)) { - // Store reported errors in $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); + $widget->flagErrors($this, $violations, $element, $form_state); } } diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index d770871..3866ddc 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\SortArray; use Drupal\Component\Utility\String; use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; /** * Base class for 'Field widget' plugin implementations. @@ -61,7 +62,6 @@ public function form(FieldItemListInterface $items, array &$form, array &$form_s $field_state = array( 'items_count' => count($items), 'array_parents' => array(), - 'constraint_violations' => array(), ); field_form_set_state($parents, $field_name, $form_state, $field_state); } @@ -359,12 +359,12 @@ public function extractFormValues(FieldItemListInterface $items, array $form, ar /** * {@inheritdoc} */ - public function flagErrors(FieldItemListInterface $items, array $form, array &$form_state) { + public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, array &$form_state) { $field_name = $this->fieldDefinition->getName(); $field_state = field_form_get_state($form['#parents'], $field_name, $form_state); - if (!empty($field_state['constraint_violations'])) { + if ($violations->count()) { $form_builder = \Drupal::formBuilder(); // Locate the correct element in the the form. @@ -388,7 +388,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f $handles_multiple = $this->handlesMultipleValues(); $violations_by_delta = array(); - foreach ($field_state['constraint_violations'] as $violation) { + foreach ($violations as $violation) { // Separate violations by delta. $property_path = explode('.', $violation->getPropertyPath()); $delta = array_shift($property_path); @@ -400,6 +400,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f $violation->arrayPropertyPath = $property_path; } + /** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $delta_violations */ foreach ($violations_by_delta as $delta => $delta_violations) { // Pass violations to the main element: // - if this is a multiple-value widget, @@ -420,9 +421,6 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f } } } - // Reinitialize the errors list for the next submit. - $field_state['constraint_violations'] = array(); - field_form_set_state($form['#parents'], $field_name, $form_state, $field_state); } } } diff --git a/core/lib/Drupal/Core/Field/WidgetBaseInterface.php b/core/lib/Drupal/Core/Field/WidgetBaseInterface.php index 8c63e23..3af0549 100644 --- a/core/lib/Drupal/Core/Field/WidgetBaseInterface.php +++ b/core/lib/Drupal/Core/Field/WidgetBaseInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Field; +use Symfony\Component\Validator\ConstraintViolationListInterface; + /** * Base interface definition for "Field widget" plugins. * @@ -59,12 +61,14 @@ public function extractFormValues(FieldItemListInterface $items, array $form, ar * * @param \Drupal\Core\Field\FieldItemListInterface $items * The field values. + * @param \Symfony\Component\Validator\ConstraintViolationListInterface|\Symfony\Component\Validator\ConstraintViolationInterface[] $violations + * The constraint violations that were detected. * @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 flagErrors(FieldItemListInterface $items, array $form, array &$form_state); + public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, array &$form_state); } diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 48c81d5..47d7fee 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -512,7 +512,7 @@ function book_node_predelete(EntityInterface $node) { /** * Implements hook_node_prepare_form(). */ -function book_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) { +function book_node_prepare_form(NodeInterface $node, $operation, array &$form_state) { // Get BookManager service $book_manager = \Drupal::service('book.manager'); diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module index 27b52e4..3a016b5 100644 --- a/core/modules/datetime/datetime.module +++ b/core/modules/datetime/datetime.module @@ -1002,7 +1002,7 @@ function datetime_form_node_form_alter(&$form, &$form_state, $form_id) { /** * Implements hook_node_prepare_form(). */ -function datetime_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) { +function datetime_node_prepare_form(NodeInterface $node, $operation, array &$form_state) { // Prepare the 'Authored on' date to use datetime. $node->date = DrupalDateTime::createFromTimestamp($node->getCreatedTime()); } diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php index e930206..658c0b2 100644 --- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php +++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php @@ -87,7 +87,7 @@ public function buildForm(array $form, array &$form_state, EntityInterface $enti } // Add the field form. - field_attach_form($form_state['entity'], $form, $form_state, $form_state['langcode'], array('field_name' => $form_state['field_name'])); + $form_state['form_display']->buildForm($entity, $form, $form_state); // Add a dummy changed timestamp field to attach form errors to. if ($entity instanceof EntityChangedInterface) { @@ -127,9 +127,15 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name $form_state['entity'] = $entity; $form_state['field_name'] = $field_name; - // @todo Allow the usage of different form modes by exposing a hook and the - // UI for them. - $form_state['form_display'] = EntityFormDisplay::collectRenderDisplay($entity, 'default'); + // Fetch the display used by the form. It is the display for the 'default' + // form mode, with only the current field visible. + $display = EntityFormDisplay::collectRenderDisplay($entity, 'default'); + foreach ($display->getComponents() as $name => $optipns) { + if ($name != $field_name) { + $display->removeComponent($name); + } + } + $form_state['form_display'] = $display; } /** @@ -137,7 +143,8 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name */ public function validateForm(array &$form, array &$form_state) { $entity = $this->buildEntity($form, $form_state); - field_attach_form_validate($entity, $form, $form_state, array('field_name' => $form_state['field_name'])); + + $form_state['form_display']->validateFormValues($entity, $form, $form_state); // Do validation on the changed field as well and assign the error to the // dummy form element we added for this. We don't know the name of this @@ -173,7 +180,7 @@ protected function buildEntity(array $form, array &$form_state) { $entity = clone $form_state['entity']; $field_name = $form_state['field_name']; - field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $field_name)); + $form_state['form_display']->extractFormValues($entity, $form, $form_state); // @todo Refine automated log messages and abstract them to all entity // types: http://drupal.org/node/1678002. diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php index 18b8dc5..b575dfb 100644 --- a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php +++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php @@ -7,6 +7,7 @@ namespace Drupal\entity\Entity; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\entity\EntityDisplayBase; @@ -49,7 +50,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn * party code to alter the display options held in the display before they are * used to generate render arrays. * - * @param \Drupal\Core\Entity\EntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity for which the form is being built. * @param string $form_mode * The form mode. @@ -60,7 +61,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn * @see entity_get_form_display() * @see hook_entity_form_display_alter() */ - public static function collectRenderDisplay($entity, $form_mode) { + public static function collectRenderDisplay(ContentEntityInterface $entity, $form_mode) { $entity_type = $entity->getEntityTypeId(); $bundle = $entity->bundle(); @@ -147,6 +148,58 @@ public function getRenderer($field_name) { /** * {@inheritdoc} */ + public function buildForm(ContentEntityInterface $entity, array &$form, array &$form_state) { + // Set #parents to 'top-level' by default. + $form += array('#parents' => array()); + + // Let each widget generate the form elements. + foreach ($entity as $name => $items) { + if ($widget = $this->getRenderer($name)) { + $items->filterEmptyItems(); + $form += $widget->form($items, $form, $form_state); + + // Assign the correct weight. This duplicates the reordering done in + // processForm(), but is needed for other forms calling this method + // directly. + $options = $this->getComponent($name); + $form[$name]['#weight'] = $options['weight']; + } + } + } + + /** + * {@inheritdoc} + */ + public function extractFormValues(ContentEntityInterface $entity, array &$form, array &$form_state) { + $extracted = array(); + foreach ($entity as $name => $items) { + if ($widget = $this->getRenderer($name)) { + $widget->extractFormValues($items, $form, $form_state); + $extracted[$name] = $name; + } + } + return $extracted; + } + + /** + * {@inheritdoc} + */ + public function validateFormValues(ContentEntityInterface $entity, array &$form, array &$form_state) { + foreach ($entity as $field_name => $items) { + // Only validate the fields that actually appear in the form, and let the + // widget assign the violations to the right form elements. + if ($widget = $this->getRenderer($field_name)) { + $violations = $items->validate(); + if (count($violations)) { + $widget->flagErrors($items, $violations, $form, $form_state); + } + } + } + } + + /** + * {@inheritdoc} + */ public function __sleep() { // Only store the definition, not external objects or derived data. $keys = array_keys($this->getExportProperties()); diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index 92e9eb6..4ca7d7e 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -11,15 +11,16 @@ /** * Exposes "pseudo-field" components on fieldable entities. * - * Field UI's "Manage fields" and "Manage display" pages let users re-order - * fields, but also non-field components. For nodes, these include the title - * and other elements exposed by modules through hook_form_alter(). + * Field UI's "Manage display" and "Manage form display" pages let users + * re-order fields rendered through the regular widget/formatter pipeline, but + * also other components: entity fields that are rendered through custom code, + * or other arbitrary components added through hook_form_alter() or + * hook_entity_view(). * * Fieldable entities or modules that want to have their components supported * should expose them using this hook. The user-defined settings (weight, * visible) are automatically applied on rendered forms and displayed entities - * in a #pre_render callback added by field_attach_form() and - * EntityViewBuilder::viewMultiple(). + * by ContentEntityFormController::form() and EntityViewBuilder::viewMultiple(). * * @see hook_field_extra_fields_alter() * @@ -162,8 +163,8 @@ function hook_field_info_alter(&$info) { * * Widgets are @link forms_api_reference.html Form API @endlink * 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(). + * WidgetInterface object are typically called by respective methods in the + * \Drupal\entity\Entity\EntityFormDisplay class. * * @see field * @see field_types @@ -292,78 +293,6 @@ function hook_field_formatter_info_alter(array &$info) { */ /** - * @addtogroup field_attach - * @{ - */ - -/** - * Act on field_attach_form(). - * - * This hook is invoked after the field module has performed the operation. - * Implementing modules should alter the $form or $form_state parameters. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity for which an edit form is being built. - * @param $form - * The form structure field elements are attached to. This might be a full - * form structure, or a sub-element of a larger form. The $form['#parents'] - * property can be used to identify the corresponding part of - * $form_state['values']. Hook implementations that need to act on the - * top-level properties of the global form (like #submit, #validate...) can - * add a #process callback to the array received in the $form parameter, and - * act on the $complete_form parameter in the process callback. - * @param $form_state - * An associative array containing the current state of the form. - * @param $langcode - * The language the field values are going to be entered in. If no language is - * provided the default site language will be used. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use the entity system instead, see https://drupal.org/developing/api/entity - */ -function hook_field_attach_form(\Drupal\Core\Entity\EntityInterface $entity, &$form, &$form_state, $langcode) { - // Add a checkbox allowing a given field to be emptied. - // See hook_field_attach_extract_form_values() for the corresponding - // processing code. - $form['empty_field_foo'] = array( - '#type' => 'checkbox', - '#title' => t("Empty the 'field_foo' field"), - ); -} - -/** - * Act on field_attach_extract_form_values(). - * - * This hook is invoked after the field module has performed the operation. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity for which an edit form is being submitted. The incoming form - * values have been extracted as field values of the $entity object. - * @param $form - * The form structure field elements are attached to. This might be a full - * form structure, or a sub-part of a larger form. The $form['#parents'] - * property can be used to identify the corresponding part of - * $form_state['values']. - * @param $form_state - * An associative array containing the current state of the form. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use the entity system instead, see https://drupal.org/developing/api/entity - */ -function hook_field_attach_extract_form_values(\Drupal\Core\Entity\EntityInterface $entity, $form, &$form_state) { - // Sample case of an 'Empty the field' checkbox added on the form, allowing - // a given field to be emptied. - $values = NestedArray::getValue($form_state['values'], $form['#parents']); - if (!empty($values['empty_field_foo'])) { - unset($entity->field_foo); - } -} - -/** - * @} End of "addtogroup field_attach". - */ - -/** * Returns the maximum weight for the entity components handled by the module. * * Field API takes care of fields and 'extra_fields'. This hook is intended for diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc deleted file mode 100644 index 3898037..0000000 --- a/core/modules/field/field.attach.inc +++ /dev/null @@ -1,160 +0,0 @@ -type. This property can be omitted if the entity type only - * exposes a single bundle (all entities of this type have the same collection - * of fields). This is the case for the 'user' entity type. - * - * @link field_language Field language API @endlink provides information about - * the structure of field objects. - * - * See @link field Field API @endlink for information about the other parts of - * the Field API. - */ - -/** - * Invokes a method on all the fields of a given entity. - * - * @param string $method - * The name of the method to invoke. - * @param callable $target_function - * A function that receives a FieldDefinitionInterface 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 - * (optional) A parameter for the invoked method. Defaults to NULL. - * @param mixed $b - * (optional) A parameter for the invoked method. Defaults to NULL. - * @param array $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. - * - * @return array - * An array of returned values. - */ -function field_invoke_method($method, $target_function, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) { - $entity_type = $entity->getEntityTypeId(); - - // Determine the list of fields to iterate on. - $field_definitions = _field_invoke_get_field_definitions($entity_type, $entity->bundle(), $options); - - // Iterate through the fields and collect results. - $return = array(); - foreach ($field_definitions as $field_definition) { - // Let the function determine the target object on which the method should be - // called. - $target = call_user_func($target_function, $field_definition); - - if (method_exists($target, $method)) { - $items = $entity->get($field_definition->getName()); - $items->filterEmptyItems(); - - $result = $target->$method($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; - } - } - } - } - - return $return; -} - -/** - * Retrieves a list of field definitions to operate on. - * - * Helper for field_invoke_method(). - * - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. - * @param $options - * An associative array of options, as provided to field_invoke_method(). Only - * the following keys are considered: - * - field_name - * See field_invoke_method() for details. - * - * @return - * The array of selected field definitions. - */ -function _field_invoke_get_field_definitions($entity_type, $bundle, $options) { - $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle); - if (isset($options['field_name'])) { - $definitions = array_intersect_key($definitions, array($options['field_name'] => TRUE)); - } - return $definitions; -} - -/** - * Defines a 'target function' for field_invoke_method(). - * - * Used to invoke methods on a field's widget. - * - * @param \Drupal\entity\Entity\EntityFormDisplay $form_display - * An EntityFormDisplay object. - * - * @return callable $target_function - * A 'target function' for field_invoke_method(). - */ -function _field_invoke_widget_target($form_display) { - return function (FieldDefinitionInterface $field_definition) use ($form_display) { - return $form_display->getRenderer($field_definition->getName()); - }; -} - -/** - * @} End of "defgroup field_attach". - */ diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc index 73a715c..50fa0a6 100644 --- a/core/modules/field/field.deprecated.inc +++ b/core/modules/field/field.deprecated.inc @@ -174,219 +174,3 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { function field_info_instance($entity_type, $field_name, $bundle_name) { return Field::fieldInfo()->getInstance($entity_type, $bundle_name, $field_name); } - -/** - * Adds form elements for all fields for an entity to a form structure. - * - * 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. - * - * For each call to field_attach_form(), field values are processed by calling - * field_attach_extract_form_values() on the same $form element. - * - * 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 - * field_attach_form(), - * '#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 received by - * field_attach_form(), - * '#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\EntityInterface $entity - * The entity for which to load form elements, used to initialize - * default form values. - * @param $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, placing field - * values at the top-level of $form_state['values']. - * @param $form_state - * An associative array containing the current state of the form. - * @param $langcode - * The language the field values are going to be entered, if no language - * is provided the default site language will be used. - * @param array $options - * An associative array of additional options. See field_invoke_method() for - * details. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use the entity system instead, see https://drupal.org/developing/api/entity - * - * @see field_form_get_state() - * @see field_form_set_state() - */ -function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langcode = NULL, array $options = array()) { - // Set #parents to 'top-level' by default. - $form += array('#parents' => array()); - - // Get the entity_form_display object for this form. - $form_display = $form_state['form_display']; - - $form += (array) field_invoke_method('form', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options); - - $form['#entity_type'] = $entity->getEntityTypeId(); - $form['#bundle'] = $entity->bundle(); - - // Let other modules make changes to the form. - // Avoid \Drupal::moduleHandler()->invokeAll() - // to let parameters be taken by reference. - foreach (\Drupal::moduleHandler()->getImplementations('field_attach_form') as $module) { - $function = $module . '_field_attach_form'; - $function($entity, $form, $form_state, $langcode); - } -} - -/** - * Performs field validation against form-submitted field values. - * - * There are two levels of validation for fields in forms: widget validation and - * and field validation. - * - Widget validation steps are specific to a given widget's own form structure - * and UI metaphors. They are executed through FAPI's #element_validate - * property during normal form validation. - * - Field validation steps are common to a given field type, independently of - * the specific widget being used in a given form. They are defined in the - * field type's implementation of hook_field_validate(). - * - * This function performs field validation in the context of a form submission. - * It converts field validation errors into form errors on the correct form - * elements. Fieldable entity types should call this function during their own - * form validation function. - * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity being submitted. The actual field values will be read - * from $form_state['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 - * An associative array containing the current state of the form. - * @param array $options - * An associative array of additional options. See field_invoke_method() for - * details. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use the entity system instead, see https://drupal.org/developing/api/entity - */ -function field_attach_form_validate(ContentEntityInterface $entity, $form, &$form_state, array $options = array()) { - $has_violations = FALSE; - foreach ($entity as $field_name => $field) { - $definition = $field->getFieldDefinition(); - if ($definition->isConfigurable() && (empty($options['field_name']) || $options['field_name'] == $field_name)) { - $field_violations = $field->validate(); - if (count($field_violations)) { - $has_violations = TRUE; - - // Place violations in $form_state. - $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); - } - } - } - - if ($has_violations) { - // Map errors back to form elements. - $form_display = $form_state['form_display']; - field_invoke_method('flagErrors', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options); - } -} - -/** - * Populates an entity object with values from a form submission. - * - * Currently, this accounts for drag-and-drop reordering of field values, and - * filtering of empty values. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity being submitted. The actual field values will be read - * from $form_state['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 - * An associative array containing the current state of the form. - * @param array $options - * An associative array of additional options. See field_invoke_method() for - * details. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use the entity system instead, see https://drupal.org/developing/api/entity - */ -function field_attach_extract_form_values(EntityInterface $entity, $form, &$form_state, array $options = array()) { - // Extract field values from submitted values. - $form_display = $form_state['form_display']; - field_invoke_method('extractFormValues', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options); - - // Let other modules act on submitting the entity. - // Avoid \Drupal::moduleHandler()->invokeAll() - // to let $form_state be taken by reference. - foreach (\Drupal::moduleHandler()->getImplementations('field_attach_extract_form_values') as $module) { - $function = $module . 'field_attach_extract_form_values'; - $function($entity, $form, $form_state); - } -} diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 734eb7c..76e56c3 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -18,7 +18,6 @@ * every page request. */ require_once __DIR__ . '/field.info.inc'; -require_once __DIR__ . '/field.attach.inc'; require_once __DIR__ . '/field.form.inc'; require_once __DIR__ . '/field.purge.inc'; require_once __DIR__ . '/field.deprecated.inc'; diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php index 1c0518e..1b817ec 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php @@ -262,22 +262,22 @@ function testFieldAttachCache() { } /** - * Test field_attach_form(). + * Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::buildForm(). * * This could be much more thorough, but it does verify that the correct * widgets show up. */ - function testFieldAttachForm() { + function testEntityFormDisplayBuildForm() { $this->createFieldWithInstance('_2'); $entity_type = 'entity_test'; $entity = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->instance->bundle)); - // When generating form for all fields. + // Test generating widgets for all fields. + $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); $form = array(); $form_state = form_state_defaults(); - $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); - field_attach_form($entity, $form, $form_state); + $display->buildForm($entity, $form, $form_state); $this->assertEqual($form[$this->field_name]['widget']['#title'], $this->instance->getLabel(), "First field's form title is {$this->instance->getLabel()}"); $this->assertEqual($form[$this->field_name_2]['widget']['#title'], $this->instance_2->getLabel(), "Second field's form title is {$this->instance_2->getLabel()}"); @@ -290,12 +290,16 @@ function testFieldAttachForm() { $this->assertEqual($form[$this->field_name_2]['widget'][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield"); } - // When generating form for a single field (the second field). - $options = array('field_name' => $this->field_name_2); + // Test generating widgets for all fields. + $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); + foreach ($display->getComponents() as $name => $options) { + if ($name != $this->field_name_2) { + $display->removeComponent($name); + } + } $form = array(); $form_state = form_state_defaults(); - $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); - field_attach_form($entity, $form, $form_state, NULL, $options); + $display->buildForm($entity, $form, $form_state); $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form'); $this->assertEqual($form[$this->field_name_2]['widget']['#title'], $this->instance_2->getLabel(), "Second field's form title is {$this->instance_2->getLabel()}"); @@ -306,19 +310,19 @@ function testFieldAttachForm() { } /** - * Test field_attach_extract_form_values(). + * Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::extractFormValues(). */ - function testFieldAttachExtractFormValues() { + function testEntityFormDisplayExtractFormValues() { $this->createFieldWithInstance('_2'); $entity_type = 'entity_test'; $entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->instance->bundle)); // Build the form for all fields. + $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); $form = array(); $form_state = form_state_defaults(); - $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default'); - field_attach_form($entity_init, $form, $form_state); + $display->buildForm($entity_init, $form, $form_state); // Simulate incoming values. // First field. @@ -356,9 +360,9 @@ function testFieldAttachExtractFormValues() { $form_state['values'][$this->field_name] = $values; $form_state['values'][$this->field_name_2] = $values_2; - // Call field_attach_extract_form_values() for all fields. + // Extract values for all fields. $entity = clone($entity_init); - field_attach_extract_form_values($entity, $form, $form_state); + $display->extractFormValues($entity, $form, $form_state); asort($weights); asort($weights_2); @@ -378,16 +382,20 @@ function testFieldAttachExtractFormValues() { $this->assertIdentical($entity->{$this->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values'); // Call field_attach_extract_form_values() for a single field (the second field). - $options = array('field_name' => $this->field_name_2); + foreach ($display->getComponents() as $name => $options) { + if ($name != $this->field_name_2) { + $display->removeComponent($name); + } + } $entity = clone($entity_init); - field_attach_extract_form_values($entity, $form, $form_state, $options); + $display->extractFormValues($entity, $form, $form_state); $expected_values_2 = array(); foreach ($weights_2 as $key => $value) { if ($key != 1) { $expected_values_2[] = array('value' => $values_2[$key]['value']); } } - $this->assertTrue($entity->{$this->field_name}->isEmpty(), 'The first field does is empty in the entity object'); + $this->assertTrue($entity->{$this->field_name}->isEmpty(), 'The first field is empty in the entity object'); $this->assertIdentical($entity->{$this->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values'); } diff --git a/core/modules/field/lib/Drupal/field/Tests/FormTest.php b/core/modules/field/lib/Drupal/field/Tests/FormTest.php index 5de1d0a..3b4a96b 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FormTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FormTest.php @@ -531,10 +531,10 @@ function testFieldFormAccess() { // apart from #access. $entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0)); + $display = entity_get_form_display($entity_type, $entity_type, 'default'); $form = array(); $form_state = form_state_defaults(); - $form_state['form_display'] = entity_get_form_display($entity_type, $entity_type, 'default'); - field_attach_form($entity, $form, $form_state); + $display->buildForm($entity, $form, $form_state); $this->assertEqual($form[$field_name_no_access]['widget'][0]['value']['#entity_type'], $entity_type, 'The correct entity type is set in the field structure.'); $this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.'); diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc index 824cbfc..e386bf0 100644 --- a/core/modules/field/tests/modules/field_test/field_test.entity.inc +++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc @@ -39,86 +39,3 @@ function field_test_entity_info_translatable($entity_type = NULL, $translatable } return $stored_value; } - -/** - * Form combining two separate entities. - * - * @deprecated Use \Drupal\field_test\Form\FieldTestForm::testEntityNestedForm() - */ -function field_test_entity_nested_form($form, &$form_state, EntityInterface $entity_1, EntityInterface $entity_2) { - // First entity. - foreach (array('id', 'type') as $key) { - $form[$key] = array( - '#type' => 'value', - '#value' => $entity_1->$key->value, - ); - } - $form_state['form_display'] = entity_get_form_display($entity_1->getEntityTypeId(), $entity_1->bundle(), 'default'); - field_attach_form($entity_1, $form, $form_state); - - // Second entity. - $form['entity_2'] = array( - '#type' => 'details', - '#title' => t('Second entity'), - '#open' => TRUE, - '#tree' => TRUE, - '#parents' => array('entity_2'), - '#weight' => 50, - ); - foreach (array('id', 'type') as $key) { - $form['entity_2'][$key] = array( - '#type' => 'value', - '#value' => $entity_2->$key->value, - ); - } - $form_state['form_display'] = entity_get_form_display($entity_1->getEntityTypeId(), $entity_1->bundle(), 'default'); - field_attach_form($entity_2, $form['entity_2'], $form_state); - - $form['save'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#weight' => 100, - ); - - return $form; -} - -/** - * Validate handler for field_test_entity_nested_form(). - */ -function field_test_entity_nested_form_validate($form, &$form_state) { - $entity_1 = entity_create('entity_test', array( - 'id' => $form_state['values']['id'], - 'type' => $form_state['values']['type'], - )); - field_attach_extract_form_values($entity_1, $form, $form_state); - field_attach_form_validate($entity_1, $form, $form_state); - - $entity_2 = entity_create('entity_test', array( - 'id' => $form_state['values']['entity_2']['id'], - 'type' => $form_state['values']['entity_2']['type'], - )); - field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state); - field_attach_form_validate($entity_2, $form['entity_2'], $form_state); -} - -/** - * Submit handler for field_test_entity_nested_form(). - */ -function field_test_entity_nested_form_submit($form, &$form_state) { - $entity_1 = entity_create('entity_test', array( - 'id' => $form_state['values']['id'], - 'type' => $form_state['values']['type'], - )); - field_attach_extract_form_values($entity_1, $form, $form_state); - $entity_1->save(); - - $entity_2 = entity_create('entity_test', array( - 'id' => $form_state['values']['entity_2']['id'], - 'type' => $form_state['values']['entity_2']['type'], - )); - field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state); - $entity_2->save(); - - drupal_set_message(t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id()))); -} diff --git a/core/modules/field/tests/modules/field_test/field_test.routing.yml b/core/modules/field/tests/modules/field_test/field_test.routing.yml index b7ba00c..cfd60fa 100644 --- a/core/modules/field/tests/modules/field_test/field_test.routing.yml +++ b/core/modules/field/tests/modules/field_test/field_test.routing.yml @@ -2,7 +2,7 @@ field_test.entity_nested_form: path: '/test-entity/nested/{entity_1}/{entity_2}' defaults: _title: 'Nested entity form' - _content: '\Drupal\field_test\Form\FieldTestForm::testEntityNestedForm' + _form: '\Drupal\field_test\Form\NestedEntityTestForm' options: parameters: entity_1: diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php deleted file mode 100644 index 8c9e913..0000000 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php +++ /dev/null @@ -1,25 +0,0 @@ -buildForm($entity_1, $form, $form_state); + + // Second entity. + $form_state['entity_2'] = $entity_2; + $form_state['form_display_2'] = EntityFormDisplay::collectRenderDisplay($entity_2, 'default'); + $form['entity_2'] = array( + '#type' => 'details', + '#title' => t('Second entity'), + '#tree' => TRUE, + '#parents' => array('entity_2'), + '#weight' => 50, + ); + + $form_state['form_display_2']->buildForm($entity_2, $form['entity_2'], $form_state); + + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 100, + ); + + return $form; + } + + /** + * {@inheritdoc] + */ + public function validateForm(array &$form, array &$form_state) { + $entity_1 = $form_state['entity_1']; + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_1 */ + $form_display_1 = $form_state['form_display_1']; + $form_display_1->extractFormValues($entity_1, $form, $form_state); + $form_display_1->validateFormValues($entity_1, $form, $form_state); + + $entity_2 = $form_state['entity_2']; + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */ + $form_display_2 = $form_state['form_display_2']; + $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state); + $form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state); + } + + /** + * {@inheritdoc] + */ + public function submitForm(array &$form, array &$form_state) { + /** @var \Drupal\Core\Entity\EntityInterface $entity_1 */ + $entity_1 = $form_state['entity_1']; + $entity_1->save(); + + /** @var \Drupal\Core\Entity\EntityInterface $entity_2 */ + $entity_2 = $form_state['entity_2']; + $entity_2->save(); + + drupal_set_message($this->t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id()))); + } + +} diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 8eb1d34..3b274ae 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -390,7 +390,7 @@ function menu_node_predelete(EntityInterface $node) { /** * Implements hook_node_prepare_form(). */ -function menu_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) { +function menu_node_prepare_form(NodeInterface $node, $operation, array &$form_state) { if (empty($node->menu)) { // Prepare the node for the edit form so that $node->menu always exists. $node_type_config = \Drupal::config('menu.entity.node.' . $node->getType()); diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 20f6257..9f23dda 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -31,13 +31,12 @@ * - Entity hooks: Generic hooks for "entity" operations. These are always * invoked on all modules. * - * Here is a list of the node and entity hooks that are invoked, field - * operations, and other steps that take place during node operations: + * Here is a list of the node and entity hooks that are invoked, and other + * steps that take place during node operations: * - Instantiating a new node: * - hook_node_create() (all) * - hook_entity_create() (all) * - Creating a new node (calling $node->save() on a new node): - * - field_attach_presave() * - hook_node_presave() (all) * - hook_entity_presave() (all) * - Node and revision records are written to the database @@ -46,7 +45,6 @@ * - hook_node_access_records() (all) * - hook_node_access_records_alter() (all) * - Updating an existing node (calling $node->save() on an existing node): - * - field_attach_presave() * - hook_node_presave() (all) * - hook_entity_presave() (all) * - Node and revision records are written to the database @@ -91,7 +89,7 @@ * existing node, it will already be loaded; see the Loading section above): * - hook_node_prepare_form() (all) * - hook_entity_prepare_form() (all) - * - field_attach_form() + * - @todo hook for EntityFormDisplay::buildForm() * - Validating a node during editing form submit (calling * node_form_validate()): * - hook_node_validate() (all) @@ -598,8 +596,6 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang * * @param \Drupal\node\NodeInterface $node * The node that is about to be shown on the form. - * @param $form_display - * The current form display. * @param $operation * The current operation. * @param array $form_state @@ -607,7 +603,7 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang * * @ingroup node_api_hooks */ -function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $form_display, $operation, array &$form_state) { +function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $operation, array &$form_state) { if (!isset($node->my_rating)) { $node->my_rating = \Drupal::config("my_rating_{$node->bundle()}")->get('enabled'); } diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index aefb73b..91b3a25 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -621,8 +621,6 @@ function hook_entity_display_build_alter(&$build, $context) { * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity that is about to be shown on the form. - * @param $form_display - * The current form display. * @param $operation * The current operation. * @param array $form_state @@ -630,7 +628,7 @@ function hook_entity_display_build_alter(&$build, $context) { * * @see \Drupal\Core\Entity\EntityFormController::prepareEntity() */ -function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $form_display, $operation, array &$form_state) { +function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, array &$form_state) { if ($operation == 'edit') { $entity->label->value = 'Altered label'; $form_state['mymodule']['label_altered'] = TRUE; diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php index 97c8130..030a2e1 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php @@ -76,7 +76,7 @@ function testUserRegistrationMultipleField() { 'name' => 'foo', 'mail' => 'bar@example.com', ); - $this->drupalPostForm('user/register', $edit, t('Add another item'), array('query' => array('field' => TRUE))); + $this->drupalPostForm('user/register', $edit, t('Add another item')); $this->assertText('Test a multiple valued field', 'Form has been rebuilt.'); $this->assertFieldByName('name', 'foo', 'Entered user name has been kept.'); $this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.'); diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index de5c2f0..43d5d25 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -1942,13 +1942,6 @@ function form_test_form_user_register_form_alter(&$form, &$form_state) { '#value' => t('Rebuild'), '#submit' => array('form_test_user_register_form_rebuild'), ); - // If requested, add the test field by attaching the node page form. - if (\Drupal::request()->request->has('field')) { - $node = entity_create('node', array( - 'type' => 'page', - )); - field_attach_form($node, $form, $form_state); - } } /** diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php index c9f1f1d..a5ef3b9 100644 --- a/core/modules/user/lib/Drupal/user/RegisterFormController.php +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -50,9 +50,6 @@ public function form(array $form, array &$form_state) { // Start with the default user account fields. $form = parent::form($form, $form_state, $account); - // Attach field widgets. - field_attach_form($account, $form, $form_state); - if ($admin) { // Redirect back to page which initiated the create request; usually // admin/people/create.