diff --git a/core/core.services.yml b/core/core.services.yml index 8f2965a..2acd8d1 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -194,6 +194,8 @@ services: entity.manager: class: Drupal\Core\Entity\EntityManager arguments: ['@container.namespaces', '@service_container', '@module_handler', '@cache.cache', '@language_manager', '@string_translation'] + entity.content.form_helper: + class: Drupal\Core\Entity\ContentEntityFormHelper plugin.manager.field.field_type: class: Drupal\Core\Field\FieldTypePluginManager arguments: ['@container.namespaces', '@cache.field', '@language_manager', '@module_handler'] diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php index 7f613bc..d0383d6 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Language\Language; +use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -15,7 +15,7 @@ * * @see \Drupal\Core\ContentEntityBase */ -class ContentEntityFormController extends EntityFormController { +class ContentEntityFormController extends EntityFormController implements ContentEntityFormControllerInterface { /** * The entity manager. @@ -25,13 +25,23 @@ class ContentEntityFormController extends EntityFormController { protected $entityManager; /** + * The ContentEntity form helper. + * + * @var \Drupal\Core\Entity\ContentEntityFormHelper + */ + protected $entityFormHelper; + + /** * Constructs a ContentEntityFormController object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper) { $this->entityManager = $entity_manager; + $this->EntityFormHelper = $entity_form_helper; } /** @@ -39,7 +49,8 @@ public function __construct(EntityManagerInterface $entity_manager) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('entity.content.form_helper') ); } @@ -47,13 +58,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. - $info = $entity->entityInfo(); - if (!empty($info['fieldable'])) { - field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state)); - } + $this->EntityFormHelper->attachWidgets($this->entity, $form_state['form_display'], $form, $form_state); // Add a process callback so we can assign weights and hide extra fields. $form['#process'][] = array($this, 'processForm'); @@ -64,30 +69,37 @@ public function form(array $form, array &$form_state) { /** * {@inheritdoc} */ + 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. + foreach ($form_display->getComponents() as $name => $options) { + if (isset($element[$name])) { + $element[$name]['#weight'] = $options['weight']; + } + } + + // Hide extra fields. + $extra_fields = field_info_extra_fields($this->entity->entityType(), $this->entity->bundle(), 'form'); + foreach ($extra_fields as $extra_field => $info) { + if (!$form_display->getComponent($extra_field)) { + $element[$extra_field]['#access'] = FALSE; + } + } + + return $element; + } + + /** + * {@inheritdoc} + */ public function validate(array $form, array &$form_state) { $this->updateFormLangcode($form_state); $entity = $this->buildEntity($form, $form_state); - $entity_type = $entity->entityType(); - $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); - } + $this->EntityFormHelper->validateWidgetsValues($entity, $form_state['form_display'], $form, $form_state); // @todo Remove this. // Execute legacy global validation handlers. @@ -103,6 +115,10 @@ protected function init(array &$form_state) { // language. $langcode = $this->getFormLangcode($form_state); $this->entity = $this->entity->getTranslation($langcode); + + $form_display = entity_get_render_form_display($this->entity, $this->getOperation()); + $this->setFormDisplay($form_display, $form_state); + parent::init($form_state); } @@ -131,34 +147,42 @@ public function isDefaultFormLangcode(array $form_state) { */ public function buildEntity(array $form, array &$form_state) { $entity = clone $this->entity; - $entity_type = $entity->entityType(); - $info = entity_get_info($entity_type); - // @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 = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values']; - $definitions = $entity->getPropertyDefinitions(); - foreach ($values_excluding_fields as $key => $value) { - if (isset($definitions[$key])) { - $entity->$key = $value; + // First, extract values from widgets. + $extracted = $this->EntityFormHelper->extractWidgetsValue($entity, $form_state['form_display'], $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, $entity, &$form, &$form_state)); + call_user_func_array($function, array($entity->entityType(), $entity, &$form, &$form_state)); } } - // Invoke field API for copying field values. - if ($info['fieldable']) { - 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 @@ + array()); + + // Let each widget generate the form elements. + foreach ($entity as $name => $items) { + if ($widget = $display->getRenderer($name)) { + $items->filterEmptyValues(); + $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 = $display->getComponent($name); + $form[$name]['#weight'] = $options['weight']; + } + } + + // @todo hook_field_attach_form() ? + // We still need a hook to allow alteration of "forms with widgets", because + // the regular "entity form" alter hook won't run non "non enity forms"... + // Although, there's already hook_field_widget_form_alter() to alter each widget ? + } + + /** + * Extracts field values from the submitted widget values into the entity. + * + * @param EntityInterface $entity + * The entity. + * @param EntityFormDisplayInterface $display + * The form display containing the widget settings. + * @param array $form + * The 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 extractWidgetsValue(EntityInterface $entity, EntityFormDisplayInterface $display, array &$form, array &$form_state) { + $extracted = array(); + + foreach ($entity as $name => $items) { + if ($widget = $display->getRenderer($name)) { + $widget->extractFormValues($items, $form, $form_state); + $extracted[$name] = $name; + } + } + // @todo hook_field_attach_extract_form_values() ? - See above. + + return $extracted; + } + + /** + * Validates submitted widget values and sets the corresponding form errors. + * + * @param EntityInterface $entity + * The entity. + * @param EntityFormDisplayInterface $display + * The form display containing the widget settings. + * @param array $form + * The form. + * @param array $form_state + * The form state. + */ + public function validateWidgetsValues(EntityInterface $entity, EntityFormDisplayInterface $display, 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 = $display->getRenderer($field_name)) { + $violations = $items->validate(); + if (count($violations)) { + $widget->flagErrors($items, $violations, $form, $form_state); + } + } + } + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 8057ca7..0445ada 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -121,9 +121,6 @@ protected function init(array &$form_state) { // Prepare the entity to be presented in the entity form. $this->prepareEntity(); - $form_display = entity_get_render_form_display($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->entityType() . '_prepare_form', $form_state); @@ -136,12 +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. - $info = $entity->entityInfo(); - if (!empty($info['fieldable'])) { - 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->entityType(), $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; } @@ -407,7 +379,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); } } @@ -416,21 +388,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 525fd7c..fa56a71 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; @@ -59,30 +58,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 0da28d0..a39e694 100644 --- a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php +++ b/core/lib/Drupal/Core/Field/ConfigFieldItemList.php @@ -100,15 +100,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 075e142..9cf5ad0 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\NestedArray; use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; /** * Base class for 'Field widget' plugin implementations. @@ -322,12 +323,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. @@ -352,7 +353,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f $is_multiple = $definition['multiple_values']; $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); diff --git a/core/lib/Drupal/Core/Field/WidgetBaseInterface.php b/core/lib/Drupal/Core/Field/WidgetBaseInterface.php index 8c63e23..69e1d52 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 $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 ec8cf4c..6d4044f 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -537,7 +537,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/book/lib/Drupal/book/Form/BookOutlineForm.php b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php index 5364d93..06d2c66 100644 --- a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php +++ b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php @@ -8,6 +8,7 @@ namespace Drupal\book\Form; use Drupal\Core\Entity\ContentEntityFormController; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\book\BookManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -36,11 +37,13 @@ class BookOutlineForm extends ContentEntityFormController { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\book\BookManager $book_manager * The BookManager service. */ - public function __construct(EntityManagerInterface $entity_manager, BookManager $book_manager) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, BookManager $book_manager) { + parent::__construct($entity_manager, $entity_form_helper); $this->bookManager = $book_manager; } @@ -50,6 +53,7 @@ public function __construct(EntityManagerInterface $entity_manager, BookManager public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('book.manager') ); } diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index 8a5d52f..8abfa2d 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -12,6 +12,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Entity\ContentEntityFormController; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Session\AccountInterface; @@ -36,6 +37,7 @@ class CommentFormController extends ContentEntityFormController { public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('field.info'), $container->get('current_user') ); @@ -46,13 +48,15 @@ public static function create(ContainerInterface $container) { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\field\FieldInfo $field_info * The field info service. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. */ - public function __construct(EntityManagerInterface $entity_manager, FieldInfo $field_info, AccountInterface $current_user) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, FieldInfo $field_info, AccountInterface $current_user) { + parent::__construct($entity_manager, $entity_form_helper); $this->fieldInfo = $field_info; $this->currentUser = $current_user; } diff --git a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php index 60b2036..9e1d126 100644 --- a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php +++ b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php @@ -10,6 +10,7 @@ use Drupal\comment\CommentManagerInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityConfirmFormBase; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -30,11 +31,13 @@ class DeleteForm extends ContentEntityConfirmFormBase { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\comment\CommentManagerInterface $comment_manager * The comment manager service. */ - public function __construct(EntityManagerInterface $entity_manager, CommentManagerInterface $comment_manager) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, CommentManagerInterface $comment_manager) { + parent::__construct($entity_manager, $entity_form_helper); $this->commentManager = $comment_manager; } @@ -44,6 +47,7 @@ public function __construct(EntityManagerInterface $entity_manager, CommentManag public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('comment.manager') ); } diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module index 1f0c81e..e14dc75 100644 --- a/core/modules/datetime/datetime.module +++ b/core/modules/datetime/datetime.module @@ -1043,7 +1043,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 78ece73..7ae8698 100644 --- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php +++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php @@ -7,6 +7,7 @@ namespace Drupal\edit\Form; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Form\FormBase; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Entity\EntityInterface; @@ -42,6 +43,13 @@ class EditFieldForm extends FormBase { protected $nodeTypeStorage; /** + * The ContentEntity form helper. + * + * @var \Drupal\Core\Entity\ContentEntityFormHelper + */ + protected $entityFormHelper; + + /** * Constructs a new EditFieldForm. * * @param \Drupal\user\TempStoreFactory $temp_store_factory @@ -51,10 +59,11 @@ class EditFieldForm extends FormBase { * @param \Drupal\Core\Entity\EntityStorageControllerInterface $node_type_storage * The node type storage. */ - public function __construct(TempStoreFactory $temp_store_factory, ModuleHandlerInterface $module_handler, EntityStorageControllerInterface $node_type_storage) { + public function __construct(TempStoreFactory $temp_store_factory, ModuleHandlerInterface $module_handler, EntityStorageControllerInterface $node_type_storage, ContentEntityFormHelper $entity_form_helper) { + $this->tempStoreFactory = $temp_store_factory; $this->moduleHandler = $module_handler; $this->nodeTypeStorage = $node_type_storage; - $this->tempStoreFactory = $temp_store_factory; + $this->entityFormHelper = $entity_form_helper; } /** @@ -64,7 +73,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('user.tempstore'), $container->get('module_handler'), - $container->get('entity.manager')->getStorageController('node_type') + $container->get('entity.manager')->getStorageController('node_type'), + $container->get('entity.content.form_helper') ); } @@ -86,7 +96,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'])); + $this->entityFormHelper->attachWidgets($entity, $form_state['form_display'], $form, $form_state); // Add a dummy changed timestamp field to attach form errors to. if ($entity instanceof EntityChangedInterface) { @@ -128,7 +138,14 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name // @todo Allow the usage of different form modes by exposing a hook and the // UI for them. - $form_state['form_display'] = entity_get_render_form_display($entity, 'default'); + $display = entity_get_render_form_display($entity, 'default'); + foreach ($display->getComponents() as $name => $optipns) { + if ($name != $field_name) { + $display->removeComponent($name); + } + } + $form_state['form_display'] = $display; + // @todo Revisit what really needs to be in $form_state... } /** @@ -136,7 +153,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'])); + + $this->entityFormHelper->validateWidgetsValues($entity, $form_state['form_display'], $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 @@ -171,7 +189,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)); + $this->entityFormHelper->extractWidgetsValue($entity, $form_state['form_display'], $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/field/field.attach.inc b/core/modules/field/field.attach.inc index 50b3cb2..f6f979e 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -247,23 +247,6 @@ function _field_invoke_get_field_definitions($entity_type, $bundle, $options) { } /** - * 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()); - }; -} - -/** * Populates the template variables with the available field values. * * The $variables array will be populated with all the field instance values diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc index 94373bc..1358149 100644 --- a/core/modules/field/field.deprecated.inc +++ b/core/modules/field/field.deprecated.inc @@ -539,24 +539,7 @@ function field_read_instances($conditions = array(), $include_additional = array * @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->entityType(); - $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); - } + \Drupal::service('entity.content.form_helper')->attachWidgets($entity, $form_state['form_display'], $form, $form_state); } /** @@ -591,27 +574,7 @@ function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langc * @deprecated as of Drupal 8.0. Use the entity system instead. */ function field_attach_form_validate(ContentEntityInterface $entity, $form, &$form_state, array $options = array()) { - $has_violations = FALSE; - foreach ($entity as $field_name => $field) { - $definition = $field->getDefinition(); - 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); - } + \Drupal::service('entity.content.form_helper')->validateWidgetsValues($entity, $form_state['form_display'], $form, $form_state); } /** @@ -635,17 +598,7 @@ function field_attach_form_validate(ContentEntityInterface $entity, $form, &$for * @deprecated as of Drupal 8.0. Use the entity system instead. */ 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); - } + \Drupal::service('entity.content.form_helper')->extractWidgetsValue($entity, $form_state['form_display'], $form, $form_state); } /** diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php index 8f77373..e73b9db 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php @@ -347,10 +347,15 @@ function testFieldAttachForm() { } // When generating form for a single field (the second field). - $options = array('field_name' => $this->field_name_2); + $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'); + $form_state['form_display'] = $display; field_attach_form($entity, $form, $form_state, NULL, $options); $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form'); @@ -434,7 +439,11 @@ 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 ($form_state['form_display']->getComponents() as $name => $options) { + if ($name != $this->field_name_2) { + $form_state['form_display']->removeComponent($name); + } + } $entity = clone($entity_init); field_attach_extract_form_values($entity, $form, $form_state, $options); $expected_values_2 = array(); diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 7fce53c..a39688a 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -403,7 +403,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/lib/Drupal/node/Form/NodeDeleteForm.php b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php index 644b069..cafb32a 100644 --- a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php +++ b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php @@ -9,6 +9,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityConfirmFormBase; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -30,11 +31,13 @@ class NodeDeleteForm extends ContentEntityConfirmFormBase { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The URL generator. */ - public function __construct(EntityManagerInterface $entity_manager, UrlGeneratorInterface $url_generator) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, UrlGeneratorInterface $url_generator) { + parent::__construct($entity_manager, $entity_form_helper); $this->urlGenerator = $url_generator; } @@ -44,6 +47,7 @@ public function __construct(EntityManagerInterface $entity_manager, UrlGenerator public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('url_generator') ); } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index d53673c..9194d3f 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -601,8 +601,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 @@ -610,7 +608,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/shortcut/lib/Drupal/shortcut/ShortcutFormController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php index 7179886..a37d052 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php @@ -8,6 +8,7 @@ namespace Drupal\shortcut; use Drupal\Core\Entity\ContentEntityFormController; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Form\FormBuilderInterface; @@ -47,6 +48,8 @@ class ShortcutFormController extends ContentEntityFormController { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager * The path alias manager. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator @@ -54,8 +57,8 @@ class ShortcutFormController extends ContentEntityFormController { * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The form builder. */ - public function __construct(EntityManagerInterface $entity_manager, AliasManagerInterface $alias_manager, UrlGeneratorInterface $url_generator, FormBuilderInterface $form_builder) { - $this->entityManager = $entity_manager; + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, AliasManagerInterface $alias_manager, UrlGeneratorInterface $url_generator, FormBuilderInterface $form_builder) { + parent::__construct($entity_manager, $entity_form_helper); $this->aliasManager = $alias_manager; $this->urlGenerator = $url_generator; $this->formBuilder = $form_builder; @@ -67,6 +70,7 @@ public function __construct(EntityManagerInterface $entity_manager, AliasManager public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('path.alias_manager'), $container->get('url_generator'), $container->get('form_builder') diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index 5b880cd..3917148 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -591,8 +591,6 @@ function hook_entity_display_alter(\Drupal\Core\Entity\Display\EntityViewDisplay * * @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 @@ -600,7 +598,7 @@ function hook_entity_display_alter(\Drupal\Core\Entity\Display\EntityViewDisplay * * @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/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php index 925d83c..7b1096b 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php @@ -10,6 +10,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Entity\ContentEntityFormController; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,11 +32,13 @@ class TermFormController extends ContentEntityFormController { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\Core\Config\ConfigFactory $config_factory * The config factory. */ - public function __construct(EntityManagerInterface $entity_manager, ConfigFactory $config_factory) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, ConfigFactory $config_factory) { + parent::__construct($entity_manager, $entity_form_helper); $this->configFactory = $config_factory; } @@ -45,6 +48,7 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('config.factory') ); } diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php index 4983b0f..ad04293 100644 --- a/core/modules/user/lib/Drupal/user/AccountFormController.php +++ b/core/modules/user/lib/Drupal/user/AccountFormController.php @@ -8,6 +8,7 @@ namespace Drupal\user; use Drupal\Core\Entity\ContentEntityFormController; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; @@ -30,11 +31,13 @@ * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\Core\Language\LanguageManager $language_manager * The language manager. */ - public function __construct(EntityManagerInterface $entity_manager, LanguageManager $language_manager) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, LanguageManager $language_manager) { + parent::__construct($entity_manager, $entity_form_helper); $this->languageManager = $language_manager; } @@ -44,6 +47,7 @@ public function __construct(EntityManagerInterface $entity_manager, LanguageMana public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('language_manager') ); } diff --git a/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php b/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php index e1a3c6a..f1cd388 100644 --- a/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php +++ b/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Entity\ContentEntityConfirmFormBase; +use Drupal\Core\Entity\ContentEntityFormHelper; use Drupal\Core\Entity\EntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -43,11 +44,13 @@ class UserCancelForm extends ContentEntityConfirmFormBase { * * @param \Drupal\Core\Config\ConfigFactory $config_factory * The config factory. + * @param \Drupal\Core\Entity\ContentEntityFormHelper $entity_form_helper + * The ContentEntity form helper. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. */ - public function __construct(EntityManagerInterface $entity_manager, ConfigFactory $config_factory) { - parent::__construct($entity_manager); + public function __construct(EntityManagerInterface $entity_manager, ContentEntityFormHelper $entity_form_helper, ConfigFactory $config_factory) { + parent::__construct($entity_manager, $entity_form_helper); $this->configFactory = $config_factory; } @@ -57,6 +60,7 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('entity.content.form_helper'), $container->get('config.factory') ); } diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php index 38612a2..7acaa4a 100644 --- a/core/modules/user/lib/Drupal/user/RegisterFormController.php +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -42,9 +42,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.