Commit a3064d20 authored by webchick's avatar webchick

Issue #1929136 by Gábor Hojtsy, alexpott, YesCT: Fixed override-free context,...

Issue #1929136 by Gábor Hojtsy, alexpott, YesCT: Fixed override-free context, move global config overrides back to an event listener.
parent 57176f50
......@@ -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;
}
......
......@@ -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;
}
......
......@@ -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);
......@@ -84,20 +83,6 @@ 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().
*/
......@@ -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;
}
}
......@@ -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);
}
<?php
/**
* @file
* Contains \Drupal\Core\Config\Context\FreeConfigContext.
*/
namespace Drupal\Core\Config\Context;
/**
* Defines the override-free configuration context object.
*/
class FreeConfigContext extends ConfigContext {
/**
* Implements \Drupal\Core\Config\Context\ContextInterface::getOverrides().
*/
public function getOverrides($config_name) {
// Do nothing as this is override free.
return FALSE;
}
/**
* Implements \Drupal\Core\Config\Context\ContextInterface::setOverride().
*/
public function setOverrides($config_name, $data) {
// Do nothing as this is override free.
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Config\Context\GlobalConfigContext.
*/
namespace Drupal\Core\Config\Context;
/**
* Defines the global configuration context object.
*
* The global configuration context allows config object data to be overridden
* with values from the $conf global.
*/
class GlobalConfigContext extends ConfigContext {
/**
* Sets global override data.
*
* @return \Drupal\Core\Config\Context\ConfigContext
* The config context object.
*/
public function setGlobalOverride() {
global $conf;
$this->init(self::OVERRIDE, $conf);
return $this;
}
}
......@@ -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'))
......
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ConfigOverrideSubscriber.
* Contains \Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigEvent;
use Drupal\Core\Config\Context\ConfigContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Override configuration values with predefined values in context.
* Defines a configuration global override for contexts.
*/
class ConfigOverrideSubscriber implements EventSubscriberInterface {
class ConfigGlobalOverrideSubscriber implements EventSubscriberInterface {
/**
* Overrides configuration values.
* Overrides configuration values with values in global $conf variable.
*
* @param \Drupal\Core\Config\ConfigEvent $event
* The Event to process.
*/
public function configInit(ConfigEvent $event) {
if ($override = $event->getContext()->get(ConfigContext::OVERRIDE)) {
$config = $event->getConfig();
if (isset($override[$config->getName()])) {
$config->setOverride($override[$config->getName()]);
}
global $conf;
$config = $event->getConfig();
if (isset($conf[$config->getName()])) {
$config->setOverride($conf[$config->getName()]);
}
}
/**
* Implements EventSubscriberInterface::getSubscribedEvents().
*/
public static function getSubscribedEvents() {
static function getSubscribedEvents() {
$events['config.init'][] = array('configInit', 30);
return $events;
}
}
......@@ -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() {
......
......@@ -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']);
......
......@@ -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;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment