/** * Set the entity of this source. */ public function setEntity(ContentEntityInterface $entity) { $this->entity = $entity; if ($this->entity->hasTranslation($this->getLanguage())) { $this->entity = $this->entity->getTranslation($this->getLanguage()); } }
/** * Ensures correct entity URLs with the method language-content-entity enabled. * * Test case with the method language-content-entity enabled and configured * with higher and also with lower priority than the method language-url. */ public function testEntityUrlLanguageWithLanguageContentEnabled() { // Define the method language-content-entity with a higher priority than // language-url. $config = $this->config('language.types'); $config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]); $config->set('negotiation.language_content.enabled', [LanguageNegotiationContentEntity::METHOD_ID => 0, LanguageNegotiationUrl::METHOD_ID => 1]); $config->save(); // Without being on an content entity route the default entity URL tests // should still pass. $this->testEntityUrlLanguage(); // Now switching to an entity route, so that the URL links are generated // while being on an entity route. $this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical'); // The method language-content-entity should run before language-url and // append query parameter for the content language and prevent language-url // from overwriting the url. $this->assertTrue(strpos($this->entity->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=en') !== FALSE); $this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=es') !== FALSE); $this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=fr') !== FALSE); // Define the method language-url with a higher priority than // language-content-entity. This configuration should match the default one, // where the language-content-entity is turned off. $config->set('negotiation.language_content.enabled', [LanguageNegotiationUrl::METHOD_ID => 0, LanguageNegotiationContentEntity::METHOD_ID => 1]); $config->save(); // The default entity URL tests should pass again with the current // configuration. $this->testEntityUrlLanguage(); }
/** * {@inheritdoc} */ public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode, $original_langcode = NULL) { $translations = $entity->getTranslationLanguages(); $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); // 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; } // If the entity language is being changed there is nothing to synchronize. $entity_type = $entity->getEntityTypeId(); $entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorage($entity_type)->loadUnchanged($entity->id()); if ($entity->getUntranslated()->language()->getId() != $entity_unchanged->getUntranslated()->language()->getId()) { return; } /** @var \Drupal\Core\Field\FieldItemListInterface $items */ foreach ($entity as $field_name => $items) { $field_definition = $items->getFieldDefinition(); $field_type_definition = $field_type_manager->getDefinition($field_definition->getType()); $column_groups = $field_type_definition['column_groups']; // Sync if the field is translatable, not empty, and the synchronization // setting is enabled. if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable() && !$items->isEmpty() && ($translation_sync = $field_definition->getThirdPartySetting('content_translation', 'translation_sync'))) { // Retrieve all the untranslatable column groups and merge them into // single list. $groups = array_keys(array_diff($translation_sync, array_filter($translation_sync))); if (!empty($groups)) { $columns = array(); foreach ($groups as $group) { $info = $column_groups[$group]; // A missing 'columns' key indicates we have a single-column group. $columns = array_merge($columns, isset($info['columns']) ? $info['columns'] : array($group)); } if (!empty($columns)) { $values = array(); foreach ($translations as $langcode => $language) { $values[$langcode] = $entity->getTranslation($langcode)->get($field_name)->getValue(); } // If a translation is being created, the original values should be // used as the unchanged items. In fact there are no unchanged items // to check against. $langcode = $original_langcode ?: $sync_langcode; $unchanged_items = $entity_unchanged->getTranslation($langcode)->get($field_name)->getValue(); $this->synchronizeItems($values, $unchanged_items, $sync_langcode, array_keys($translations), $columns); foreach ($translations as $langcode => $language) { $entity->getTranslation($langcode)->get($field_name)->setValue($values[$langcode]); } } } } } }
/** * Tests enabling the language negotiator language_content_entity. */ public function testEnabledLanguageContentNegotiator() { // Define the method language-url with a higher priority than // language-content-entity. This configuration should match the default one, // where the language-content-entity is turned off. $config = $this->config('language.types'); $config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]); $config->set('negotiation.language_content.enabled', [LanguageNegotiationUrl::METHOD_ID => 0, LanguageNegotiationContentEntity::METHOD_ID => 1]); $config->save(); // In order to reflect the changes for a multilingual site in the container // we have to rebuild it. $this->rebuildContainer(); // The tests for the default configuration should still pass. $this->testDefaultConfiguration(); // Define the method language-content-entity with a higher priority than // language-url. $config->set('negotiation.language_content.enabled', [LanguageNegotiationContentEntity::METHOD_ID => 0, LanguageNegotiationUrl::METHOD_ID => 1]); $config->save(); // In order to reflect the changes for a multilingual site in the container // we have to rebuild it. $this->rebuildContainer(); // The method language-content-entity should run before language-url and // append query parameter for the content language and prevent language-url // from overwriting the URL. $default_site_langcode = $this->config('system.site')->get('default_langcode'); // Now switching to an entity route, so that the URL links are generated // while being on an entity route. $this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical'); $translation = $this->entity; $this->drupalGet($translation->urlInfo()); $last = $this->container->get('state')->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertTrue($last_interface_language == $default_site_langcode && $last_interface_language == $last_content_language && $last_content_language == $translation->language()->getId(), 'Interface language and Content language are the same as the default translation language of the entity.'); $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); $translation = $this->entity->getTranslation('es'); $this->drupalGet($translation->urlInfo()); $last = $this->container->get('state')->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); $translation = $this->entity->getTranslation('fr'); $this->drupalGet($translation->urlInfo()); $last = $this->container->get('state')->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); }
/** * Populates target values with the source values. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity being translated. * @param \Drupal\Core\Language\LanguageInterface $source * The language to be used as source. * @param \Drupal\Core\Language\LanguageInterface $target * The language to be used as target. */ public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) { /* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */ $source_translation = $entity->getTranslation($source->getId()); $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray()); // Make sure we do not inherit the affected status from the source values. if ($entity->getEntityType()->isRevisionable()) { $target_translation->setRevisionTranslationAffected(NULL); } /** @var \Drupal\user\UserInterface $user */ $user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id()); $metadata = $this->manager->getTranslationMetadata($target_translation); // Update the translation author to current user, as well the translation // creation time. $metadata->setAuthor($user); $metadata->setCreatedTime(REQUEST_TIME); }
/** * Saves fields that use the shared tables. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. * @param string $table_name * (optional) The table name to save to. Defaults to the data table. * @param bool $new_revision * (optional) Whether we are dealing with a new revision. By default fetches * the information from the entity object. */ protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) { if (!isset($table_name)) { $table_name = $this->dataTable; } if (!isset($new_revision)) { $new_revision = $entity->isNewRevision(); } $revision = $table_name != $this->dataTable; if (!$revision || !$new_revision) { $key = $revision ? $this->revisionKey : $this->idKey; $value = $revision ? $entity->getRevisionId() : $entity->id(); // Delete and insert to handle removed values. $this->database->delete($table_name)->condition($key, $value)->execute(); } $query = $this->database->insert($table_name); foreach ($entity->getTranslationLanguages() as $langcode => $language) { $translation = $entity->getTranslation($langcode); $record = $this->mapToDataStorageRecord($translation, $table_name); $values = (array) $record; $query->fields(array_keys($values))->values($values); } $query->execute(); }
/** * Builds the entity translation for the provided translation data. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity for which the translation should be returned. * @param array $data * The translation data for the fields. * @param string $target_langcode * The target language. * * @return \Drupal\Core\Entity\ContentEntityInterface $translation * Translation data. */ protected function makePreview(ContentEntityInterface $entity, array $data, $target_langcode) { // If the translation for this language does not exist yet, initialize it. if (!$entity->hasTranslation($target_langcode)) { $entity->addTranslation($target_langcode, $entity->toArray()); } $embeded_fields = $this->config('tmgmt_content.settings')->get('embedded_fields'); $translation = $entity->getTranslation($target_langcode); foreach ($data as $name => $field_data) { foreach (Element::children($field_data) as $delta) { $field_item = $field_data[$delta]; foreach (Element::children($field_item) as $property) { $property_data = $field_item[$property]; // If there is translation data for the field property, save it. if (isset($property_data['#translation']['#text']) && $property_data['#translate']) { $translation->get($name)->offsetGet($delta)->set($property, $property_data['#translation']['#text']); } elseif (isset($embeded_fields[$entity->getEntityTypeId()][$name])) { $this->makePreview($translation->get($name)->offsetGet($delta)->{$property}, $property_data, $target_langcode); } } } } return $translation; }
/** * Populates the affected flag for all the revision translations. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * An entity object being saved. */ protected function populateAffectedRevisionTranslations(ContentEntityInterface $entity) { if ($this->entityType->isTranslatable() && $this->entityType->isRevisionable()) { $languages = $entity->getTranslationLanguages(); foreach ($languages as $langcode => $language) { $translation = $entity->getTranslation($langcode); // Avoid populating the value if it was already manually set. $affected = $translation->isRevisionTranslationAffected(); if (!isset($affected) && $translation->hasTranslationChanges()) { $translation->setRevisionTranslationAffected(TRUE); } } } }
/** * Saves values of fields that use dedicated tables. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity. * @param bool $update * TRUE if the entity is being updated, FALSE if it is being inserted. */ protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE) { $vid = $entity->getRevisionId(); $id = $entity->id(); $bundle = $entity->bundle(); $entity_type = $entity->getEntityTypeId(); $default_langcode = $entity->getUntranslated()->language()->getId(); $translation_langcodes = array_keys($entity->getTranslationLanguages()); $table_mapping = $this->getTableMapping(); if (!isset($vid)) { $vid = $id; } foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) { $storage_definition = $field_definition->getFieldStorageDefinition(); if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) { continue; } $table_name = $table_mapping->getDedicatedDataTableName($storage_definition); $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition); // Delete and insert, rather than update, in case a value was added. if ($update) { // Only overwrite the field's base table if saving the default revision // of an entity. if ($entity->isDefaultRevision()) { $this->database->delete($table_name)->condition('entity_id', $id)->execute(); } if ($this->entityType->isRevisionable()) { $this->database->delete($revision_name)->condition('entity_id', $id)->condition('revision_id', $vid)->execute(); } } // Prepare the multi-insert query. $do_insert = FALSE; $columns = array('entity_id', 'revision_id', 'bundle', 'delta', 'langcode'); foreach ($storage_definition->getColumns() as $column => $attributes) { $columns[] = $table_mapping->getFieldColumnName($storage_definition, $column); } $query = $this->database->insert($table_name)->fields($columns); if ($this->entityType->isRevisionable()) { $revision_query = $this->database->insert($revision_name)->fields($columns); } $langcodes = $field_definition->isTranslatable() ? $translation_langcodes : array($default_langcode); foreach ($langcodes as $langcode) { $delta_count = 0; $items = $entity->getTranslation($langcode)->get($field_name); $items->filterEmptyItems(); foreach ($items as $delta => $item) { // We now know we have something to insert. $do_insert = TRUE; $record = array('entity_id' => $id, 'revision_id' => $vid, 'bundle' => $bundle, 'delta' => $delta, 'langcode' => $langcode); foreach ($storage_definition->getColumns() as $column => $attributes) { $column_name = $table_mapping->getFieldColumnName($storage_definition, $column); // Serialize the value if specified in the column schema. $record[$column_name] = !empty($attributes['serialize']) ? serialize($item->{$column}) : $item->{$column}; } $query->values($record); if ($this->entityType->isRevisionable()) { $revision_query->values($record); } if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) { break; } } } // Execute the query if we have values to insert. if ($do_insert) { // Only overwrite the field's base table if saving the default revision // of an entity. if ($entity->isDefaultRevision()) { $query->execute(); } if ($this->entityType->isRevisionable()) { $revision_query->execute(); } } } }
/** * Populates target values with the source values. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity being translated. * @param \Drupal\Core\Language\LanguageInterface $source * The language to be used as source. * @param \Drupal\Core\Language\LanguageInterface $target * The language to be used as target. */ public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) { /* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */ $source_translation = $entity->getTranslation($source->getId()); $entity->addTranslation($target->getId(), $source_translation->toArray()); }
/** * Invokes a method on the Field objects within an entity. * * @param string $method * The method name. * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. */ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { $translation = $entity->getTranslation($langcode); foreach ($translation->getFields() as $field) { $field->{$method}(); } } }
/** * Saves translation data in an entity translation. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity for which the translation should be saved. * @param array $data * The translation data for the fields. * @param string $target_langcode * The target language. */ protected function doSaveTranslations(ContentEntityInterface $entity, array $data, $target_langcode) { // If the translation for this language does not exist yet, initialize it. if (!$entity->hasTranslation($target_langcode)) { $entity->addTranslation($target_langcode, $entity->toArray()); } $embeded_fields = \Drupal::config('tmgmt_content.settings')->get('embedded_fields'); $translation = $entity->getTranslation($target_langcode); $manager = \Drupal::service('content_translation.manager'); $manager->getTranslationMetadata($translation)->setSource($entity->language()->getId()); foreach ($data as $name => $field_data) { foreach (Element::children($field_data) as $delta) { $field_item = $field_data[$delta]; foreach (Element::children($field_item) as $property) { $property_data = $field_item[$property]; // If there is translation data for the field property, save it. if (isset($property_data['#translation']['#text']) && $property_data['#translate']) { $translation->get($name)->offsetGet($delta)->set($property, $property_data['#translation']['#text']); } elseif (isset($embeded_fields[$entity->getEntityTypeId()][$name])) { $this->doSaveTranslations($translation->get($name)->offsetGet($delta)->{$property}, $property_data, $target_langcode); } } } } $translation->save(); }
/** * Builds a table row for overview form. * * @param array ContentEntityInterface $entity * Data needed to build the list row. * @param array $bundles * The array of bundles. * * @return array */ public function overviewRow(ContentEntityInterface $entity, array $bundles) { $label = $entity->label() ?: $this->t('@type: @id', array('@type' => $entity->getEntityTypeId(), '@id' => $entity->id())); // Get existing translations and current job items for the entity // to determine translation statuses $translations = $entity->getTranslationLanguages(); $source_lang = $entity->language()->getId(); $current_job_items = tmgmt_job_item_load_latest('content', $entity->getEntityTypeId(), $entity->id(), $source_lang); $row = array('id' => $entity->id(), 'title' => $entity->hasLinkTemplate('canonical') ? $entity->toLink($label, 'canonical')->toString() : ($entity->label() ?: $entity->id())); if (count($bundles) > 1) { $row['bundle'] = isset($bundles[$entity->bundle()]) ? $bundles[$entity->bundle()] : t('Unknown'); } // Load entity translation specific data. $manager = \Drupal::service('content_translation.manager'); foreach (\Drupal::languageManager()->getLanguages() as $langcode => $language) { $translation_status = 'current'; if ($langcode == $source_lang) { $translation_status = 'original'; } elseif (!isset($translations[$langcode])) { $translation_status = 'missing'; } elseif ($translation = $entity->getTranslation($langcode)) { $metadata = $manager->getTranslationMetadata($translation); if ($metadata->isOutdated()) { $translation_status = 'outofdate'; } } $build = $this->buildTranslationStatus($translation_status, isset($current_job_items[$langcode]) ? $current_job_items[$langcode] : NULL); $row['langcode-' . $langcode] = ['data' => \Drupal::service('renderer')->render($build), 'class' => array('langstatus-' . $langcode)]; } return $row; }
/** * Checks whether the field values changed compared to the original entity. * * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * Field definition of field to compare for changes. * @param \Drupal\Core\Entity\ContentEntityInterface $entity * Entity to check for field changes. * @param \Drupal\Core\Entity\ContentEntityInterface $original * Original entity to compare against. * * @return bool * True if the field value changed from the original entity. */ protected function hasFieldValueChanged(FieldDefinitionInterface $field_definition, ContentEntityInterface $entity, ContentEntityInterface $original) { $field_name = $field_definition->getName(); $langcodes = array_keys($entity->getTranslationLanguages()); if ($langcodes !== array_keys($original->getTranslationLanguages())) { // If the list of langcodes has changed, we need to save. return TRUE; } foreach ($langcodes as $langcode) { $items = $entity->getTranslation($langcode)->get($field_name)->filterEmptyItems(); $original_items = $original->getTranslation($langcode)->get($field_name)->filterEmptyItems(); // If the field items are not equal, we need to save. if (!$items->equals($original_items)) { return TRUE; } } return FALSE; }