diff --git a/core/core.services.yml b/core/core.services.yml index 6abd05c..c975b04 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -80,7 +80,7 @@ services: tags: - { name: persist } - { name: event_subscriber } - arguments: ['@config.storage', '@event_dispatcher', '@config.typed'] + arguments: ['@config.storage', '@event_dispatcher', '@config.typed', '@language.default'] config.installer: class: Drupal\Core\Config\ConfigInstaller arguments: ['@config.factory', '@config.storage', '@config.typed', '@entity.manager', '@event_dispatcher'] @@ -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\Language + 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..cbe405f 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\Language') + ->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,19 @@ 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\Language') + ->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')); + ->addArgument(new Reference('config.typed')) + ->addArgument(new Reference('language.default')); // 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 +480,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 +1021,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 +2698,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..e397dc4 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -613,7 +613,7 @@ 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. */ -function drupal_install_system() { +function drupal_install_system($install_state) { // Create tables. drupal_install_schema('system'); @@ -651,6 +651,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..b7be985 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,44 @@ public function getRawData() { return $this->data; } + /** + * Gets original data from this configuration object. + * + * @see \Drupal\Core\Config\Config::get() + * + * @return mixed + * The data that was requested. + */ + public function getOriginal($key = '') { + if (!$this->isLoaded) { + $this->load(); + } + + // 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..3c67246 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -87,7 +87,7 @@ class ConfigFactory implements EventSubscriberInterface { * 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, Language $language) { $this->storage = $storage; $this->eventDispatcher = $event_dispatcher; $this->typedConfigManager = $typed_config; diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 0160119..b969288 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,13 @@ protected function buildContainer() { } $container->setParameter('container.namespaces', $namespaces); + $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/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index d21cb9f..1bbd3c7 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -37,6 +37,16 @@ class LanguageManager implements LanguageManagerInterface { protected $defaultLanguage; /** + * Constructs the language manager. + * + * @param \Drupal\Core\Language\Language $default_language + * The default language + */ + public function __construct(Language $default_language) { + $this->defaultLanguage = $default_language; + } + + /** * {@inheritdoc} */ function setTranslation(TranslationInterface $translation) { @@ -89,9 +99,6 @@ public function reset($type = NULL) { * {@inheritdoc} */ public function getDefaultLanguage() { - if (!isset($this->defaultLanguage)) { - $this->defaultLanguage = new Language(Language::$defaultValues); - } return $this->defaultLanguage; } 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..7e5569b 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,9 @@ 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(); } $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..497f4b1 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php @@ -98,7 +98,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(Language $default_language, ConfigFactory $config_factory, ModuleHandlerInterface $module_handler) { + $this->defaultLanguage = $default_language; $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; } @@ -217,7 +218,6 @@ public function reset($type = NULL) { $this->languageTypes = NULL; $this->languageTypesInfo = NULL; $this->languages = NULL; - $this->defaultLanguage = NULL; if ($this->negotiator) { $this->negotiator->reset(); } @@ -253,19 +253,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/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/LanguageServiceProvider.php b/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php index 28a719d..02ac18e 100644 --- a/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php +++ b/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php @@ -18,6 +18,8 @@ */ class LanguageServiceProvider extends ServiceProviderBase { + const CONFIG_PREFIX = 'language.entity.'; + /** * {@inheritdoc} */ @@ -50,6 +52,9 @@ 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); + } } /** @@ -59,7 +64,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 +77,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/LanguageDependencyInjectionTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php index 969a836..f60bde4 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php @@ -50,29 +50,32 @@ function testDependencyInjectedNewLanguage() { */ function testDependencyInjectedNewDefaultLanguage() { // 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); + )); + language_save($new_language_default); + // Saving a new default language will have deleted the container from PHP + // storage but we need to rebuild it to update the container in memory. + $this->rebuildContainer(); // Initialize the language system. $this->languageManager->init(); // 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_default variable we previously set. - variable_del('language_default'); + // Delete the language to check that we fallback to the default. + language_delete('fr'); + $this->rebuildContainer(); + $result = \Drupal::languageManager()->getCurrentLanguage(); + $this->assertIdentical($result->id, 'en'); } } 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..77602b7 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -94,8 +94,13 @@ 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. $language = new Language(array( 'id' => $langcode_browser_fallback, + 'default' => TRUE, )); language_save($language); $language = new Language(array( @@ -103,21 +108,20 @@ function testUILanguageNegotiation() { )); language_save($language); + // Saving a new default language will have deleted the container from PHP + // storage but we need to rebuild it to update the container in memory. + $this->rebuildContainer(); + // We will look for this string in the admin/config screen to see if the // 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'); + \Drupal::config('system.site')->set('langcode', 'en')->save(); + $this->rebuildContainer(); // Translate the string. $language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback";