diff --git a/core/includes/config.inc b/core/includes/config.inc index b4a0666..3af1ee1 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -2,7 +2,7 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\FileStorage; -use Drupal\Core\Config\NullStorage; +use Drupal\Core\Config\Context\ContextInterface; use Drupal\Core\Config\StorageInterface; /** @@ -24,6 +24,11 @@ * The name of the module or theme to install default configuration for. */ function config_install_default_config($type, $name) { + // Use the override free context for config importing so that any overrides do + // not change the data on import. + $config_factory = drupal_container()->get('config.factory'); + $config_factory->enterContext(drupal_container()->get('config.context.free')); + // If this module defines any ConfigEntity types then create an empty // manifest file for each of them. foreach (config_get_module_config_entities($name) as $entity_info) { @@ -47,6 +52,11 @@ function config_install_default_config($type, $name) { $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); config_sync_changes($remaining_changes, $source_storage, $target_storage); } + // Exit the override free context and ensure that any new manifest data is + // available. + $config_factory + ->leaveContext() + ->reset('manifest'); } /** @@ -87,7 +97,7 @@ function config_get_storage_names_with_prefix($prefix = '') { * @code config('book.admin') @endcode will return a configuration object in * which the book module can store its administrative settings. * - * @param $name + * @param string $name * The name of the configuration object to retrieve. The name corresponds to * a configuration file. For @code config('book.admin') @endcode, the config * object returned will contain the contents of book.admin configuration file. @@ -173,10 +183,11 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * The storage to synchronize configuration to. */ function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) { + $target_context = drupal_container()->get('config.context.free'); $factory = drupal_container()->get('config.factory'); foreach (array('delete', 'create', 'change') as $op) { foreach ($config_changes[$op] as $name) { - $config = new Config($name, $target_storage); + $config = new Config($name, $target_storage, $target_context); if ($op == 'delete') { $config->delete(); } @@ -218,8 +229,19 @@ function config_import() { $success = TRUE; try { + // Use the override free context for config importing so that any overrides do + // not change the data on import. + $config_factory = drupal_container()->get('config.factory'); + $config_factory->enterContext(drupal_container()->get('config.context.free')); + $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); config_sync_changes($remaining_changes, $source_storage, $target_storage); + + // Exit the override free context and ensure that any new manifest data is + // available. + $config_factory + ->leaveContext() + ->reset('manifest'); } catch (ConfigException $e) { watchdog_exception('config_import', $e); @@ -243,6 +265,10 @@ function config_import() { * @todo Add support for other extension types; e.g., themes etc. */ function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) { + $factory = drupal_container()->get('config.factory'); + // Use the admin context for config importing so that any overrides do not + // change the data on import. + $free_context = drupal_container()->get('config.context.free'); // Allow modules to take over configuration change operations for // higher-level configuration data. // First pass deleted, then new, and lastly changed configuration, in order to @@ -256,11 +282,11 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou // Validate the configuration object name before importing it. Config::validateName($name); if ($entity_type = config_get_entity_type_by_name($name)) { - $old_config = new Config($name, $target_storage); + $old_config = new Config($name, $target_storage, $free_context); $old_config->load(); $data = $source_storage->read($name); - $new_config = new Config($name, $target_storage); + $new_config = new Config($name, $source_storage, $free_context); if ($data !== FALSE) { $new_config->setData($data); } @@ -269,6 +295,7 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou $handled_by_module = $manager->getStorageController($entity_type)->$method($name, $new_config, $old_config); } if (!empty($handled_by_module)) { + $factory->reset($name); unset($config_changes[$op][$key]); } } diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index ec5ec0e..42d292d 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -337,9 +337,16 @@ function install_begin_request(&$install_state) { $container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher'); $container->register('config.storage', 'Drupal\Core\Config\InstallStorage'); + $container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory') + ->addArgument(new Reference('event_dispatcher')); + + $container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface') + ->setFactoryService(new Reference('config.context.factory')) + ->setFactoryMethod('get'); + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) - ->addArgument(new Reference('event_dispatcher')); + ->addArgument(new Reference('config.context')); // Register the 'language_manager' service. $container->register('language_manager', 'Drupal\Core\Language\LanguageManager'); diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index c1f0632..0337d28 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -9,7 +9,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigNameException; -use Symfony\Component\EventDispatcher\EventDispatcher; +use Drupal\Core\Config\Context\ContextInterface; /** * Defines the default configuration object. @@ -71,11 +71,11 @@ class Config { protected $storage; /** - * The event dispatcher used to notify subscribers. + * The configuration context used for this configuration object. * - * @var Symfony\Component\EventDispatcher\EventDispatcher + * @var \Drupal\Core\Config\Context\ContextInterface */ - protected $eventDispatcher; + protected $context; /** * Whether the config object has already been loaded. @@ -89,16 +89,16 @@ class Config { * * @param string $name * The name of the configuration object being constructed. - * @param Drupal\Core\Config\StorageInterface $storage + * @param \Drupal\Core\Config\StorageInterface $storage * A storage controller object to use for reading and writing the * configuration data. - * @param Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher - * The event dispatcher used to notify subscribers. + * @param \Drupal\Core\Config\Context\ContextInterface $context + * The configuration context used for this configuration object. */ - public function __construct($name, StorageInterface $storage, EventDispatcher $event_dispatcher = NULL) { + public function __construct($name, StorageInterface $storage, ContextInterface $context) { $this->name = $name; $this->storage = $storage; - $this->eventDispatcher = $event_dispatcher ? $event_dispatcher : drupal_container()->get('event_dispatcher'); + $this->context = $context; } /** @@ -491,7 +491,7 @@ public function getStorage() { * Dispatch a config event. */ protected function notify($config_event_name) { - $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this)); + $this->context->notify($config_event_name, $this); } /** diff --git a/core/lib/Drupal/Core/Config/ConfigEvent.php b/core/lib/Drupal/Core/Config/ConfigEvent.php index aabd1d8..c1df902 100644 --- a/core/lib/Drupal/Core/Config/ConfigEvent.php +++ b/core/lib/Drupal/Core/Config/ConfigEvent.php @@ -2,10 +2,11 @@ namespace Drupal\Core\Config; +use Drupal\Core\Config\Context\ContextInterface; use Symfony\Component\EventDispatcher\Event; -use Drupal\Core\Config\Config; class ConfigEvent extends Event { + /** * Configuration object. * @@ -14,10 +15,23 @@ class ConfigEvent extends Event { protected $config; /** - * Constructor. + * Configuration context object. + * + * @var \Drupal\Core\Config\Context\ContextInterface + */ + protected $context; + + /** + * Constructs a configuration event object. + * + * @param \Drupal\Core\Config\Context\ContextInterface + * Configuration context object. + * @param \Drupal\Core\Config\Config + * (optional) Configuration object. */ - public function __construct(Config $config) { + public function __construct(ContextInterface $context, Config $config = NULL) { $this->config = $config; + $this->context = $context; } /** @@ -26,4 +40,14 @@ public function __construct(Config $config) { public function getConfig() { return $this->config; } + + /** + * Get configuration context object. + * + * @return \Drupal\Core\Config\Context\ContextInterface + * Configuration context. + */ + public function getContext() { + return $this->context; + } } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 00d9773..1f37a23 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Config; -use Symfony\Component\EventDispatcher\EventDispatcher; +use Drupal\Core\Config\Context\ContextInterface; /** * Defines the configuration object factory. @@ -21,22 +21,28 @@ * is used for reading and writing the configuration data. * * @see Drupal\Core\Config\StorageInterface + * + * A configuration context is an object containing parameters that will be + * available to the configuration plug-ins for them to customize the + * configuration data in different ways. + * + * @see Drupal\Core\Config\Context\ContextInterface */ class ConfigFactory { /** * A storage controller instance for reading and writing configuration data. * - * @var Drupal\Core\Config\StorageInterface + * @var \Drupal\Core\Config\StorageInterface */ protected $storage; /** - * An event dispatcher instance to use for configuration events. + * A stack of configuration contexts the last being the context in use. * - * @var Symfony\Component\EventDispatcher\EventDispatcher + * @var array */ - protected $eventDispatcher; + protected $contextStack = array(); /** * Cached configuration objects. @@ -48,33 +54,31 @@ class ConfigFactory { /** * Constructs the Config factory. * - * @param Drupal\Core\Config\StorageInterface $storage - * The storage controller object to use for reading and writing - * configuration data. - * @param Symfony\Component\EventDispatcher\EventDispatcher - * An event dispatcher instance to use for configuration events. + * @param \Drupal\Core\Config\StorageInterface + * The configuration storage engine. + * @param \Drupal\Core\Config\Context\ContextInterface + * Configuration context object. */ - public function __construct(StorageInterface $storage, EventDispatcher $event_dispatcher) { + public function __construct(StorageInterface $storage, ContextInterface $context) { $this->storage = $storage; - $this->eventDispatcher = $event_dispatcher; + $this->enterContext($context); } /** - * Returns a configuration object for a given name. + * Returns a configuration object for a given name and context. * * @param string $name * The name of the configuration object to construct. - * - * @return Drupal\Core\Config\Config - * A configuration object with the given $name. */ public function get($name) { - if (isset($this->cache[$name])) { - return $this->cache[$name]; + $context = $this->getContext(); + $cache_key = $this->getCacheKey($name, $context); + if (isset($this->cache[$cache_key])) { + return $this->cache[$cache_key]; } - $this->cache[$name] = new Config($name, $this->storage, $this->eventDispatcher); - return $this->cache[$name]->init(); + $this->cache[$cache_key] = new Config($name, $this->storage, $context); + return $this->cache[$cache_key]->init(); } /** @@ -83,11 +87,15 @@ public function get($name) { * @param string $name * (optional) The name of the configuration object to reset. If omitted, all * configuration objects are reset. + * + * @return \Drupal\Core\Config\ConfigFactory + * The config factory object. */ public function reset($name = NULL) { if ($name) { - if (isset($this->cache[$name])) { - $this->cache[$name]->init(); + // Reinitialise the configuration object in all contexts. + foreach ($this->getCacheKeys($name) as $cache_key) { + $this->cache[$cache_key]->init(); } } else { @@ -95,6 +103,7 @@ public function reset($name = NULL) { $config->init(); } } + return $this; } /** @@ -108,14 +117,76 @@ public function reset($name = NULL) { * @todo D8: Remove after http://drupal.org/node/1865206. */ public function rename($old_name, $new_name) { - if (isset($this->cache[$old_name])) { - $config = $this->cache[$old_name]; + $old_cache_key = $this->getCacheKey($old_name, $this->getContext()); + $new_cache_key = $this->getCacheKey($new_name, $this->getContext()); + if (isset($this->cache[$old_cache_key])) { + $config = $this->cache[$old_cache_key]; // Clone the object into the existing slot. - $this->cache[$old_name] = clone $config; + $this->cache[$old_cache_key] = clone $config; // Change the object's name and re-initialize it. $config->setName($new_name)->init(); - $this->cache[$new_name] = $config; + $this->cache[$new_cache_key] = $config; } } + + /** + * Sets the config context by adding it to the context stack. + * + * @param \Drupal\Core\Config\Context\ContextInterface $context + * The configuration context to add. + * + * @return \Drupal\Core\Config\ConfigFactory + * The config factory object. + */ + public function enterContext(ContextInterface $context) { + $this->contextStack[] = $context; + return $this; + } + + /** + * Gets the current config context. + * + * @return \Drupal\Core\Config\Context\ContextInterface $context + * The current configuration context. + */ + public function getContext() { + return end($this->contextStack); + } + + /** + * Leaves the current context by removing it from the context stack. + * + * @return \Drupal\Core\Config\ConfigFactory + * The config factory object. + */ + public function leaveContext() { + if (count($this->contextStack) > 1) { + array_pop($this->contextStack); + } + return $this; + } + + /* + * Gets the cache key for a given config name in a particular context. + * + * @return string + * The cache key. + */ + public function getCacheKey($name, ContextInterface $context) { + return $name . '.' . $context->getUuid(); + } + + /** + * Gets all the cache keys that match the provided config name. + * + * @return array + * An array of cache keys that match the provided config name. + */ + public function getCacheKeys($name) { + $cache_keys = array_keys($this->cache); + return array_filter($cache_keys, function($key) use ($name) { + return ( strpos($key, $name) !== false ); + }); + } } diff --git a/core/lib/Drupal/Core/Config/Context/ConfigContext.php b/core/lib/Drupal/Core/Config/Context/ConfigContext.php new file mode 100644 index 0000000..2b7ba7f --- /dev/null +++ b/core/lib/Drupal/Core/Config/Context/ConfigContext.php @@ -0,0 +1,122 @@ +eventDispatcher = $event_dispatcher; + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::init(). + */ + public function init($context_key, $data) { + if ($data) { + $this->set($context_key, $data); + } + $this->setUuid(); + // Notify event listeners that a configuration context has been created. + $this->notify('context', NULL); + return $this; + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::get(). + */ + public function get($key) { + return array_key_exists($key, $this->data) ? $this->data[$key] : NULL; + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::set(). + */ + public function set($key, $value) { + $this->data[$key] = $value; + } + + /** + * Sets override data. + * + * @param mixed $data + * Override data to store. + * + * @return \Drupal\Core\Config\Context\ConfigContext + * The config context object. + */ + public function setOverride($data) { + $this->init(self::OVERRIDE, $data); + return $this; + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::setUuid(). + */ + public function setUuid() { + $uuid = new Uuid(); + $this->uuid = $uuid->generate(); + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::getUuid(). + */ + public function getUuid() { + return $this->uuid; + } + + /** + * Implements Drupal\Core\Config\Context\ContextInterface::notify(). + */ + public function notify($config_event_name, Config $config = NULL) { + $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this, $config)); + } + +} diff --git a/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php b/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php new file mode 100644 index 0000000..a5100d8 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php @@ -0,0 +1,65 @@ +eventDispatcher = $event_dispatcher; + } + + /** + * Returns a configuration context object. + * + * @param string $class + * (Optional) The name of the configuration class to use. Defaults to + * Drupal\Core\Config\Context\ConfigContext + * @param \Drupal\Core\Config\Context\ContextInterface $context + * (Optional) The configuration context to use. + */ + public function get($class = NULL) { + if (!$class) { + $class = "Drupal\\Core\\Config\\Context\\ConfigContext"; + } + if (class_exists($class)) { + $context = new $class($this->eventDispatcher); + } + else { + throw new \Drupal\Core\Config\ConfigException(sprintf('Unknown config context class: %s', $class)); + } + return $context; + } + + +} diff --git a/core/lib/Drupal/Core/Config/Context/ContextInterface.php b/core/lib/Drupal/Core/Config/Context/ContextInterface.php new file mode 100644 index 0000000..b3107a7 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Context/ContextInterface.php @@ -0,0 +1,88 @@ +init(self::OVERRIDE, $conf); + return $this; + } +} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 035f282..69f266c 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -51,9 +51,25 @@ public function build(ContainerBuilder $container) { ->addArgument(new Reference('config.cachedstorage.storage')) ->addArgument(new Reference('cache.config')); + $container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory') + ->addArgument(new Reference('event_dispatcher')); + + $container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface') + ->setFactoryService(new Reference('config.context.factory')) + ->setFactoryMethod('get') + ->addArgument('Drupal\Core\Config\Context\GlobalConfigContext') + ->addTag('persist') + ->addMethodCall('setGlobalOverride'); + + // Register a config context with no overrides for use in administration + // forms, enabling modules and importing configuration. + $container->register('config.context.free', 'Drupal\Core\Config\Context\ContextInterface') + ->setFactoryService(new Reference('config.context.factory')) + ->setFactoryMethod('get'); + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) - ->addArgument(new Reference('event_dispatcher')) + ->addArgument(new Reference('config.context')) ->addTag('persist'); // Register staging configuration storage. @@ -251,7 +267,7 @@ public function build(ContainerBuilder $container) { $container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber') ->addArgument(new Reference('module_handler')) ->addTag('event_subscriber'); - $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber') + $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigOverrideSubscriber') ->addTag('event_subscriber'); $container->register('language_request_subscriber', 'Drupal\Core\EventSubscriber\LanguageRequestSubscriber') ->addArgument(new Reference('language_manager')) diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php deleted file mode 100644 index 6ee6a3d..0000000 --- a/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php +++ /dev/null @@ -1,39 +0,0 @@ -getConfig(); - if (isset($conf[$config->getName()])) { - $config->setOverride($conf[$config->getName()]); - } - } - - /** - * Implements EventSubscriberInterface::getSubscribedEvents(). - */ - static function getSubscribedEvents() { - $events['config.init'][] = array('configInit', 30); - return $events; - } -} diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php new file mode 100644 index 0000000..376da10 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php @@ -0,0 +1,41 @@ +getContext()->get(ConfigContext::OVERRIDE)) { + $config = $event->getConfig(); + if (isset($override[$config->getName()])) { + $config->setOverride($override[$config->getName()]); + } + } + } + + /** + * Implements EventSubscriberInterface::getSubscribedEvents(). + */ + public static function getSubscribedEvents() { + $events['config.init'][] = array('configInit', 30); + return $events; + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php b/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php new file mode 100644 index 0000000..5b8dcc9 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php @@ -0,0 +1,137 @@ + 'Locale override', + 'description' => 'Confirm that locale overrides work', + 'group' => 'Configuration', + ); + } + + public function setUp() { + parent::setUp(); + config_install_default_config('module', 'config_test'); + } + + /* + * Tests basic locale override. + */ + function testConfigLocaleOverride() { + $name = 'config_test.system'; + // The default language is en so the config key should be localised. + $config = config($name); + $this->assertIdentical($config->get('foo'), 'en bar'); + + // Ensure that we get the expected value when we use system_config. + $config = system_config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'bar'); + } + + /* + * Tests locale override based on user's preferred language. + */ + function testConfigLocaleUserOverride() { + $this->installSchema('system', 'variable'); + $this->installSchema('language', 'language'); + language_save(new Language(array( + 'name' => 'French', + 'langcode' => 'fr', + ))); + language_save(new Language(array( + 'name' => 'English', + 'langcode' => 'en', + ))); + language_save(new Language(array( + 'name' => 'German', + 'langcode' => 'de', + ))); + + $this->installSchema('user', 'users'); + $account = entity_create('user', array( + 'name' => 'French user', + 'mail' => 'test@example.com', + 'created' => REQUEST_TIME, + 'status' => 1, + 'preferred_langcode' => 'fr', + )); + + $user_config_context = drupal_container()->get('config.context.user'); + $config_factory = drupal_container()->get('config.factory'); + + $config_factory->enterContext($user_config_context->setAccount($account)); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'fr bar'); + + // Ensure that we get the expected value when we leave the user context. + $config_factory->leaveContext(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + + $account = entity_create('user', array( + 'name' => 'German user', + 'mail' => 'test@example.com', + 'created' => REQUEST_TIME, + 'status' => 1, + 'preferred_langcode' => 'de', + )); + + $config_factory->enterContext($user_config_context->setAccount($account)); + // Should not have to re-initialise config object to get new overrides as + // the new context will have a different uuid. + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'de bar'); + + // Enter an english context on top of the german context. + $account = entity_create('user', array( + 'name' => 'English user', + 'mail' => 'test@example.com', + 'created' => REQUEST_TIME, + 'status' => 1, + 'preferred_langcode' => 'en', + )); + $en_user_config_context = clone $user_config_context; + $config_factory->enterContext($en_user_config_context->setAccount($account)); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + + // Ensure that we get the expected value when we leave the english user + // context. + $config_factory->leaveContext(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'de bar'); + + // Ensure that we get the expected value when we leave the german user + // context. + $config_factory->leaveContext(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + + // Ensure that we cannot leave the default context. + $config_factory->leaveContext(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php index e2ddb59..2727850 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php @@ -8,6 +8,7 @@ namespace Drupal\config\Tests; use Drupal\simpletest\DrupalUnitTestBase; +use Drupal\Core\Config\Context\ConfigContext; /** * Tests configuration overrides via $conf in settings.php. @@ -29,12 +30,6 @@ public static function getInfo() { ); } - function setUp() { - parent::setUp(); - - config_install_default_config('module', 'config_test'); - } - /** * Tests configuration override. */ @@ -46,16 +41,39 @@ function testConfOverride() { '404' => 'herp', ); + // Set globals before installing to prove that the installed file does not + // contain these values. + $conf['config_test.system']['foo'] = 'overridden'; + $conf['config_test.system']['baz'] = 'injected'; + $conf['config_test.system']['404'] = 'derp'; + drupal_container()->get('config.context')->setGlobalOverride(); + + config_install_default_config('module', 'config_test'); + + // Verify that the original configuration data exists. Have to read storage + // directly otherwise overrides will apply. + $active = $this->container->get('config.storage'); + $data = $active->read('config_test.system'); + $this->assertIdentical($data['foo'], $expected_original_data['foo']); + $this->assertFalse(isset($data['baz'])); + $this->assertIdentical($data['404'], $expected_original_data['404']); + + // Remove the $conf overrides and reset value in config.context service. + unset($conf['config_test.system']); + drupal_container()->get('config.context')->setGlobalOverride(); + // Verify that the original configuration data exists. $config = config('config_test.system'); $this->assertIdentical($config->get('foo'), $expected_original_data['foo']); $this->assertIdentical($config->get('baz'), $expected_original_data['baz']); $this->assertIdentical($config->get('404'), $expected_original_data['404']); - // Apply the overridden data. + // Apply the overridden data, that needs to be set into the config.context + // service. $conf['config_test.system']['foo'] = 'overridden'; $conf['config_test.system']['baz'] = 'injected'; $conf['config_test.system']['404'] = 'derp'; + drupal_container()->get('config.context')->setGlobalOverride(); // Verify that the in-memory configuration object still contains the // original data. @@ -93,14 +111,45 @@ function testConfOverride() { $this->assertIdentical($config->get('baz'), $conf['config_test.system']['baz']); $this->assertIdentical($config->get('404'), $conf['config_test.system']['404']); - // Remove the $conf overrides. + // Remove the $conf overrides and reset value in config.context service. unset($conf['config_test.system']); + drupal_container()->get('config.context')->setGlobalOverride(); // Reload it and verify that it still contains the original data. $config->init(); $this->assertIdentical($config->get('foo'), $expected_original_data['foo']); $this->assertIdentical($config->get('baz'), $expected_original_data['baz']); $this->assertIdentical($config->get('404'), $expected_original_data['404']); + + // Set globals before importing to prove that the imported file does not + // contain these values. + $conf['config_test.system']['foo'] = 'overridden'; + $conf['config_test.system']['baz'] = 'injected'; + $conf['config_test.system']['404'] = 'derp'; + + // Write file to staging + drupal_container()->get('config.context')->setGlobalOverride(); + $staging = $this->container->get('config.storage.staging'); + $expected_new_data = array( + 'foo' => 'barbar', + '404' => 'herpderp', + ); + $staging->write('config_test.system', $expected_new_data); + + // Import changed data from staging to active. + config_import(); + $data = $active->read('config_test.system'); + + // Verify that the new configuration data exists. Have to read storage + // directly otherwise overrides will apply. + $this->assertIdentical($data['foo'], $expected_new_data['foo']); + $this->assertFalse(isset($data['baz'])); + $this->assertIdentical($data['404'], $expected_new_data['404']); + + // Verifiy the overrides are still working. + $this->assertIdentical($config->get('foo'), $conf['config_test.system']['foo']); + $this->assertIdentical($config->get('baz'), $conf['config_test.system']['baz']); + $this->assertIdentical($config->get('404'), $conf['config_test.system']['404']); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php b/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php deleted file mode 100644 index 20f835d..0000000 --- a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php +++ /dev/null @@ -1,38 +0,0 @@ - 'Locale override', - 'description' => 'Confirm that locale overrides work', - 'group' => 'Configuration', - ); - } - - function testLocaleConfigOverride() { - $name = 'config_test.system'; - // Verify the default configuration values exist. - $config = config($name); - $this->assertIdentical($config->get('foo'), 'en bar'); - } -} diff --git a/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml b/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml new file mode 100644 index 0000000..301e5a5 --- /dev/null +++ b/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml @@ -0,0 +1 @@ +foo: de bar diff --git a/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml b/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml new file mode 100644 index 0000000..c36dbf7 --- /dev/null +++ b/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml @@ -0,0 +1 @@ +foo: fr bar diff --git a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php index 4bd8dc0..7c2ca6e 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php @@ -22,6 +22,7 @@ class LocaleBundle extends Bundle { public function build(ContainerBuilder $container) { $container->register('locale_config_subscriber', 'Drupal\locale\LocaleConfigSubscriber') ->addArgument(new Reference('language_manager')) + ->addArgument(new Reference('config.context')) ->addTag('event_subscriber'); } diff --git a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php index 54d91e7..b7b281b 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php @@ -1,15 +1,21 @@ languageManager = $language_manager; + $this->defaultConfigContext = $config_context; + } + + /** + * Initialize configuration context with language. + * + * @param \Drupal\Core\Config\ConfigEvent $event + * The Event to process. + */ + public function configContext(ConfigEvent $event) { + $context = $event->getContext(); + + // Add user's language for user context. + if ($account = $context->get('user.account')) { + $context->set('locale.language', language_load(user_preferred_langcode($account))); + } + elseif ($language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)) { + $context->set('locale.language', $language); + } } /** * Override configuration values with localized data. * - * @param Drupal\Core\Config\ConfigEvent $event + * @param \Drupal\Core\Config\ConfigEvent $event * The Event to process. */ public function configLoad(ConfigEvent $event) { - $config = $event->getConfig(); - $language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE); - $locale_name = $this->getLocaleConfigName($config->getName(), $language); - if ($override = $config->getStorage()->read($locale_name)) { - $config->setOverride($override); + $context = $event->getContext(); + if ($language = $context->get('locale.language')) { + $config = $event->getConfig(); + $locale_name = $this->getLocaleConfigName($config->getName(), $language); + // Check to see if the config storage has an appropriately named file + // containing override data. + if ($override = $event->getConfig()->getStorage()->read($locale_name)) { + $config->setOverride($override); + } + } + } + + public function onKernelRequestSetDefaultConfigContextLocale(GetResponseEvent $event) { + if ($language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)) { + $this->defaultConfigContext->set('locale.language', $language); } } @@ -57,8 +95,16 @@ public function configLoad(ConfigEvent $event) { * * It will be the same name with a prefix depending on language code: * locale.config.LANGCODE.NAME + * + * @param string $name + * The name of the config object. + * @param \Drupal\Core\Language\Language $language + * The language object. + * + * @return string + * The localised config name. */ - public function getLocaleConfigName($name, $language) { + public function getLocaleConfigName($name, Language $language) { return 'locale.config.' . $language->langcode . '.' . $name; } @@ -66,7 +112,9 @@ public function getLocaleConfigName($name, $language) { * Implements EventSubscriberInterface::getSubscribedEvents(). */ static function getSubscribedEvents() { + $events['config.context'][] = array('configContext', 20); $events['config.load'][] = array('configLoad', 20); + $events[KernelEvents::REQUEST][] = array('onKernelRequestSetDefaultConfigContextLocale', 20); return $events; } } diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 6583d57..aa76a85 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -1403,7 +1403,7 @@ function system_modules_uninstall_submit($form, &$form_state) { * @see system_settings_form() */ function system_site_information_settings($form, &$form_state) { - $site_config = config('system.site'); + $site_config = system_config('system.site'); $site_mail = $site_config->get('mail'); if (empty($site_mail)) { $site_mail = ini_get('sendmail_from'); @@ -1509,7 +1509,7 @@ function system_site_information_settings_validate($form, &$form_state) { * Form submission handler for system_site_information_settings(). */ function system_site_information_settings_submit($form, &$form_state) { - config('system.site') + system_config('system.site') ->set('name', $form_state['values']['site_name']) ->set('mail', $form_state['values']['site_mail']) ->set('slogan', $form_state['values']['site_slogan']) @@ -1549,7 +1549,7 @@ function system_cron_settings($form, &$form_state) { $form['cron']['cron_safe_threshold'] = array( '#type' => 'select', '#title' => t('Run cron every'), - '#default_value' => config('system.cron')->get('threshold.autorun'), + '#default_value' => system_config('system.cron')->get('threshold.autorun'), '#options' => array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval'), ); @@ -1562,7 +1562,7 @@ function system_cron_settings($form, &$form_state) { * @ingroup forms */ function system_cron_settings_submit($form, &$form_state) { - config('system.cron') + system_config('system.cron') ->set('threshold.autorun', $form_state['values']['cron_safe_threshold']) ->save(); } @@ -1594,7 +1594,7 @@ function system_logging_settings($form, &$form_state) { $form['error_level'] = array( '#type' => 'radios', '#title' => t('Error messages to display'), - '#default_value' => config('system.logging')->get('error_level'), + '#default_value' => system_config('system.logging')->get('error_level'), '#options' => array( ERROR_REPORTING_HIDE => t('None'), ERROR_REPORTING_DISPLAY_SOME => t('Errors and warnings'), @@ -1613,7 +1613,7 @@ function system_logging_settings($form, &$form_state) { * @ingroup forms */ function system_logging_settings_submit($form, &$form_state) { - config('system.logging') + system_config('system.logging') ->set('error_level', $form_state['values']['error_level']) ->save(); } @@ -1626,7 +1626,7 @@ function system_logging_settings_submit($form, &$form_state) { */ function system_performance_settings($form, &$form_state) { drupal_add_library('system', 'drupal.system'); - $config = config('system.performance'); + $config = system_config('system.performance'); $form['clear_cache'] = array( '#type' => 'details', @@ -1715,7 +1715,7 @@ function system_performance_settings($form, &$form_state) { * @ingroup forms */ function system_performance_settings_submit($form, &$form_state) { - $config = config('system.performance'); + $config = system_config('system.performance'); $config->set('cache.page.enabled', $form_state['values']['cache']); $config->set('cache.page.max_age', $form_state['values']['page_cache_maximum_age']); $config->set('response.gzip', $form_state['values']['page_compression']); @@ -1860,7 +1860,7 @@ function system_image_toolkit_settings() { * @ingroup forms */ function system_rss_feeds_settings($form, &$form_state) { - $rss_config = config('system.rss'); + $rss_config = system_config('system.rss'); $form['feed_description'] = array( '#type' => 'textarea', '#title' => t('Feed description'), @@ -1895,7 +1895,7 @@ function system_rss_feeds_settings($form, &$form_state) { * @ingroup forms */ function system_rss_feeds_settings_submit($form, &$form_state) { - config('system.rss') + system_config('system.rss') ->set('channel.description', $form_state['values']['feed_description']) ->set('items.limit', $form_state['values']['feed_default_items']) ->set('items.view_mode', $form_state['values']['feed_item_length']) @@ -2015,7 +2015,7 @@ function system_regional_settings_submit($form, &$form_state) { * @see system_site_maintenance_mode_submit() */ function system_site_maintenance_mode($form, &$form_state) { - $config = config('system.maintenance'); + $config = system_config('system.maintenance'); $form['maintenance_mode'] = array( '#type' => 'checkbox', '#title' => t('Put site into maintenance mode'), @@ -2037,7 +2037,7 @@ function system_site_maintenance_mode($form, &$form_state) { * @ingroup forms */ function system_site_maintenance_mode_submit($form, &$form_state) { - config('system.maintenance') + system_config('system.maintenance') ->set('enabled', $form_state['values']['maintenance_mode']) ->set('message', $form_state['values']['maintenance_mode_message']) ->save(); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 223af2e..c5dfafe 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -3439,6 +3439,35 @@ function system_admin_compact_page($mode = 'off') { } /** + * Retrieves a configuration object for administration. + * + * This configuration object will be created using a special configuration + * context (config.admin = TRUE) and configuration plug-ins should mostly keep + * hands off it and don't override it. + * + * This is intended for administration forms or any other case when we need to + * get / set the configuration data without any overrides, as oppossed to + * regular configuration used for runtime operations that may be altered by + * plug-ins depending on other page request parameters: + * + * To get regular configuration objects for the page request use config() + * instead of this function. + * + * @param $name + * The name of the configuration object to retrieve. + * + * @return Drupal\Core\Config\Config + * A configuration object. + * + * @see config() + */ +function system_config($name) { + $config_factory = drupal_container()->get('config.factory'); + $config_factory->enterContext(drupal_container()->get('config.context.free')); + return config($name); +} + +/** * Generate a list of tasks offered by a specified module. * * @param $module diff --git a/core/modules/user/lib/Drupal/user/UserBundle.php b/core/modules/user/lib/Drupal/user/UserBundle.php index cf2f7de..92c8d71 100644 --- a/core/modules/user/lib/Drupal/user/UserBundle.php +++ b/core/modules/user/lib/Drupal/user/UserBundle.php @@ -30,5 +30,9 @@ public function build(ContainerBuilder $container) { $container->register('user.autocomplete', 'Drupal\user\UserAutocomplete') ->addArgument(new Reference('database')) ->addArgument(new Reference('config.factory')); + $container->register('config.context.user', 'Drupal\Core\Config\Context\ContextInterface') + ->setFactoryService(new Reference('config.context.factory')) + ->setFactoryMethod('get') + ->addArgument('Drupal\user\UserConfigContext'); } } diff --git a/core/modules/user/lib/Drupal/user/UserConfigContext.php b/core/modules/user/lib/Drupal/user/UserConfigContext.php new file mode 100644 index 0000000..ed0b957 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserConfigContext.php @@ -0,0 +1,46 @@ +uuid = $this->get(self::USER_KEY)->uuid(); + } + + /* + * Helper function to create config context for user accounts. + * + * @param \Drupal\user\Plugin\Core\Entity\User $account + * The account to add to the config context. + * + * @return \Drupal\user\UserConfigContext + * The user config context object. + */ + public function setAccount(User $account) { + $this->init(self::USER_KEY, $account); + return $this; + } + +} diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc index 198bd9b..6c83d7d 100644 --- a/core/modules/user/user.admin.inc +++ b/core/modules/user/user.admin.inc @@ -292,8 +292,8 @@ function user_admin_account_validate($form, &$form_state) { * @see user_admin_settings_submit() */ function user_admin_settings($form, &$form_state) { - $config = config('user.settings'); - $mail_config = config('user.mail'); + $config = system_config('user.settings'); + $mail_config = system_config('user.mail'); // Settings for anonymous users. $form['anonymous_settings'] = array( diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 19dc974..a10c9b0 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -8,6 +8,7 @@ use Drupal\user\Plugin\Core\Entity\User; use Drupal\user\UserRole; use Drupal\Core\Template\Attribute; +use Drupal\user\UserConfigContext; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -1755,35 +1756,27 @@ function user_view_multiple($accounts, $view_mode = 'full', $langcode = NULL) { function user_mail($key, &$message, $params) { $langcode = $message['langcode']; $variables = array('user' => $params['account']); - $message['subject'] .= _user_mail_text($key . '.subject', $langcode, $variables); - $message['body'][] = _user_mail_text($key . '.body', $langcode, $variables); -} -/** - * Returns a mail string for a variable name. - * - * @param string $key - * The config key that provides the mail text. - * @param string $langcode - * (optional) A language code to use to generate the e-mail text. - * @param array $variables - * (optional) An array of token keys and values. - * - * @return - * A string value containing the text for the user.mail config key. - */ -function _user_mail_text($key, $langcode = NULL, $variables = array()) { - // We do not sanitize the token replacement, since the output of this - // replacement is intended for an e-mail message, not a web browser. - return token_replace(config('user.mail')->get($key), $variables, array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE)); + // Get configuration objects customized for this user, that may be localized + // for the user's language if the locale module is enabled. + $config_factory = drupal_container()->get('config.factory'); + $user_config_context = drupal_container()->get('config.context.user'); + $config_factory->enterContext($user_config_context->setAccount($params['account'])); + $mail_config = config('user.mail'); + + // We do not sanitize the token replacement, since the output of this + // replacement is intended for an e-mail message, not a web browser. + $token_options = array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE); + $message['subject'] .= token_replace($mail_config->get($key . '.subject'), $variables, $token_options); + $message['body'][] = token_replace($mail_config->get($key . '.body'), $variables, $token_options); + $config_factory->leaveContext(); } /** * Token callback to add unsafe tokens for user mails. * - * This function is used by the token_replace() call at the end of - * _user_mail_text() to set up some additional tokens that can be - * used in email messages generated by user_mail(). + * This function is used by the token_replace() to set up some additional + * tokens that can be used in email messages generated by user_mail(). * * @param $replacements * An associative array variable containing mappings from token names to