You are here

public function LingotekContentTranslationService::saveTargetData in Lingotek Translation 3.1.x

Same name and namespace in other branches
  1. 8 src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  2. 8.2 src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  3. 4.0.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  4. 3.0.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  5. 3.2.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  6. 3.3.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  7. 3.4.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  8. 3.5.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  9. 3.6.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  10. 3.7.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  11. 3.8.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()

Save the entity translation.

Parameters

\Drupal\Core\Entity\ContentEntityInterface &$entity: The entity we want to save a translation for.

$locale: The locale of the translation being saved.

$data: The data being saved.

Return value

\Drupal\Core\Entity\ContentEntityInterface Returns the entity which translations are saved.

Overrides LingotekContentTranslationServiceInterface::saveTargetData

2 calls to LingotekContentTranslationService::saveTargetData()
LingotekContentTranslationService::downloadDocument in src/LingotekContentTranslationService.php
Downloads a document from the Lingotek service for a given locale.
LingotekContentTranslationService::downloadDocuments in src/LingotekContentTranslationService.php
Downloads a document from the Lingotek service for all available locales.

File

src/LingotekContentTranslationService.php, line 1272

Class

LingotekContentTranslationService
Service for managing Lingotek content translations.

Namespace

Drupal\lingotek

Code

public function saveTargetData(ContentEntityInterface &$entity, $langcode, $data) {

  // Without a defined langcode, we can't proceed
  if (!$langcode) {

    // TODO: log warning that downloaded translation's langcode is not enabled.
    return FALSE;
  }
  $storage_definitions = $this->entityFieldManager
    ->getFieldStorageDefinitions($entity
    ->getEntityTypeId());
  try {

    // We need to load the revision that was uploaded for consistency. For that,
    // we check if we have a valid revision in the response, and if not, we
    // check the date of the uploaded document.

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $revision = isset($data['_lingotek_metadata']) && isset($data['_lingotek_metadata']['_entity_revision']) ? $data['_lingotek_metadata']['_entity_revision'] : NULL;
    $revision = $this
      ->loadUploadedRevision($entity, $revision);

    // Initialize the translation on the Drupal side, if necessary.

    /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
    if (!$entity
      ->hasTranslation($langcode)) {
      $entity
        ->addTranslation($langcode, $revision
        ->toArray());
    }
    $translation = $entity
      ->getTranslation($langcode);
    foreach ($data as $name => $field_data) {
      if (strpos($name, '_') === 0) {

        // Skip special fields underscored.
        break;
      }
      $field_definition = $entity
        ->getFieldDefinition($name);
      if ($field_definition && ($field_definition
        ->isTranslatable() || $field_definition
        ->getType() === 'entity_reference_revisions') && $this->lingotekConfiguration
        ->isFieldLingotekEnabled($entity
        ->getEntityTypeId(), $entity
        ->bundle(), $name)) {

        // First we check if this is a entity reference, and save the translated entity.
        $field_type = $field_definition
          ->getType();
        if ($field_type === 'entity_reference' || $field_type === 'er_viewmode' || $field_type === 'bricks') {
          $target_entity_type_id = $field_definition
            ->getFieldStorageDefinition()
            ->getSetting('target_type');
          $translation->{$name} = NULL;
          $delta = 0;
          foreach ($field_data as $index => $field_item) {
            if (isset($field_item['_lingotek_metadata'])) {
              $target_entity_type_id = $field_item['_lingotek_metadata']['_entity_type_id'];
              $embedded_entity_id = $field_item['_lingotek_metadata']['_entity_id'];
              $embedded_entity_revision_id = $field_item['_lingotek_metadata']['_entity_revision'];
            }
            else {

              // Try to get it from the revision itself. It may have been
              // modified, so this can be a source of errors, but we need this
              // because we didn't have metadata before.
              $embedded_entity_id = $revision->{$name}
                ->get($index)
                ->get('target_id')
                ->getValue();
            }
            $embedded_entity = $this->entityTypeManager
              ->getStorage($target_entity_type_id)
              ->load($embedded_entity_id);

            // We may have orphan references, so ensure that they exist before
            // continuing.
            if ($embedded_entity !== NULL) {

              // ToDo: It can be a content entity, or a config entity.
              if ($embedded_entity instanceof ContentEntityInterface) {
                if ($this->lingotekConfiguration
                  ->isEnabled($embedded_entity
                  ->getEntityTypeId(), $embedded_entity
                  ->bundle())) {
                  $this
                    ->saveTargetData($embedded_entity, $langcode, $field_item);
                }
                else {
                  \Drupal::logger('lingotek')
                    ->warning($this
                    ->t('Field %field not saved as its referenced entity is not translatable by Lingotek', [
                    '%field' => $name,
                  ]));
                }
              }
              elseif ($embedded_entity instanceof ConfigEntityInterface) {
                $this->lingotekConfigTranslation
                  ->saveTargetData($embedded_entity, $langcode, $field_item);
              }

              // Now the embedded entity is saved, but we need to ensure
              // the reference will be saved too.
              $translation->{$name}
                ->set($delta, $embedded_entity_id);
              $delta++;
            }
          }
        }
        elseif ($field_type === 'entity_reference_revisions') {
          $paragraphTranslatable = $field_definition
            ->isTranslatable();
          $target_entity_type_id = $field_definition
            ->getFieldStorageDefinition()
            ->getSetting('target_type');
          if ($paragraphTranslatable) {
            $translation->{$name} = NULL;
          }
          $delta = 0;
          $fieldValues = [];
          foreach ($field_data as $index => $field_item) {
            $embedded_entity_id = $revision->{$name}
              ->get($index)
              ->get('target_id')
              ->getValue();

            /** @var \Drupal\Core\Entity\RevisionableInterface $embedded_entity */
            $embedded_entity = $this->entityTypeManager
              ->getStorage($target_entity_type_id)
              ->load($embedded_entity_id);
            if ($embedded_entity !== NULL) {

              // If there is asymmetrical paragraphs enabled, we need a new one duplicated and stored.
              if ($paragraphTranslatable && \Drupal::moduleHandler()
                ->moduleExists('paragraphs_asymmetric_translation_widgets')) {

                /** @var \Drupal\paragraphs\ParagraphInterface $duplicate */
                $duplicate = $embedded_entity
                  ->createDuplicate();
                if ($duplicate
                  ->isTranslatable()) {

                  // If there is already a translation for the language we
                  // want to set as default, we have to remove it. This should
                  // never happen, but there may different previous approaches
                  // to translating paragraphs, so we need to make sure the
                  // download does not break because of this.
                  if ($duplicate
                    ->hasTranslation($langcode)) {
                    $duplicate
                      ->removeTranslation($langcode);
                    $duplicate
                      ->save();
                  }
                  $duplicate
                    ->set('langcode', $langcode);
                  foreach ($duplicate
                    ->getTranslationLanguages(FALSE) as $translationLanguage) {
                    try {
                      $duplicate
                        ->removeTranslation($translationLanguage
                        ->getId());
                    } catch (\InvalidArgumentException $e) {

                      // Should never happen.
                    }
                  }
                }
                $embedded_entity = $duplicate;
              }
              $this
                ->saveTargetData($embedded_entity, $langcode, $field_item);

              // Now the embedded entity is saved, but we need to ensure
              // the reference will be saved too. Ensure it's the same revision.
              $fieldValues[$delta] = [
                'target_id' => $embedded_entity
                  ->id(),
                'target_revision_id' => $embedded_entity
                  ->getRevisionId(),
              ];
              $delta++;
            }
          }

          // If the paragraph was not translatable, we avoid at all costs to modify the field,
          // as this will override the source and may have unintended consequences.
          if ($paragraphTranslatable) {
            $translation->{$name} = $fieldValues;
          }
        }
        elseif ($field_type === 'path') {
          $pid = NULL;
          $source = '/' . $entity
            ->toUrl()
            ->getInternalPath();

          /** @var \Drupal\Core\Entity\EntityStorageInterface $aliasStorage */
          $alias_storage = $this->entityTypeManager
            ->getStorage('path_alias');

          /** @var \Drupal\path_alias\PathAliasInterface[] $original_paths */
          $original_paths = $alias_storage
            ->loadByProperties([
            'path' => $source,
            'langcode' => $entity
              ->getUntranslated()
              ->language()
              ->getId(),
          ]);
          $original_path = NULL;
          $alias = $field_data[0]['alias'];

          // Validate the alias before saving.
          if (!UrlHelper::isValid($alias)) {
            \Drupal::logger('lingotek')
              ->warning($this
              ->t('Alias for %type %label in language %langcode not saved, invalid uri "%uri"', [
              '%type' => $entity
                ->getEntityTypeId(),
              '%label' => $entity
                ->label(),
              '%langcode' => $langcode,
              '%uri' => $alias,
            ]));

            // Default to the original path.
            if (count($original_paths) > 0) {
              $original_path = reset($original_paths);
              $alias = $original_path
                ->getAlias();
            }
            else {
              $alias = $source;
            }
            if (\Drupal::moduleHandler()
              ->moduleExists('pathauto')) {
              $alias = '';
              $translation
                ->get($name)
                ->offsetGet(0)
                ->set('alias', $alias);
              $translation
                ->get($name)
                ->offsetGet(0)
                ->set('pathauto', TRUE);
            }
          }
          if ($alias !== NULL) {
            $translation
              ->get($name)
              ->offsetGet(0)
              ->set('alias', $alias);
            if (\Drupal::moduleHandler()
              ->moduleExists('pathauto') && !empty($alias) && $alias !== $original_path) {
              $translation
                ->get($name)
                ->offsetGet(0)
                ->set('pathauto', FALSE);
            }
          }
        }
        elseif ($field_type === 'metatag') {
          $index = 0;
          foreach ($field_data as $field_item) {
            $metatag_value = serialize($field_item);
            $translation->{$name}
              ->set($index, $metatag_value);
            ++$index;
          }
        }
        elseif ($field_type === 'block_field') {
          $translation->{$name} = NULL;
          foreach ($field_data as $index => $field_item) {

            /** @var \Drupal\Core\Block\BlockPluginInterface $block */
            $block = $revision
              ->get($name)
              ->get($index)
              ->getBlock();
            if ($block !== NULL) {
              $entityData = NULL;
              if (isset($field_item['entity'])) {
                $entityData = $field_item['entity'];
                unset($field_item['entity']);
              }
              $configuration = $block
                ->getConfiguration();
              $newConfiguration = array_replace($configuration, $field_item);
              $translation->{$name}
                ->set($index, [
                'plugin_id' => $block
                  ->getPluginId(),
                'settings' => $newConfiguration,
              ]);
              if ($entityData !== NULL) {
                $embedded_entity_id = NULL;
                if (isset($entityData['_lingotek_metadata'])) {
                  $target_entity_type_id = $entityData['_lingotek_metadata']['_entity_type_id'];
                  $embedded_entity_id = $entityData['_lingotek_metadata']['_entity_id'];
                  $embedded_entity_revision_id = $entityData['_lingotek_metadata']['_entity_revision'];
                  $embedded_entity = $this->entityTypeManager
                    ->getStorage($target_entity_type_id)
                    ->load($embedded_entity_id);

                  // We may have orphan references, so ensure that they exist before
                  // continuing.
                  if ($embedded_entity !== NULL) {
                    if ($embedded_entity instanceof ContentEntityInterface) {
                      if ($this->lingotekConfiguration
                        ->isEnabled($embedded_entity
                        ->getEntityTypeId(), $embedded_entity
                        ->bundle())) {
                        $this
                          ->saveTargetData($embedded_entity, $langcode, $entityData);
                      }
                      else {
                        \Drupal::logger('lingotek')
                          ->warning($this
                          ->t('Field %field not saved as its referenced entity is not translatable by Lingotek', [
                          '%field' => $name,
                        ]));
                      }
                    }
                  }
                }
              }
            }
          }
        }
        else {

          // Initialize delta in case there are no items in $field_data.
          $delta = -1;

          // Save regular fields.
          foreach ($field_data as $delta => $delta_data) {
            foreach ($delta_data as $property => $property_data) {
              $property_definition = $storage_definitions[$name]
                ->getPropertyDefinition($property);
              $data_type = $property_definition
                ->getDataType();
              if ($data_type === 'uri') {

                // Validate an uri.
                if (!\Drupal::pathValidator()
                  ->isValid($property_data)) {
                  \Drupal::logger('lingotek')
                    ->warning($this
                    ->t('Field %field for %type %label in language %langcode not saved, invalid uri "%uri"', [
                    '%field' => $name,
                    '%type' => $entity
                      ->getEntityTypeId(),
                    '%label' => $entity
                      ->label(),
                    '%langcode' => $langcode,
                    '%uri' => $property_data,
                  ]));

                  // Let's default to the original value given that there was a problem.
                  $property_data = $revision
                    ->get($name)
                    ->offsetGet($delta)->{$property};
                }
              }
              if (method_exists($translation
                ->get($name)
                ->offsetGet($delta), "set")) {
                $translation
                  ->get($name)
                  ->offsetGet($delta)
                  ->set($property, html_entity_decode($property_data));
              }
              elseif ($translation
                ->get($name)) {
                $translation
                  ->get($name)
                  ->appendItem()
                  ->set($property, html_entity_decode($property_data));
              }
            }
          }

          // Remove the rest of deltas that were no longer found in the document downloaded from lingotek.
          $continue = TRUE;
          while ($continue) {
            if ($translation
              ->get($name)
              ->offsetExists($delta + 1)) {
              $translation
                ->get($name)
                ->removeItem($delta + 1);
            }
            else {
              $continue = FALSE;
            }
          }
        }
      }
    }

    // We need to set the content_translation source so the files are synced
    // properly. See https://www.drupal.org/node/2544696 for more information.
    $translation
      ->set('content_translation_source', $entity
      ->getUntranslated()
      ->language()
      ->getId());
    $entity->lingotek_processed = TRUE;

    // Allow other modules to alter the translation before is saved.
    \Drupal::moduleHandler()
      ->invokeAll('lingotek_content_entity_translation_presave', [
      &$translation,
      $langcode,
      $data,
    ]);
    $status_field = $entity
      ->getEntityType()
      ->getKey('status');
    $status_field_definition = $entity
      ->getFieldDefinition($status_field);
    if ($status_field_definition !== NULL && $status_field_definition
      ->isTranslatable()) {
      $status_setting = $this->lingotekConfiguration
        ->getPreference('target_download_status');
      if ($status_setting !== "same-as-source") {
        $status_value = $status_setting === 'published' ? NodeInterface::PUBLISHED : NodeInterface::NOT_PUBLISHED;
        $translation
          ->set($status_field, $status_value);
      }
    }

    // If there is any content moderation module is enabled, we may need to
    // perform a transition in their workflow.

    /** @var \Drupal\lingotek\Moderation\LingotekModerationFactoryInterface $moderation_factory */
    $moderation_factory = \Drupal::service('lingotek.moderation_factory');
    $moderation_handler = $moderation_factory
      ->getModerationHandler();
    $moderation_handler
      ->performModerationTransitionIfNeeded($translation);
    if ($moderation_handler
      ->isModerationEnabled($translation) && $translation instanceof RevisionLogInterface) {
      $requestTime = \Drupal::time()
        ->getRequestTime();
      $translation
        ->setNewRevision(TRUE);
      $translation
        ->setRevisionUserId(\Drupal::currentUser()
        ->id());
      $translation
        ->setRevisionCreationTime($requestTime);
      $translation
        ->setRevisionTranslationAffected(TRUE);
      $translation
        ->setChangedTime($requestTime);
      $translation
        ->setRevisionLogMessage((string) new FormattableMarkup('Document translated into @langcode by Lingotek.', [
        '@langcode' => strtoupper($langcode),
      ]));
    }
    $translation
      ->save();
    return $entity;
  } catch (EntityStorageException $storage_exception) {
    $this
      ->setTargetStatus($entity, $langcode, Lingotek::STATUS_ERROR);
    throw new LingotekContentEntityStorageException($entity, $storage_exception, $storage_exception
      ->getMessage());
  }
}