diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index f554197c635ca741eaf6ef49e1b7343244e98bfe..aae28963b119978ae1e7fa306b4f2e42330125f9 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -151,6 +151,20 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C */ protected $translatableEntityKeys = array(); + /** + * Whether entity validation was performed. + * + * @var bool + */ + protected $validated = FALSE; + + /** + * Whether entity validation is required before saving the entity. + * + * @var bool + */ + protected $validationRequired = FALSE; + /** * Overrides Entity::__construct(). */ @@ -331,6 +345,23 @@ public function isTranslatable() { return !empty($bundles[$this->bundle()]['translatable']) && !$this->getUntranslated()->language()->isLocked() && $this->languageManager()->isMultilingual(); } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + // An entity requiring validation should not be saved if it has not been + // actually validated. + if ($this->validationRequired && !$this->validated) { + // @todo Make this an assertion in https://www.drupal.org/node/2408013. + throw new \LogicException('Entity validation was skipped.'); + } + else { + $this->validated = FALSE; + } + + parent::preSave($storage); + } + /** * {@inheritdoc} */ @@ -341,10 +372,26 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco * {@inheritdoc} */ public function validate() { + $this->validated = TRUE; $violations = $this->getTypedData()->validate(); return new EntityConstraintViolationList($this, iterator_to_array($violations)); } + /** + * {@inheritdoc} + */ + public function isValidationRequired() { + return (bool) $this->validationRequired; + } + + /** + * {@inheritdoc} + */ + public function setValidationRequired($required) { + $this->validationRequired = $required; + return $this; + } + /** * Clear entity translation object cache to remove stale references. */ diff --git a/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php index 887fcae3676ccdce8217e5398d8174e3c08565bd..9ca52212858b5aff18b10dcebef4e71c1d55045e 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php @@ -86,9 +86,6 @@ protected function actions(array $form, FormStateInterface $form_state) { 'submit' => array( '#type' => 'submit', '#value' => $this->getConfirmText(), - '#validate' => array( - array($this, 'validate'), - ), '#submit' => array( array($this, 'submitForm'), ), @@ -121,9 +118,10 @@ public function delete(array $form, FormStateInterface $form_state) {} /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state) { // Override the default validation implementation as it is not necessary // nor possible to validate an entity in a confirmation form. + return $this->entity; } } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php index 2c1604bb9b8849e6583cf098be5d8de278c02118..1fd99dcbc21de4159b609f248142dfab4ac1d54f 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php @@ -62,19 +62,30 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function buildEntity(array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = parent::buildEntity($form, $form_state); + + // Mark the entity as requiring validation. + $entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated')); + + return $entity; + } + /** * {@inheritdoc} * - * Note that extending classes should not override this method to add entity - * validation logic, but define further validation constraints using the - * entity validation API and/or provide a new validation constraint if - * necessary. This is the only way to ensure that the validation logic - * is correctly applied independently of form submissions; e.g., for REST - * requests. - * For more information about entity validation, see - * https://www.drupal.org/node/2015613. + * Button-level validation handlers are highly discouraged for entity forms, + * as they will prevent entity validation from running. If the entity is going + * to be saved during the form submission, this method should be manually + * invoked from the button-level validation handler, otherwise an exception + * will be thrown. */ - public function validate(array $form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $this->buildEntity($form, $form_state); @@ -87,10 +98,10 @@ public function validate(array $form, FormStateInterface $form_state) { $this->flagViolations($violations, $form, $form_state); - // @todo Remove this. - // Execute legacy global validation handlers. - $form_state->setValidateHandlers([]); - \Drupal::service('form_validator')->executeValidateHandlers($form, $form_state); + // The entity was validated. + $entity->setValidationRequired(FALSE); + $form_state->setTemporaryValue('entity_validated', TRUE); + return $entity; } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php index 8fae58efdeb20bf5a3fcdbeafa9aaba744d652c9..f210611aae960119a97ddb693333be25ea75d6dd 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php @@ -36,6 +36,8 @@ public function getFormDisplay(FormStateInterface $form_state); * The form display that the current form operates with. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * + * @return $this */ public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state); @@ -61,4 +63,21 @@ public function getFormLangcode(FormStateInterface $form_state); */ public function isDefaultFormLangcode(FormStateInterface $form_state); + /** + * {@inheritdoc} + * + * Note that extending classes should not override this method to add entity + * validation logic, but define further validation constraints using the + * entity validation API and/or provide a new validation constraint if + * necessary. This is the only way to ensure that the validation logic + * is correctly applied independently of form submissions; e.g., for REST + * requests. + * For more information about entity validation, see + * https://www.drupal.org/node/2015613. + * + * @return \Drupal\Core\Entity\ContentEntityTypeInterface + * The built entity. + */ + public function validateForm(array &$form, FormStateInterface $form_state); + } diff --git a/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php b/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php index a11e27f0a71070b3b047e2f513292b597a05b3e3..7b320e1682578f335e03454c3e76a193fc70ab90 100644 --- a/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php +++ b/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php @@ -81,9 +81,6 @@ protected function actions(array $form, FormStateInterface $form_state) { 'submit' => array( '#type' => 'submit', '#value' => $this->getConfirmText(), - '#validate' => array( - array($this, 'validate'), - ), '#submit' => array( array($this, 'submitForm'), ), diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index 6d61d0c6025b875e43b2ed0284ac1434fb671d51..e900309b3d413ebf40eea20d7afffd9c9a7ce4ca 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -219,7 +219,6 @@ protected function actions(array $form, FormStateInterface $form_state) { $actions['submit'] = array( '#type' => 'submit', '#value' => $this->t('Save'), - '#validate' => array('::validate'), '#submit' => array('::submitForm', '::save'), ); @@ -244,16 +243,6 @@ protected function actions(array $form, FormStateInterface $form_state) { return $actions; } - /** - * {@inheritdoc} - */ - public function validate(array $form, FormStateInterface $form_state) { - // @todo Remove this. - // Execute legacy global validation handlers. - $form_state->setValidateHandlers([]); - \Drupal::service('form_validator')->executeValidateHandlers($form, $form_state); - } - /** * {@inheritdoc} * diff --git a/core/lib/Drupal/Core/Entity/EntityFormInterface.php b/core/lib/Drupal/Core/Entity/EntityFormInterface.php index 26ce9098ab5f6cf35419f2fbf4858c7bc4c72be4..8ae1a9142b9826e97d177ff3a0cdb305432cf24d 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityFormInterface.php @@ -94,19 +94,6 @@ public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entit */ public function buildEntity(array $form, FormStateInterface $form_state); - /** - * Validates the submitted form values of the entity form. - * - * @param array $form - * A nested array form elements comprising the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return \Drupal\Core\Entity\ContentEntityTypeInterface - * The built entity. - */ - public function validate(array $form, FormStateInterface $form_state); - /** * Form submission handler for the 'save' action. * diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php index b4a19602671df1a7b494059ab5ab9f1635454be5..2e5cce76fb678b9f40521d42a56afcaf85cf51ac 100644 --- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php @@ -212,4 +212,22 @@ public function onChange($field_name); */ public function validate(); + /** + * Checks whether entity validation is required before saving the entity. + * + * @return bool + * TRUE if validation is required, FALSE if not. + */ + public function isValidationRequired(); + + /** + * Sets whether entity validation is required before saving the entity. + * + * @param bool $required + * TRUE if validation is required, FALSE otherwise. + * + * @return $this + */ + public function setValidationRequired($required); + } diff --git a/core/modules/action/src/ActionFormBase.php b/core/modules/action/src/ActionFormBase.php index bdddc9818e6742939a5a20a28cbe997b422e2c02..0b972d493ed59e1b1423b355d6b7c9bfe96b450b 100644 --- a/core/modules/action/src/ActionFormBase.php +++ b/core/modules/action/src/ActionFormBase.php @@ -123,8 +123,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); if ($this->plugin instanceof PluginFormInterface) { $this->plugin->validateConfigurationForm($form, $form_state); diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index ac9406a8feb5302411299d041f42a2c095c48002..07da65b5770c2bb0dd648d4d3d9bf9da07a97c1d 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -273,8 +273,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // The Block Entity form puts all block plugin form elements in the // settings form element, so just pass that to the block for validation. diff --git a/core/modules/block_content/src/BlockContentForm.php b/core/modules/block_content/src/BlockContentForm.php index 379e5763560e06728b2a03e446f2b43dd7cec7de..ec267cc39311112f4e00c9bc854490e72ebadaf9 100644 --- a/core/modules/block_content/src/BlockContentForm.php +++ b/core/modules/block_content/src/BlockContentForm.php @@ -224,7 +224,8 @@ public function save(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { - if ($this->entity->isNew()) { + $entity = parent::validateForm($form, $form_state); + if ($entity->isNew()) { $exists = $this->blockContentStorage->loadByProperties(array('info' => $form_state->getValue(['info', 0, 'value']))); if (!empty($exists)) { $form_state->setErrorByName('info', $this->t('A block with description %name already exists.', array( @@ -232,6 +233,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { ))); } } + return $entity; } } diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php index 0bcacee9eda2d6b91cf238d5242374bdff031d75..e786fe1c77a01877bcecc32d7b0593b56ed11976 100644 --- a/core/modules/comment/src/CommentForm.php +++ b/core/modules/comment/src/CommentForm.php @@ -258,7 +258,6 @@ protected function actions(array $form, FormStateInterface $form_state) { '#type' => 'submit', '#value' => $this->t('Preview'), '#access' => $preview_mode != DRUPAL_DISABLED, - '#validate' => array('::validate'), '#submit' => array('::submitForm', '::preview'), ); diff --git a/core/modules/contact/src/ContactFormEditForm.php b/core/modules/contact/src/ContactFormEditForm.php index 3300c593b69c5509675dea0a23e784357ffa5658..c545b0e53abec012c0e0877cd3613a334b8e6880 100644 --- a/core/modules/contact/src/ContactFormEditForm.php +++ b/core/modules/contact/src/ContactFormEditForm.php @@ -112,8 +112,8 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // Validate and each email recipient. $recipients = explode(',', $form_state->getValue('recipients')); diff --git a/core/modules/contact/src/MessageForm.php b/core/modules/contact/src/MessageForm.php index 78de6ddc596a066992a3a92c326f152121e7d866..27352c7a69d713141f135b0073e27b2b26fbd1b3 100644 --- a/core/modules/contact/src/MessageForm.php +++ b/core/modules/contact/src/MessageForm.php @@ -168,8 +168,8 @@ public function actions(array $form, FormStateInterface $form_state) { $elements = parent::actions($form, $form_state); $elements['submit']['#value'] = $this->t('Send message'); $elements['preview'] = array( + '#type' => 'submit', '#value' => $this->t('Preview'), - '#validate' => array('::validate'), '#submit' => array('::submitForm', '::preview'), ); return $elements; @@ -187,10 +187,8 @@ public function preview(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); - - $message = $this->entity; + public function validateForm(array &$form, FormStateInterface $form_state) { + $message = parent::validateForm($form, $form_state); // Check if flood control has been activated for sending emails. if (!$this->currentUser()->hasPermission('administer contact forms') && (!$message->isPersonal() || !$this->currentUser()->hasPermission('administer users'))) { @@ -204,6 +202,8 @@ public function validate(array $form, FormStateInterface $form_state) { ))); } } + + return $message; } /** diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index 28529eb0b29023d3503df3c81675af9af0bf042d..10cf5f3b84a99596379a2fdfff2824d0145c227f 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -473,9 +473,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En $form['#entity_builders'][] = array($this, 'entityFormEntityBuild'); // Handle entity validation. - if (isset($form['actions']['submit'])) { - $form['actions']['submit']['#validate'][] = array($this, 'entityFormValidate'); - } + $form['#validate'][] = array($this, 'entityFormValidate'); // Handle entity deletion. if (isset($form['actions']['delete'])) { diff --git a/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php b/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php index d6d801b7551c41bd59397fac5de391a6695933b5..350a2e99badba11261e27560d1577cecc0b6c9ab 100644 --- a/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php +++ b/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php @@ -38,8 +38,8 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); $form_state->setValueForElement($form['id'], $this->targetEntityTypeId . '.' . $form_state->getValue('id')); } diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index 866ff9a48ab6e3625740313e51d75a73912a6715..52ace9581f780bc937e183c904267aa53774a2ae 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -150,8 +150,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); if (isset($form['default_value'])) { $item = $form['#entity']->get($this->entity->getName()); diff --git a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php index 58c4ea9030d856b71874a3ad602e74f0802cba8c..adafcc79000b21a0a9a9c702032136c4f0a18b93 100644 --- a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php @@ -146,8 +146,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // Validate field cardinality. if ($form_state->getValue('cardinality') === 'number' && !$form_state->getValue('cardinality_number')) { diff --git a/core/modules/filter/src/FilterFormatFormBase.php b/core/modules/filter/src/FilterFormatFormBase.php index ff140c3b0bc114c37b53076775eb674c6a5b1d25..77b5433598432a71080f1ff7fca99190b777fa30 100644 --- a/core/modules/filter/src/FilterFormatFormBase.php +++ b/core/modules/filter/src/FilterFormatFormBase.php @@ -204,8 +204,8 @@ public function exists($format_id) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // @todo Move trimming upstream. $format_format = trim($form_state->getValue('format')); diff --git a/core/modules/forum/src/Form/Overview.php b/core/modules/forum/src/Form/Overview.php index 24f5142aa62a06d3e363650a973ccc999f20af7b..dae7fb54c0b525201b8ac49e1b3899b0a11b99d0 100644 --- a/core/modules/forum/src/Form/Overview.php +++ b/core/modules/forum/src/Form/Overview.php @@ -86,9 +86,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Remove the alphabetical reset. unset($form['actions']['reset_alphabetical']); - // The form needs to have submit and validate handlers set explicitly. // Use the existing taxonomy overview submit handler. - $form['#submit'] = array('::submitForm'); $form['terms']['#empty'] = $this->t('No containers or forums available. <a href="@container">Add container</a> or <a href="@forum">Add forum</a>.', array( '@container' => $this->url('forum.add_container'), '@forum' => $this->url('forum.add_forum') diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index 0c097ba363ff4d97ce0739b53646e348b010de7a..f26dade62d1c85e5475c899146cacc790a2d0678 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -415,7 +415,7 @@ function menu_ui_form_node_type_form_alter(&$form, FormStateInterface $form_stat ); $options_cacheability->applyTo($form['menu']['menu_parent']); - $form['actions']['submit']['#validate'][] = 'menu_ui_form_node_type_form_validate'; + $form['#validate'][] = 'menu_ui_form_node_type_form_validate'; $form['#entity_builders'][] = 'menu_ui_form_node_type_form_builder'; } diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php index c5846606831da4ba23fb730eba94476f51752874..e363dca9923d3a972e5e432465daa859079e062b 100644 --- a/core/modules/node/src/NodeForm.php +++ b/core/modules/node/src/NodeForm.php @@ -297,7 +297,6 @@ protected function actions(array $form, FormStateInterface $form_state) { '#access' => $preview_mode != DRUPAL_DISABLED && ($node->access('create') || $node->access('update')), '#value' => t('Preview'), '#weight' => 20, - '#validate' => array('::validate'), '#submit' => array('::submitForm', '::preview'), ); diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php index 6dfc669df7cb345cc59278aefa9e77f17190f8b0..ff757f98b37921cb54fd5a1fce081a4349dd2040 100644 --- a/core/modules/node/src/NodeTypeForm.php +++ b/core/modules/node/src/NodeTypeForm.php @@ -204,8 +204,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); $id = trim($form_state->getValue('type')); // '0' is invalid, since elsewhere we check it using empty(). diff --git a/core/modules/responsive_image/src/ResponsiveImageStyleForm.php b/core/modules/responsive_image/src/ResponsiveImageStyleForm.php index a22742ae74b73062592bfd9d82f7052c56f752a0..5763bc85f7392287e8eeccfb15d37b540d4cd39a 100644 --- a/core/modules/responsive_image/src/ResponsiveImageStyleForm.php +++ b/core/modules/responsive_image/src/ResponsiveImageStyleForm.php @@ -142,7 +142,8 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // Only validate on edit. if ($form_state->hasValue('keyed_styles')) { // Check if another breakpoint group is selected. diff --git a/core/modules/search/src/Form/SearchPageFormBase.php b/core/modules/search/src/Form/SearchPageFormBase.php index 95afb4ba962d8686934edc724a68d8aa4ea557a3..c41fd5be585647e82217616e3b14f56737e662a8 100644 --- a/core/modules/search/src/Form/SearchPageFormBase.php +++ b/core/modules/search/src/Form/SearchPageFormBase.php @@ -144,8 +144,8 @@ public function exists($id) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // Ensure each path is unique. $path = $this->entityQuery->get('search_page') diff --git a/core/modules/system/src/Form/DateFormatFormBase.php b/core/modules/system/src/Form/DateFormatFormBase.php index aeaa12a7b740b8e4b7287e3eb7e4a6dea3271851..b8d0e178825264d064db7343f5efdf8fe81408fd 100644 --- a/core/modules/system/src/Form/DateFormatFormBase.php +++ b/core/modules/system/src/Form/DateFormatFormBase.php @@ -126,8 +126,8 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // The machine name field should already check to see if the requested // machine name is available. Regardless of machine_name or human readable diff --git a/core/modules/system/src/Tests/Entity/EntityFormTest.php b/core/modules/system/src/Tests/Entity/EntityFormTest.php index f268dd751124b0c8493fb82b9a19bd2b96a7d708..ffa5cca84ac37da4e46e346f4ae723e753fc45dd 100644 --- a/core/modules/system/src/Tests/Entity/EntityFormTest.php +++ b/core/modules/system/src/Tests/Entity/EntityFormTest.php @@ -149,4 +149,25 @@ protected function loadEntityByName($entity_type, $name) { $entities = $entity_storage->loadByProperties(array('name' => $name)); return $entities ? current($entities) : NULL; } + + /** + * Checks that validation handlers works as expected. + */ + public function testValidationHandlers() { + /** @var \Drupal\Core\State\StateInterface $state */ + $state = $this->container->get('state'); + + // Check that from-level validation handlers can be defined and can alter + // the form array. + $state->set('entity_test.form.validate.test', 'form-level'); + $this->drupalPostForm('entity_test/add', [], 'Save'); + $this->assertTrue($state->get('entity_test.form.validate.result'), 'Form-level validation handlers behave correctly.'); + + // Check that defining a button-level validation handler causes an exception + // to be thrown. + $state->set('entity_test.form.validate.test', 'button-level'); + $this->drupalPostForm('entity_test/add', [], 'Save'); + $this->assertEqual($state->get('entity_test.form.save.exception'), 'Drupal\Core\Entity\EntityStorageException: Entity validation was skipped.', 'Button-level validation handlers behave correctly.'); + } + } diff --git a/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php b/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php index 426ac5b4398567724808245b5717d969261d95be..4f223e3533977da90dea9e3e79c21204c237dd3f 100644 --- a/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php +++ b/core/modules/system/src/Tests/Entity/FieldWidgetConstraintValidatorTest.php @@ -89,8 +89,12 @@ protected function getErrorsForEntity(EntityInterface $entity, $hidden_fields = \Drupal::formBuilder()->processForm('field_test_entity_form', $form, $form_state); // Validate the field constraint. - $form_state->getFormObject()->setEntity($entity)->setFormDisplay($display, $form_state); - $form_state->getFormObject()->validate($form, $form_state); + /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */ + $form_object = $form_state->getFormObject(); + $form_object + ->setEntity($entity) + ->setFormDisplay($display, $form_state) + ->validateForm($form, $form_state); return $form_state->getErrors(); } diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 28f1e993d7784b5c367574ee3c9a5f5f1bcf3de8..1e7fa2b1f9e6156086f3aadd08b4dc36b8effa06 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -275,6 +275,37 @@ function entity_test_entity_extra_field_info() { return $extra; } +/** + * Implements hook_form_BASE_FORM_ID_alter(). + */ +function entity_test_form_entity_test_form_alter(&$form) { + switch (\Drupal::state()->get('entity_test.form.validate.test')) { + case 'form-level': + $form['#validate'][] = 'entity_test_form_entity_test_form_validate'; + $form['#validate'][] = 'entity_test_form_entity_test_form_validate_check'; + break; + + case 'button-level': + $form['actions']['submit']['#validate'][] = 'entity_test_form_entity_test_form_validate'; + } +} + +/** + * Validation handler for the entity_test entity form. + */ +function entity_test_form_entity_test_form_validate(array &$form, FormStateInterface $form_state) { + $form['#entity_test_form_validate'] = TRUE; +} + +/** + * Validation handler for the entity_test entity form. + */ +function entity_test_form_entity_test_form_validate_check(array &$form, FormStateInterface $form_state) { + if (!empty($form['#entity_test_form_validate'])) { + \Drupal::state()->set('entity_test.form.validate.result', TRUE); + } +} + /** * Implements hook_form_BASE_FORM_ID_alter(). */ diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php b/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php index fb3c486dad943f02d161a07333bb844e8bad5e85..59e798b5b88539ba132d69637786b9613bd48064 100644 --- a/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestForm.php @@ -51,35 +51,40 @@ public function form(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { - $entity = $this->entity; + try { + $entity = $this->entity; - // Save as a new revision if requested to do so. - if (!$form_state->isValueEmpty('revision')) { - $entity->setNewRevision(); - } + // Save as a new revision if requested to do so. + if (!$form_state->isValueEmpty('revision')) { + $entity->setNewRevision(); + } - $is_new = $entity->isNew(); - $entity->save(); + $is_new = $entity->isNew(); + $entity->save(); - if ($is_new) { - $message = t('%entity_type @id has been created.', array('@id' => $entity->id(), '%entity_type' => $entity->getEntityTypeId())); - } - else { - $message = t('%entity_type @id has been updated.', array('@id' => $entity->id(), '%entity_type' => $entity->getEntityTypeId())); - } - drupal_set_message($message); + if ($is_new) { + $message = t('%entity_type @id has been created.', array('@id' => $entity->id(), '%entity_type' => $entity->getEntityTypeId())); + } + else { + $message = t('%entity_type @id has been updated.', array('@id' => $entity->id(), '%entity_type' => $entity->getEntityTypeId())); + } + drupal_set_message($message); - if ($entity->id()) { - $entity_type = $entity->getEntityTypeId(); - $form_state->setRedirect( - "entity.$entity_type.edit_form", - array($entity_type => $entity->id()) - ); + if ($entity->id()) { + $entity_type = $entity->getEntityTypeId(); + $form_state->setRedirect( + "entity.$entity_type.edit_form", + array($entity_type => $entity->id()) + ); + } + else { + // Error on save. + drupal_set_message(t('The entity could not be saved.'), 'error'); + $form_state->setRebuild(); + } } - else { - // Error on save. - drupal_set_message(t('The entity could not be saved.'), 'error'); - $form_state->setRebuild(); + catch (\Exception $e) { + \Drupal::state()->set('entity_test.form.save.exception', get_class($e) . ': ' . $e->getMessage()); } } diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php index a0d1279f0d61ba0b1067250a6c9a02314a936512..9da0993643d3572ba49258abbf167b19a14b8c58 100644 --- a/core/modules/taxonomy/src/TermForm.php +++ b/core/modules/taxonomy/src/TermForm.php @@ -96,8 +96,8 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); // Ensure numeric values. if ($form_state->hasValue('weight') && !is_numeric($form_state->getValue('weight'))) { diff --git a/core/modules/views_ui/src/ViewAddForm.php b/core/modules/views_ui/src/ViewAddForm.php index 6db3e95e6cdfdfeafd1ff2c2e9db659ef83e4c57..5b816d93cb733dcc1540c70b6fbbfe8c63f05b1d 100644 --- a/core/modules/views_ui/src/ViewAddForm.php +++ b/core/modules/views_ui/src/ViewAddForm.php @@ -162,7 +162,7 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state) { $wizard_type = $form_state->getValue(array('show', 'wizard_key')); $wizard_instance = $this->wizardManager->createInstance($wizard_type); $form_state->set('wizard', $wizard_instance->getPluginDefinition()); diff --git a/core/modules/views_ui/src/ViewDuplicateForm.php b/core/modules/views_ui/src/ViewDuplicateForm.php index 36f1a7fc5463597d70e95be2c9a2eb0874ac5697..ccc1a49f5517064a2b6606426522c35a1b33065b 100644 --- a/core/modules/views_ui/src/ViewDuplicateForm.php +++ b/core/modules/views_ui/src/ViewDuplicateForm.php @@ -58,7 +58,6 @@ protected function actions(array $form, FormStateInterface $form_state) { $actions['submit'] = array( '#type' => 'submit', '#value' => $this->t('Duplicate'), - '#submit' => array('::submitForm'), ); return $actions; } diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 9e598f28d94e5ce53e8e2283702faf159f1ad6b3..b11f54afe2d89adaeff1802d2afa1abcc9fb10aa 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -264,8 +264,8 @@ protected function actions(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validate(array $form, FormStateInterface $form_state) { - parent::validate($form, $form_state); + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); $view = $this->entity; if ($view->isLocked()) { diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php index b3bd6bd784c09f223aeb7bd941433f4206d34ad7..f00ecfa401102d9dd55106fc164ad7ff5f02b55b 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\UnitTestCase; @@ -349,6 +350,64 @@ public function testValidate() { $this->assertSame(1, count($this->entity->validate())); } + /** + * Tests required validation. + * + * @covers ::validate + * @covers ::isValidationRequired + * @covers ::setValidationRequired + * @covers ::save + * @covers ::preSave + * + * @expectedException \LogicException + * @expectedExceptionMessage Entity validation was skipped. + */ + public function testRequiredValidation() { + $validator = $this->getMock('\Symfony\Component\Validator\ValidatorInterface'); + /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */ + $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList') + ->setMethods(NULL) + ->getMock(); + $validator->expects($this->at(0)) + ->method('validate') + ->with($this->entity->getTypedData()) + ->will($this->returnValue($empty_violation_list)); + $this->typedDataManager->expects($this->any()) + ->method('getValidator') + ->will($this->returnValue($validator)); + + /** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject $storage */ + $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); + $storage->expects($this->any()) + ->method('save') + ->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) { + $entity->preSave($storage); + }); + + $this->entityManager->expects($this->any()) + ->method('getStorage') + ->with($this->entityTypeId) + ->will($this->returnValue($storage)); + + // Check that entities can be saved normally when validation is not + // required. + $this->assertFalse($this->entity->isValidationRequired()); + $this->entity->save(); + + // Make validation required and check that if the entity is validated, it + // can be saved normally. + $this->entity->setValidationRequired(TRUE); + $this->assertTrue($this->entity->isValidationRequired()); + $this->entity->validate(); + $this->entity->save(); + + // Check that the "validated" status is reset after saving the entity and + // that trying to save a non-validated entity when validation is required + // results in an exception. + $this->assertTrue($this->entity->isValidationRequired()); + $this->entity->save(); + } + /** * @covers ::bundle */