diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php index ec3ce71..7702496 100644 --- a/core/lib/Drupal/Core/Language/Language.php +++ b/core/lib/Drupal/Core/Language/Language.php @@ -80,7 +80,9 @@ class Language implements LanguageInterface { public function __construct(array $values = array()) { // Set all the provided properties for the language. foreach ($values as $key => $value) { - $this->{$key} = $value; + if (property_exists($this, $key)) { + $this->{$key} = $value; + } } // If some values were not set, set sane defaults of a predefined language. if (!isset($values['name']) || !isset($values['direction'])) { diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index 3729894..555b425 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -27,6 +27,17 @@ class LanguageManager implements LanguageManagerInterface { protected $translation; /** + * An array of arrays the available languages keyed by language code. The top + * level array is keyed by the language the language names are translated in. + * The next level is keyed by the flags provided to self::getLanguages(). + * + * @var \Drupal\Core\Language\LanguageInterface[] + * + * @see \Drupal\Core\Language\LanguageManager::getLanguages() + */ + protected $languages = array(); + + /** * The default language object. * * @var \Drupal\Core\Language\LanguageDefault @@ -123,15 +134,18 @@ public function getDefaultLanguage() { * {@inheritdoc} */ public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) { - // If this language manager is used, there are no configured languages. - // The default language and locked languages comprise the full language - // list. - $default = $this->getDefaultLanguage(); - $languages = array($default->getId() => $default); - $languages += $this->getDefaultLockedLanguages($default->getWeight()); - - // Filter the full list of languages based on the value of $flags. - return $this->filterLanguages($languages, $flags); + $static_cache_id = $this->getCurrentLanguage()->getId(); + if (!isset($this->languages[$static_cache_id][$flags])) { + // If this language manager is used, there are no configured languages. + // The default language and locked languages comprise the full language + // list. + $default = $this->getDefaultLanguage(); + $this->languages[$static_cache_id][$flags] = array($default->getId() => $default); + $this->languages[$static_cache_id][$flags] += $this->getDefaultLockedLanguages($default->getWeight()); + // Filter the full list of languages based on the value of $flags. + $this->languages[$static_cache_id][$flags] = $this->filterLanguages($this->languages[$static_cache_id][$flags], $flags); + } + return $this->languages[$static_cache_id][$flags]; } /** diff --git a/core/modules/language/src/ConfigurableLanguageManager.php b/core/modules/language/src/ConfigurableLanguageManager.php index 2eaee36..0746696 100644 --- a/core/modules/language/src/ConfigurableLanguageManager.php +++ b/core/modules/language/src/ConfigurableLanguageManager.php @@ -246,6 +246,7 @@ public function reset($type = NULL) { $this->negotiatedMethods = array(); $this->languageTypes = NULL; $this->languageTypesInfo = NULL; + $this->languages = array(); if ($this->negotiator) { $this->negotiator->reset(); } @@ -277,29 +278,44 @@ public function setNegotiator(LanguageNegotiatorInterface $negotiator) { * {@inheritdoc} */ public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) { - // Initialize the language list with the default language and default - // locked languages. These cannot be removed. This serves as a fallback - // list if this method is invoked while the language module is installed - // and the configuration entities for languages are not yet fully imported. - $languages = parent::getLanguages(LanguageInterface::STATE_ALL); - - // Load configurable languages on top of the defaults. Ideally this could - // use the entity API to load and instantiate ConfigurableLanguage objects. - // However the entity API depends on the language system, so that would - // result in infinite loops. We use the configuration system directly and - // instantiate runtime Language objects. When language entities are imported - // those cover the default and locked languages, so site-specific - // configuration will prevail over the fallback values. Having them in the - // array already ensures if this is invoked in the middle of importing - // language configuration entities, the defaults are always present. - $config_ids = $this->configFactory->listAll('language.entity.'); - foreach ($this->configFactory->loadMultiple($config_ids) as $config) { - $data = $config->get(); - $data['name'] = $data['label']; - $languages[$data['id']] = new Language($data); + // If a config override is set cache using that language's ID. + if ($override_language = $this->getConfigOverrideLanguage()) { + $static_cache_id = $override_language->getId(); + } + else { + $static_cache_id = $this->getCurrentLanguage()->getId(); + } + if (!isset($this->languages[$static_cache_id][$flags])) { + + // Initialize the language list with the default language and default + // locked languages. These cannot be removed. This serves as a fallback + // list if this method is invoked while the language module is installed + // and the configuration entities for languages are not yet fully imported. + $default = $this->getDefaultLanguage(); + $this->languages[$static_cache_id][$flags] = array($default->getId() => $default); + $this->languages[$static_cache_id][$flags] += $this->getDefaultLockedLanguages($default->getWeight()); + // Filter the full list of languages based on the value of $flags. + $this->languages[$static_cache_id][$flags] = $this->filterLanguages($this->languages[$static_cache_id][$flags], $flags); + + // Load configurable languages on top of the defaults. Ideally this could + // use the entity API to load and instantiate ConfigurableLanguage objects. + // However the entity API depends on the language system, so that would + // result in infinite loops. We use the configuration system directly and + // instantiate runtime Language objects. When language entities are imported + // those cover the default and locked languages, so site-specific + // configuration will prevail over the fallback values. Having them in the + // array already ensures if this is invoked in the middle of importing + // language configuration entities, the defaults are always present. + $config_ids = $this->configFactory->listAll('language.entity.'); + foreach ($this->configFactory->loadMultiple($config_ids) as $config) { + $data = $config->get(); + $data['name'] = $data['label']; + $this->languages[$static_cache_id][$flags][$data['id']] = new Language($data); + } + Language::sort($this->languages[$static_cache_id][$flags]); + $this->languages[$static_cache_id][$flags] = $this->filterLanguages($this->languages[$static_cache_id][$flags], $flags); } - Language::sort($languages); - return $this->filterLanguages($languages, $flags); + return $this->languages[$static_cache_id][$flags]; } /** diff --git a/core/tests/Drupal/Tests/Core/Language/LanguageUnitTest.php b/core/tests/Drupal/Tests/Core/Language/LanguageUnitTest.php index e21ab6f..49a9dbf 100644 --- a/core/tests/Drupal/Tests/Core/Language/LanguageUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Language/LanguageUnitTest.php @@ -18,6 +18,20 @@ class LanguageUnitTest extends UnitTestCase { /** + * @covers ::__construct + */ + public function testConstruct() { + $name = $this->randomMachineName(); + $language_code = $this->randomMachineName(2); + $uuid = $this->randomMachineName(); + $language = new Language(array('id' => $language_code, 'name' => $name, 'uuid' => $uuid)); + // Test that nonexistent properties are not added to the language object. + $this->assertTrue(property_exists($language, 'id')); + $this->assertTrue(property_exists($language, 'name')); + $this->assertFalse(property_exists($language, 'uuid')); + } + + /** * @covers ::getName() * @covers ::setName() */