You are here

public function ContentEntityCdfNormalizer::denormalize in Acquia Content Hub 8

Denormalizes data back into an object of the given class.

Parameters

mixed $data: Data to restore.

string $class: The expected class to instantiate.

string $format: Format the given data was extracted from.

array $context: Options available to the denormalizer.

Return value

array Returns denormalized data.

File

src/Normalizer/ContentEntityCdfNormalizer.php, line 1176

Class

ContentEntityCdfNormalizer
Converts the Drupal entity object to a Acquia Content Hub CDF array.

Namespace

Drupal\acquia_contenthub\Normalizer

Code

public function denormalize($data, $class, $format = NULL, array $context = []) {
  $context += [
    'account' => NULL,
  ];

  // Exit if the class does not support denormalization of the given data,
  // class and format.
  if (!$this
    ->supportsDenormalization($data, $class, $format)) {
    return NULL;
  }
  $contenthub_entity = new ContentHubEntity($data);

  // Allow other modules to intercept and do changes to the Content Hub CDF
  // before it is denormalized to a Drupal Entity.
  $this->moduleHandler
    ->alter('acquia_contenthub_cdf_from_hub', $contenthub_entity);
  $entity_type = $contenthub_entity
    ->getType();
  $bundle_key = $this->entityTypeManager
    ->getDefinition($entity_type)
    ->getKey('bundle');
  $bundle = $contenthub_entity
    ->getAttribute($bundle_key) ? reset($contenthub_entity
    ->getAttribute($bundle_key)['value']) : NULL;
  $langcodes = !empty($contenthub_entity
    ->getAttribute('default_langcode')['value']) ? array_keys($contenthub_entity
    ->getAttribute('default_langcode')['value']) : [
    $this->languageManager
      ->getDefaultLanguage()
      ->getId(),
  ];

  // Get default langcode and remove from attributes.
  if (!empty($contenthub_entity
    ->getAttribute('default_langcode')['value'])) {
    foreach ($contenthub_entity
      ->getAttribute('default_langcode')['value'] as $key => $value) {
      if ($value[0] == TRUE) {
        $default_langcode = $key;
        continue;
      }
    }
  }
  else {
    if ($entity_type == 'file') {
      $default_langcode = key($data['attributes']['url']['value']);
      $langcodes = array_keys($data['attributes']['url']['value']);
    }
    else {
      $default_langcode = $this->languageManager
        ->getDefaultLanguage()
        ->getId();
      if ($langcode = $contenthub_entity
        ->getAttribute('langcode')) {
        $langcodes = $langcode['value'];
        if (!in_array($default_langcode, $langcodes)) {
          $default_langcode = reset($langcodes);
        }
      }
    }
  }

  // Store the translation source outside the CDF.
  $content_translation_source = $contenthub_entity
    ->getAttribute('content_translation_source');
  $contenthub_entity
    ->removeAttribute('content_translation_source');

  // Make sure those langcodes exist in the site.
  $site_langcodes = array_keys($this->languageManager
    ->getLanguages());
  if (!in_array($default_langcode, $site_langcodes, TRUE)) {
    $langcodes = array_intersect($site_langcodes, $langcodes);

    // The default language in the CDF does not exist in the site, then we
    // can use the site default's language as the entity default language and
    // if that does not exist either, then just take the first language of
    // the available ones in this site.
    $site_default_language = $this->languageManager
      ->getDefaultLanguage()
      ->getId();
    $default_langcode = in_array($site_default_language, $langcodes, TRUE) ? $site_default_language : reset($langcodes);
  }

  // Default Langcode is only used for initial entity creation. Remove now.
  $contenthub_entity
    ->removeAttribute('langcode');

  // Does this entity exist in this site already?
  $source_entity = $this->entityRepository
    ->loadEntityByUuid($entity_type, $contenthub_entity
    ->getUuid());
  $this
    ->processPassAlias($entity_type, $contenthub_entity, $langcodes);
  if ($source_entity == NULL) {

    // Transforming Content Hub Entity into a Drupal Entity.
    $values = [
      'uuid' => $contenthub_entity
        ->getUuid(),
    ];
    if ($bundle) {
      $values[$bundle_key] = $bundle;
    }

    // Set the content_translation source of whatever the default langcode
    // says.
    $values['content_translation_source'] = $content_translation_source['value'][$default_langcode][0] ?? NULL;
    if (empty($values['content_translation_source'])) {
      unset($values['content_translation_source']);

      // Set the default langcode of the parent entity.
      $values['default_langcode'] = $default_langcode == $this->languageManager
        ->getDefaultLanguage()
        ->getId();
    }
    else {
      if (!in_array($values['content_translation_source'], $langcodes, TRUE)) {
        $values['content_translation_source'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
        $contenthub_entity
          ->removeAttribute('default_langcode');
      }
      else {

        // Set the default langcode of the parent entity.
        $values['default_langcode'] = $default_langcode;
      }
    }

    // Special treatment according to entity types.
    switch ($entity_type) {
      case 'node':
        $author = $contenthub_entity
          ->getAttribute('author') ? $contenthub_entity
          ->getAttribute('author')['value'][$default_langcode] : FALSE;
        $user = Uuid::isValid($author) ? $this->entityRepository
          ->loadEntityByUuid('user', $author) : \Drupal::currentUser();
        $values['uid'] = $user
          ->id() ? $user
          ->id() : 0;

        // Set a status for the default language entity.
        $status = $contenthub_entity
          ->getAttribute('status') ? $contenthub_entity
          ->getAttribute('status')['value'][$default_langcode] : 0;
        $values['status'] = $status ? $status : 0;

        // Check if Workbench Moderation or Content Moderation modules are
        // enabled, if so, set the moderation_state to "published".
        $workbench_moderation_enabled = \Drupal::moduleHandler()
          ->moduleExists('workbench_moderation');
        $content_moderation_enabled = \Drupal::moduleHandler()
          ->moduleExists('content_moderation');
        if ($values['status'] && ($workbench_moderation_enabled || $content_moderation_enabled)) {
          $values['moderation_state'] = 'published';
        }
        break;
      case 'media':
        $attribute = $contenthub_entity
          ->getAttribute($bundle_key);
        foreach ($langcodes as $lang) {
          if (isset($attribute['value'][$lang])) {
            $value = reset($attribute['value'][$lang]);

            // Media entity didn't import by previous version of the module.
            $values[$bundle_key] = $value;
          }
        }

        // Remove an attribute to avoid the 'Error reading entity with
        // UUID="image" from Content Hub' error.
        if (!empty($values[$bundle_key])) {
          $contenthub_entity
            ->removeAttribute($bundle_key);
        }
        break;
      case 'file':

        // If this is a file, then download the asset (image) locally.
        $attribute = $contenthub_entity
          ->getAttribute('url');
        foreach ($langcodes as $lang) {
          if (isset($attribute['value'][$lang])) {
            $remote_uri = is_array($attribute['value'][$lang]) ? array_values($attribute['value'][$lang])[0] : $attribute['value'][$lang];
            $filepath = $this
              ->getFilePath($contenthub_entity);
            if ($file_drupal_path = system_retrieve_file($remote_uri, $filepath, FALSE)) {
              $values['uri'] = $file_drupal_path;
            }
            else {

              // If the file URL is not publicly accessible, then this file
              // entity cannot be created. There is no point in trying to
              // complete the creation of this entity because it will fail
              // to be saved in the system.
              // Return a NULL entity and deal with it afterwards.
              $message = $this
                ->t('File Entity with UUID = "%uuid" cannot be created: The remote resource %uri could not be downloaded into the system. Make sure this resource has a publicly accessible URL.', [
                '%uuid' => $values['uuid'],
                '%uri' => $remote_uri,
              ]);
              $this->loggerFactory
                ->get('acquia_contenthub')
                ->error($message);
              \Drupal::messenger()
                ->addError($message);
              return NULL;
            }
          }
        }
        break;
      case 'taxonomy_term':

        // If it is a taxonomy_term, assing the vocabulary.
        // @todo This is a hack. It should work with vocabulary entities.
        $attribute = $contenthub_entity
          ->getAttribute('vocabulary');
        foreach ($langcodes as $lang) {
          $vocabulary_machine_name = $attribute['value'][$lang];
          $vocabulary = $this
            ->getVocabularyByName($vocabulary_machine_name);
          if (isset($vocabulary)) {
            $values['vid'] = $vocabulary
              ->getOriginalId();
          }
        }
        break;
      case 'paragraph':

        // In case of paragraphs, we need to strip out the parent_uuid and
        // change it for parent_id.
        // Fix a bug happening during export where parent_uuid only includes
        // one language, which will fail during multilingual import. Extract
        // the parent UUID and replicate it into all the languages.
        $parent_uuid = current(array_filter($contenthub_entity
          ->getAttribute('parent_uuid')['value']));
        $parent_type = current(current(array_filter($contenthub_entity
          ->getAttribute('parent_type')['value'])));
        $parent_entity = $this->entityRepository
          ->loadEntityByUuid($parent_type, $parent_uuid);
        $parent_id_attribute = new Attribute(Attribute::TYPE_ARRAY_STRING);
        foreach ($langcodes as $lang) {
          $parent_id_attribute
            ->setValue([
            $parent_entity
              ->id(),
          ], $lang);
        }

        // Add parent_id attribute to entity and remove parent_uuid.
        $attributes = $contenthub_entity
          ->getAttributes();
        $attributes['parent_id'] = (array) $parent_id_attribute;
        $contenthub_entity
          ->setAttributes($attributes);
        $contenthub_entity
          ->removeAttribute('parent_uuid');
        break;
    }
    if ($contenthub_entity
      ->getAttribute('path_uuid')) {
      $contenthub_entity
        ->removeAttribute('path_uuid');
    }
    $langcode_key = $this->entityTypeManager
      ->getDefinition($entity_type)
      ->getKey('langcode');
    $values[$langcode_key] = [
      $default_langcode,
    ];
    $source_entity = $this->entityTypeManager
      ->getStorage($entity_type)
      ->create($values);
  }
  else {
    $contenthub_entity
      ->removeAttribute('default_langcode');
    $delete_translations = $this->config
      ->get('acquia_contenthub.entity_config')
      ->get('delete_mismatching_translations');
    if ($delete_translations) {

      // Make sure that if there are local translations that have been deleted
      // from Content Hub, they are deleted locally too.
      $local_languages = $source_entity
        ->getTranslationLanguages();
      $local_langcodes = array_keys($local_languages);
      $delete_translations = array_diff($local_langcodes, array_values($langcodes));
      foreach ($delete_translations as $lang) {
        if ($source_entity
          ->hasTranslation($lang)) {
          try {
            $translated = $source_entity
              ->getTranslation($lang);

            // We cannot remove default translations.
            if (!$translated
              ->isDefaultTranslation()) {
              $source_entity
                ->removeTranslation($lang);
            }
          } catch (\Exception $e) {
            $this->loggerFactory
              ->get('acquia_contenthub')
              ->error('Cannot remove translation "@lang" for entity type = @type, id = @id, uuid = @uuid: @message', [
              '@lang' => $lang,
              '@type' => $source_entity
                ->getEntityTypeId(),
              '@id' => $source_entity
                ->id(),
              '@uuid' => $source_entity
                ->uuid(),
              '@message' => $e
                ->getMessage(),
            ]);
          }
        }
      }
    }
  }
  $entity = $source_entity;
  foreach ($langcodes as $langcode) {

    // If this language exist in incoming data but is not supported in the
    // importing site, don't import the data under this language.
    if (!$this->languageManager
      ->getLanguage($langcode)) {
      continue;
    }

    // Make sure the entity language is one of the language contained in the
    // Content Hub Entity.
    if ($source_entity
      ->hasTranslation($langcode)) {
      $localized_entity = $source_entity
        ->getTranslation($langcode);
      $entity = $this
        ->addFieldsToDrupalEntity($localized_entity, $contenthub_entity, $langcode, $context);
      continue;
    }
    if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED || $langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
      $entity = $this
        ->addFieldsToDrupalEntity($source_entity, $contenthub_entity, $langcode, $context);
      continue;
    }
    $localized_entity = $source_entity
      ->addTranslation($langcode, $source_entity
      ->toArray());
    $localized_entity->content_translation_source = $content_translation_source['value'][$langcode][0];

    // Grab status for the language.
    $status = $contenthub_entity
      ->getAttribute('status') ? $contenthub_entity
      ->getAttribute('status')['value'][$langcode] : 0;
    $localized_entity->status = $status ? $status : 0;
    $entity = $this
      ->addFieldsToDrupalEntity($localized_entity, $contenthub_entity, $langcode, $context);
  }

  // Allow other modules to intercept and do changes to the Drupal entity
  // after it has been denormalized from a Content Hub CDF.
  $this->moduleHandler
    ->alter('acquia_contenthub_drupal_from_cdf', $entity_type, $entity);
  return $entity;
}