Commit ad293099 authored by catch's avatar catch

Issue #2219499 by Berdir, alexpott, Gábor Hojtsy, vijaycs85, swentel:...

Issue #2219499 by Berdir, alexpott, Gábor Hojtsy, vijaycs85, swentel: Generalize language config override responsibility.
parent ee80c2b5
......@@ -8,12 +8,6 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Type\FloatInterface;
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\Language\Language;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
......@@ -33,30 +27,16 @@ class Config extends StorableConfigBase {
*/
protected $eventDispatcher;
/**
* The language object used to override configuration data.
*
* @var \Drupal\Core\Language\Language
*/
protected $language;
/**
* The current runtime data.
*
* The configuration data from storage merged with language, module and
* settings overrides.
* The configuration data from storage merged with module and settings
* overrides.
*
* @var array
*/
protected $overriddenData;
/**
* The current language overrides.
*
* @var array
*/
protected $languageOverrides;
/**
* The current module overrides.
*
......@@ -83,15 +63,12 @@ class Config extends StorableConfigBase {
* An event dispatcher instance to use for configuration events.
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
* The typed configuration manager service.
* @param \Drupal\Core\Language\Language $language
* The language object used to override configuration data.
*/
public function __construct($name, StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManager $typed_config, Language $language = NULL) {
public function __construct($name, StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManager $typed_config) {
$this->name = $name;
$this->storage = $storage;
$this->eventDispatcher = $event_dispatcher;
$this->typedConfigManager = $typed_config;
$this->language = $language;
}
/**
......@@ -100,7 +77,6 @@ public function __construct($name, StorageInterface $storage, EventDispatcherInt
public function initWithData(array $data) {
parent::initWithData($data);
$this->settingsOverrides = array();
$this->languageOverrides = array();
$this->moduleOverrides = array();
$this->setData($data);
return $this;
......@@ -169,37 +145,19 @@ public function setModuleOverride(array $data) {
return $this;
}
/**
* Sets language overrides for this configuration object.
*
* @param array $data
* The overridden values of the configuration data.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function setLanguageOverride(array $data) {
$this->languageOverrides = $data;
$this->resetOverriddenData();
return $this;
}
/**
* Sets the current data for this configuration object.
*
* Configuration overrides operate at three distinct layers: language, modules
* and settings.php, with the last of these taking precedence. Overrides in
* settings.php take precedence over values provided by modules. Overrides
* provided by modules take precedence over language.
* Configuration overrides operate at two distinct layers: modules and
* settings.php. Overrides in settings.php take precedence over values
* provided by modules. Precedence or different module overrides is
* determined by the priority of the config.factory.override tagged services.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
protected function setOverriddenData() {
$this->overriddenData = $this->data;
if (isset($this->languageOverrides) && is_array($this->languageOverrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->languageOverrides), TRUE);
}
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->moduleOverrides), TRUE);
}
......@@ -282,15 +240,6 @@ public function delete() {
return $this;
}
/**
* Returns the language object for this Config object.
*
* @return \Drupal\Core\Language\Language
*/
public function getLanguage() {
return $this->language;
}
/**
* Gets the raw data without overrides.
*
......@@ -322,9 +271,6 @@ public function getOriginal($key = '', $apply_overrides = TRUE) {
$original_data = $this->originalData;
if ($apply_overrides) {
// Apply overrides.
if (isset($this->languageOverrides) && is_array($this->languageOverrides)) {
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->languageOverrides), TRUE);
}
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->moduleOverrides), TRUE);
}
......
......@@ -7,9 +7,6 @@
namespace Drupal\Core\Config;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Drupal\Component\Utility\NestedArray;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -50,13 +47,6 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
*/
protected $useOverrides = TRUE;
/**
* The language object used to override configuration data.
*
* @var \Drupal\Core\Language\Language
*/
protected $language;
/**
* Cached configuration objects.
*
......@@ -126,23 +116,13 @@ public function get($name) {
// If the configuration object does not exist in the configuration
// storage or static cache create a new object and add it to the static
// cache.
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
if ($this->canOverride($name)) {
// Get and apply any language overrides.
if ($this->language) {
$language_overrides = $this->storage->read($this->getLanguageConfigName($this->language->id, $name));
}
else {
$language_overrides = FALSE;
}
if (is_array($language_overrides)) {
$this->cache[$cache_key]->setLanguageOverride($language_overrides);
}
// Get and apply any module overrides.
$module_overrides = $this->loadModuleOverrides(array($name));
if (isset($module_overrides[$name])) {
$this->cache[$cache_key]->setModuleOverride($module_overrides[$name]);
if ($this->useOverrides) {
// Get and apply any overrides.
$overrides = $this->loadOverrides(array($name));
if (isset($overrides[$name])) {
$this->cache[$cache_key]->setModuleOverride($overrides[$name]);
}
// Apply any settings.php overrides.
if (isset($GLOBALS['config'][$name])) {
......@@ -174,38 +154,19 @@ public function loadMultiple(array $names) {
if (!empty($names)) {
// Initialise override information.
$module_overrides = array();
$language_names = array();
if ($this->useOverrides) {
// In order to make just one call to storage, add in language names.
// Keep track of them separately, so we can get language override data
// returned from storage and set it on new Config objects.
$language_names = $this->getLanguageConfigNames($names);
}
$storage_data = $this->storage->readMultiple(array_merge($names, array_values($language_names)));
$storage_data = $this->storage->readMultiple($names);
if ($this->useOverrides && !empty($storage_data)) {
// Only fire module override event if we have configuration to override.
$module_overrides = $this->loadModuleOverrides($names);
// Only get module overrides if we have configuration to override.
$module_overrides = $this->loadOverrides($names);
}
foreach ($storage_data as $name => $data) {
if (in_array($name, $language_names)) {
// Language override configuration is used to override other
// configuration. Therefore, when it has been added to the
// $storage_data it is not statically cached in the config factory or
// overridden in any way.
continue;
}
$cache_key = $this->getCacheKey($name);
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
$this->cache[$cache_key]->initWithData($data);
if ($this->canOverride($name)) {
if (isset($language_names[$name]) && isset($storage_data[$language_names[$name]])) {
$this->cache[$cache_key]->setLanguageOverride($storage_data[$language_names[$name]]);
}
if ($this->useOverrides) {
if (isset($module_overrides[$name])) {
$this->cache[$cache_key]->setModuleOverride($module_overrides[$name]);
}
......@@ -229,7 +190,7 @@ public function loadMultiple(array $names) {
* @return array
* An array of overrides keyed by the configuration object name.
*/
protected function loadModuleOverrides(array $names) {
protected function loadOverrides(array $names) {
$overrides = array();
foreach ($this->configFactoryOverrides as $override) {
// Existing overrides take precedence since these will have been added
......@@ -280,11 +241,14 @@ public function rename($old_name, $new_name) {
* {@inheritdoc}
*/
public function getCacheKey($name) {
$can_override = $this->canOverride($name);
$cache_key = $name . ':' . ($can_override ? 'overrides' : 'raw');
if ($can_override && isset($this->language)) {
$cache_key = $cache_key . ':' . $this->language->id;
if ($this->useOverrides) {
$cache_key = $name . ':overrides';
foreach($this->configFactoryOverrides as $override) {
$cache_key = $cache_key . ':' . $override->getCacheSuffix();
}
}
else {
$cache_key = $name . ':raw';
}
return $cache_key;
}
......@@ -307,54 +271,6 @@ public function clearStaticCache() {
return $this;
}
/**
* {@inheritdoc}
*/
public function setLanguage(Language $language = NULL) {
$this->language = $language;
return $this;
}
/**
* {@inheritdoc}
*/
public function setLanguageFromDefault(LanguageDefault $language_default) {
$this->language = $language_default->get();
return $this;
}
/**
* {@inheritdoc}
*/
public function getLanguage() {
return $this->language;
}
/**
* {@inheritdoc}
*/
public 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;
}
/**
* {@inheritdoc}
*/
public function getLanguageConfigName($langcode, $name) {
if (strpos($name, static::LANGUAGE_CONFIG_PREFIX) === 0) {
return FALSE;
}
return static::LANGUAGE_CONFIG_PREFIX . '.' . $langcode . '.' . $name;
}
/**
* {@inheritdoc}
*/
......@@ -362,21 +278,6 @@ public function listAll($prefix = '') {
return $this->storage->listAll($prefix);
}
/**
* Determines if a particular configuration object can be overridden.
*
* Language override configuration should not be overridden.
*
* @param string $name
* The name of the configuration object.
*
* @return bool
* TRUE if the configuration object can be overridden.
*/
protected function canOverride($name) {
return $this->useOverrides && !(strpos($name, static::LANGUAGE_CONFIG_PREFIX) === 0);
}
/**
* Removes stale static cache entries when configuration is saved.
*
......
......@@ -15,11 +15,6 @@
*/
interface ConfigFactoryInterface {
/**
* Prefix for all language configuration files.
*/
const LANGUAGE_CONFIG_PREFIX = 'language.config';
/**
* Sets the override state.
*
......@@ -116,67 +111,6 @@ public function getCacheKeys($name);
*/
public function clearStaticCache();
/**
* Sets the language to be used in configuration overrides.
*
* @param \Drupal\Core\Language\Language|null $language
* The language object to be set on the config factory. Used to override
* configuration by language.
*
* @return $this
*/
public function setLanguage(Language $language = NULL);
/**
* Sets the language for configuration overrides using the default language.
*
* @param \Drupal\Core\Language\LanguageDefault $language_default
* The default language service. This sets the initial language on the
* config factory to the site's default. The language can be used to
* override configuration data if language overrides are available.
*
* @return $this
*/
public function setLanguageFromDefault(LanguageDefault $language_default);
/**
* Gets the language Used to override configuration.
*
* @return \Drupal\Core\Language\Language
*/
public function getLanguage();
/**
* Gets configuration names for this language.
*
* It will be the same name with a prefix depending on language code:
* language.config.LANGCODE.NAME
*
* @param array $names
* A list of configuration object names.
*
* @return array
* The localized config names, keyed by configuration object name.
*/
public function getLanguageConfigNames(array $names);
/**
* Gets configuration name for the provided language.
*
* The name will be the same name with a prefix depending on language code:
* language.config.LANGCODE.NAME
*
* @param string $langcode
* The language code.
* @param string $name
* The name of the configuration object.
*
* @return bool|string
* The configuration name for configuration object providing overrides.
* Returns false if the name already starts with the language config prefix.
*/
public function getLanguageConfigName($langcode, $name);
/**
* Gets configuration object names starting with a given prefix.
*
......
......@@ -24,4 +24,12 @@ interface ConfigFactoryOverrideInterface {
*/
public function loadOverrides($names);
/**
* The string to append to the configuration static cache name.
*
* @return string
* A string to append to the configuration static cache name.
*/
public function getCacheSuffix();
}
......@@ -215,11 +215,10 @@ protected function t($string, array $args = array(), array $options = array()) {
*/
protected function dateFormat($format, $langcode) {
if (!isset($this->dateFormats[$format][$langcode])) {
// Enter a language specific context so the right date format is loaded.
$original_language = $this->configFactory->getLanguage();
$this->configFactory->setLanguage(new Language(array('id' => $langcode)));
$original_language = $this->languageManager->getConfigOverrideLanguage();
$this->languageManager->setConfigOverrideLanguage(new Language(array('id' => $langcode)));
$this->dateFormats[$format][$langcode] = $this->dateFormatStorage->load($format);
$this->configFactory->setLanguage($original_language);
$this->languageManager->setConfigOverrideLanguage($original_language);
}
return $this->dateFormats[$format][$langcode];
}
......
......@@ -329,4 +329,24 @@ public static function getStandardLanguageList() {
);
}
/**
* {@inheritdoc}
*
* This function is a noop since the configuration can not be overridden by
* language unless the Language module is enabled. That replaces the default
* language manger with a configurable language manager.
*
* @see \Drupal\language\ConfigurableLanguageManager::setConfigOverrideLanguage()
*/
public function setConfigOverrideLanguage(Language $language = NULL) {
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfigOverrideLanguage() {
return $this->getCurrentLanguage();
}
}
......@@ -168,4 +168,22 @@ public function getFallbackCandidates($langcode = NULL, array $context = array()
*/
function getLanguageSwitchLinks($type, $path);
/**
* Sets the configuration override language.
*
* @param \Drupal\Core\Language\Language $language
* The language to override configuration with.
*
* @return $this
*/
public function setConfigOverrideLanguage(Language $language = NULL);
/**
* Gets the current configuration override language.
*
* @return \Drupal\Core\Language\Language $language
* The current configuration override language.
*/
public function getConfigOverrideLanguage();
}
......@@ -339,7 +339,7 @@ function testLanguages() {
* Tests that CKEditor plugins participate in JS translation.
*/
function testJSTranslation() {
$this->enableModules(array('locale'));
$this->enableModules(array('language', 'locale'));
$this->installSchema('locale', 'locales_source');
$this->installSchema('locale', 'locales_location');
$editor = entity_load('editor', 'filtered_html');
......
......@@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\config\Tests\ConfigLanguageOverride.
* Contains \Drupal\config\Tests\ConfigLanguageOverrideTest.
*/
namespace Drupal\config\Tests;
......@@ -13,14 +13,14 @@
/**
* Tests language config override.
*/
class ConfigLanguageOverride extends DrupalUnitTestBase {
class ConfigLanguageOverrideTest extends DrupalUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'user', 'language', 'system', 'field');
public static $modules = array('user', 'language', 'config_test', 'system', 'field');
public static function getInfo() {
return array(
......@@ -39,16 +39,10 @@ public function setUp() {
* Tests locale override based on language.
*/
function testConfigLanguageOverride() {
// The default configuration factory does not have the default language
// injected unless the Language module is enabled.
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'bar');
// \Drupal\language\LanguageServiceProvider::alter() calls
// \Drupal\Core\Config\ConfigFactory::setLanguageFromDefault() to set the
// language when the Language module is enabled. This test ensures that
// The language module implements a config factory override object that
// overrides configuration when the Language module is enabled. This test ensures that
// English overrides work.
\Drupal::configFactory()->setLanguageFromDefault(\Drupal::service('language.default'));
\Drupal::languageManager()->setConfigOverrideLanguage(language_load('en'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'en bar');
......@@ -65,20 +59,21 @@ function testConfigLanguageOverride() {
'id' => 'de',
)));
\Drupal::configFactory()->setLanguage(language_load('fr'));
\Drupal::languageManager()->setConfigOverrideLanguage(language_load('fr'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'fr bar');
\Drupal::configFactory()->setLanguage(language_load('de'));
\Drupal::languageManager()->setConfigOverrideLanguage(language_load('de'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'de bar');
// Test overrides of completely new configuration objects. In normal runtime
// this should only happen for configuration entities as we should not be
// creating simple configuration objects on the fly.
$language_config_name = \Drupal::configFactory()->getLanguageConfigName('de', 'config_test.new');
\Drupal::config($language_config_name)->set('language', 'override')->save();
\Drupal::config('config_test.new');
\Drupal::languageManager()
->getLanguageConfigOverride('de', 'config_test.new')
->set('language', 'override')
->save();
$config = \Drupal::config('config_test.new');
$this->assertTrue($config->isNew(), 'The configuration object config_test.new is new');
$this->assertIdentical($config->get('language'), 'override');
......@@ -87,11 +82,6 @@ function testConfigLanguageOverride() {
$config = \Drupal::config('config_test.new');
$this->assertIdentical($config->get('language'), NULL);
\Drupal::configFactory()->setOverrideState($old_state);
// Ensure that language configuration overrides can not be overridden.
global $conf;
$conf[$language_config_name]['language'] = 'conf cannot override';
$this->assertIdentical(\Drupal::config($language_config_name)->get('language'), 'override');
}
}
......@@ -45,10 +45,10 @@ function testSiteNameTranslation() {
'direction' => '0',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
// Save an override for the XX language.
$config_name = \Drupal::configFactory()->getLanguageConfigName('xx', 'system.site');
\Drupal::config($config_name)->set('name', 'XX site name')->save();
\Drupal::languageManager()
->getLanguageConfigOverride($langcode, 'system.site')
->set('name', 'XX site name')
->save();
$this->drupalLogout();
......
......@@ -15,7 +15,7 @@
*/
class ConfigOverridesPriorityTest extends DrupalUnitTestBase {
public static $modules = array('system', 'config', 'config_override');
public static $modules = array('system', 'config', 'config_override', 'language');
public static function getInfo() {
return array(
......@@ -59,10 +59,9 @@ public function testOverridePriorities() {
'name' => 'French',
'id' => 'fr',
));
$config_factory->setLanguage($language);
$language_config_name = $config_factory->getLanguageConfigName($language->id, 'system.site');
$config_factory
->get($language_config_name)
\Drupal::languageManager()->setConfigOverrideLanguage($language);
\Drupal::languageManager()
->getLanguageConfigOverride($language->id, 'system.site')
->set('name', $language_overridden_name)
->set('mail', $language_overridden_mail)
->save();
......
......@@ -21,7 +21,7 @@ class ConfigSchemaTest extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('system', 'locale', 'field', 'image', 'config_test');
public static $modules = array('system', 'language', 'locale', 'field', 'image', 'config_test');
public static function getInfo() {
return array(
......
......@@ -33,5 +33,12 @@ public function loadOverrides($names) {
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigOverrider';
}
}
......@@ -34,5 +34,12 @@ public function loadOverrides($names) {
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigOverriderLowPriority';
}
}
......@@ -9,10 +9,9 @@
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
......@@ -23,11 +22,11 @@
class ConfigTranslationDeleteForm extends ConfirmFormBase {
/**
* The configuration storage.
* The language manager.
*
* @var \Drupal\Core\Config\StorageInterface $config_storage
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $configStorage;
protected $languageManager;
/**
* The configuration mapper manager.
......@@ -60,20 +59,17 @@ class ConfigTranslationDeleteForm extends ConfirmFormBase {
/**
* Constructs a ConfigTranslationDeleteForm.
*
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The configuration storage.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language override configuration storage.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The configuration mapper manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler