// Retrieve language fallback candidates to perform the entity language
// negotiation, unless the current translation is already the desired one.
if($entity->language()->getId() != $langcode){ $context['data'] = $entity; $context += ['operation' => 'entity_view', 'langcode' => $langcode]; $candidates = $this->languageManager->getFallbackCandidates($context);
/**
* Indexes a single node.
*
* @param \Drupal\node\NodeInterface $node
* The node to index.
*
* @return array
* An array of words to update after indexing.
*/ protectedfunctionindexNode(NodeInterface $node){ $words = []; $languages = $node->getTranslationLanguages(); $node_render = $this->entityTypeManager->getViewBuilder('node');
// Add the title to text so it is searchable.
$build['search_title'] = [
// make sure data was stored correctly.
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $this->storage->loadRevision($entity->getRevisionId()); $entity = $entity->getTranslation($active_langcode); /** @var \Drupal\Core\Entity\ContentEntityInterface $default_entity */ $default_entity = $this->storage->loadUnchanged($entity->id());
// Verify that the values for the current revision translation match the
// expected ones, while for the other translations they match the default
// revision. We also need to verify that only the current revision
// translation was marked as affected.
foreach($entity->getTranslationLanguages()as$langcode => $language){ $translation = $entity->getTranslation($langcode); $rta_expected = $langcode == $active_langcode||($untranslatable_update && $all_translations_affected); $this->assertEquals($rta_expected, $translation->isRevisionTranslationAffected(), $this->formatMessage("'$langcode' translation incorrectly affected")); $label_expected = $label; if($langcode !== $active_langcode){ $default_translation = $default_entity->hasTranslation($langcode) ? $default_entity->getTranslation($langcode) : $default_entity; $label_expected = $default_translation->label(); } $this->assertEquals($label_expected, $translation->label(), $this->formatMessage("Incorrect '$langcode' translation label")); } }
// untranslatable fields in pending revisions for multilingual entities. The
// only case where changes in pending revisions are acceptable is when
// untranslatable fields affect only the default translation, in which case
// a pending revision contains only one affected translation. Even in this
// case, multiple translations would be affected in a single revision, if we
// allowed changes to untranslatable fields while editing non-default
// translations, so that is forbidden too. For the same reason, when changes
// to untranslatable fields affect all translations, we can only allow them
// in default revisions.
if($this->hasUntranslatableFieldsChanges($entity)){ if($entity->isDefaultTranslationAffectedOnly()){ foreach($entity->getTranslationLanguages(FALSE)as$langcode => $language){ if($entity->getTranslation($langcode)->hasTranslationChanges()){ $this->context->addViolation($constraint->defaultTranslationMessage); break; } } } else{ $this->context->addViolation($constraint->defaultRevisionMessage); } } }
// We need to update the entity, so that the destination row IDs are
// correct.
$entity = $this->updateEntity($entity, $row); $entity->isDefaultRevision(TRUE); if($entity instanceof EntityChangedInterface && $entity instanceof ContentEntityInterface){ // If we updated any untranslatable fields, update the timestamp for the
// other translations.
/** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\EntityChangedInterface $entity */ foreach($entity->getTranslationLanguages()as$langcode => $language){ // If we updated an untranslated field, then set the changed time for
// for all translations to match the current row that we are saving.
// In this context, getChangedTime() should return the value we just
// set in the updateEntity() call above.
if($entity->getTranslation($langcode)->hasTranslationChanges()){ $entity->getTranslation($langcode)->setChangedTime($entity->getChangedTime()); } } } return$entity; }
$field_langcodes = array_keys($entity->getTranslationLanguages()); sort($field_langcodes); $this->assertEquals($translation_langcodes, $field_langcodes, 'Missing translations did not get a default value.');
// @todo Test every translation once the Entity Translation API allows for
// multilingual defaults.
$langcode = $entity->language()->getId(); $this->assertEquals($field->getDefaultValueLiteral(), $entity->getTranslation($langcode)->{$field_name_default}->getValue(), newFormattableMarkup('Default value correctly populated for language %language.', ['%language' => $langcode]));
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id); // Check that explicit empty values are not overridden with default values.
foreach([NULL, []]as$empty_items){
trait EntityChangedTrait {
/**
* Returns the timestamp of the last entity change across all translations.
*
* @return int
* The timestamp of the last entity save operation across all
* translations.
*/ publicfunctiongetChangedTimeAcrossTranslations(){ $changed = $this->getUntranslated()->getChangedTime(); foreach($this->getTranslationLanguages(FALSE)as$language){ $translation_changed = $this->getTranslation($language->getId())->getChangedTime(); $changed = max($translation_changed, $changed); } return$changed; }
/**
* Gets the timestamp of the last entity change for the current translation.
*
* @return int|null
* The timestamp of the last entity save operation. Some entities allow a
* NULL value indicating the changed time is unknown.
*/
// If we have defined a granted langcode, use it. But if not, add a grant
// for every language this node is translated to.
$fallback_langcode = $node->getUntranslated()->language()->getId(); foreach($grantsas$grant){ if($realm && $realm != $grant['realm']){ continue; } if(isset($grant['langcode'])){ $grant_languages = [$grant['langcode'] => $this->languageManager->getLanguage($grant['langcode'])]; } else{ $grant_languages = $node->getTranslationLanguages(TRUE); } foreach($grant_languagesas$grant_langcode => $grant_language){ // Only write grants; denies are implicit.
if($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']){ $grant['nid'] = $node->id(); $grant['langcode'] = $grant_langcode; // The record with the original langcode is used as the fallback.
if($grant['langcode'] == $fallback_langcode){ $grant['fallback'] = 1; } else{
$node = $this->loadNode(1); $this->assertNull($node, '1: Node has been deleted'); $node = $this->loadNode(2); $this->assertNull($node, '2: Node has been deleted'); $node = $this->loadNode(3); $result = count($node->getTranslationLanguages()) && $node->language()->getId() == 'it'; $this->assertTrue($result, '3: English translation has been deleted'); $node = $this->loadNode(4); $this->assertNull($node, '4: Node has been deleted'); $node = $this->loadNode(5); $this->assertNotEmpty($node, '5: Node has not been deleted');
/**
* {@inheritdoc}
*/ publicfunctiongetTranslationAccess(EntityInterface $entity, $op){ // @todo Move this logic into a translation access control handler checking also
// the translation language and the given account.
// Check that a non-revisionable new entity is handled correctly.
$entity = EntityTestMul::create(); $this->assertEmpty($entity->getTranslationLanguages(FALSE)); $this->assertFalse($method->invoke($storage, $entity)); $entity->addTranslation('it'); $this->assertNotEmpty($entity->getTranslationLanguages(FALSE)); $this->assertFalse($method->invoke($storage, $entity));
// Check that not yet stored translations are handled correctly.
$entity = EntityTestMul::create(); $entity->save(); $entity->addTranslation('it'); $this->assertNotEmpty($entity->getTranslationLanguages(FALSE)); $this->assertFalse($method->invoke($storage, $entity));
// If we have no information about what to sync to, if we are creating a new
// entity, if we have no translations for the current entity and we are not
// creating one, then there is nothing to synchronize.
if(empty($sync_langcode) || $entity->isNew() || count($translations) < 2){ return; }
/**
* Checks whether any stored entity revision is translated.
*
* A revisionable entity can have translations in a pending revision, hence
* the default revision may appear as not translated. This determines whether
* the entity has any translation in the storage and thus should be considered
* as multilingual.
*
* @param \Drupal\Core\Entity\TranslatableInterface $entity
* The entity object to be checked.
*
* @return bool
* TRUE if the entity has at least one translation in any revision, FALSE
* otherwise.
*
* @see \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages()
* @see \Drupal\Core\Entity\ContentEntityStorageBase::isAnyRevisionTranslated()
*/