diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 0337d28..c0d2617 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -50,14 +50,7 @@ class Config { protected $data; /** - * The overridden data of the configuration object. - * - * @var array - */ - protected $overrides = array(); - - /** - * The current runtime data ($data + $overrides). + * The current runtime data ($data + $overrides from Config Context). * * @var array */ @@ -275,7 +268,7 @@ protected function replaceData(array $data) { * The configuration object. */ public function setOverride(array $data) { - $this->overrides = NestedArray::mergeDeepArray(array($this->overrides, $data), TRUE); + $this->context->setOverrides($this->getName(), $data); $this->resetOverriddenData(); return $this; } @@ -290,8 +283,9 @@ public function setOverride(array $data) { */ protected function setOverriddenData() { $this->overriddenData = $this->data; - if (!empty($this->overrides)) { - $this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->overrides), TRUE); + $overrides = $this->context->getOverrides($this->getName()); + if (is_array($overrides)) { + $this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $overrides), TRUE); } return $this; } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 4edbead..0cd0382 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -140,7 +140,8 @@ public function rename($old_name, $new_name) { * The config factory object. */ public function enterContext(ContextInterface $context) { - $this->contextStack[] = $context; + // Initialize the context as it is being entered. + $this->contextStack[] = $context->init(); return $this; } diff --git a/core/lib/Drupal/Core/Config/Context/ConfigContext.php b/core/lib/Drupal/Core/Config/Context/ConfigContext.php index 9be0f71..a0ee595 100644 --- a/core/lib/Drupal/Core/Config/Context/ConfigContext.php +++ b/core/lib/Drupal/Core/Config/Context/ConfigContext.php @@ -9,29 +9,31 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigEvent; +use Drupal\Component\Utility\NestedArray; use Drupal\Component\Uuid\Uuid; use Symfony\Component\EventDispatcher\EventDispatcher; /** * Defines the base configuration context object. * - * A configuration context object provides a data array that can be used: - * - as a parameter to get customized configuration objects. - * - as a store of config data used to override values. + * A configuration context object provides a data array that can be used as + * parameters to get customized configuration objects. */ class ConfigContext implements ContextInterface { /** - * Predefined key, values to override specific configuration objects. + * The actual storage of key-value pairs. + * + * @var array */ - const OVERRIDE = 'config.override'; + protected $data = array(); /** - * The actual storage of key-value pairs. + * Any config overrides of key-value pairs. * * @var array */ - protected $data = array(); + protected $overrides = array(); /** * An event dispatcher instance to use for configuration events. @@ -60,10 +62,7 @@ public function __construct(EventDispatcher $event_dispatcher) { /** * Implements Drupal\Core\Config\Context\ContextInterface::init(). */ - public function init($context_key, $data) { - if ($data) { - $this->set($context_key, $data); - } + public function init() { $this->setUuid(); // Notify event listeners that a configuration context has been created. $this->notify('context', NULL); @@ -85,20 +84,6 @@ public function set($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() { @@ -120,4 +105,26 @@ public function notify($config_event_name, Config $config = NULL) { $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this, $config)); } + /** + * Implements \Drupal\Core\Config\Context\ContextInterface::setOverride(). + */ + public function setOverrides($config_name, $data) { + if (!isset($this->overrides[$config_name])) { + $this->overrides[$config_name] = $data; + } + else { + $this->overrides[$config_name] = NestedArray::mergeDeepArray(array($this->overrides[$config_name], $data), TRUE); + } + } + + /** + * Implements \Drupal\Core\Config\Context\ContextInterface::getOverrides(). + */ + public function getOverrides($config_name) { + if (isset($this->overrides[$config_name])) { + return $this->overrides[$config_name]; + } + return FALSE; + } + } diff --git a/core/lib/Drupal/Core/Config/Context/ContextInterface.php b/core/lib/Drupal/Core/Config/Context/ContextInterface.php index b3107a7..fae5d2d 100644 --- a/core/lib/Drupal/Core/Config/Context/ContextInterface.php +++ b/core/lib/Drupal/Core/Config/Context/ContextInterface.php @@ -22,21 +22,13 @@ */ interface ContextInterface { - /* + /** * Initialises a config context for use. * - * Creates a unique context identifier, adds data and notifies system about - * the new context. - * - * @param string $context_key - * The key that is used to set context data. - * @param mixed $data - * The context config data. - * * @return \Drupal\Core\Config\Context\ConfigContext * The config context object. */ - public function init($context_key, $data); + public function init(); /** * Returns the stored value for a given key. @@ -85,4 +77,25 @@ public function getUuid(); */ public function notify($config_event_name, Config $config = NULL); + /** + * Sets the override data for a configuration object. + * + * @param string $config_name + * Configuration name. + * @param array data + * The override data. + */ + public function setOverrides($config_name, $data); + + /** + * Gets the override data for a configuration object. + * + * @param string $config_name + * Configuration name. + * + * @return mixed + * The override data or FALSE if there is none. + */ + public function getOverrides($config_name); + } diff --git a/core/lib/Drupal/Core/Config/Context/FreeConfigContext.php b/core/lib/Drupal/Core/Config/Context/FreeConfigContext.php new file mode 100644 index 0000000..4307f97 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Context/FreeConfigContext.php @@ -0,0 +1,30 @@ +init(self::OVERRIDE, $conf); - return $this; - } -} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 2968e7a..871b859 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -58,15 +58,14 @@ public function build(ContainerBuilder $container) { $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'); + ->addTag('persist'); // 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'); + ->setFactoryMethod('get') + ->addArgument('Drupal\Core\Config\Context\FreeConfigContext'); $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) @@ -280,7 +279,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\ConfigOverrideSubscriber') + $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber') ->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 new file mode 100644 index 0000000..02bf2cb --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php @@ -0,0 +1,42 @@ +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 deleted file mode 100644 index 376da10..0000000 --- a/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php +++ /dev/null @@ -1,41 +0,0 @@ -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 index 78253d3..5788b36 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php @@ -36,7 +36,7 @@ public function setUp() { config_install_default_config('module', 'config_test'); } - /* + /** * Tests basic locale override. */ function testConfigLocaleOverride() { @@ -44,19 +44,22 @@ function testConfigLocaleOverride() { // The default language is en so the config key should be localised. $config = config($name); $this->assertIdentical($config->get('foo'), 'en bar'); + $this->assertIdentical($config->get('404'), 'herp'); - // Ensure that we get the expected value when we use system_config. + // Ensure that we get the expected value when we avoid overrides. config_context_enter('config.context.free'); - $config_admin = config('config_test.system'); + $config_admin = config($name); $this->assertIdentical($config_admin->get('foo'), 'bar'); + $this->assertIdentical($config_admin->get('404'), 'herp'); // Leave the non override context. config_context_leave(); $config = config($name); $this->assertIdentical($config->get('foo'), 'en bar'); + $this->assertIdentical($config->get('404'), 'herp'); } - /* + /** * Tests locale override based on user's preferred language. */ function testConfigLocaleUserOverride() { @@ -83,15 +86,19 @@ function testConfigLocaleUserOverride() { 'status' => 1, 'preferred_langcode' => 'fr', )); - $config_factory = drupal_container()->get('config.factory'); - $user_config_context = config_context_enter("Drupal\\user\\UserConfigContext"); + $user_config_context = config_context_enter('Drupal\user\UserConfigContext'); $user_config_context->setAccount($account); $config = config('config_test.system'); $this->assertIdentical($config->get('foo'), 'fr bar'); + // Ensure the non-overriden value is still the same. + $this->assertIdentical($config->get('404'), 'herp'); - // Ensure that we get the expected value when we leave the user context. - $config_factory->leaveContext(); + // Ensure that we get the expected value when we leave the user context. The + // locale overrides contain an English override too, so although we are not + // in a user based language override context, the English language override + // applies due to the negotiated language for the page. + config_context_leave(); $config = config('config_test.system'); $this->assertIdentical($config->get('foo'), 'en bar'); @@ -103,6 +110,7 @@ function testConfigLocaleUserOverride() { 'preferred_langcode' => 'de', )); + $config_factory = drupal_container()->get('config.factory'); $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. @@ -118,30 +126,92 @@ function testConfigLocaleUserOverride() { 'preferred_langcode' => 'en', )); // Create a new user config context to stack on top of the existign one. - $en_user_config_context = config_context_enter("Drupal\\user\\UserConfigContext"); + $en_user_config_context = config_context_enter('Drupal\user\UserConfigContext'); $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_context_leave(); $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_context_leave(); $config = config('config_test.system'); $this->assertIdentical($config->get('foo'), 'en bar'); // Ensure that we cannot leave the default context. - $config_factory->leaveContext(); + config_context_leave(); $config = config('config_test.system'); $this->assertIdentical($config->get('foo'), 'en bar'); } - /* + /** + * Tests locale override in combination with global overrides. + */ + function testConfigLocaleUserAndGlobalOverride() { + global $conf; + + // Globally override value for the keys in config_test.system. Although we + // override the foo key, there are also language overrides, which trump + // global overrides so the 'foo' key override will never surface. + $conf['config_test.system']['foo'] = 'global bar'; + $conf['config_test.system']['404'] = 'global herp'; + + $this->installSchema('system', 'variable'); + $this->installSchema('language', 'language'); + language_save(new Language(array( + 'name' => 'French', + 'langcode' => 'fr', + ))); + + $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 = config_context_enter('Drupal\user\UserConfigContext'); + $user_config_context->setAccount($account); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'fr bar'); + // Ensure the value overriden from global $conf works. + $this->assertIdentical($config->get('404'), 'global herp'); + + // Ensure that we get the expected value when we leave the user context. The + // locale overrides contain an English override too, so although we are not + // in a user based language override context, the English language override + // applies due to the negotiated language for the page. + config_context_leave(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + // Global override should still apply. + $this->assertIdentical($config->get('404'), 'global herp'); + + // Ensure that we cannot leave the default context. + config_context_leave(); + $config = config('config_test.system'); + $this->assertIdentical($config->get('foo'), 'en bar'); + // Global override should still apply. + $this->assertIdentical($config->get('404'), 'global herp'); + + // Ensure that we get the expected value when we avoid overrides. + config_context_enter('config.context.free'); + $config_admin = config('config_test.system'); + // Language override should not apply anymore. + $this->assertIdentical($config_admin->get('foo'), 'bar'); + // Global override should not apply. + $this->assertIdentical($config_admin->get('404'), 'herp'); + config_context_leave(); + } + + /** * Tests config_context_enter() invalid context name handling. */ function testInvalidContextName() { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php index 2fdf122..6dfcf4e 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php @@ -51,7 +51,6 @@ function testConfOverride() { $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'); @@ -63,31 +62,17 @@ function testConfOverride() { $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. + // Enter an override-free context to ensure the original data remains. + config_context_enter('config.context.free'); $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']); + config_context_leave(); - // 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. - $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']); - - // Reload the configuration object. - $config->init(); + // Get the configuration object in an overriden context (the one set by + // default). + $config = config('config_test.system'); // Verify that it contains the overridden data from $conf. $this->assertIdentical($config->get('foo'), $conf['config_test.system']['foo']); @@ -116,24 +101,15 @@ 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 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(); + // Enter an override-free context to ensure the original data remains saved. + config_context_enter('config.context.free'); + $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']); + config_context_leave(); - // 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(); + // Write file to staging. $staging = $this->container->get('config.storage.staging'); $expected_new_data = array( 'foo' => 'barbar', @@ -152,6 +128,7 @@ function testConfOverride() { $this->assertIdentical($data['404'], $expected_new_data['404']); // Verifiy the overrides are still working. + $config = config('config_test.system'); $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/user/lib/Drupal/user/UserConfigContext.php b/core/modules/user/lib/Drupal/user/UserConfigContext.php index d6e4114..917787d 100644 --- a/core/modules/user/lib/Drupal/user/UserConfigContext.php +++ b/core/modules/user/lib/Drupal/user/UserConfigContext.php @@ -26,14 +26,6 @@ class UserConfigContext extends ConfigContext { */ const USER_KEY = 'user.account'; - /** - * Implements \Drupal\Core\Config\Context\ContextInterface::setUuid(). - */ - public function setUuid() { - // Use the user's uuid to identify the config context. - $this->uuid = $this->get(self::USER_KEY)->uuid(); - } - /* * Helper function to create config context for user accounts. * @@ -44,7 +36,9 @@ public function setUuid() { * The user config context object. */ public function setAccount(User $account) { - $this->init(self::USER_KEY, $account); + $this->set(self::USER_KEY, $account); + // Re-initialize since the user change changes the context fundamentally. + $this->init(); return $this; }