From 1fad1e0093620c688c60c199560d742176e90d77 Mon Sep 17 00:00:00 2001 From: effulgentsia <alex.bronstein@acquia.com> Date: Fri, 1 Sep 2023 09:46:12 -0700 Subject: [PATCH] Issue #3364506 by Wim Leers, effulgentsia, catch, lauriii, borisson_, smustgrave, tim.plunkett, Dave Reid: Add optional validation constraint support to ConfigFormBase --- core/config/schema/core.data_types.schema.yml | 3 + core/lib/Drupal/Core/Entity/EntityForm.php | 2 + core/lib/Drupal/Core/Form/ConfigFormBase.php | 182 +++++++++++++++++- core/misc/cspell/dictionary.txt | 1 + .../src/Form/NegotiationBrowserForm.php | 6 +- .../src/Form/NegotiationConfigureForm.php | 8 +- .../language/src/Form/NegotiationUrlForm.php | 8 +- .../media/src/Form/MediaSettingsForm.php | 8 +- .../statistics/src/StatisticsSettingsForm.php | 8 +- .../system/src/Form/FileSystemForm.php | 8 +- .../system/src/Form/ImageToolkitForm.php | 8 +- .../src/Form/MenuLinksetSettingsForm.php | 17 +- .../system/src/Form/PerformanceForm.php | 8 +- core/modules/system/src/Form/RegionalForm.php | 8 +- .../system/src/Form/SiteInformationForm.php | 8 +- .../src/Form/SiteMaintenanceModeForm.php | 8 +- .../system/src/Form/ThemeSettingsForm.php | 8 +- .../modules/form_test/form_test.services.yml | 2 +- .../src/FormTestControllerObject.php | 3 +- .../tests/src/Kernel/Form/FormObjectTest.php | 2 +- .../modules/update/src/UpdateSettingsForm.php | 122 +++++------- .../src/Functional/UpdateSettingsFormTest.php | 97 ++++++++++ core/modules/user/src/AccountSettingsForm.php | 8 +- .../views_ui/src/Form/BasicSettingsForm.php | 8 +- 24 files changed, 437 insertions(+), 104 deletions(-) create mode 100644 core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 56cc1506b412..8935e21ef151 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/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index 90d3d635ff0c..18a70a62dfb7 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -323,6 +323,8 @@ public function buildEntity(array $form, FormStateInterface $form_state) { * A nested array of form elements comprising the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * + * @see \Drupal\Core\Form\ConfigFormBase::copyFormValuesToConfig() */ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { $values = $form_state->getValues(); diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php index 707e580d5a6d..eefc0ea5ba7d 100644 --- a/core/lib/Drupal/Core/Form/ConfigFormBase.php +++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php @@ -2,11 +2,17 @@ namespace Drupal\Core\Form; +use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base class for implementing system configuration forms. + * + * Subclasses of this form can choose to use config validation instead of form- + * -specific validation logic. To do that, override copyFormValuesToConfig(). */ abstract class ConfigFormBase extends FormBase { use ConfigFormBaseTrait; @@ -16,9 +22,18 @@ abstract class ConfigFormBase extends FormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface|null $typedConfigManager + * The typed config manager. */ - public function __construct(ConfigFactoryInterface $config_factory) { + public function __construct( + ConfigFactoryInterface $config_factory, + protected ?TypedConfigManagerInterface $typedConfigManager = NULL, + ) { $this->setConfigFactory($config_factory); + if ($this->typedConfigManager === NULL) { + @trigger_error('Calling ConfigFormBase::__construct() without the $typedConfigManager argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3373502', E_USER_DEPRECATED); + $this->typedConfigManager = \Drupal::service('config.typed'); + } } /** @@ -26,7 +41,8 @@ public function __construct(ConfigFactoryInterface $config_factory) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory') + $container->get('config.factory'), + $container->get('config.typed') ); } @@ -47,11 +63,173 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + assert($this->typedConfigManager instanceof TypedConfigManagerInterface); + foreach ($this->getEditableConfigNames() as $config_name) { + $config = $this->config($config_name); + try { + static::copyFormValuesToConfig($config, $form_state); + } + catch (\BadMethodCallException $e) { + // Nothing to do: this config form does not yet use validation + // constraints. Continue trying the other editable config, to allow + // partial adoption. + continue; + } + $typed_config = $this->typedConfigManager->createFromNameAndData($config_name, $config->getRawData()); + + $violations = $typed_config->validate(); + // Rather than immediately applying all violation messages to the + // corresponding form elements, first collect the messages. The structure + // of the form may cause a single form element to contain multiple config + // property paths thanks to `type: sequence`. Common example: a <textarea> + // with one line per sequence item. + // @see \Drupal\Core\Config\Schema\Sequence + // @see \Drupal\Core\Config\Schema\SequenceDataDefinition + $violations_per_form_element = []; + foreach ($violations as $violation) { + $property_path = $violation->getPropertyPath(); + $form_element_name = static::mapConfigKeyToFormElementName($config_name, $property_path); + // Default to index 0. + $index = 0; + // Detect if this is a sequence property path, and if so, determine the + // actual sequence index. + $matches = []; + if (preg_match("/.*\.(\d+)$/", $property_path, $matches) === 1) { + $index = intval($matches[1]); + } + $violations_per_form_element[$form_element_name][$index] = $violation; + } + + // Now that we know how many constraint violation messages exist per form + // element, set them. This is crucial because FormState::setErrorByName() + // only allows a single validation error message per form element. + // @see \Drupal\Core\Form\FormState::setErrorByName() + foreach ($violations_per_form_element as $form_element_name => $violations) { + // When only a single message exists, just set it. + if (count($violations) === 1) { + $form_state->setErrorByName($form_element_name, reset($violations)->getMessage()); + continue; + } + + // However, if multiple exist, that implies it's a single form element + // containing a `type: sequence`. + $form_state->setErrorByName($form_element_name, $this->formatMultipleViolationsMessage($form_element_name, $violations)); + } + } + } + + /** + * Formats multiple violation messages associated with a single form element. + * + * Validation constraints only know the internal data structure (the + * configuration schema structure), but this need not be a disadvantage: + * rather than informing the user some values are wrong, it is possible + * guide them directly to the Nth entry in the sequence. + * + * To further improve the user experience, it is possible to override + * method in subclasses to use specific knowledge about the structure of the + * form and the nature of the data being validated, to instead generate more + * precise and/or shortened violation messages. + * + * @param string $form_element_name + * The form element for which to format multiple violation messages. + * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations + * The list of constraint violations that apply to this form element. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + */ + protected function formatMultipleViolationsMessage(string $form_element_name, array $violations): TranslatableMarkup { + $transformed_message_parts = []; + foreach ($violations as $index => $violation) { + // Note that `@validation_error_message` (should) already contain a + // trailing period, hence it is intentionally absent here. + $transformed_message_parts[] = $this->t('Entry @human_index: @validation_error_message', [ + // Humans start counting from 1, not 0. + '@human_index' => $index + 1, + // Translators may not necessarily know what "violation constraint + // messages" are, but they definitely know "validation errors". + '@validation_error_message' => $violation->getMessage(), + ]); + } + return $this->t(implode("\n", $transformed_message_parts)); + } + /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { + foreach ($this->getEditableConfigNames() as $config_name) { + $config = $this->config($config_name); + try { + static::copyFormValuesToConfig($config, $form_state); + $config->save(); + } + catch (\BadMethodCallException $e) { + // Nothing to do: this config form does not yet use validation + // constraints. Continue trying the other editable config, to allow + // partial adoption. + continue; + } + } $this->messenger()->addStatus($this->t('The configuration options have been saved.')); } + /** + * Copies form values to Config keys. + * + * This should not change existing Config key-value pairs that are not being + * edited by this form. + * + * @param \Drupal\Core\Config\Config $config + * The configuration being edited. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @see \Drupal\Core\Entity\EntityForm::copyFormValuesToEntity() + */ + protected static function copyFormValuesToConfig(Config $config, FormStateInterface $form_state): void { + // This allows ::submitForm() and ::validateForm() to know that this config + // form is not yet using constraint-based validation. + throw new \BadMethodCallException(); + } + + /** + * Maps the given Config key to a form element name. + * + * @param string $config_name + * The name of the Config whose value triggered a validation error. + * @param string $key + * The Config key that triggered a validation error (which corresponds to a + * property path on the validation constraint violation). + * + * @return string + * The corresponding form element name. + */ + protected static function mapConfigKeyToFormElementName(string $config_name, string $key) : string { + return self::defaultMapConfigKeyToFormElementName($config_name, $key); + } + + /** + * Default implementation for ::mapConfigKeyToFormElementName(). + * + * Suitable when the configuration is mapped 1:1 to form elements: when the + * keys in the Config match the form element names exactly. + * + * @param string $config_name + * The name of the Config whose value triggered a validation error. + * @param string $key + * The Config key that triggered a validation error (which corresponds to a + * property path on the validation constraint violation). + * + * @return string + * The corresponding form element name. + */ + final protected static function defaultMapConfigKeyToFormElementName(string $config_name, string $key) : string { + return str_replace('.', '][', $key); + } + } diff --git a/core/misc/cspell/dictionary.txt b/core/misc/cspell/dictionary.txt index a4cd7d4c83f0..b1a8df4dce91 100644 --- a/core/misc/cspell/dictionary.txt +++ b/core/misc/cspell/dictionary.txt @@ -1324,6 +1324,7 @@ userid userinfo userref usuario +validatable vampirize varchar veniam diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php index 1ffd1a2c5c3f..e0e50442de8a 100644 --- a/core/modules/language/src/Form/NegotiationBrowserForm.php +++ b/core/modules/language/src/Form/NegotiationBrowserForm.php @@ -3,6 +3,7 @@ namespace Drupal\language\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -26,8 +27,8 @@ class NegotiationBrowserForm extends ConfigFormBase { /** * {@inheritdoc} */ - public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ConfigurableLanguageManagerInterface $language_manager) { + parent::__construct($config_factory, $typedConfigManager); $this->languageManager = $language_manager; } @@ -37,6 +38,7 @@ public function __construct(ConfigFactoryInterface $config_factory, Configurable public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('language_manager') ); } diff --git a/core/modules/language/src/Form/NegotiationConfigureForm.php b/core/modules/language/src/Form/NegotiationConfigureForm.php index 2b808d54d408..d25305f0306c 100644 --- a/core/modules/language/src/Form/NegotiationConfigureForm.php +++ b/core/modules/language/src/Form/NegotiationConfigureForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Block\BlockManagerInterface; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Form\ConfigFormBase; @@ -68,6 +69,8 @@ class NegotiationConfigureForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager * The language manager. * @param \Drupal\language\LanguageNegotiatorInterface $negotiator @@ -79,8 +82,8 @@ class NegotiationConfigureForm extends ConfigFormBase { * @param \Drupal\Core\Entity\EntityStorageInterface $block_storage * The block storage, or NULL if not available. */ - public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) { + parent::__construct($config_factory, $typedConfigManager); $this->languageTypes = $this->config('language.types'); $this->languageManager = $language_manager; $this->negotiator = $negotiator; @@ -97,6 +100,7 @@ public static function create(ContainerInterface $container) { $block_storage = $entity_type_manager->hasHandler('block', 'storage') ? $entity_type_manager->getStorage('block') : NULL; return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('language_manager'), $container->get('language_negotiator'), $container->get('plugin.manager.block'), diff --git a/core/modules/language/src/Form/NegotiationUrlForm.php b/core/modules/language/src/Form/NegotiationUrlForm.php index f0f0a39f0bb8..6cb54e4a3228 100644 --- a/core/modules/language/src/Form/NegotiationUrlForm.php +++ b/core/modules/language/src/Form/NegotiationUrlForm.php @@ -2,6 +2,7 @@ namespace Drupal\language\Form; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; @@ -30,11 +31,13 @@ class NegotiationUrlForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, LanguageManagerInterface $language_manager) { + parent::__construct($config_factory, $typedConfigManager); $this->languageManager = $language_manager; } @@ -44,6 +47,7 @@ public function __construct(ConfigFactoryInterface $config_factory, LanguageMana public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('language_manager') ); } diff --git a/core/modules/media/src/Form/MediaSettingsForm.php b/core/modules/media/src/Form/MediaSettingsForm.php index b70aa19d24f6..3d8a4e373c57 100644 --- a/core/modules/media/src/Form/MediaSettingsForm.php +++ b/core/modules/media/src/Form/MediaSettingsForm.php @@ -3,6 +3,7 @@ namespace Drupal\media\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\ConfigFormBase; @@ -35,13 +36,15 @@ class MediaSettingsForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory service. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\media\IFrameUrlHelper $iframe_url_helper * The iFrame URL helper service. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. */ - public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHelper $iframe_url_helper, EntityTypeManagerInterface $entity_type_manager) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, IFrameUrlHelper $iframe_url_helper, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($config_factory, $typedConfigManager); $this->iFrameUrlHelper = $iframe_url_helper; $this->entityTypeManager = $entity_type_manager; } @@ -52,6 +55,7 @@ public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHel public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('media.oembed.iframe_url_helper'), $container->get('entity_type.manager') ); diff --git a/core/modules/statistics/src/StatisticsSettingsForm.php b/core/modules/statistics/src/StatisticsSettingsForm.php index 49b6a6dacc94..51218583edc3 100644 --- a/core/modules/statistics/src/StatisticsSettingsForm.php +++ b/core/modules/statistics/src/StatisticsSettingsForm.php @@ -2,6 +2,7 @@ namespace Drupal\statistics; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Config\ConfigFactoryInterface; @@ -27,11 +28,13 @@ class StatisticsSettingsForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler) { + parent::__construct($config_factory, $typedConfigManager); $this->moduleHandler = $module_handler; } @@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('module_handler') ); } diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php index 18c1cb8ce160..f0b4391724ed 100644 --- a/core/modules/system/src/Form/FileSystemForm.php +++ b/core/modules/system/src/Form/FileSystemForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; @@ -47,6 +48,8 @@ class FileSystemForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter service. * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager @@ -54,8 +57,8 @@ class FileSystemForm extends ConfigFormBase { * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system. */ - public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager, FileSystemInterface $file_system) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager, FileSystemInterface $file_system) { + parent::__construct($config_factory, $typedConfigManager); $this->dateFormatter = $date_formatter; $this->streamWrapperManager = $stream_wrapper_manager; $this->fileSystem = $file_system; @@ -67,6 +70,7 @@ public function __construct(ConfigFactoryInterface $config_factory, DateFormatte public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('date.formatter'), $container->get('stream_wrapper_manager'), $container->get('file_system') diff --git a/core/modules/system/src/Form/ImageToolkitForm.php b/core/modules/system/src/Form/ImageToolkitForm.php index f11f695a42af..adbbd4c7c5dd 100644 --- a/core/modules/system/src/Form/ImageToolkitForm.php +++ b/core/modules/system/src/Form/ImageToolkitForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\ImageToolkit\ImageToolkitManager; @@ -27,11 +28,13 @@ class ImageToolkitForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\ImageToolkit\ImageToolkitManager $manager * The image toolkit plugin manager. */ - public function __construct(ConfigFactoryInterface $config_factory, ImageToolkitManager $manager) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ImageToolkitManager $manager) { + parent::__construct($config_factory, $typedConfigManager); foreach ($manager->getAvailableToolkits() as $id => $definition) { $this->availableToolkits[$id] = $manager->createInstance($id); @@ -44,6 +47,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ImageToolkit public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('image.toolkit.manager') ); } diff --git a/core/modules/system/src/Form/MenuLinksetSettingsForm.php b/core/modules/system/src/Form/MenuLinksetSettingsForm.php index fd7663612e9e..4353a27572d6 100644 --- a/core/modules/system/src/Form/MenuLinksetSettingsForm.php +++ b/core/modules/system/src/Form/MenuLinksetSettingsForm.php @@ -2,6 +2,8 @@ namespace Drupal\system\Form; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteBuilderInterface; @@ -13,12 +15,21 @@ class MenuLinksetSettingsForm extends ConfigFormBase { /** - * Constructs the routerBuilder service. + * Constructs a MenuLinksetSettingsForm object. * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Routing\RouteBuilderInterface $routerBuilder * The router builder service. */ - public function __construct(protected readonly RouteBuilderInterface $routerBuilder) { + public function __construct( + ConfigFactoryInterface $config_factory, + TypedConfigManagerInterface $typedConfigManager, + protected readonly RouteBuilderInterface $routerBuilder + ) { + parent::__construct($config_factory, $typedConfigManager); } /** @@ -26,6 +37,8 @@ public function __construct(protected readonly RouteBuilderInterface $routerBuil */ public static function create(ContainerInterface $container) { return new static( + $container->get('config.factory'), + $container->get('config.typed'), $container->get('router.builder') ); } diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php index cf26b6dbf1f3..7ba2cc7813fd 100644 --- a/core/modules/system/src/Form/PerformanceForm.php +++ b/core/modules/system/src/Form/PerformanceForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Asset\AssetCollectionOptimizerInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Config\ConfigFactoryInterface; @@ -51,6 +52,8 @@ class PerformanceForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter service. * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer @@ -60,8 +63,8 @@ class PerformanceForm extends ConfigFormBase { * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, ModuleHandlerInterface $module_handler) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, ModuleHandlerInterface $module_handler) { + parent::__construct($config_factory, $typedConfigManager); $this->dateFormatter = $date_formatter; $this->cssCollectionOptimizer = $css_collection_optimizer; @@ -75,6 +78,7 @@ public function __construct(ConfigFactoryInterface $config_factory, DateFormatte public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('date.formatter'), $container->get('asset.css.collection_optimizer'), $container->get('asset.js.collection_optimizer'), diff --git a/core/modules/system/src/Form/RegionalForm.php b/core/modules/system/src/Form/RegionalForm.php index f2028ec3fb51..bd64a67a8d46 100644 --- a/core/modules/system/src/Form/RegionalForm.php +++ b/core/modules/system/src/Form/RegionalForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Datetime\TimeZoneFormHelper; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Locale\CountryManagerInterface; @@ -28,11 +29,13 @@ class RegionalForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Locale\CountryManagerInterface $country_manager * The country manager. */ - public function __construct(ConfigFactoryInterface $config_factory, CountryManagerInterface $country_manager) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, CountryManagerInterface $country_manager) { + parent::__construct($config_factory, $typedConfigManager); $this->countryManager = $country_manager; } @@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, CountryManag public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('country_manager') ); } diff --git a/core/modules/system/src/Form/SiteInformationForm.php b/core/modules/system/src/Form/SiteInformationForm.php index 4049f1ff1061..372e4e8d2462 100644 --- a/core/modules/system/src/Form/SiteInformationForm.php +++ b/core/modules/system/src/Form/SiteInformationForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Path\PathValidatorInterface; @@ -43,6 +44,8 @@ class SiteInformationForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\path_alias\AliasManagerInterface $alias_manager * The path alias manager. * @param \Drupal\Core\Path\PathValidatorInterface $path_validator @@ -50,8 +53,8 @@ class SiteInformationForm extends ConfigFormBase { * @param \Drupal\Core\Routing\RequestContext $request_context * The request context. */ - public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) { + parent::__construct($config_factory, $typedConfigManager); $this->aliasManager = $alias_manager; $this->pathValidator = $path_validator; $this->requestContext = $request_context; @@ -63,6 +66,7 @@ public function __construct(ConfigFactoryInterface $config_factory, AliasManager public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('path_alias.manager'), $container->get('path.validator'), $container->get('router.request_context') diff --git a/core/modules/system/src/Form/SiteMaintenanceModeForm.php b/core/modules/system/src/Form/SiteMaintenanceModeForm.php index 359b9abd6331..d35658464081 100644 --- a/core/modules/system/src/Form/SiteMaintenanceModeForm.php +++ b/core/modules/system/src/Form/SiteMaintenanceModeForm.php @@ -3,6 +3,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Form\ConfigFormBase; @@ -36,13 +37,15 @@ class SiteMaintenanceModeForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\State\StateInterface $state * The state keyvalue collection to use. * @param \Drupal\user\PermissionHandlerInterface $permission_handler * The permission handler. */ - public function __construct(ConfigFactoryInterface $config_factory, StateInterface $state, PermissionHandlerInterface $permission_handler) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, StateInterface $state, PermissionHandlerInterface $permission_handler) { + parent::__construct($config_factory, $typedConfigManager); $this->state = $state; $this->permissionHandler = $permission_handler; } @@ -53,6 +56,7 @@ public function __construct(ConfigFactoryInterface $config_factory, StateInterfa public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('state'), $container->get('user.permissions') ); diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php index 8ab05bea5503..b68fa58cde13 100644 --- a/core/modules/system/src/Form/ThemeSettingsForm.php +++ b/core/modules/system/src/Form/ThemeSettingsForm.php @@ -2,6 +2,7 @@ namespace Drupal\system\Form; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; @@ -72,6 +73,8 @@ class ThemeSettingsForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler instance to use. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler @@ -83,8 +86,8 @@ class ThemeSettingsForm extends ConfigFormBase { * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $mime_type_guesser, ThemeManagerInterface $theme_manager, FileSystemInterface $file_system) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $mime_type_guesser, ThemeManagerInterface $theme_manager, FileSystemInterface $file_system) { + parent::__construct($config_factory, $typedConfigManager); $this->moduleHandler = $module_handler; $this->themeHandler = $theme_handler; @@ -99,6 +102,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('module_handler'), $container->get('theme_handler'), $container->get('file.mime_type.guesser'), diff --git a/core/modules/system/tests/modules/form_test/form_test.services.yml b/core/modules/system/tests/modules/form_test/form_test.services.yml index 24513a66e0e6..e6dfe4a62a79 100644 --- a/core/modules/system/tests/modules/form_test/form_test.services.yml +++ b/core/modules/system/tests/modules/form_test/form_test.services.yml @@ -1,7 +1,7 @@ services: form_test.form.serviceform: class: Drupal\form_test\FormTestServiceObject - arguments: ['@config.factory'] + arguments: ['@config.factory', '@config.typed'] form_test.event_subscriber: class: Drupal\form_test\EventSubscriber\FormTestEventSubscriber tags: diff --git a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php index 428e9cef35f0..8e28437a4266 100644 --- a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php +++ b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php @@ -34,7 +34,8 @@ protected function getEditableConfigNames() { public static function create(ContainerInterface $container) { \Drupal::messenger()->addStatus(t('The FormTestControllerObject::create() method was used for this form.')); return new static( - $container->get('config.factory') + $container->get('config.factory'), + $container->get('config.typed') ); } diff --git a/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php b/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php index ecf7c5047d94..5f4553698e46 100644 --- a/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php +++ b/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php @@ -25,7 +25,7 @@ class FormObjectTest extends ConfigFormTestBase { protected function setUp(): void { parent::setUp(); - $this->form = new FormTestObject($this->container->get('config.factory')); + $this->form = new FormTestObject($this->container->get('config.factory'), $this->container->get('config.typed')); $this->values = [ 'bananas' => [ '#value' => $this->randomString(10), diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php index 1527b0a5a41d..80c726dc6730 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\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Url; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Config\Config; use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Url; 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 ConfigFormBase { /** * {@inheritdoc} @@ -105,7 +73,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'You can choose to send email only if a security update is available, or to be notified about all newer versions. If there are updates available of Drupal core or any of your installed modules and themes, your site will always print a message on the <a href=":status_report">status report</a> page. If there is a security update, an error message will be printed on administration pages for users with <a href=":update_permissions">permission to view update notifications</a>.', [ ':status_report' => Url::fromRoute('system.status')->toString(), - ':update_permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-update'])->toString(), + ':update_permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-update']) + ->toString(), ] ), ]; @@ -116,34 +85,56 @@ 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; - } + protected static function copyFormValuesToConfig(Config $config, FormStateInterface $form_state): void { + switch ($config->getName()) { + case 'update.settings': + $config + ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled')) + ->set('check.interval_days', $form_state->getValue('update_check_frequency')) + ->set('notification.emails', array_map('trim', explode("\n", trim($form_state->getValue('update_notify_emails', ''))))) + ->set('notification.threshold', $form_state->getValue('update_notification_threshold')); + break; + } + } + + /** + * {@inheritdoc} + */ + protected static function mapConfigKeyToFormElementName(string $config_name, string $key): string { + switch ($config_name) { + case 'update.settings': + // A `type: sequence` of emails is mapped to a single textarea. Property + // paths are `notification.emails.0`, `notification.emails.1`, etc. + if (str_starts_with($key, 'notification.emails.')) { + return 'update_notify_emails'; } - } - 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)])); - } + + return match ($key) { + 'check.disabled_extensions' => 'update_check_disabled', + 'check.interval_days' => 'update_check_frequency', + 'notification.emails' => 'update_notify_emails', + 'notification.threshold' => 'update_notification_threshold', + default => self::defaultMapConfigKeyToFormElementName($config_name, $key), + }; + + default: + throw new \InvalidArgumentException(); } + } - parent::validateForm($form, $form_state); + /** + * {@inheritdoc} + */ + protected function formatMultipleViolationsMessage(string $form_element_name, array $violations): TranslatableMarkup { + if ($form_element_name !== 'update_notify_emails') { + return parent::formatMultipleViolationsMessage($form_element_name, $violations); + } + + $invalid_email_addresses = []; + foreach ($violations as $violation) { + $invalid_email_addresses[] = $violation->getInvalidValue(); + } + return $this->t('%emails are not valid email addresses.', ['%emails' => implode(', ', $invalid_email_addresses)]); } /** @@ -157,13 +148,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 new file mode 100644 index 000000000000..6f2554e77f43 --- /dev/null +++ b/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php @@ -0,0 +1,97 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\update\Functional; + +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests the the update_settings form. + * + * @group update + * @group Form + */ +class UpdateSettingsFormTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['update']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Tests the update_settings form. + */ + public function testUpdateSettingsForm() { + $url = Url::fromRoute('update.settings'); + + // Users without the appropriate permissions should not be able to access. + $this->drupalGet($url); + $this->assertSession()->pageTextContains('Access denied'); + + // Users with permission should be able to access the form. + $permissions = ['administer site configuration']; + $account = $this->setUpCurrentUser([ + 'name' => 'system_admin', + 'pass' => 'adminPass', + ], $permissions); + $this->drupalLogin($account); + $this->drupalGet($url); + $this->assertSession()->fieldExists('update_notify_emails'); + + $values_to_enter = [ + 'http://example.com', + 'sofie@example.com', + 'http://example.com/also-not-an-email-address', + 'dries@example.com', + ]; + + // Fill in `http://example.com` as the email address to notify. We expect + // this to trigger a validation error, because it's not an email address, + // and for the corresponding form item to be highlighted. + $this->assertSession()->fieldExists('update_notify_emails')->setValue($values_to_enter[0]); + $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->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error')); + $this->assertSame([], $this->config('update.settings')->get('notification.emails')); + + // Next, set an invalid email addresses, but make sure it's second entry. + $this->assertSession()->fieldExists('update_notify_emails')->setValue(implode("\n", array_slice($values_to_enter, 1, 2))); + $this->submitForm([], 'Save configuration'); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING); + $this->assertSession()->statusMessageContains('"http://example.com/also-not-an-email-address" is not a valid email address.', MessengerInterface::TYPE_ERROR); + $this->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error')); + $this->assertSame([], $this->config('update.settings')->get('notification.emails')); + + // Next, set multiple invalid email addresses, and assert the same as above + // except the message should be adjusted now. + $this->assertSession()->fieldExists('update_notify_emails')->setValue(implode("\n", $values_to_enter)); + $this->submitForm([], 'Save configuration'); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING); + $this->assertSession()->statusMessageContains('http://example.com, http://example.com/also-not-an-email-address are not valid email addresses.', MessengerInterface::TYPE_ERROR); + $this->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error')); + $this->assertSame([], $this->config('update.settings')->get('notification.emails')); + + // Now fill in valid email addresses, now the form should be saved + // successfully. + $this->assertSession()->fieldExists('update_notify_emails')->setValue("$values_to_enter[1]\r\n$values_to_enter[3]"); + $this->submitForm([], 'Save configuration'); + $this->assertSession()->statusMessageContains('The configuration options have been saved.', MessengerInterface::TYPE_STATUS); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING); + $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_ERROR); + $this->assertFalse($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error')); + $this->assertSame(['sofie@example.com', 'dries@example.com'], $this->config('update.settings')->get('notification.emails')); + } + +} diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php index 6b4afbeb8b40..f059e0637442 100644 --- a/core/modules/user/src/AccountSettingsForm.php +++ b/core/modules/user/src/AccountSettingsForm.php @@ -2,6 +2,7 @@ namespace Drupal\user; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -36,13 +37,15 @@ class AccountSettingsForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\user\RoleStorageInterface $role_storage * The role storage. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) { + parent::__construct($config_factory, $typedConfigManager); $this->moduleHandler = $module_handler; $this->roleStorage = $role_storage; } @@ -53,6 +56,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('module_handler'), $container->get('entity_type.manager')->getStorage('user_role') ); diff --git a/core/modules/views_ui/src/Form/BasicSettingsForm.php b/core/modules/views_ui/src/Form/BasicSettingsForm.php index 92f91ddfbb66..e45bca2cbf7d 100644 --- a/core/modules/views_ui/src/Form/BasicSettingsForm.php +++ b/core/modules/views_ui/src/Form/BasicSettingsForm.php @@ -3,6 +3,7 @@ namespace Drupal\views_ui\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; @@ -27,11 +28,13 @@ class BasicSettingsForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager + * The typed config manager. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. */ - public function __construct(ConfigFactoryInterface $config_factory, ThemeHandlerInterface $theme_handler) { - parent::__construct($config_factory); + public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ThemeHandlerInterface $theme_handler) { + parent::__construct($config_factory, $typedConfigManager); $this->themeHandler = $theme_handler; } @@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ThemeHandler public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('config.typed'), $container->get('theme_handler') ); } -- GitLab