diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index ed15979..b524890 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -62,20 +62,6 @@ protected $fields = array(); /** - * Local cache for the entity language. - * - * @var \Drupal\Core\Language\Language - */ - protected $language; - - /** - * Local cache for the available language objects. - * - * @var array - */ - protected $languages; - - /** * Local cache for field definitions. * * @see ContentEntityBase::getPropertyDefinitions() @@ -92,6 +78,13 @@ protected $uriPlaceholderReplacements; /** + * Local cache for the available language objects. + * + * @var array + */ + protected $languages; + + /** * Language code identifying the entity active language. * * This is the language field accessors will use to determine which field @@ -102,6 +95,13 @@ protected $activeLangcode = Language::LANGCODE_DEFAULT; /** + * Local cache for the default language code. + * + * @var string + */ + protected $defaultLangcode; + + /** * An array of entity translation metadata. * * An associative array keyed by translation language code. Every value is an @@ -150,14 +150,14 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans $this->values[$key] = $value; } - // Initialize translations. Ensure we have at least an entry for the entity - // original language. + // Initialize translations. Ensure we have at least an entry for the default + // language. $data = array('status' => static::TRANSLATION_EXISTING); $this->translations[Language::LANGCODE_DEFAULT] = $data; - $this->initializeDefaultLanguage(); + $this->setDefaultLangcode(); if ($translations) { foreach ($translations as $langcode) { - if ($langcode != $this->language->id && $langcode != Language::LANGCODE_DEFAULT) { + if ($langcode != $this->defaultLangcode && $langcode != Language::LANGCODE_DEFAULT) { $this->translations[$langcode] = $data; } } @@ -385,56 +385,56 @@ public function get($property_name) { * * @return \Drupal\Core\Field\FieldItemListInterface */ - protected function getTranslatedField($property_name, $langcode) { + protected function getTranslatedField($name, $langcode) { if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) { $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.'; throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode))); } // Populate $this->fields to speed-up further look-ups and to keep track of // fields objects, possibly holding changes to field values. - if (!isset($this->fields[$property_name][$langcode])) { - $definition = $this->getPropertyDefinition($property_name); + if (!isset($this->fields[$name][$langcode])) { + $definition = $this->getPropertyDefinition($name); if (!$definition) { - throw new \InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.'); + throw new \InvalidArgumentException('Field ' . check_plain($name) . ' is unknown.'); } // Non-translatable fields are always stored with // Language::LANGCODE_DEFAULT as key. $default = $langcode == Language::LANGCODE_DEFAULT; if (!$default && empty($definition['translatable'])) { - if (!isset($this->fields[$property_name][Language::LANGCODE_DEFAULT])) { - $this->fields[$property_name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT); + if (!isset($this->fields[$name][Language::LANGCODE_DEFAULT])) { + $this->fields[$name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($name, Language::LANGCODE_DEFAULT); } - $this->fields[$property_name][$langcode] = &$this->fields[$property_name][Language::LANGCODE_DEFAULT]; + $this->fields[$name][$langcode] = &$this->fields[$name][Language::LANGCODE_DEFAULT]; } else { $value = NULL; - if (isset($this->values[$property_name][$langcode])) { - $value = $this->values[$property_name][$langcode]; + if (isset($this->values[$name][$langcode])) { + $value = $this->values[$name][$langcode]; } - $field = \Drupal::typedData()->getPropertyInstance($this, $property_name, $value); - if (!$default) { - $field->setLangcode($langcode); + $field = \Drupal::typedData()->getPropertyInstance($this, $name, $value); + if ($default) { + // $this->defaultLangcode might not be set if we are initializing the + // default language code cache, in which case there is no valid + // langcode to assign. + $field_langcode = isset($this->defaultLangcode) ? $this->defaultLangcode : Language::LANGCODE_NOT_SPECIFIED; } - // If we are initializing the default language cache, the variable is - // not populated, thus we have no valid value to set. - elseif (isset($this->language)) { - $field->setLangcode($this->language->id); + else { + $field_langcode = $langcode; } - $this->fields[$property_name][$langcode] = $field; + $field->setLangcode($field_langcode); + $this->fields[$name][$langcode] = $field; } } - return $this->fields[$property_name][$langcode]; + return $this->fields[$name][$langcode]; } /** * {@inheritdoc} */ - public function set($property_name, $value, $notify = TRUE) { - $this->get($property_name)->setValue($value, FALSE); - - if ($property_name == 'langcode') { - $this->onChange($property_name); - } + public function set($name, $value, $notify = TRUE) { + // If default language changes we need to react to that. + $notify = $name == 'langcode'; + $this->get($name)->setValue($value, $notify); } /** @@ -536,42 +536,36 @@ public function access($operation = 'view', AccountInterface $account = NULL) { * {@inheritdoc} */ public function language() { + $language = NULL; if ($this->activeLangcode != Language::LANGCODE_DEFAULT) { if (!isset($this->languages[$this->activeLangcode])) { $this->languages += language_list(Language::STATE_ALL); } - return $this->languages[$this->activeLangcode]; + $language = $this->languages[$this->activeLangcode]; } else { - return $this->language; + $language = $this->languages[$this->defaultLangcode]; } + return $language; } /** - * Initializes the entity original language local cache. - * - * @return \Drupal\Core\Language\Language - * A language object. + * Populates the local cache for the default language code. */ - protected function initializeDefaultLanguage() { - // Keep a local cache of the language object and clear it if the langcode - // gets changed, see ContentEntityBase::onChange(). - if (!isset($this->language)) { - // Get the language code if the property exists. - if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) { - $this->language = $item->language; - } - if (empty($this->language)) { - // Make sure we return a proper language object. - $this->language = new Language(array('id' => Language::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE)); - } - // This needs to be initialized manually as it is skipped when - // instantiating the language field object to avoid infinite recursion. - if (!empty($this->fields['langcode'])) { - $this->fields['langcode'][Language::LANGCODE_DEFAULT]->setLangcode($this->language->id); - } + protected function setDefaultLangcode() { + // Get the language code if the property exists. + if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) { + $this->defaultLangcode = $item->language->id; + } + if (empty($this->defaultLangcode)) { + // Make sure we return a proper language object. + $this->defaultLangcode = Language::LANGCODE_NOT_SPECIFIED; + } + // This needs to be initialized manually as it is skipped when instantiating + // the language field object to avoid infinite recursion. + if (!empty($this->fields['langcode'])) { + $this->fields['langcode'][Language::LANGCODE_DEFAULT]->setLangcode($this->defaultLangcode); } - return $this->language; } /** @@ -581,11 +575,9 @@ protected function initializeDefaultLanguage() { * A language object. */ protected function updateFieldLangcodes($langcode) { - if (!empty($this->fields)) { - foreach ($this->fields as $name => $items) { - if (!empty($items[Language::LANGCODE_DEFAULT])) { - $items[Language::LANGCODE_DEFAULT]->setLangcode($langcode); - } + foreach ($this->fields as $name => $items) { + if (!empty($items[Language::LANGCODE_DEFAULT])) { + $items[Language::LANGCODE_DEFAULT]->setLangcode($langcode); } } } @@ -597,9 +589,8 @@ public function onChange($property_name) { if ($property_name == 'langcode') { // Avoid using unset as this unnecessarily triggers magic methods later // on. - $this->language = NULL; - $this->initializeDefaultLanguage(); - $this->updateFieldLangcodes($this->language->id); + $this->setDefaultLangcode(); + $this->updateFieldLangcodes($this->defaultLangcode); } } @@ -611,7 +602,7 @@ public function onChange($property_name) { public function getTranslation($langcode) { // Ensure we always use the default language code when dealing with the // original entity language. - if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->language->id) { + if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->defaultLangcode) { $langcode = Language::LANGCODE_DEFAULT; } @@ -634,12 +625,11 @@ public function getTranslation($langcode) { else { // If we were given a valid language and there is no translation for it, // we return a new one. - $languages = language_list(Language::STATE_ALL); - if (isset($languages[$langcode])) { + if (isset($this->languages[$langcode])) { // If the entity or the requested language is not a configured // language, we fall back to the entity itself, since in this case it // cannot have translations. - $translation = empty($this->language->locked) && empty($languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this; + $translation = empty($this->languages[$this->defaultLangcode]->locked) && empty($this->languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this; } } } @@ -700,7 +690,7 @@ protected function initializeTranslation($langcode) { * {@inheritdoc} */ public function hasTranslation($langcode) { - if ($langcode == $this->language->id) { + if ($langcode == $this->defaultLangcode) { $langcode = Language::LANGCODE_DEFAULT; } return !empty($this->translations[$langcode]['status']); @@ -710,8 +700,7 @@ public function hasTranslation($langcode) { * {@inheritdoc} */ public function addTranslation($langcode, array $values = array()) { - $languages = language_list(Language::STATE_ALL); - if (!isset($languages[$langcode]) || $this->hasTranslation($langcode)) { + if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) { $message = 'Invalid translation language (@langcode) specified.'; throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode))); } @@ -747,7 +736,7 @@ public function addTranslation($langcode, array $values = array()) { * {@inheritdoc} */ public function removeTranslation($langcode) { - if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->language->id) { + if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) { foreach ($this->getPropertyDefinitions() as $name => $definition) { if (!empty($definition['translatable'])) { unset($this->values[$name][$langcode]); @@ -766,7 +755,7 @@ public function removeTranslation($langcode) { * {@inheritdoc} */ public function initTranslation($langcode) { - if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->language->id) { + if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) { $this->translations[$langcode]['status'] = static::TRANSLATION_EXISTING; } } @@ -779,11 +768,11 @@ public function getTranslationLanguages($include_default = TRUE) { unset($translations[Language::LANGCODE_DEFAULT]); if ($include_default) { - $translations[$this->language->id] = TRUE; + $translations[$this->defaultLangcode] = TRUE; } // Now load language objects based upon translation langcodes. - return array_intersect_key(language_list(Language::STATE_ALL), $translations); + return array_intersect_key($this->languages, $translations); } /** @@ -935,8 +924,8 @@ public function __clone() { } } - // Ensure the translations array is actually cloned by removing the - // original reference and re-creating its values. + // Ensure the translations array is actually cloned by overwriting the + // original reference with one pointing to a copy of the array. $this->clearTranslationCache(); $translations = $this->translations; $this->translations = &$translations; diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php index 11666f3..a4c2659 100644 --- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php @@ -849,7 +849,7 @@ protected function doLoadFieldItems($entities, $age) { } // Load field data. - $all_langcodes = array_keys(language_list(Language::STATE_ALL)); + $langcodes = array_keys(language_list(Language::STATE_ALL)); foreach ($fields as $field_name => $field) { $table = $load_current ? static::_fieldTableName($field) : static::_fieldRevisionTableName($field); @@ -860,18 +860,17 @@ protected function doLoadFieldItems($entities, $age) { ->fields('t') ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN') ->condition('deleted', 0) - ->condition('langcode', $all_langcodes, 'IN') + ->condition('langcode', $langcodes, 'IN') ->orderBy('delta') ->execute(); - $translatable = $field->isFieldTranslatable(); $delta_count = array(); foreach ($results as $row) { // Ensure that records for non-translatable fields having invalid // languages are skipped. // @todo Remove BC support for 'und' untranslatable fields as soon as // can write a migration for them. - if ($translatable || $row->langcode == Language::LANGCODE_NOT_SPECIFIED || $row->langcode == $entities[$row->entity_id]->getUntranslated()->language()->id) { + if ($field->isFieldTranslatable() || $row->langcode == Language::LANGCODE_NOT_SPECIFIED || $row->langcode == $entities[$row->entity_id]->getUntranslated()->language()->id) { if (!isset($delta_count[$row->entity_id][$row->langcode])) { $delta_count[$row->entity_id][$row->langcode] = 0; } diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php index 0174a99..825a638 100644 --- a/core/lib/Drupal/Core/Language/Language.php +++ b/core/lib/Drupal/Core/Language/Language.php @@ -59,8 +59,11 @@ class Language { /** * Language code referring to the default language of data, e.g. of an entity. + * + * See the BCP 47 syntax to define private language tags: + * http://www.rfc-editor.org/rfc/bcp/bcp47.txt */ - const LANGCODE_DEFAULT = 'xx-default'; + const LANGCODE_DEFAULT = 'x-default'; /** * The language state when referring to configurable languages. diff --git a/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php index e1d7a30..32bc279 100644 --- a/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php +++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php @@ -206,7 +206,7 @@ public function testPatchDenormailzation() { // Assert that all fields are NULL and not set to default values. Example: // the UUID field is NULL and not initialized as usual. foreach ($denormalized as $field_name => $field) { - // The 'langcode' field has always a value. + // The 'langcode' field always has a value. if ($field_name != 'langcode') { $this->assertFalse(isset($denormalized->$field_name), "$field_name is not set."); }