core/config/schema/core.data_types.schema.yml | 3 + .../Drupal/Core/Form/ValidatableConfigFormBase.php | 98 ++++++++++++++++++++++ core/misc/cspell/dictionary.txt | 1 + core/modules/update/src/UpdateSettingsForm.php | 87 +++++-------------- .../src/Functional/UpdateSettingsFormTest.php | 2 +- 5 files changed, 122 insertions(+), 69 deletions(-) diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index d866c01b1e..5a5f7209c2 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -25,6 +25,9 @@ boolean: email: label: 'Email' class: '\Drupal\Core\TypedData\Plugin\DataType\Email' + constraints: + Email: + message: "%value is not a valid email address." integer: label: 'Integer' class: '\Drupal\Core\TypedData\Plugin\DataType\IntegerData' diff --git a/core/lib/Drupal/Core/Form/ValidatableConfigFormBase.php b/core/lib/Drupal/Core/Form/ValidatableConfigFormBase.php new file mode 100644 index 0000000000..6130b0cade --- /dev/null +++ b/core/lib/Drupal/Core/Form/ValidatableConfigFormBase.php @@ -0,0 +1,98 @@ +get('config.factory'), + $container->get('config.typed') + ); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + foreach ($this->getEditableConfigNames() as $config_name) { + $config = static::mapFormValuesToConfig($form_state, $this->config($config_name)); + $typed_config = $this->typedConfigManager->createFromNameAndData($config_name, $config->getRawData()); + + $violations = $typed_config->validate(); + foreach ($violations as $violation) { + $form_state->setErrorByName(static::mapConfigPropertyPathToFormElementName($config_name, $violation->getPropertyPath()), $violation->getMessage()); + } + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + foreach ($this->getEditableConfigNames() as $config_name) { + $config = static::mapFormValuesToConfig($form_state, $this->config($config_name)); + $config->save(); + } + parent::submitForm($form, $form_state); + } + + /** + * Maps the form values from form state onto the given editable Config. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Config\Config $config + * The configuration being edited. + * + * @return \Drupal\Core\Config\Config + * The updated configuration object. + */ + abstract protected static function mapFormValuesToConfig(FormStateInterface $form_state, Config $config): Config; + + /** + * Maps the given violation constraint property path to a form name. + * + * @param string $config_name + * The name of the Config whose property path triggered a validation error. + * @param string $property_path + * The property path that triggered a validation error. + * + * @return string + * The corresponding form name. + */ + protected static function mapConfigPropertyPathToFormElementName(string $config_name, string $property_path) : string { + // A default implementation, which is suitable when the configuration is + // mapped 1:1 to form elements. + return str_replace('.', '][', $property_path); + } + +} diff --git a/core/misc/cspell/dictionary.txt b/core/misc/cspell/dictionary.txt index c9e0b9e9b6..b09a5fd4f3 100644 --- a/core/misc/cspell/dictionary.txt +++ b/core/misc/cspell/dictionary.txt @@ -1401,6 +1401,7 @@ userinfo userref userreference usuario +validatable vampirize varchar veniam diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php index 1527b0a5a4..d359a38e6f 100644 --- a/core/modules/update/src/UpdateSettingsForm.php +++ b/core/modules/update/src/UpdateSettingsForm.php @@ -2,50 +2,18 @@ namespace Drupal\update; -use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\Config; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Form\ValidatableConfigFormBase; use Drupal\Core\Url; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Component\Utility\EmailValidatorInterface; /** * Configure update settings for this site. * * @internal */ -class UpdateSettingsForm extends ConfigFormBase implements ContainerInjectionInterface { - - /** - * The email validator. - * - * @var \Drupal\Component\Utility\EmailValidatorInterface - */ - protected $emailValidator; - - /** - * Constructs an UpdateSettingsForm object. - * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. - * @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator - * The email validator. - */ - public function __construct(ConfigFactoryInterface $config_factory, EmailValidatorInterface $email_validator) { - parent::__construct($config_factory); - $this->emailValidator = $email_validator; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('config.factory'), - $container->get('email.validator') - ); - } +class UpdateSettingsForm extends ValidatableConfigFormBase implements ContainerInjectionInterface { /** * {@inheritdoc} @@ -116,34 +84,24 @@ public function buildForm(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function validateForm(array &$form, FormStateInterface $form_state) { - $form_state->set('notify_emails', []); - if (!$form_state->isValueEmpty('update_notify_emails')) { - $valid = []; - $invalid = []; - foreach (explode("\n", trim($form_state->getValue('update_notify_emails'))) as $email) { - $email = trim($email); - if (!empty($email)) { - if ($this->emailValidator->isValid($email)) { - $valid[] = $email; - } - else { - $invalid[] = $email; - } - } - } - if (empty($invalid)) { - $form_state->set('notify_emails', $valid); - } - elseif (count($invalid) == 1) { - $form_state->setErrorByName('update_notify_emails', $this->t('%email is not a valid email address.', ['%email' => reset($invalid)])); - } - else { - $form_state->setErrorByName('update_notify_emails', $this->t('%emails are not valid email addresses.', ['%emails' => implode(', ', $invalid)])); - } + protected static function mapFormValuesToConfig(FormStateInterface $form_state, Config $config): Config { + $config + ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled')) + ->set('check.interval_days', $form_state->getValue('update_check_frequency')) + ->set('notification.emails', explode("\n", trim($form_state->getValue('update_notify_emails', '')))) + ->set('notification.threshold', $form_state->getValue('update_notification_threshold')); + return $config; + } + + /** + * {@inheritdoc} + */ + protected static function mapConfigPropertyPathToFormElementName(string $config_name, string $property_path) : string { + if ($property_path === 'notification.emails') { + return 'update_notify_emails'; } - parent::validateForm($form, $form_state); + return parent::mapConfigPropertyPathToFormElementName($config_name, $property_path); } /** @@ -157,13 +115,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { update_storage_clear(); } - $config - ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled')) - ->set('check.interval_days', $form_state->getValue('update_check_frequency')) - ->set('notification.emails', $form_state->get('notify_emails')) - ->set('notification.threshold', $form_state->getValue('update_notification_threshold')) - ->save(); - parent::submitForm($form, $form_state); } diff --git a/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php b/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php index 2a4a70ad3e..f0e25e8a03 100644 --- a/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php +++ b/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php @@ -71,7 +71,7 @@ public function testUpdateSettingsForm() { $this->submitForm([], 'Save configuration'); $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS); $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING); - $this->assertSession()->statusMessageContains('http://example.com is not a valid email address.', MessengerInterface::TYPE_ERROR); + $this->assertSession()->statusMessageContains('"http://example.com" is not a valid email address.', MessengerInterface::TYPE_ERROR); $this->assertSame([], $this->config('update.settings')->get('notification.emails')); // Now fill in a valid e-mail address, now the form should be saved=