Commit 6f6a2f3d authored by catch's avatar catch

Issue #2108599 by alexpott, Gábor Hojtsy, vijaycs85: Convert language_default to CMI.

parent 15606012
......@@ -228,6 +228,10 @@ services:
arguments: ['@event_dispatcher', '@service_container', '@controller_resolver']
language_manager:
class: Drupal\Core\Language\LanguageManager
arguments: ['@language.default']
language.default:
class: Drupal\Core\Language\LanguageDefault
arguments: ['%language.default_values%']
string_translator.custom_strings:
class: Drupal\Core\StringTranslation\Translator\CustomStrings
arguments: ['@settings']
......
......@@ -299,22 +299,20 @@ function install_begin_request(&$install_state) {
exit;
}
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
// If we have a language selected and it is not yet saved in the system
// (eg. pre-database data screens we are unable to persistently store
// the default language), we should set language_default so the proper
// language is used to display installer pages as early as possible.
// The language list is stored in configuration and cannot be saved either
// until later in the process. Language negotiation bootstrapping needs
// the new default language to be in the list though, so inject it in.
if (!empty($install_state['parameters']['langcode']) && language_default()->id != $install_state['parameters']['langcode']) {
$GLOBALS['conf']['language_default'] = array('id' => $install_state['parameters']['langcode']);
$languages = &drupal_static('language_list');
$languages[$install_state['parameters']['langcode']] = new Language($GLOBALS['conf']['language_default']);
// If we have a language selected and it is not yet saved in the system (eg.
// pre-database data screens we are unable to persistently store the default
// language), we should set language_default so the proper language is used to
// display installer pages as early as possible.
$default_language_values = Language::$defaultValues;
if (!empty($install_state['parameters']['langcode']) && $default_language_values['id'] != $install_state['parameters']['langcode']) {
$default_language_values = array('id' => $install_state['parameters']['langcode']);
}
// Register the 'language_manager' service.
$container->setParameter('language.default_values', $default_language_values);
$container->register('language.default', 'Drupal\Core\Language\LanguageDefault')
->addArgument('%language.default_values%');
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager')
->addArgument(new Reference('language.default'));
require_once __DIR__ . '/../modules/system/system.install';
require_once __DIR__ . '/common.inc';
......@@ -383,13 +381,18 @@ function install_begin_request(&$install_state) {
->addArgument(new Reference('config.storage.schema'))
->addArgument(new Reference('cache.config'));
$container->setParameter('language.default_values', Language::$defaultValues);
$container->register('language.default', 'Drupal\Core\Language\LanguageDefault')
->addArgument('%language.default_values%');
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
->addArgument(new Reference('config.storage'))
->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('config.typed'));
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager')
->addArgument(new Reference('language.default'));
// Register the translation services.
install_register_translation_service($container);
......@@ -474,6 +477,12 @@ function install_begin_request(&$install_state) {
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('cache.cache'))
->addArgument(new Reference('info_parser'));
// Overrides can not work at this point since this would cause the
// ConfigFactory to try to load language override configuration which is not
// supported by \Drupal\Core\Config\InstallStorage since loading a
// non-existing file would throw an exception.
$container->get('config.factory')->disableOverrides();
}
// Set the request in the kernel to the new created Request above
......@@ -1009,7 +1018,7 @@ function install_verify_requirements(&$install_state) {
*/
function install_base_system(&$install_state) {
// Install system.module.
drupal_install_system();
drupal_install_system($install_state);
// Call file_ensure_htaccess() to ensure that all of Drupal's standard
// directories (e.g., the public files directory and config directory) have
......@@ -2686,7 +2695,6 @@ function install_configure_form_submit($form, &$form_state) {
\Drupal::config('system.site')
->set('name', $form_state['values']['site_name'])
->set('mail', $form_state['values']['site_mail'])
->set('langcode', language_default()->id)
->save();
\Drupal::config('system.date')
......
......@@ -604,8 +604,12 @@ function drupal_verify_profile($install_state) {
*
* Separated from the installation of other modules so core system
* functions can be made available while other modules are installed.
*
* @param array $install_state
* An array of information about the current installation state. This is used
* to set the default language.
*/
function drupal_install_system() {
function drupal_install_system($install_state) {
// Create tables.
drupal_install_schema('system');
......@@ -643,6 +647,13 @@ function drupal_install_system() {
\Drupal::service('config.installer')->installDefaultConfig('module', 'system');
// Ensure default language is saved.
if (isset($install_state['parameters']['langcode'])) {
\Drupal::config('system.site')
->set('langcode', $install_state['parameters']['langcode'])
->save();
}
module_invoke('system', 'install');
}
......
......@@ -69,6 +69,13 @@ class Config {
*/
protected $data;
/**
* The original data of the configuration object.
*
* @var array
*/
protected $originalData;
/**
* The current runtime data.
*
......@@ -160,6 +167,7 @@ public function initWithData(array $data) {
$this->moduleOverrides = array();
$this->isNew = FALSE;
$this->replaceData($data);
$this->originalData = $this->data;
return $this;
}
......@@ -467,6 +475,7 @@ public function load() {
$this->isNew = FALSE;
$this->replaceData($data);
}
$this->originalData = $this->data;
$this->isLoaded = TRUE;
return $this;
}
......@@ -497,6 +506,7 @@ public function save() {
$this->storage->write($this->name, $this->data);
$this->isNew = FALSE;
$this->notify('save');
$this->originalData = $this->data;
return $this;
}
......@@ -513,6 +523,7 @@ public function delete() {
$this->isNew = TRUE;
$this->resetOverriddenData();
$this->notify('delete');
$this->originalData = $this->data;
return $this;
}
......@@ -650,5 +661,55 @@ public function getRawData() {
return $this->data;
}
/**
* Gets original data from this configuration object.
*
* Original data is the data as it is immediately after loading from
* configuration storage before any changes. If this is a new configuration
* object it will be an empty array.
*
* @see \Drupal\Core\Config\Config::get()
*
* @param string $key
* A string that maps to a key within the configuration data.
* @param bool $apply_overrides
* Apply any overrides to the original data. Defaults to TRUE.
*
* @return mixed
* The data that was requested.
*/
public function getOriginal($key = '', $apply_overrides = TRUE) {
if (!$this->isLoaded) {
$this->load();
}
if ($apply_overrides) {
// Apply overrides.
$original_data = $this->originalData;
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);
}
if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->settingsOverrides), TRUE);
}
}
if (empty($key)) {
return $original_data;
}
else {
$parts = explode('.', $key);
if (count($parts) == 1) {
return isset($original_data[$key]) ? $original_data[$key] : NULL;
}
else {
$value = NestedArray::getValue($original_data, $parts, $key_exists);
return $key_exists ? $value : NULL;
}
}
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Config;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -82,16 +83,11 @@ class ConfigFactory implements EventSubscriberInterface {
* An event dispatcher instance to use for configuration events.
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Language\Language
* (optional) The language for this configuration. The config factory will
* use it to override configuration data if language overrides are
* available.
*/
public function __construct(StorageInterface $storage, EventDispatcher $event_dispatcher, TypedConfigManager $typed_config, Language $language = NULL) {
public function __construct(StorageInterface $storage, EventDispatcher $event_dispatcher, TypedConfigManager $typed_config) {
$this->storage = $storage;
$this->eventDispatcher = $event_dispatcher;
$this->typedConfigManager = $typed_config;
$this->language = $language;
}
/**
......@@ -360,7 +356,7 @@ public function clearStaticCache() {
}
/**
* Set the language to be used in configuration overrides.
* Sets the language to be used in configuration overrides.
*
* @param \Drupal\Core\Language\Language $language
* The language object to be set on the config factory. Used to override
......@@ -374,6 +370,22 @@ public function setLanguage(Language $language = NULL) {
return $this;
}
/**
* 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 \Drupal\Core\Config\ConfigFactory
* The config factory object.
*/
public function setLanguageFromDefault(LanguageDefault $language_default) {
$this->language = $language_default->get();
return $this;
}
/**
* Gets the language Used to override configuration.
*
......
......@@ -13,6 +13,7 @@
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\YamlFileLoader;
use Drupal\Core\Language\Language;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
......@@ -516,6 +517,18 @@ protected function buildContainer() {
}
$container->setParameter('container.namespaces', $namespaces);
// Store the default language values on the container. This is so that the
// default language can be configured using the configuration factory. This
// avoids the circular dependencies that would created by
// \Drupal\language\LanguageServiceProvider::alter() and allows the default
// language to not be English in the installer.
$system = BootstrapConfigStorageFactory::get()->read('system.site');
$default_language_values = Language::$defaultValues;
if ($default_language_values['id'] != $system['langcode']) {
$default_language_values = array('id' => $system['langcode'], 'default' => TRUE);
}
$container->setParameter('language.default_values', $default_language_values);
// Register synthetic services.
$container->register('class_loader')->setSynthetic(TRUE);
$container->register('kernel', 'Symfony\Component\HttpKernel\KernelInterface')->setSynthetic(TRUE);
......
<?php
namespace Drupal\Core\Language;
/**
* Provides a simple get and set wrapper to a language object.
*
* The default language must be provided without dependencies since it is both
* configured and a dependency of the configuration system. The LanguageDefault
* object is a container service. The default values are stored on the container
* by \Drupal\Core\DrupalKernel::buildContainer(). This allows services to
* override this parameter in a ServiceProvider, for example,
* \Drupal\language\LanguageServiceProvider::alter().
*/
class LanguageDefault {
/**
* The default language.
*
* @var \Drupal\Core\Language\Language
*/
protected $language;
/**
* Constructs the default language object.
*
* @param array $default_values
*/
public function __construct(array $default_values) {
$this->set(new Language($default_values));
}
/**
* Gets the default language.
*
* @return \Drupal\Core\Language\Language
* The default language.
*/
public function get() {
return $this->language;
}
/**
* Sets the default language.
*
* @param \Drupal\Core\Language\Language $language
* The default language.
*/
public function set(Language $language) {
$language->default = TRUE;
$this->language = $language;
}
}
......@@ -32,10 +32,20 @@ class LanguageManager implements LanguageManagerInterface {
/**
* The default language object.
*
* @var \Drupal\Core\Language\Language
* @var \Drupal\Core\Language\LanguageDefault
*/
protected $defaultLanguage;
/**
* Constructs the language manager.
*
* @param \Drupal\Core\Language\Language $default_language
* The default language.
*/
public function __construct(LanguageDefault $default_language) {
$this->defaultLanguage = $default_language;
}
/**
* {@inheritdoc}
*/
......@@ -83,16 +93,14 @@ public function getCurrentLanguage($type = Language::TYPE_INTERFACE) {
* {@inheritdoc}
*/
public function reset($type = NULL) {
return $this;
}
/**
* {@inheritdoc}
*/
public function getDefaultLanguage() {
if (!isset($this->defaultLanguage)) {
$this->defaultLanguage = new Language(Language::$defaultValues);
}
return $this->defaultLanguage;
return $this->defaultLanguage->get();
}
/**
......
......@@ -62,6 +62,9 @@ public function getCurrentLanguage($type = Language::TYPE_INTERFACE);
* (optional) The language type to reset as a string, e.g.,
* Language::TYPE_INTERFACE, or NULL to reset all language types. Defaults
* to NULL.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
* The language manager that has been reset.
*/
public function reset($type = NULL);
......
......@@ -73,8 +73,6 @@ public function testLanguageBlockVisibility() {
);
$this->drupalPostForm('admin/config/regional/settings', $edit, t('Save configuration'));
// Reset the static cache of the language list.
$this->container->get('language_manager')->reset();
// Check that a page has a block.
$this->drupalGet('en');
$this->assertText('Powered by Drupal', 'The body of the custom block appears on the page.');
......
......@@ -33,16 +33,29 @@ public static function getInfo() {
public function setUp() {
parent::setUp();
$this->installConfig(array('config_test'));
\Drupal::configFactory()->setLanguage(language_default());
}
/**
* 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
// English overrides work.
\Drupal::configFactory()->setLanguageFromDefault(\Drupal::service('language.default'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'en bar');
// Ensure that the raw data is not translated.
$raw = $config->getRawData();
$this->assertIdentical($raw['foo'], 'bar');
language_save(new Language(array(
'name' => 'French',
'id' => 'fr',
......
......@@ -65,6 +65,21 @@ function testSiteNameTranslation() {
// @see \Drupal\Core\PathProcessor::processInbound()
$this->drupalGet('xx');
$this->assertText('XX site name');
// Set the xx language to be the default language and delete the English
// language so the site is no longer multilingual and confirm configuration
// overrides still work.
$language_manager = \Drupal::languageManager()->reset();
$this->assertTrue($language_manager->isMultilingual(), 'The test site is multilingual.');
$language = \Drupal::languageManager()->getLanguage('xx');
$language->default = TRUE;
language_save($language);
language_delete('en');
$this->assertFalse($language_manager->isMultilingual(), 'The test site is monolingual.');
$this->drupalGet('xx');
$this->assertText('XX site name');
}
}
......@@ -32,9 +32,6 @@ function language_install() {
* Implements hook_uninstall().
*/
function language_uninstall() {
// Clear variables.
variable_del('language_default');
// Clear variables.
foreach (\Drupal::languageManager()->getDefinedLanguageTypes() as $type) {
variable_del("language_negotiation_$type");
......
......@@ -5,6 +5,7 @@
* Add language handling functionality to Drupal.
*/
use Drupal\Component\PhpStorage\PhpStorageFactory;
use Drupal\Core\Language\Language;
use Drupal\language\ConfigurableLanguageManager;
use Drupal\language\ConfigurableLanguageManagerInterface;
......@@ -469,8 +470,10 @@ function language_save($language) {
}
if (!empty($language->default)) {
// Set the new version of this language as default in a variable.
variable_set('language_default', (array) $language);
// Update the config. Saving the configuration fires and event that causes
// the container to be rebuilt.
\Drupal::config('system.site')->set('langcode', $language->id)->save();
\Drupal::service('language.default')->set($language);
}
$language_manager = \Drupal::languageManager();
......
......@@ -7,3 +7,7 @@ services:
arguments: ['@language_manager', '@plugin.manager.language_negotiation_method', '@config.factory', '@settings']
calls:
- [initLanguageManager]
language.config_subscriber:
class: Drupal\language\EventSubscriber\ConfigSubscriber
tags:
- { name: event_subscriber }
......@@ -12,6 +12,7 @@
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManager;
use Symfony\Component\HttpFoundation\Request;
......@@ -98,18 +99,12 @@ public static function rebuildServices() {
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler) {
public function __construct(LanguageDefault $default_language, ConfigFactory $config_factory, ModuleHandlerInterface $module_handler) {
$this->defaultLanguage = $default_language;
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function initConfigOverrides() {
$this->configFactory->setLanguage($this->getCurrentLanguage());
}
/**
* {@inheritdoc}
*/
......@@ -217,7 +212,6 @@ public function reset($type = NULL) {
$this->languageTypes = NULL;
$this->languageTypesInfo = NULL;
$this->languages = NULL;
$this->defaultLanguage = NULL;
if ($this->negotiator) {
$this->negotiator->reset();
}
......@@ -225,6 +219,7 @@ public function reset($type = NULL) {
elseif (isset($this->negotiatedLanguages[$type])) {
unset($this->negotiatedLanguages[$type]);
}
return $this;
}
/**
......@@ -250,19 +245,6 @@ public function setNegotiator(LanguageNegotiatorInterface $negotiator) {
$this->negotiatedLanguages = array();
}
/**
* {@inheritdoc}
*/
public function getDefaultLanguage() {
if (!isset($this->defaultLanguage)) {
// @todo Convert to CMI https://drupal.org/node/1827038 and
// https://drupal.org/node/2108599.
$default_info = variable_get('language_default', Language::$defaultValues);
$this->defaultLanguage = new Language($default_info + array('default' => TRUE));
}
return $this->defaultLanguage;
}
/**
* {@inheritdoc}
*/
......
......@@ -78,9 +78,4 @@ public function saveLanguageTypesConfiguration(array $config);
*/
public function updateLockedLanguageWeights();
/**
* Initializes per-language overrides for configuration.
*/
public function initConfigOverrides();
}
......@@ -9,6 +9,7 @@
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\language\Exception\DeleteDefaultLanguageException;
use Drupal\language\LanguageInterface;
/**
......@@ -97,4 +98,17 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
$this->langcode = 'en';
}
/**
* {@inheritdoc}
*
* @throws \RuntimeException
*/
public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
$default_language = \Drupal::service('language.default')->get();
foreach ($entities as $entity) {
if ($entity->id() == $default_language->id) {
throw new DeleteDefaultLanguageException('Can not delete the default language');
}
}
}
}
<?php
/**
* @file
* Contains \Drupal\language\EventSubscriber\ConfigSubscriber.
*/
namespace Drupal\language\EventSubscriber;
use Drupal\Component\PhpStorage\PhpStorageFactory;
use Drupal\Core\Config\ConfigEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Deletes the container if default language has changed.
*/
class ConfigSubscriber implements EventSubscriberInterface {
/**
* Causes the container to be rebuilt on the next request.
*
* @param ConfigEvent $event
* The configuration event.
*/
public function onConfigSave(ConfigEvent $event) {
$saved_config = $event->getConfig();
if ($saved_config->getName() == 'system.site' && $saved_config->get('langcode') != $saved_config->getOriginal('langcode')) {
// Trigger a container rebuild on the next request by deleting compiled
// from PHP storage.
PhpStorageFactory::get('service_container')->deleteAll();
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events['config.save'][] = array('onConfigSave', 0);
return $events;
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\language\EventSubscriber;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
......@@ -50,6 +51,13 @@ class LanguageRequestSubscriber implements EventSubscriberInterface {
*/
protected $currentUser;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* Constructs a LanguageRequestSubscriber object.
*
......@@ -61,12 +69,15 @@ class LanguageRequestSubscriber implements EventSubscriberInterface {
* The translation service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The configuration factory.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, TranslatorInterface $translation, AccountInterface $current_user) {
public function __construct(ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, TranslatorInterface $translation, AccountInterface $current_user, ConfigFactory $config_factory) {
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->translation = $translation;
$this->currentUser = $current_user;
$this->configFactory = $config_factory;
}
/**
......@@ -83,7 +94,7 @@ public function onKernelRequestLanguage(GetResponseEvent $event) {
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$this->languageManager->setNegotiator($this->negotiator);
$this->languageManager->setRequest($request);
$this->languageManager->initConfigOverrides();
$this->configFactory->setLanguage($this->languageManager->getCurrentLanguage());
}
// After the language manager has initialized, set the default langcode
// for the string translations.
......