diff --git a/core/core.services.yml b/core/core.services.yml index 0543272d334b4e03a830c37c5687753c632c9f34..5b410a2a81d31bb85c755aa016729c1e3a872826 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -77,7 +77,7 @@ services: arguments: [discovery] config.manager: class: Drupal\Core\Config\ConfigManager - arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage'] + arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage', '@event_dispatcher'] config.factory: class: Drupal\Core\Config\ConfigFactory tags: diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 5360edd0d9c88bf75b5100445941c3efa1463a8b..b53d1395e25981de8802b47ff4ddba36877c8f59 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -237,7 +237,6 @@ public function save() { * The configuration object. */ public function delete() { - // @todo Consider to remove the pruning of data for Config::delete(). $this->data = array(); $this->storage->delete($this->name); $this->isNew = TRUE; diff --git a/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php b/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..43be83e3ac62df58eddd08cea7ebe28c6bd1e1eb --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigCollectionInfo.php @@ -0,0 +1,79 @@ +collections[$collection] = $override_service; + } + + /** + * Gets the list of possible collection names. + * + * @param bool $include_default + * (Optional) Include the default collection. Defaults to TRUE. + * + * @return array + * The list of possible collection names. + */ + public function getCollectionNames($include_default = TRUE) { + $collection_names = array_keys($this->collections); + sort($collection_names); + if ($include_default) { + array_unshift($collection_names, StorageInterface::DEFAULT_COLLECTION); + } + return $collection_names; + } + + /** + * Gets the config factory override service responsible for the collection. + * + * @param string $collection + * The configuration collection. + * + * @return \Drupal\Core\Config\ConfigFactoryOverrideInterface|NULL + * The override service responsible for the collection if one exists. NULL + * if not. + */ + public function getOverrideService($collection) { + return isset($this->collections[$collection]) ? $this->collections[$collection] : NULL; + } + +} diff --git a/core/lib/Drupal/Core/Config/ConfigCollectionNamesEvent.php b/core/lib/Drupal/Core/Config/ConfigCollectionNamesEvent.php deleted file mode 100644 index 3633d4367e10b3806a487886e2ff33145cc106ca..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/Config/ConfigCollectionNamesEvent.php +++ /dev/null @@ -1,59 +0,0 @@ -collections = array_merge($this->collections, $collections); - } - - /** - * Adds a name to the list of possible collections. - * - * @param string $collection - * Collection name to add. - */ - public function addCollectionName($collection) { - $this->addCollectionNames(array($collection)); - } - - /** - * Gets the list of possible collection names. - * - * @return array - * The list of possible collection names. - */ - public function getCollectionNames($include_default = TRUE) { - sort($this->collections); - $collections = array_unique($this->collections); - if ($include_default) { - array_unshift($collections, StorageInterface::DEFAULT_COLLECTION); - } - return $collections; - } - -} diff --git a/core/lib/Drupal/Core/Config/ConfigEvents.php b/core/lib/Drupal/Core/Config/ConfigEvents.php index 2d12a50a4933412fe54fe699fd8364aff05d3649..7fa22a58b6aa56de11fce89bf555f318eefd9d4e 100644 --- a/core/lib/Drupal/Core/Config/ConfigEvents.php +++ b/core/lib/Drupal/Core/Config/ConfigEvents.php @@ -51,10 +51,11 @@ final class ConfigEvents { const IMPORT = 'config.importer.import'; /** - * Name of event fired to discover all the possible configuration collections. + * Name of event fired to collect information on all collections. * - * @see \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() + * @see \Drupal\Core\Config\ConfigManager::getConfigCollectionInfo() + * @see \Drupal\Core\Config\ConfigCollectionInfo */ - const COLLECTION_NAMES = 'config.collection_names'; + const COLLECTION_INFO = 'config.collection_info'; } diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php index 06e1f3f5d9ca4ccbc962b3377d3e5bb448c5f731..10d5dc7e665c9a0c0bbe0a72dd5bf80501b603d0 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php @@ -32,4 +32,30 @@ public function loadOverrides($names); */ public function getCacheSuffix(); + /** + * Creates a configuration object for use during install and synchronization. + * + * If the overrider stores it's overrides in configuration collections then + * it can have its own implementation of + * \Drupal\Core\Config\StorableConfigBase. Configuration overriders can link + * themselves to a configuration collection by listening to the + * \Drupal\Core\Config\ConfigEvents::COLLECTION_INFO event and adding the + * collections they are responsible for. Doing this will allow installation + * and synchronization to use the overrider's implementation of + * StorableConfigBase. + * + * @see \Drupal\Core\Config\ConfigCollectionInfo + * @see \Drupal\Core\Config\ConfigImporter::importConfig() + * @see \Drupal\Core\Config\ConfigInstaller::createConfiguration() + * + * @param string $name + * The configuration object name. + * @param string $collection + * The configuration collection. + * + * @return \Drupal\Core\Config\StorableConfigBase + * The configuration object for the provided name and collection. + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION); + } diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 716a6aed48ddbe2f9ebde112a90dc0219d4b9c1e..ec25c6cbc09843a9a59545aa7f72b85353b4e9ae 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -896,7 +896,15 @@ protected function checkOp($collection, $op, $name) { * The name of the configuration to process. */ protected function importConfig($collection, $op, $name) { - $config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); + // Allow config factory overriders to use a custom configuration object if + // they are responsible for the collection. + $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection); + if ($overrider) { + $config = $overrider->createConfigObject($name, $collection); + } + else { + $config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); + } if ($op == 'delete') { $config->delete(); } diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index cea8b53eec2b8559fefecdd879769411fc285111..c8defe72e5e80cb3cb0b377a6874292e79956672 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -96,10 +96,8 @@ public function installDefaultConfig($type, $name) { $this->typedConfig->clearCachedDefinitions(); } - // Gather all the supported collection names. - $event = new ConfigCollectionNamesEvent(); - $this->eventDispatcher->dispatch(ConfigEvents::COLLECTION_NAMES, $event); - $collections = $event->getCollectionNames(); + // Gather information about all the supported collections. + $collection_info = $this->configManager->getConfigCollectionInfo(); $old_state = $this->configFactory->getOverrideState(); $this->configFactory->setOverrideState(FALSE); @@ -110,7 +108,7 @@ public function installDefaultConfig($type, $name) { $enabled_extensions = array_keys((array) $extension_config->get('module')); $enabled_extensions += array_keys((array) $extension_config->get('theme')); - foreach ($collections as $collection) { + foreach ($collection_info->getCollectionNames(TRUE) as $collection) { $config_to_install = $this->listDefaultConfigCollection($collection, $type, $name, $enabled_extensions); if (!empty($config_to_install)) { $this->createConfiguration($collection, $config_to_install); @@ -185,7 +183,15 @@ protected function createConfiguration($collection, array $config_to_install) { $config_to_install = array_diff($config_to_install, $this->getActiveStorage($collection)->listAll()); foreach ($config_to_install as $name) { - $new_config = new Config($name, $this->getActiveStorage($collection), $this->eventDispatcher, $this->typedConfig); + // Allow config factory overriders to use a custom configuration object if + // they are responsible for the collection. + $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection); + if ($overrider) { + $new_config = $overrider->createConfigObject($name, $collection); + } + else { + $new_config = new Config($name, $this->getActiveStorage($collection), $this->eventDispatcher, $this->typedConfig); + } if ($data[$name] !== FALSE) { $new_config->setData($data[$name]); } diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index 7d33de66c614fb28f2f951f256a57513629d4409..bd1c78ba6cee301602c4db3f76e48ac46fba8f38 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\StringTranslation\TranslationManager; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * The ConfigManager provides helper functions for the configuration system. @@ -53,6 +54,27 @@ class ConfigManager implements ConfigManagerInterface { */ protected $activeStorage; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * The configuration collection info. + * + * @var \Drupal\Core\Config\ConfigCollectionInfo + */ + protected $configCollectionInfo; + + /** + * The configuration storages keyed by collection name. + * + * @var \Drupal\Core\Config\StorageInterface[] + */ + protected $storages; + /** * Creates ConfigManager objects. * @@ -64,13 +86,18 @@ class ConfigManager implements ConfigManagerInterface { * The typed config manager. * @param \Drupal\Core\StringTranslation\TranslationManager $string_translation * The string translation service. + * @param \Drupal\Core\Config\StorageInterface $active_storage + * The active configuration storage. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher + * The event dispatcher. */ - public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, TypedConfigManager $typed_config_manager, TranslationManager $string_translation, StorageInterface $active_storage) { + public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, TypedConfigManager $typed_config_manager, TranslationManager $string_translation, StorageInterface $active_storage, EventDispatcherInterface $event_dispatcher) { $this->entityManager = $entity_manager; $this->configFactory = $config_factory; $this->typedConfigManager = $typed_config_manager; $this->stringTranslation = $string_translation; $this->activeStorage = $active_storage; + $this->eventDispatcher = $event_dispatcher; } /** @@ -246,4 +273,16 @@ public function findConfigEntityDependentsAsEntities($type, array $names) { public function supportsConfigurationEntities($collection) { return $collection == StorageInterface::DEFAULT_COLLECTION; } + + /** + * {@inheritdoc} + */ + public function getConfigCollectionInfo() { + if (!isset($this->configCollectionInfo)) { + $this->configCollectionInfo = new ConfigCollectionInfo(); + $this->eventDispatcher->dispatch(ConfigEvents::COLLECTION_INFO, $this->configCollectionInfo); + } + return $this->configCollectionInfo; + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php index 3be67c893adbba3ab876537fb5f3f157191401d7..c0ea6cfa177d80c6419806d6ed15b64d6d721c92 100644 --- a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php @@ -123,4 +123,12 @@ public function findConfigEntityDependentsAsEntities($type, array $names); */ public function supportsConfigurationEntities($collection); + /** + * Gets available collection information using the event system. + * + * @return \Drupal\Core\Config\ConfigCollectionInfo + * The object which contains information about the available collections. + */ + public function getConfigCollectionInfo(); + } diff --git a/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php b/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php index df1b33500f213a2cfe73e7f3d4f1c6f719f3d62e..e90a05889556351cae99267eb8d0d7ab21ddf34b 100644 --- a/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php @@ -51,9 +51,9 @@ public function testDefaultConfig() { $default_config_storage = new TestInstallStorage(); foreach ($default_config_storage->listAll() as $config_name) { - // @todo: remove once migration (https://drupal.org/node/2183957) and - // translation (https://drupal.org/node/2168609) schemas are in. - if (strpos($config_name, 'migrate.migration') === 0 || strpos($config_name, 'language.config') === 0) { + // @todo: remove once migration (https://drupal.org/node/2183957) schemas + // are in. + if (strpos($config_name, 'migrate.migration') === 0) { continue; } diff --git a/core/modules/config/tests/config_collection_install_test/lib/Drupal/config_collection_install_test/EventSubscriber.php b/core/modules/config/tests/config_collection_install_test/lib/Drupal/config_collection_install_test/EventSubscriber.php index bb481e0eaed459f34fe1914dffe118d886cbf9b8..f06aaa5fad7fd79bcf2b6a16b1cf29531d16d74b 100644 --- a/core/modules/config/tests/config_collection_install_test/lib/Drupal/config_collection_install_test/EventSubscriber.php +++ b/core/modules/config/tests/config_collection_install_test/lib/Drupal/config_collection_install_test/EventSubscriber.php @@ -7,7 +7,7 @@ namespace Drupal\config_collection_install_test; -use Drupal\Core\Config\ConfigCollectionNamesEvent; +use Drupal\Core\Config\ConfigCollectionInfo; use Drupal\Core\Config\ConfigEvents; use Drupal\Core\State\StateInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -34,18 +34,21 @@ public function __construct(StateInterface $state) { /** * Reacts to the ConfigEvents::COLLECTION_NAMES event. * - * @param \Drupal\Core\Config\ConfigCollectionNamesEvent $event + * @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info * The configuration collection names event. */ - public function addCollectionNames(ConfigCollectionNamesEvent $event) { - $event->addCollectionNames($this->state->get('config_collection_install_test.collection_names', array())); + public function addCollections(ConfigCollectionInfo $collection_info) { + $collections = $this->state->get('config_collection_install_test.collection_names', array()); + foreach ($collections as $collection) { + $collection_info->addCollection($collection); + } } /** * {@inheritdoc} */ static function getSubscribedEvents() { - $events[ConfigEvents::COLLECTION_NAMES][] = array('addCollectionNames'); + $events[ConfigEvents::COLLECTION_INFO][] = array('addCollections'); return $events; } diff --git a/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverrider.php b/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverrider.php index 106df3d1854556229bce0f129f117a57556babee..1da4ed7bc6aa051f4035e4d7c943bc39246f9841 100644 --- a/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverrider.php +++ b/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverrider.php @@ -10,6 +10,7 @@ use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\ConfigModuleOverridesEvent; +use Drupal\Core\Config\StorageInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -40,5 +41,12 @@ public function getCacheSuffix() { return 'ConfigOverrider'; } + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + } diff --git a/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverriderLowPriority.php b/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverriderLowPriority.php index a9a435e5da48edf86d1f5a7791e0ce1b0b6b7949..16aa406a744092c818e7130a391fd4ebab7495cd 100644 --- a/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverriderLowPriority.php +++ b/core/modules/config/tests/config_override/lib/Drupal/config_override/ConfigOverriderLowPriority.php @@ -8,6 +8,7 @@ namespace Drupal\config_override; use Drupal\Core\Config\ConfigFactoryOverrideInterface; +use Drupal\Core\Config\StorageInterface; /** * Tests module overrides for configuration. @@ -41,5 +42,12 @@ public function getCacheSuffix() { return 'ConfigOverriderLowPriority'; } + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + } diff --git a/core/modules/config/tests/config_test/config/install/language.config.de.config_test.system.yml b/core/modules/config/tests/config_test/config/install/language/de/config_test.system.yml similarity index 100% rename from core/modules/config/tests/config_test/config/install/language.config.de.config_test.system.yml rename to core/modules/config/tests/config_test/config/install/language/de/config_test.system.yml diff --git a/core/modules/config/tests/config_test/config/install/language.config.en.config_test.system.yml b/core/modules/config/tests/config_test/config/install/language/en/config_test.system.yml similarity index 100% rename from core/modules/config/tests/config_test/config/install/language.config.en.config_test.system.yml rename to core/modules/config/tests/config_test/config/install/language/en/config_test.system.yml diff --git a/core/modules/config/tests/config_test/config/install/language.config.fr.config_test.system.yml b/core/modules/config/tests/config_test/config/install/language/fr/config_test.system.yml similarity index 100% rename from core/modules/config/tests/config_test/config/install/language.config.fr.config_test.system.yml rename to core/modules/config/tests/config_test/config/install/language/fr/config_test.system.yml diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php b/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php index fb3ba889ae5f76a45e80e95cd02ef710aa005ea6..13311d90aec485fc2c7ff9e876562b6cd45aadce 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php @@ -15,6 +15,7 @@ use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Language\Language; +use Drupal\language\Config\LanguageConfigOverride; use Drupal\language\ConfigurableLanguageManagerInterface; use Drupal\locale\StringStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -352,7 +353,7 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d * Set the configuration in this language. * @param \Drupal\Core\Config\Config $base_config * Base configuration values, in the source language. - * @param \Drupal\Core\Config\Config $config_translation + * @param \Drupal\language\Config\LanguageConfigOverride $config_translation * Translation configuration override data. * @param array $config_values * A simple one dimensional or recursive array: @@ -372,7 +373,7 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d * @return array * Translation configuration override data. */ - protected function setConfig(Language $language, Config $base_config, Config $config_translation, array $config_values, $shipped_config = FALSE) { + protected function setConfig(Language $language, Config $base_config, LanguageConfigOverride $config_translation, array $config_values, $shipped_config = FALSE) { foreach ($config_values as $key => $value) { if (is_array($value) && !isset($value['translation'])) { // Traverse into this level in the configuration. diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 8a2e37299958afdc819ee2060e020356dffb2793..fb950011081a1dc1fa780305609aa7f0ce3c2349 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -425,6 +425,8 @@ function language_save($language) { $language_entity->save(); $t_args = array('%language' => $language->name, '%langcode' => $language->id); if ($language->is_new) { + // Install any available language configuration overrides for the language. + \Drupal::service('language.config_factory_override')->installLanguageOverrides($language->getId()); watchdog('language', 'The %language (%langcode) language has been created.', $t_args); } else { diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index 7396d575253f51209c3c0a0c2ccbcd2643b16a02..e183e6ddacfb3602e289b66c7179c70b1daf470c 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -16,3 +16,4 @@ services: arguments: ['@config.storage', '@event_dispatcher', '@config.typed'] tags: - { name: config.factory.override, priority: -254 } + - { name: event_subscriber } diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php index bfadec0e42d90b8fb5150fd8709aeff30a1afb94..f6fa8adf64db5cf2e65e9557d412be60da321d11 100644 --- a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php +++ b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php @@ -7,24 +7,37 @@ namespace Drupal\language\Config; -use Drupal\Core\Config\Config; +use Drupal\Component\Utility\String; +use Drupal\Core\Config\ConfigCollectionInfo; +use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageDefault; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Provides language overrides for the configuration factory. */ -class LanguageConfigFactoryOverride implements LanguageConfigFactoryOverrideInterface { +class LanguageConfigFactoryOverride implements LanguageConfigFactoryOverrideInterface, EventSubscriberInterface { /** * The configuration storage. * + * Do not access this directly. Should be accessed through self::getStorage() + * so that the cache of storages per langcode is used. + * * @var \Drupal\Core\Config\StorageInterface */ - protected $storage; + protected $baseStorage; + + /** + * An array of configuration storages keyed by langcode. + * + * @var \Drupal\Core\Config\StorageInterface[] + */ + protected $storages; /** * The typed config manager. @@ -58,7 +71,7 @@ class LanguageConfigFactoryOverride implements LanguageConfigFactoryOverrideInte * The typed configuration manager. */ public function __construct(StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) { - $this->storage = $storage; + $this->baseStorage = $storage; $this->eventDispatcher = $event_dispatcher; $this->typedConfigManager = $typed_config; } @@ -67,72 +80,34 @@ public function __construct(StorageInterface $storage, EventDispatcherInterface * {@inheritdoc} */ public function loadOverrides($names) { - $data = array(); - $language_names = $this->getLanguageConfigNames($names); - if ($language_names) { - $data = $this->storage->readMultiple(array_values($language_names)); - // Re-key the data array to use configuration names rather than override - // names. - $prefix_length = strlen(static::LANGUAGE_CONFIG_PREFIX . '.' . $this->language->id) + 1; - foreach ($data as $key => $value) { - unset($data[$key]); - $key = substr($key, $prefix_length); - $data[$key] = $value; - } + if ($this->language) { + $storage = $this->getStorage($this->language->getId()); + return $storage->readMultiple($names); } - return $data; + return array(); } /** * {@inheritdoc} */ public function getOverride($langcode, $name) { - $override_name = $this->getLanguageConfigName($langcode, $name); - $overrides = $this->storage->read($override_name); - $config = new Config($override_name, $this->storage, $this->eventDispatcher, $this->typedConfigManager); - if (!empty($overrides)) { - $config->initWithData($overrides); + $storage = $this->getStorage($langcode); + $data = $storage->read($name); + $override = new LanguageConfigOverride($name, $storage, $this->typedConfigManager); + if (!empty($data)) { + $override->initWithData($data); } - return $config; + return $override; } /** - * Generate a list of configuration names based on base names. - * - * @param array $names - * List of configuration names. - * - * @return array - * List of configuration names for language override files if applicable. - */ - protected function getLanguageConfigNames(array $names) { - $language_names = array(); - if (isset($this->language)) { - foreach ($names as $name) { - if ($language_name = $this->getLanguageConfigName($this->language->id, $name)) { - $language_names[$name] = $language_name; - } - } - } - return $language_names; - } - - /** - * Get language override name for given language and configuration name. - * - * @param string $langcode - * Language code. - * @param string $name - * Configuration name. - * - * @return bool|string - * Configuration name or FALSE if not applicable. + * {@inheritdoc} */ - protected function getLanguageConfigName($langcode, $name) { - if (strpos($name, static::LANGUAGE_CONFIG_PREFIX) === 0) { - return FALSE; + public function getStorage($langcode) { + if (!isset($this->storages[$langcode])) { + $this->storages[$langcode] = $this->baseStorage->createCollection($this->createConfigCollectionName($langcode)); } - return static::LANGUAGE_CONFIG_PREFIX . '.' . $langcode . '.' . $name; + return $this->storages[$langcode]; } /** @@ -165,4 +140,77 @@ public function setLanguageFromDefault(LanguageDefault $language_default = NULL) return $this; } + /** + * {@inheritdoc} + */ + public function installLanguageOverrides($langcode) { + /** @var \Drupal\Core\Config\ConfigInstallerInterface $config_installer */ + $config_installer = \Drupal::service('config.installer'); + $config_installer->installCollectionDefaultConfig($this->createConfigCollectionName($langcode)); + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + $langcode = $this->getLangcodeFromCollectionName($collection); + return $this->getOverride($langcode, $name); + } + + /** + * Creates a configuration collection name based on a langcode. + * + * @param string $langcode + * The langcode. + * + * @return string + * The configuration collection name for a langcode. + */ + protected function createConfigCollectionName($langcode) { + return 'language.' . $langcode; + } + + /** + * Converts a configuration collection name to a langcode. + * + * @param string $collection + * The configuration collection name. + * + * @return string + * The langcode of the collection. + * + * @throws \InvalidArgumentException + * Exception thrown if the provided collection name is not in the format + * "language.LANGCODE". + * + * @see self::createConfigCollectionName() + */ + protected function getLangcodeFromCollectionName($collection) { + preg_match('/^language\.(.*)$/', $collection, $matches); + if (!isset($matches[1])) { + throw new \InvalidArgumentException(String::format('!collection is not a valid language override collection', array('!collection' => $collection))); + } + return $matches[1]; + } + + /** + * Reacts to the ConfigEvents::COLLECTION_INFO event. + * + * @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info + * The configuration collection names event. + */ + public function addCollections(ConfigCollectionInfo $collection_info) { + foreach (\Drupal::languageManager()->getLanguages() as $language) { + $collection_info->addCollection($this->createConfigCollectionName($language->getId()), $this); + } + } + + /** + * {@inheritdoc} + */ + static function getSubscribedEvents() { + $events[ConfigEvents::COLLECTION_INFO][] = array('addCollections'); + return $events; + } + } diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverrideInterface.php b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverrideInterface.php index 1c3de55ae9357a33a7ec18848a31ad17dbad9810..c7b297d5c1437ec039e17c072b7ecc2510e0e1fc 100644 --- a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverrideInterface.php +++ b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverrideInterface.php @@ -16,11 +16,6 @@ */ interface LanguageConfigFactoryOverrideInterface extends ConfigFactoryOverrideInterface { - /** - * Prefix for all language configuration files. - */ - const LANGUAGE_CONFIG_PREFIX = 'language.config'; - /** * Gets the language object used to override configuration data. * @@ -62,4 +57,23 @@ public function setLanguageFromDefault(LanguageDefault $language_default = NULL) */ public function getOverride($langcode, $name); + /** + * Returns the storage instance for a particular langcode. + * + * @param string $langcode + * Language code. + * + * @return \Drupal\Core\Config\StorageInterface + * The storage instance for a particular langcode. + */ + public function getStorage($langcode); + + /** + * Installs available language configuration overrides for a given langcode. + * + * @param string $langcode + * Language code. + */ + public function installLanguageOverrides($langcode); + } diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageConfigOverride.php b/core/modules/language/lib/Drupal/language/Config/LanguageConfigOverride.php new file mode 100644 index 0000000000000000000000000000000000000000..19fb54980afe0b02aa8143406b6bd453e4ede000 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/Config/LanguageConfigOverride.php @@ -0,0 +1,63 @@ +name = $name; + $this->storage = $storage; + $this->typedConfigManager = $typed_config; + } + + /** + * {@inheritdoc} + */ + public function save() { + // @todo Use configuration schema to validate. + // https://drupal.org/node/2270399 + // Perform basic data validation. + foreach ($this->data as $key => $value) { + $this->validateValue($key, $value); + } + $this->storage->write($this->name, $this->data); + $this->isNew = FALSE; + $this->originalData = $this->data; + return $this; + } + + /** + * {@inheritdoc} + */ + public function delete() { + $this->data = array(); + $this->storage->delete($this->name); + $this->isNew = TRUE; + $this->originalData = $this->data; + return $this; + } + +} diff --git a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php index 101449c3cebad6c890cf2c48508b0a97d13a8a87..b28e02b8b4fa1e94c5c6ebff07c55209fce1ee80 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php @@ -407,6 +407,13 @@ public function getLanguageConfigOverride($langcode, $name) { return $this->configFactoryOverride->getOverride($langcode, $name); } + /** + * {@inheritdoc} + */ + public function getLanguageConfigOverrideStorage($langcode) { + return $this->configFactoryOverride->getStorage($langcode); + } + /** * {@inheritdoc} */ diff --git a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php index 9bbf2ce10d175798b5a2f2e176be22b0a92f9906..1ebcb7eccdc8b6bcfa9e312f0d600103e85cb6c4 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php @@ -91,11 +91,23 @@ public function updateLockedLanguageWeights(); * @param string $name * The language configuration object name. * - * @return \Drupal\Core\Config\Config + * @return \Drupal\language\Config\LanguageConfigOverride * The language config override object. */ public function getLanguageConfigOverride($langcode, $name); + /** + * Gets a language configuration override storage object. + * + * @param string $langcode + * The language code for the override. + * + * @return \Drupal\Core\Config\StorageInterface $storage + * A storage object to use for reading and writing the + * configuration override. + */ + public function getLanguageConfigOverrideStorage($langcode); + /** * Returns the standard language list excluding already configured languages. * diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideImportTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideImportTest.php new file mode 100644 index 0000000000000000000000000000000000000000..30a5232d1a1647ea4afaff46434f6081812ead40 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideImportTest.php @@ -0,0 +1,108 @@ + 'Language config override synchronize', + 'description' => 'Ensures the language config overrides can be synchronized.', + 'group' => 'Language', + ); + } + + /** + * Tests that language can be enabled and overrides are created during a sync. + */ + public function testConfigOverrideImport() { + language_save(new Language(array( + 'name' => 'French', + 'id' => 'fr', + ))); + /* @var \Drupal\Core\Config\StorageInterface $staging */ + $staging = \Drupal::service('config.storage.staging'); + $this->copyConfig(\Drupal::service('config.storage'), $staging); + + // Uninstall the language module and its dependencies so we can test + // enabling the language module and creating overrides at the same time + // during a configuration synchronisation. + \Drupal::moduleHandler()->uninstall(array('language')); + // Ensure that the current site has no overrides registered to the + // ConfigFactory. + $this->rebuildContainer(); + + /* @var \Drupal\Core\Config\StorageInterface $override_staging */ + $override_staging = $staging->createCollection('language.fr'); + // Create some overrides in staging. + $override_staging->write('system.site', array('name' => 'FR default site name')); + $override_staging->write('system.maintenance', array('message' => 'FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience')); + + $this->configImporter()->import(); + $this->rebuildContainer(); + \Drupal::service('router.builder')->rebuild(); + + $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site'); + $this->assertEqual('FR default site name', $override->get('name')); + $this->drupalGet('fr'); + $this->assertText('FR default site name'); + + $this->drupalLogin($this->root_user); + $this->drupalGet('admin/config/development/maintenance/translate/fr/edit'); + $this->assertText('FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience'); + } + + /** + * Tests that configuration events are not fired during a sync of overrides. + */ + public function testConfigOverrideImportEvents() { + // Enable the config_events_test module so we can record events occurring. + \Drupal::moduleHandler()->install(array('config_events_test')); + $this->rebuildContainer(); + + language_save(new Language(array( + 'name' => 'French', + 'id' => 'fr', + ))); + /* @var \Drupal\Core\Config\StorageInterface $staging */ + $staging = \Drupal::service('config.storage.staging'); + $this->copyConfig(\Drupal::service('config.storage'), $staging); + + /* @var \Drupal\Core\Config\StorageInterface $override_staging */ + $override_staging = $staging->createCollection('language.fr'); + // Create some overrides in staging. + $override_staging->write('system.site', array('name' => 'FR default site name')); + \Drupal::state()->set('config_events_test.event', FALSE); + + $this->configImporter()->import(); + $this->rebuildContainer(); + \Drupal::service('router.builder')->rebuild(); + + // Test that no config save event has been fired during the import because + // language configuration overrides do not fire events. + $event_recorder = \Drupal::state()->get('config_events_test.event', FALSE); + $this->assertFalse($event_recorder); + + $this->drupalGet('fr'); + $this->assertText('FR default site name'); + } + +} diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideInstallTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideInstallTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ccf56ad977efef66c6b4324632120806d5809dfc --- /dev/null +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigOverrideInstallTest.php @@ -0,0 +1,48 @@ + 'Language config override installation', + 'description' => 'Ensures the language config overrides can be installed.', + 'group' => 'Language', + ); + } + + /** + * Tests the configuration events are not fired during install of overrides. + */ + public function testLanguageConfigOverrideInstall() { + language_save(new Language(array('id' => 'de'))); + // Need to enable test module after creating the language otherwise saving + // the language will install the configuration. + $this->enableModules(array('language_config_override_test')); + \Drupal::state()->set('config_events_test.event', FALSE); + $this->installConfig(array('language_config_override_test')); + $event_recorder = \Drupal::state()->get('config_events_test.event', FALSE); + $this->assertFalse($event_recorder); + $config = \Drupal::service('language.config_factory_override')->getOverride('de', 'language_config_override_test.settings'); + $this->assertEqual($config->get('name'), 'Deutsch'); + } + +} diff --git a/core/modules/language/tests/language_config_override_test/config/install/language/de/language_config_override_test.settings.yml b/core/modules/language/tests/language_config_override_test/config/install/language/de/language_config_override_test.settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..c3629805cf5c06191d3f30c38199311c9f83d0c4 --- /dev/null +++ b/core/modules/language/tests/language_config_override_test/config/install/language/de/language_config_override_test.settings.yml @@ -0,0 +1 @@ +name: 'Deutsch' diff --git a/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml b/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..4345db5a4089cdf74c5ef3cfd104378153113068 --- /dev/null +++ b/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml @@ -0,0 +1,6 @@ +name: 'Language config overridetest' +type: module +description: 'Support module for the language config override test.' +core: 8.x +package: Testing +version: VERSION diff --git a/core/modules/locale/lib/Drupal/locale/LocaleConfigManager.php b/core/modules/locale/lib/Drupal/locale/LocaleConfigManager.php index 3c0dc577bf654cbd4909ee71e615acd739109736..5b0aa4eaa127150cc521991eca9d35ddd02a4370 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleConfigManager.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleConfigManager.php @@ -12,7 +12,6 @@ use Drupal\Core\Config\TypedConfigManager; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\language\Config\LanguageConfigFactoryOverrideInterface; use Drupal\language\ConfigurableLanguageManagerInterface; /** @@ -230,9 +229,9 @@ public function getStringNames(array $lids) { * Language code to delete. */ public function deleteLanguageTranslations($langcode) { - $locale_name = LanguageConfigFactoryOverrideInterface::LANGUAGE_CONFIG_PREFIX . '.' . $langcode . '.'; - foreach ($this->configStorage->listAll($locale_name) as $name) { - $this->configStorage->delete($name); + $storage = $this->languageManager->getLanguageConfigOverrideStorage($langcode); + foreach ($storage->listAll() as $name) { + $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete(); } } diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigManagerTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigManagerTest.php index bdde87fe87755549b4dfde6a7d54b27e45850ecd..50cbc7d30ccc98b0c1edcb8048a5513a8e5c6622 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigManagerTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigManagerTest.php @@ -42,7 +42,7 @@ public function testHasTranslation() { $this->installConfig(array('locale_test')); $locale_config_manager = \Drupal::service('locale.config.typed'); - $language = new Language(array('id' => 'de')); + $language = language_save(new Language(array('id' => 'de'))); $result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language); $this->assertFalse($result, 'There is no translation for locale_test.no_translation configuration.'); diff --git a/core/modules/locale/tests/modules/locale_test/config/install/language.config.de.locale_test.translation.yml b/core/modules/locale/tests/modules/locale_test/config/install/language/de/locale_test.translation.yml similarity index 100% rename from core/modules/locale/tests/modules/locale_test/config/install/language.config.de.locale_test.translation.yml rename to core/modules/locale/tests/modules/locale_test/config/install/language/de/locale_test.translation.yml diff --git a/core/modules/system/tests/modules/menu_test/config/install/language.config.nl.menu_test.menu_item.yml b/core/modules/system/tests/modules/menu_test/config/install/language/nl/menu_test.menu_item.yml similarity index 100% rename from core/modules/system/tests/modules/menu_test/config/install/language.config.nl.menu_test.menu_item.yml rename to core/modules/system/tests/modules/menu_test/config/install/language/nl/menu_test.menu_item.yml diff --git a/core/modules/tour/tests/tour_test/config/install/language.config.it.tour.tour.tour-test.yml b/core/modules/tour/tests/tour_test/config/install/language/it/tour.tour.tour-test.yml similarity index 100% rename from core/modules/tour/tests/tour_test/config/install/language.config.it.tour.tour.tour-test.yml rename to core/modules/tour/tests/tour_test/config/install/language/it/tour.tour.tour-test.yml