You are here

public function LingotekContentTranslationService::saveTargetData in Lingotek Translation 8

Same name and namespace in other branches
  1. 8.2 src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  2. 4.0.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  3. 3.0.x src/LingotekContentTranslationService.php \Drupal\lingotek\LingotekContentTranslationService::saveTargetData()
  4. 3.1.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

ContentEntityInterface Returns the entity which translations are saved.

Overrides LingotekContentTranslationServiceInterface::saveTargetData

1 call to LingotekContentTranslationService::saveTargetData()
LingotekContentTranslationService::downloadDocument in src/LingotekContentTranslationService.php
Downloads a document from the Lingotek service for a given locale.

File

src/LingotekContentTranslationService.php, line 853
Contains \Drupal\lingotek\LingotekContentTranslationService.

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->entityManager
    ->getFieldStorageDefinitions($entity
    ->getEntityTypeId());
  $lock = \Drupal::lock();
  $lock_name = __FUNCTION__ . ':' . $entity
    ->getEntityTypeId() . ':' . $entity
    ->id();
  $held = $lock
    ->acquire($lock_name);

  // It is critical that we acquire a lock on this entity since we want to ensure
  // that our translation is saved.
  if (!$held) {
    if ($lock
      ->wait($lock_name) === FALSE) {
      $held = $lock
        ->acquire($lock_name);
    }
  }
  if (!$held) {

    // We were unable to acquire the lock even after waiting, so we have to bail.
    // (We don't have to call release() here since we never succeeded at acquiring it)
    throw new \Exception(new FormattableMarkup('Unable to acquire lock for entity @id of type @type.', [
      '@id' => $entity
        ->id(),
      '@type' => $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 ContentEntityInterface $entity */
    $revision = isset($data['_lingotek_metadata']) ? $data['_lingotek_metadata']['_entity_revision'] : NULL;
    $revision = $this
      ->loadUploadedRevision($entity, $revision);

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

    /** @var 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
        ->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') {
          $target_entity_type_id = $field_definition
            ->getFieldStorageDefinition()
            ->getSetting('target_type');
          $index = 0;
          foreach ($field_data as $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->entityManager
              ->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 it\'s 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($index, $embedded_entity_id);
            }
            ++$index;
          }
        }
        else {
          if ($field_type === 'entity_reference_revisions') {
            $target_entity_type_id = $field_definition
              ->getFieldStorageDefinition()
              ->getSetting('target_type');
            $index = 0;
            foreach ($field_data as $field_item) {
              $embedded_entity_id = $revision->{$name}
                ->get($index)
                ->get('target_id')
                ->getValue();

              /** @var \Drupal\Core\Entity\RevisionableInterface $embedded_entity */
              $embedded_entity = $this->entityManager
                ->getStorage($target_entity_type_id)
                ->load($embedded_entity_id);
              $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.
              $translation->{$name}
                ->set($index, [
                'target_id' => $embedded_entity_id,
                'target_revision_id' => $embedded_entity
                  ->getRevisionId(),
              ]);
              ++$index;
            }
          }
          else {
            if ($field_type === 'path') {
              $pid = NULL;
              $source = '/' . $entity
                ->toUrl()
                ->getInternalPath();

              /** @var \Drupal\Core\Path\AliasStorageInterface $aliasStorage */
              $alias_storage = \Drupal::service('path.alias_storage');
              $path = $alias_storage
                ->load([
                'source' => $source,
                'langcode' => $langcode,
              ]);
              $original_path = $alias_storage
                ->load([
                'source' => $source,
                'langcode' => $entity
                  ->getUntranslated()
                  ->language()
                  ->getId(),
              ]);
              if ($path) {
                $pid = $path['pid'];
              }
              $alias = $field_data[0]['alias'];

              // Validate the alias before saving.
              if (!\Drupal::pathValidator()
                ->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.
                $alias = $original_path ? $original_path['alias'] : $source;
              }
              if ($alias !== NULL) {
                \Drupal::service('path.alias_storage')
                  ->save($source, $alias, $langcode, $pid);
              }
            }
            else {
              if ($field_type === 'metatag') {
                $index = 0;
                foreach ($field_data as $field_item) {
                  $metatag_value = serialize($field_item);
                  $translation->{$name}
                    ->set($index, $metatag_value);
                  ++$index;
                }
              }
              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, $property_data);
                    }
                    elseif ($translation
                      ->get($name)) {
                      $translation
                        ->get($name)
                        ->appendItem()
                        ->set($property, $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' ? NODE_PUBLISHED : NODE_NOT_PUBLISHED;
        $translation
          ->set($status_field, $status_value);
      }
    }
    $translation
      ->save();
    return $entity;
  } catch (EntityStorageException $storage_exception) {
    throw new LingotekContentEntityStorageException($entity, $storage_exception);
  } finally {

    // Ensure the lock is released, even if we crash.
    $lock
      ->release($lock_name);
  }
}