diff --git a/core/core.services.yml b/core/core.services.yml index 50d2640..96d6ad6 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -225,6 +225,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'] diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 5a46c8c..9a1c576 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -301,22 +301,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'; @@ -385,13 +383,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); @@ -476,6 +479,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 @@ -1011,7 +1020,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 @@ -2688,7 +2697,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') diff --git a/core/includes/install.inc b/core/includes/install.inc index 67c18b7..b9ae3bd 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -612,8 +612,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'); @@ -651,6 +655,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'); } diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index eed4f89..9c6bd3f 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -70,6 +70,13 @@ class Config { protected $data; /** + * The original data of the configuration object. + * + * @var array + */ + protected $originalData; + + /** * The current runtime data. * * The configuration data from storage merged with language, module and @@ -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; + } + } + } } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 8aeb301..e277787 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -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 @@ -375,6 +371,22 @@ 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 \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. * * @return \Drupal\Core\Language\Language diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 0160119..b3d7b84 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -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); diff --git a/core/lib/Drupal/Core/Language/LanguageDefault.php b/core/lib/Drupal/Core/Language/LanguageDefault.php new file mode 100644 index 0000000..ad58ebe --- /dev/null +++ b/core/lib/Drupal/Core/Language/LanguageDefault.php @@ -0,0 +1,54 @@ +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; + } + +} diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index d21cb9f..2bd3f20 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -32,11 +32,21 @@ 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} */ function setTranslation(TranslationInterface $translation) { @@ -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(); } /** diff --git a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php index 159c39c..16c7c01 100644 --- a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php +++ b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php @@ -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); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php index e2bb9da..246e92c 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php @@ -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.'); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php b/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php index 4d7bdf9..4da038c 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php @@ -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', diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideWebTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideWebTest.php index d8412b4..30cba5a 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideWebTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideWebTest.php @@ -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 multi-lingual 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'); + } } diff --git a/core/modules/language/language.install b/core/modules/language/language.install index 3738831..fedc2b4 100644 --- a/core/modules/language/language.install +++ b/core/modules/language/language.install @@ -33,9 +33,6 @@ function language_install() { */ function language_uninstall() { // Clear variables. - variable_del('language_default'); - - // Clear variables. foreach (\Drupal::languageManager()->getDefinedLanguageTypes() as $type) { variable_del("language_negotiation_$type"); variable_del("language_negotiation_methods_weight_$type"); diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 883ab88..e31807f 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -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(); diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index 81aede7..422644a 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -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 } diff --git a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php index 49c7ea4..9de3726 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php @@ -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,7 +99,8 @@ 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; } @@ -106,13 +108,6 @@ public function __construct(ConfigFactory $config_factory, ModuleHandlerInterfac /** * {@inheritdoc} */ - public function initConfigOverrides() { - $this->configFactory->setLanguage($this->getCurrentLanguage()); - } - - /** - * {@inheritdoc} - */ public function init() { if (!$this->initialized) { foreach ($this->getDefinedLanguageTypes() as $type) { @@ -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; } /** @@ -253,19 +248,6 @@ public function setNegotiator(LanguageNegotiatorInterface $negotiator) { /** * {@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} - */ public function getLanguages($flags = Language::STATE_CONFIGURABLE) { if (!isset($this->languages)) { // Prepopulate the language list with the default language to keep things diff --git a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php index c9a13d6..02c8205 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManagerInterface.php @@ -78,9 +78,4 @@ public function saveLanguageTypesConfiguration(array $config); */ public function updateLockedLanguageWeights(); - /** - * Initializes per-language overrides for configuration. - */ - public function initConfigOverrides(); - } diff --git a/core/modules/language/lib/Drupal/language/Entity/Language.php b/core/modules/language/lib/Drupal/language/Entity/Language.php index 60b06a1..d9d6c3f 100644 --- a/core/modules/language/lib/Drupal/language/Entity/Language.php +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php @@ -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'); + } + } + } } diff --git a/core/modules/language/lib/Drupal/language/EventSubscriber/ConfigSubscriber.php b/core/modules/language/lib/Drupal/language/EventSubscriber/ConfigSubscriber.php new file mode 100644 index 0000000..819c2d3 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/EventSubscriber/ConfigSubscriber.php @@ -0,0 +1,42 @@ +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; + } + +} diff --git a/core/modules/language/lib/Drupal/language/EventSubscriber/LanguageRequestSubscriber.php b/core/modules/language/lib/Drupal/language/EventSubscriber/LanguageRequestSubscriber.php index c9551f4..26c8190 100644 --- a/core/modules/language/lib/Drupal/language/EventSubscriber/LanguageRequestSubscriber.php +++ b/core/modules/language/lib/Drupal/language/EventSubscriber/LanguageRequestSubscriber.php @@ -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; @@ -51,6 +52,13 @@ class LanguageRequestSubscriber implements EventSubscriberInterface { protected $currentUser; /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $configFactory; + + /** * Constructs a LanguageRequestSubscriber object. * * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager @@ -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. diff --git a/core/modules/language/lib/Drupal/language/Exception/DeleteDefaultLanguageException.php b/core/modules/language/lib/Drupal/language/Exception/DeleteDefaultLanguageException.php new file mode 100644 index 0000000..2235aa2 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/Exception/DeleteDefaultLanguageException.php @@ -0,0 +1,13 @@ +addArgument(new Reference('language_manager')) ->addArgument(new Reference('language_negotiator')) ->addArgument(new Reference('string_translation')) - ->addArgument(new Reference('current_user')); + ->addArgument(new Reference('current_user')) + ->addArgument(new Reference('config.factory')); $container->register('path_processor_language', 'Drupal\language\HttpKernel\PathProcessorLanguage') ->addTag('path_processor_inbound', array('priority' => 300)) @@ -50,6 +53,14 @@ public function alter(ContainerBuilder $container) { $definition->setClass('Drupal\language\ConfigurableLanguageManager') ->addArgument(new Reference('config.factory')) ->addArgument(new Reference('module_handler')); + if ($default_language_values = $this->getDefaultLanguageValues()) { + $container->setParameter('language.default_values', $default_language_values); + // Ensure that configuration is can be localised if site is monolingual + // but the Language module is enabled. This will the case for monolingual + // sites not in english. + $definition = $container->getDefinition('config.factory'); + $definition->addMethodCall('setLanguageFromDefault', array(new Reference('language.default'))); + } } /** @@ -59,7 +70,9 @@ public function alter(ContainerBuilder $container) { * TRUE if the site is multilingual, FALSE otherwise. */ protected function isMultilingual() { - $prefix = 'language.entity.'; + // Assign the prefix to a local variable so it can be used in an anonymous + // function. + $prefix = static::CONFIG_PREFIX; // @todo Try to swap out for config.storage to take advantage of database // and caching. This might prove difficult as this is called before the // container has finished building. @@ -70,4 +83,21 @@ protected function isMultilingual() { return count($config_ids) > 1; } + /** + * Gets the default language values. + * + * @return array|bool + * Returns the default language values for the language configured in + * system.site:langcode if the corresponding configuration entity exists, + * otherwise FALSE. + */ + protected function getDefaultLanguageValues() { + $config_storage = BootstrapConfigStorageFactory::get(); + $system = $config_storage->read('system.site'); + $default_language = $config_storage->read(static::CONFIG_PREFIX . $system['langcode']); + if (is_array($default_language)) { + return $default_language + array('default' => TRUE); + } + return FALSE; + } } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationTest.php index 6f9c552..6093263 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationTest.php @@ -106,7 +106,8 @@ function testLanguageConfiguration() { // Remove English language and add a new Language to check if langcode of // Language entity is 'en'. - $this->assert(language_delete('en'), 'Deleted English language.'); + $this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete')); + $this->assertRaw(t('The %language (%langcode) language has been removed.', array('%language' => 'English', '%langcode' => 'en'))); $edit = array( 'predefined_langcode' => 'de', ); diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php index 969a836..938f515 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php @@ -9,6 +9,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Language\Language; +use Drupal\language\Exception\DeleteDefaultLanguageException; /** * Test for dependency injected language object. @@ -49,30 +50,37 @@ function testDependencyInjectedNewLanguage() { * @see \Drupal\Core\Language\Language */ function testDependencyInjectedNewDefaultLanguage() { + $default_language = language_default(); // Change the language default object to different values. - $new_language_default = array( + $new_language_default = new Language(array( 'id' => 'fr', 'name' => 'French', 'direction' => 0, 'weight' => 0, 'method_id' => 'language-default', 'default' => TRUE, - ); - variable_set('language_default', $new_language_default); - - // Initialize the language system. - $this->languageManager->init(); + )); + language_save($new_language_default); // The language system creates a Language object which contains the // same properties as the new default language object. - $expected = new Language($new_language_default); - $result = $this->languageManager->getCurrentLanguage(); - foreach ($expected as $property => $value) { - $this->assertEqual($expected->$property, $result->$property, format_string('The dependency injected language object %prop property equals the default language object %prop property.', array('%prop' => $property))); + $result = \Drupal::languageManager()->getCurrentLanguage(); + $this->assertIdentical($result->id, 'fr'); + + // Delete the language to check that we fallback to the default. + try { + language_delete('fr'); + $this->fail('Expected DeleteDefaultLanguageException thrown.'); + } + catch (DeleteDefaultLanguageException $e) { + $this->pass('Expected DeleteDefaultLanguageException thrown.'); } - // Delete the language_default variable we previously set. - variable_del('language_default'); + // Re-save the previous default language and the delete should work. + language_save($default_language); + language_delete('fr'); + $result = \Drupal::languageManager()->getCurrentLanguage(); + $this->assertIdentical($result->id, $default_language->id); } } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php index 840f8fc..799795e 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php @@ -50,6 +50,10 @@ function setUp() { // Delete English. $this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete')); + // Changing the default language causes a container rebuild. Therefore need + // to rebuild the container in the test environment. + $this->rebuildContainer(); + // Verify that French is the only language. $this->container->get('language_manager')->reset(); $this->assertFalse(\Drupal::languageManager()->isMultilingual(), 'Site is mono-lingual'); diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageTestBase.php b/core/modules/language/lib/Drupal/language/Tests/LanguageTestBase.php index 70a5122..a1780b7 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageTestBase.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageTestBase.php @@ -14,6 +14,7 @@ */ abstract class LanguageTestBase extends DrupalUnitTestBase { + public static $modules = array('system', 'language', 'language_test'); /** * The language manager. * @@ -34,7 +35,6 @@ protected function setUp() { parent::setUp(); - $this->enableModules(array('system', 'language', 'language_test')); $this->installSchema('system', array('variable')); $this->installConfig(array('language')); diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php index e8b3b26..394691b 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -94,8 +94,14 @@ function testUILanguageNegotiation() { $language_domain = 'example.cn'; // Setup the site languages by installing two languages. + // Set the default language in order for the translated string to be registered + // into database when seen by t(). Without doing this, our target string + // is for some reason not found when doing translate search. This might + // be some bug. + $default_language = language_default(); $language = new Language(array( 'id' => $langcode_browser_fallback, + 'default' => TRUE, )); language_save($language); $language = new Language(array( @@ -107,17 +113,15 @@ function testUILanguageNegotiation() { // corresponding translated string is shown. $default_string = 'Configure languages for content and the user interface'; - // Set the default language in order for the translated string to be registered - // into database when seen by t(). Without doing this, our target string - // is for some reason not found when doing translate search. This might - // be some bug. - $this->container->get('language_manager')->reset(); - $languages = language_list(); - variable_set('language_default', (array) $languages['vi']); // First visit this page to make sure our target string is searchable. $this->drupalGet('admin/config'); + // Now the t()'ed string is in db so switch the language back to default. - variable_del('language_default'); + // This will rebuild the container so we need to rebuild the container in + // the test environment. + language_save($default_language); + \Drupal::config('language.negotiation')->set('url.prefixes.en', '')->save(); + $this->rebuildContainer(); // Translate the string. $language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback"; diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php index 989ac42..9f24b89 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageDefault; use Drupal\Core\Language\LanguageManager; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; @@ -95,7 +96,7 @@ function testUninstallProcess() { // Change language negotiation options. drupal_load('module', 'locale'); // Pick only core language types. - $language_manager = new LanguageManager(); + $language_manager = new LanguageManager(new LanguageDefault(Language::$defaultValues)); $default_types = $language_manager->getLanguageTypes(); \Drupal::config('language.types')->set('configurable', $default_types + array('language_custom' => TRUE))->save(); $config = array_flip(array_keys(\Drupal::service('plugin.manager.language_negotiation_method')->getDefinitions())); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index b7848de..ff288e5 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -10,6 +10,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DrupalKernel; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\Language\Language; use Symfony\Component\DependencyInjection\Reference; use Drupal\Core\Database\Database; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -153,6 +154,9 @@ public function containerBuild(ContainerBuilder $container) { // Keep the container object around for tests. $this->container = $container; + // Set the default language on the minimal container. + $this->container->setParameter('language.default_values', Language::$defaultValues); + $container->register('lock', 'Drupal\Core\Lock\NullLockBackend'); $this->settingsSet('cache', array('default' => 'cache.backend.memory')); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 669c615..d8e545c 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -972,7 +972,11 @@ protected function prepareEnvironment() { $this->container = new ContainerBuilder(); // @todo Remove this once this class has no calls to t() and format_plural() - $this->container->register('language_manager', 'Drupal\Core\Language\LanguageManager'); + $this->container->setParameter('language.default_values', Language::$defaultValues); + $this->container->register('language.default', 'Drupal\Core\Language\LanguageDefault') + ->addArgument('%language.default_values%'); + $this->container->register('language_manager', 'Drupal\Core\Language\LanguageManager') + ->addArgument(new Reference('language.default')); $this->container->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager') ->addArgument(new Reference('language_manager')); diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php index 8ce7d1a..0aa1f1f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php @@ -80,11 +80,10 @@ protected function setUp() { $this->installLanguages(); // Assign Lolspeak (xx) to be the default language. - $edit = array('site_default_language' => 'xx'); - $this->drupalPostForm('admin/config/regional/settings', $edit, t('Save configuration')); - - // Reset the static cache of the language list. - $this->container->get('language_manager')->reset(); + $language = \Drupal::languageManager()->getLanguage('xx'); + $language->default = TRUE; + language_save($language); + $this->rebuildContainer(); // Check that lolspeak is the default language for the site. $this->assertEqual(language_default()->id, 'xx', 'Lolspeak is the default language');