You are here

class Exporter in GatherContent 8.5

Class for handling import/update logic from GatherContent to Drupal.

Hierarchy

Expanded class hierarchy of Exporter

1 file declares its use of Exporter
GatherContentUploadTestBase.php in gathercontent_upload/tests/src/Kernel/GatherContentUploadTestBase.php

File

gathercontent_upload/src/Export/Exporter.php, line 29

Namespace

Drupal\gathercontent_upload\Export
View source
class Exporter implements ContainerInjectionInterface {

  /**
   * Drupal GatherContent Client.
   *
   * @var \Drupal\gathercontent\DrupalGatherContentClient
   */
  protected $client;

  /**
   * Meta tag Query.
   *
   * @var \Drupal\gathercontent\MetatagQuery
   */
  protected $metatag;

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Content translation manager.
   *
   * @var \Drupal\content_translation\ContentTranslationManagerInterface
   */
  protected $contentTranslation;

  /**
   * Filesystem service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * Collected reference revisions.
   *
   * @var array
   */
  protected $collectedReferenceRevisions = [];

  /**
   * Collected file fields.
   *
   * @var array
   */
  protected $collectedFileFields = [];

  /**
   * List of allowed repeatable Drupal field types.
   *
   * @var array
   */
  const ALLOWED_MULTI_VALUE_TYPES = [
    'text',
    'text_long',
    'text_with_summary',
  ];

  /**
   * Exporter constructor.
   *
   * @param \Cheppers\GatherContent\GatherContentClientInterface $client
   * @param \Drupal\gathercontent\MetatagQuery $metatag
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   */
  public function __construct(GatherContentClientInterface $client, MetatagQuery $metatag, EntityTypeManagerInterface $entityTypeManager, EventDispatcherInterface $eventDispatcher, ModuleHandlerInterface $moduleHandler, FileSystemInterface $fileSystem) {
    $this->client = $client;
    $this->metatag = $metatag;
    $this->entityTypeManager = $entityTypeManager;
    $this->eventDispatcher = $eventDispatcher;
    $this->moduleHandler = $moduleHandler;
    $this->fileSystem = $fileSystem;
    if ($this->moduleHandler
      ->moduleExists('content_translation')) {
      $this->contentTranslation = Drupal::service('content_translation.manager');
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('gathercontent.client'), $container
      ->get('gathercontent.metatag'), $container
      ->get('entity_type.manager'), $container
      ->get('event_dispatcher'), $container
      ->get('module_handler'), $container
      ->get('file_system'));
  }

  /**
   * Getter GatherContentClient.
   */
  public function getClient() {
    return $this->client;
  }

  /**
   * Exports the changes made in Drupal contents.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity entity object.
   * @param \Drupal\gathercontent\Entity\MappingInterface $mapping
   *   Mapping object.
   * @param int|null $gcId
   *   GatherContent ID.
   * @param array $context
   *   Batch context.
   *
   * @return int|null|string
   *   Returns entity ID.
   *
   * @throws \Exception
   */
  public function export(EntityInterface $entity, MappingInterface $mapping, $gcId = NULL, array &$context = []) {
    $this->collectedReferenceRevisions = [];
    $data = $this
      ->processGroups($entity, $mapping);
    $event = $this->eventDispatcher
      ->dispatch(GatherUploadContentEvents::PRE_NODE_UPLOAD, new PreNodeUploadEvent($entity, $data));

    /** @var \Drupal\gathercontent_upload\Event\PreNodeUploadEvent $event */
    $data = $event
      ->getGathercontentValues();
    if (!empty($gcId)) {
      $item = $this->client
        ->itemUpdatePost($gcId, $data['content'], $data['assets']);
      $this
        ->updateFileGcIds($item->assets);
    }
    else {
      $data['name'] = $entity
        ->label();
      $data['template_id'] = $mapping
        ->getGathercontentTemplateId();
      $item = $this->client
        ->itemPost($mapping
        ->getGathercontentProjectId(), new Item($data));
      $gcId = $item['data']->id;
      $this
        ->updateFileGcIds($item['meta']->assets);
    }
    $this->eventDispatcher
      ->dispatch(GatherUploadContentEvents::POST_NODE_UPLOAD, new PostNodeUploadEvent($entity, $data));
    if (empty($context['results']['mappings'][$mapping
      ->id()])) {
      $context['results']['mappings'][$mapping
        ->id()] = [
        'mapping' => $mapping,
        'gcIds' => [
          $gcId => [],
        ],
      ];
    }
    $context['results']['mappings'][$mapping
      ->id()]['gcIds'][$gcId][] = $entity;
    foreach ($this->collectedReferenceRevisions as $reference) {
      $context['results']['mappings'][$mapping
        ->id()]['gcIds'][$gcId][] = $reference;
    }
    return $entity
      ->id();
  }

  /**
   * Manages the panes and changes the Item object values.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity object.
   * @param \Drupal\gathercontent\Entity\MappingInterface $mapping
   *   Mappig object.
   *
   * @return array
   *   Returns Content array.
   *
   * @throws \Exception
   */
  public function processGroups(EntityInterface $entity, MappingInterface $mapping) {
    $mappingData = unserialize($mapping
      ->getData());
    if (empty($mappingData)) {
      throw new Exception("Mapping data is empty.");
    }
    $templateData = unserialize($mapping
      ->getTemplate());
    $data = [
      'content' => [],
      'assets' => [],
    ];
    foreach ($templateData->related->structure->groups as $group) {
      $isTranslatable = $this->moduleHandler
        ->moduleExists('content_translation') && $this->contentTranslation
        ->isEnabled($mapping
        ->getMappedEntityType(), $mapping
        ->getContentType()) && isset($mappingData[$group->uuid]['language']) && $mappingData[$group->uuid]['language'] != Language::LANGCODE_NOT_SPECIFIED;
      if ($isTranslatable) {
        $language = $mappingData[$group->uuid]['language'];
      }
      else {
        $language = Language::LANGCODE_NOT_SPECIFIED;
      }
      $fields = $this
        ->processFields($group, $entity, $mappingData, $isTranslatable, $language);
      $data['content'] += $fields['content'];
      $data['assets'] += $fields['assets'];
    }
    return $data;
  }

  /**
   * Processes field data.
   *
   * @param object $group
   *   Group object.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity.
   * @param array $mappingData
   *   Mapping array.
   * @param bool $isTranslatable
   *   Translatable.
   * @param string $language
   *   Language.
   *
   * @return array
   *   Returns data.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function processFields(object $group, EntityInterface $entity, array $mappingData, bool $isTranslatable, string $language) {
    $exportedFields = [];
    $fields = [];
    $assets = [];
    foreach ($group->fields as $field) {

      // Skip field if it is not mapped.
      if (empty($mappingData[$group->uuid]['elements'][$field->uuid])) {
        continue;
      }
      $localFieldId = $mappingData[$group->uuid]['elements'][$field->uuid];
      if (isset($mappingData[$group->uuid]['type']) && $mappingData[$group->uuid]['type'] === 'content' || !isset($mappingData[$group->uuid]['type'])) {
        $localIdArray = explode('||', $localFieldId);

        /** @var \Drupal\field\Entity\FieldConfig $fieldInfo */
        $fieldInfo = FieldConfig::load($localIdArray[0]);
        $currentEntity = $entity;
        $type = '';
        $bundle = '';
        $titleField = $currentEntity
          ->getEntityTypeId() . '.' . $currentEntity
          ->bundle() . '.title';
        if ($localIdArray[0] === $titleField || $localIdArray[0] === 'title') {
          $currentFieldName = 'title';
        }
        else {
          $currentFieldName = $fieldInfo
            ->getName();
          $type = $fieldInfo
            ->getType();
          $bundle = $fieldInfo
            ->getTargetBundle();
        }

        // Get the deepest field's value, we need this to collect
        // the referenced entities values.
        $this
          ->processTargets($currentEntity, $currentFieldName, $type, $bundle, $exportedFields, $localIdArray, $isTranslatable, $language);
        $this->collectedReferenceRevisions[] = $currentEntity;
        $isRepeatable = FALSE;
        if ($fieldInfo) {
          $fieldType = $fieldInfo
            ->getType();

          // Field can be an entity reference.
          if (!in_array($fieldType, self::ALLOWED_MULTI_VALUE_TYPES)) {
            if (!empty($localIdArray[1])) {
              $fieldInfo = FieldConfig::load($localIdArray[1]);
            }
          }
          if ($fieldInfo) {
            $fieldType = $fieldInfo
              ->getType();
            $isMultiple = $fieldInfo
              ->getFieldStorageDefinition()
              ->isMultiple();
            $isGcFieldRepeatable = FALSE;
            if (property_exists($field, 'metadata')) {
              if (!empty($field->metadata) && property_exists($field->metadata, 'repeatable')) {
                $isGcFieldRepeatable = $field->metadata->repeatable->isRepeatable;
              }
            }
            if ($isMultiple && $isGcFieldRepeatable && in_array($fieldType, self::ALLOWED_MULTI_VALUE_TYPES)) {
              $isRepeatable = TRUE;
            }
          }
        }
        $value = $this
          ->processSetFields($field, $currentEntity, $isTranslatable, $language, $currentFieldName, $bundle, $isRepeatable);
        if (!empty($value)) {
          $fields[$field->uuid] = $value;
        }
        $asset = $this
          ->processSetAssets($field, $currentEntity, $isTranslatable, $language, $currentFieldName);
        if (!empty($asset)) {
          $assets[$field->uuid] = $asset;
        }
      }
      elseif ($mappingData[$group->uuid]['type'] === 'metatag') {
        if ($this->moduleHandler
          ->moduleExists('metatag') && $this->metatag
          ->checkMetatag($entity
          ->getEntityTypeId(), $entity
          ->bundle())) {
          $fields[$field->uuid] = $this
            ->processMetaTagFields($entity, $localFieldId, $isTranslatable, $language);
        }
      }
    }
    return [
      'content' => $fields,
      'assets' => $assets,
    ];
  }

  /**
   * Processes the target ids for a field.
   *
   * @param \Drupal\Core\Entity\EntityInterface $currentEntity
   *   Entity object.
   * @param string $currentFieldName
   *   Current field name.
   * @param string $type
   *   Current type name.
   * @param string $bundle
   *   Current bundle name.
   * @param array $exportedFields
   *   Array of exported fields, preventing duplications.
   * @param array $localIdArray
   *   Array of mapped embedded field id array.
   * @param bool $isTranslatable
   *   Translatable.
   * @param string $language
   *   Language.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function processTargets(EntityInterface &$currentEntity, string &$currentFieldName, string &$type, string &$bundle, array &$exportedFields, array $localIdArray, bool $isTranslatable, string $language) {
    $idCount = count($localIdArray);

    // Loop through the references, going deeper and deeper.
    for ($i = 0; $i < $idCount - 1; $i++) {
      $localId = $localIdArray[$i];
      $fieldInfo = FieldConfig::load($localId);
      $currentFieldName = $fieldInfo
        ->getName();
      $type = $fieldInfo
        ->getType();
      $bundle = $fieldInfo
        ->getTargetBundle();
      if ($isTranslatable && $currentEntity
        ->hasTranslation($language)) {
        $targetFieldValue = $currentEntity
          ->getTranslation($language)
          ->get($currentFieldName)
          ->getValue();
      }
      else {
        $targetFieldValue = $currentEntity
          ->get($currentFieldName)
          ->getValue();
      }

      // Load the targeted entity and process the data.
      if (!empty($targetFieldValue)) {
        $fieldTargetInfo = FieldConfig::load($localIdArray[$i + 1]);
        $entityStorage = $this->entityTypeManager
          ->getStorage($fieldTargetInfo
          ->getTargetEntityTypeId());
        $childFieldName = $fieldTargetInfo
          ->getName();
        $childType = $fieldInfo
          ->getType();
        $childBundle = $fieldInfo
          ->getTargetBundle();
        foreach ($targetFieldValue as $target) {
          $exportKey = $target['target_id'] . '_' . $childFieldName;

          // The field is already collected.
          if (!empty($exportedFields[$exportKey])) {
            continue;
          }
          $childEntity = $entityStorage
            ->loadByProperties([
            'id' => $target['target_id'],
            'type' => $fieldTargetInfo
              ->getTargetBundle(),
          ]);
          if (!empty($childEntity[$target['target_id']])) {
            $currentEntity = $childEntity[$target['target_id']];
            $currentFieldName = $childFieldName;
            $type = $childType;
            $bundle = $childBundle;
            if ($i == $idCount - 2) {
              $exportedFields[$exportKey] = TRUE;
            }
            break;
          }
        }
      }
    }
  }

  /**
   * Processes meta fields.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity object.
   * @param string $localFieldName
   *   Field name.
   * @param bool $isTranslatable
   *   Translatable bool.
   * @param string $language
   *   Language string.
   *
   * @return string
   *   Returns value.
   */
  public function processMetaTagFields(EntityInterface $entity, string $localFieldName, bool $isTranslatable, string $language) {
    $fieldName = $this->metatag
      ->getFirstMetatagField($entity
      ->getEntityTypeId(), $entity
      ->bundle());
    if ($isTranslatable && $entity
      ->hasTranslation($language)) {
      $currentValue = unserialize($entity
        ->getTranslation($language)->{$fieldName}->value);
    }
    else {
      $currentValue = unserialize($entity->{$fieldName}->value);
    }
    return $currentValue[$localFieldName] ?? '';
  }

  /**
   * Set value of the field.
   *
   * @param object $field
   *   Field object.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity object.
   * @param bool $isTranslatable
   *   Translatable bool.
   * @param string $language
   *   Language string.
   * @param string $localFieldName
   *   Field Name.
   * @param string $bundle
   *   Local field Info bundle string.
   * @param bool $isRepeatable
   *   Repeatable bool.
   *
   * @return array|string
   *   Returns value.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function processSetFields(object $field, EntityInterface $entity, bool $isTranslatable, string $language, string $localFieldName, string $bundle, bool $isRepeatable) {
    $value = NULL;
    switch ($field->field_type) {
      case 'attachment':

        // Fetch file targets.
        if ($isTranslatable && $entity
          ->hasTranslation($language)) {
          $targets = $entity
            ->getTranslation($language)->{$localFieldName}
            ->getValue();
        }
        else {
          $targets = $entity->{$localFieldName}
            ->getValue();
        }
        $value = [];
        foreach ($targets as $target) {
          $file = $this->entityTypeManager
            ->getStorage('file')
            ->load($target['target_id']);
          if (empty($file) || $file
            ->get('gc_file_id')
            ->isEmpty()) {
            continue;
          }
          $value[] = $file
            ->get('gc_file_id')
            ->first()
            ->getValue()['value'];
        }
        break;
      case 'choice_radio':
      case 'choice_checkbox':

        // Fetch local selected option.
        if ($isTranslatable && $entity
          ->hasTranslation($language)) {
          $targets = $entity
            ->getTranslation($language)->{$localFieldName}
            ->getValue();
        }
        else {
          $targets = $entity->{$localFieldName}
            ->getValue();
        }
        $value = [];
        foreach ($targets as $target) {
          $conditionArray = [
            'tid' => $target['target_id'],
          ];
          if ($isTranslatable && $this->moduleHandler
            ->moduleExists('content_translation') && $this->contentTranslation
            ->isEnabled('taxonomy_term', $bundle) && $language !== LanguageInterface::LANGCODE_NOT_SPECIFIED) {
            $conditionArray['langcode'] = $language;
          }
          $terms = $this->entityTypeManager
            ->getStorage('taxonomy_term')
            ->loadByProperties($conditionArray);

          /** @var \Drupal\taxonomy\Entity\Term $term */
          $term = array_shift($terms);
          if (!empty($term)) {
            $optionIds = $term->gathercontent_option_ids
              ->getValue();
            $options = $field->metadata->choice_fields->options;
            foreach ($optionIds as $optionId) {
              if (!$this
                ->validOptionId($options, $optionId['value'])) {
                continue;
              }
              $value[] = [
                'id' => $optionId['value'],
              ];
            }
          }
        }
        break;
      case 'guidelines':

        // We don't upload this because this field shouldn't be
        // edited.
        break;
      default:
        if ($localFieldName === 'title') {
          if ($isTranslatable && $entity
            ->hasTranslation($language)) {
            $value = $entity
              ->getTranslation($language)
              ->getTitle();
          }
          else {
            $value = $entity
              ->getTitle();
          }
        }
        else {
          if ($isTranslatable && $entity
            ->hasTranslation($language)) {
            if ($isRepeatable) {
              $value = $this
                ->getRepeatableFieldValues($entity
                ->getTranslation($language)->{$localFieldName});
            }
            else {
              $value = $entity
                ->getTranslation($language)->{$localFieldName}->value;
            }
          }
          else {
            if ($isRepeatable) {
              $value = $this
                ->getRepeatableFieldValues($entity->{$localFieldName});
            }
            else {
              $value = $entity->{$localFieldName}->value;
            }
          }
        }
        break;
    }
    return $value;
  }

  /**
   * Set assets.
   *
   * @param object $field
   *   Field object.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity object.
   * @param bool $isTranslatable
   *   Translatable bool.
   * @param string $language
   *   Language string.
   * @param string $localFieldName
   *   Field Name.
   *
   * @return array|string
   *   Returns value.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function processSetAssets(object $field, EntityInterface $entity, bool $isTranslatable, string $language, string $localFieldName) {
    $value = NULL;
    switch ($field->field_type) {
      case 'attachment':

        // Fetch file targets.
        if ($isTranslatable && $entity
          ->hasTranslation($language)) {
          $targets = $entity
            ->getTranslation($language)->{$localFieldName}
            ->getValue();
        }
        else {
          $targets = $entity->{$localFieldName}
            ->getValue();
        }
        $value = [];
        foreach ($targets as $target) {

          /** @var \Drupal\file\FileInterface $file */
          $file = $this->entityTypeManager
            ->getStorage('file')
            ->load($target['target_id']);
          if (empty($file) || !$file
            ->get('gc_file_id')
            ->isEmpty()) {
            continue;
          }
          $value[] = $this->fileSystem
            ->realpath($file
            ->getFileUri());
        }
        $this->collectedFileFields[$field->uuid] = $targets;
        break;
    }
    return $value;
  }

  /**
   * Updates the file managed table to include the new GC ID for a given file.
   *
   * @param array $returnedAssets
   *   The assets returned by GC.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function updateFileGcIds(array $returnedAssets) {
    if (empty($this->collectedFileFields) || empty($returnedAssets)) {
      return;
    }
    foreach ($this->collectedFileFields as $fieldUuid => $fileField) {
      if (empty($returnedAssets[$fieldUuid])) {
        continue;
      }
      foreach ($fileField as $delta => $target) {

        /** @var \Drupal\file\FileInterface $file */
        $file = $this->entityTypeManager
          ->getStorage('file')
          ->load($target['target_id']);
        if (empty($file) || empty($returnedAssets[$fieldUuid][$delta])) {
          continue;
        }
        $file
          ->set('gc_file_id', $returnedAssets[$fieldUuid][$delta]);
        $file
          ->save();
      }
    }
  }

  /**
   * Check if the given option ID is valid for the template.
   *
   * @param array $options
   *   Options array.
   * @param string $optionId
   *   Option ID.
   *
   * @return bool
   *   Returns if the option ID is valid for a given template.
   */
  protected function validOptionId(array $options, string $optionId) {
    foreach ($options as $option) {
      if ($option->optionId === $optionId) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Moves field values into an array.
   *
   * @param \Drupal\Core\Field\FieldItemListInterface $fieldItemList
   *   The list of field values.
   *
   * @return array
   *   Field values in an array.
   */
  protected function getRepeatableFieldValues(FieldItemListInterface $fieldItemList) : array {
    $fieldValues = $fieldItemList
      ->getValue();
    $values = [];
    foreach ($fieldValues as $fieldValue) {
      $values[] = $fieldValue['value'];
    }
    return $values;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Exporter::$client protected property Drupal GatherContent Client.
Exporter::$collectedFileFields protected property Collected file fields.
Exporter::$collectedReferenceRevisions protected property Collected reference revisions.
Exporter::$contentTranslation protected property Content translation manager.
Exporter::$entityTypeManager protected property Entity type manager.
Exporter::$eventDispatcher protected property Event dispatcher.
Exporter::$fileSystem protected property Filesystem service.
Exporter::$metatag protected property Meta tag Query.
Exporter::$moduleHandler protected property Module handler.
Exporter::ALLOWED_MULTI_VALUE_TYPES constant List of allowed repeatable Drupal field types.
Exporter::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create
Exporter::export public function Exports the changes made in Drupal contents.
Exporter::getClient public function Getter GatherContentClient.
Exporter::getRepeatableFieldValues protected function Moves field values into an array.
Exporter::processFields public function Processes field data.
Exporter::processGroups public function Manages the panes and changes the Item object values.
Exporter::processMetaTagFields public function Processes meta fields.
Exporter::processSetAssets public function Set assets.
Exporter::processSetFields public function Set value of the field.
Exporter::processTargets public function Processes the target ids for a field.
Exporter::updateFileGcIds public function Updates the file managed table to include the new GC ID for a given file.
Exporter::validOptionId protected function Check if the given option ID is valid for the template.
Exporter::__construct public function Exporter constructor.