diff --git a/core/lib/Drupal/Core/Config/ConfigEvents.php b/core/lib/Drupal/Core/Config/ConfigEvents.php index dd60540..6edbddc 100644 --- a/core/lib/Drupal/Core/Config/ConfigEvents.php +++ b/core/lib/Drupal/Core/Config/ConfigEvents.php @@ -21,13 +21,9 @@ * object is saved. The event listener method receives a * \Drupal\Core\Config\ConfigCrudEvent instance. * - * This event will be fired when configuration is saved by hook_update_N() - * implementations. Subscribers to this event can not make any assumption that - * the config data is valid and should ensure that their actions are safe - * regardless of the structure of the underlying configuration. When changing - * configuration values, the value must be the correct data type. - * Configuration must be saved with the $trusted_data flag set to TRUE so that - * configuration schema are not used. + * See hook_update_N() documentation for safe configuration API usage and + * restrictions as this event will be fired when configuration is saved by + * hook_update_N(). * * @Event * @@ -47,13 +43,9 @@ * object is deleted. The event listener method receives a * \Drupal\Core\Config\ConfigCrudEvent instance. * - * This event will be fired when configuration is deleted by hook_update_N() - * implementations. Subscribers to this event can not make any assumption that - * the config data is valid and should ensure that their actions are safe - * regardless of the structure of the underlying configuration. When changing - * configuration values, the value must be the correct data type. - * Configuration must be saved with the $trusted_data flag set to TRUE so that - * configuration schema are not used. + * See hook_update_N() documentation for safe configuration API usage and + * restrictions as this event will be fired when configuration is deleted by + * hook_update_N(). * * @Event * @@ -73,13 +65,9 @@ * object's name is changed. The event listener method receives a * \Drupal\Core\Config\ConfigRenameEvent instance. * - * This event will be fired when configuration is renamed by hook_update_N() - * implementations. Subscribers to this event can not make any assumption that - * the config data is valid and should ensure that their actions are safe - * regardless of the structure of the underlying configuration. When changing - * configuration values, the value must be the correct data type. - * Configuration must be saved with the $trusted_data flag set to TRUE so that - * configuration schema are not used. + * See hook_update_N() documentation for safe configuration API usage and + * restrictions as this event will be fired when configuration is renamed by + * hook_update_N(). * * @Event * diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php index 55c2741..119ab7b 100644 --- a/core/lib/Drupal/Core/Extension/module.api.php +++ b/core/lib/Drupal/Core/Extension/module.api.php @@ -474,14 +474,19 @@ function hook_install_tasks_alter(&$tasks, $install_state) { * The following actions are examples that are safe: * - Cache invalidation. * - Using \Drupal::configFactory()->getEditable() and \Drupal::config(). - * Implementations can not make any assumption that the config data is valid. - * Implementations must use the $trusted_data argument for - * \Drupal\Core\Config\Config::save() and pass schema correct data so that - * configuration schema are not used whilst saving configuration. + * Implementations must: + * - not make any assumption that the config data is valid. + * - use be the correct data type when changing configuration values as + * specified by its configuration schema at the time the update hook is + * written. If the data type changes in a subsequent code change, a + * subsequent update hook is responsible for ensuring the final data type + * aligns with the configuration schema. + * - use the $has_trusted_data argument for \Drupal\Core\Config\Config::save() + * so that configuration schemas are not used whilst saving configuration. * - Marking a container for rebuild. * * The following actions are examples that are unsafe: - * - Calling Node::save(). + * - Loading, saving, or performing any other operation on an entity. * - Rebuilding the router using \Drupal::service('router.builder')->rebuild(). * * The $sandbox parameter should be used when a multipass update is needed, in diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index fc9bba1..62ca19c 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -9,7 +9,7 @@ services: - [initLanguageManager] language.config_subscriber: class: Drupal\language\EventSubscriber\ConfigSubscriber - arguments: ['@language_manager', '@language.default', '@config.factory'] + arguments: ['@language_manager', '@language.default', '@config.factory', '@config.typed'] tags: - { name: event_subscriber } language.config_factory_override: diff --git a/core/modules/language/src/EventSubscriber/ConfigSubscriber.php b/core/modules/language/src/EventSubscriber/ConfigSubscriber.php index 4f087e3..738a1b0 100644 --- a/core/modules/language/src/EventSubscriber/ConfigSubscriber.php +++ b/core/modules/language/src/EventSubscriber/ConfigSubscriber.php @@ -8,6 +8,7 @@ namespace Drupal\language\EventSubscriber; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageDefault; use Drupal\Core\Language\LanguageManagerInterface; @@ -43,6 +44,13 @@ class ConfigSubscriber implements EventSubscriberInterface { protected $configFactory; /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManagerInterface + */ + protected $typedConfig; + + /** * Constructs a new class object. * * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager @@ -51,28 +59,44 @@ class ConfigSubscriber implements EventSubscriberInterface { * The default language. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. + * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config + * The typed config manager. */ - public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory) { + public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config) { $this->languageManager = $language_manager; $this->languageDefault = $language_default; $this->configFactory = $config_factory; + $this->typedConfig = $typed_config; } /** * Causes the container to be rebuilt on the next request. * + * This event assumes that the new default langcode and old default langcode + * are valid langcodes. If the schema definition of either + * system.site:default_langcode or language.negotiation::url.prefixes changes + * then this event must changed to work with both the old and new schema + * definition so this event is update safe. + * * @param ConfigCrudEvent $event * The configuration event. */ public function onConfigSave(ConfigCrudEvent $event) { $saved_config = $event->getConfig(); if ($saved_config->getName() == 'system.site' && $event->isChanged('default_langcode')) { + // Ensure that the configuration that will be changed or relied upon has + // the expected data type of 'string'. + $default_langcode_definition = $this->typedConfig->get('system.site')->get('default_langcode')->getDataDefinition(); + $old_default_langcode = $saved_config->getOriginal('default_langcode'); + $url_prefix_definition = $this->typedConfig->get('language.negotiation')->get('url.prefixes.' . $old_default_langcode)->getDataDefinition(); + if ($default_langcode_definition->getDataType() !== 'string' || $url_prefix_definition->getDataType() != 'string') { + return; + } $new_default_langcode = $saved_config->get('default_langcode'); $default_language = $this->configFactory->get('language.entity.' . $new_default_langcode); // During an import the language might not exist yet. if (!$default_language->isNew()) { - $old_default_langcode = $saved_config->getOriginal('default_langcode'); $this->languageDefault->set(new Language($default_language->get())); $this->languageManager->reset();