class ImportService in Entity Share 8.3
Class ImportService.
This class is responsible to handle import from ImportContext.
@package Drupal\entity_share_client\Service
Hierarchy
- class \Drupal\entity_share_client\Service\ImportService implements ImportServiceInterface uses StringTranslationTrait
Expanded class hierarchy of ImportService
1 string reference to 'ImportService'
- entity_share_client.services.yml in modules/
entity_share_client/ entity_share_client.services.yml - modules/entity_share_client/entity_share_client.services.yml
1 service uses ImportService
- entity_share_client.import_service in modules/
entity_share_client/ entity_share_client.services.yml - Drupal\entity_share_client\Service\ImportService
File
- modules/
entity_share_client/ src/ Service/ ImportService.php, line 26
Namespace
Drupal\entity_share_client\ServiceView source
class ImportService implements ImportServiceInterface {
use StringTranslationTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The remote manager service.
*
* @var \Drupal\entity_share_client\Service\RemoteManagerInterface
*/
protected $remoteManager;
/**
* The import config manipulator service.
*
* @var \Drupal\entity_share_client\Service\ImportConfigManipulatorInterface
*/
protected $importConfigManipulator;
/**
* The JSON:API helper service.
*
* @var \Drupal\entity_share_client\Service\JsonapiHelperInterface
*/
protected $jsonapiHelper;
/**
* The runtime import context.
*
* @var \Drupal\entity_share_client\RuntimeImportContext
*/
protected $runtimeImportContext;
/**
* The import processors instances by stages.
*
* @var \Drupal\entity_share_client\ImportProcessor\ImportProcessorInterface[][]
*/
protected $importProcessors;
/**
* RemoteManager constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Psr\Log\LoggerInterface $logger
* The logger service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\entity_share_client\Service\RemoteManagerInterface $remote_manager
* The remote manager service.
* @param \Drupal\entity_share_client\Service\ImportConfigManipulatorInterface $import_config_manipulator
* The import config manipulator service.
* @param \Drupal\entity_share_client\Service\JsonapiHelperInterface $jsonapi_helper
* The JSON:API helper service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, MessengerInterface $messenger, RemoteManagerInterface $remote_manager, ImportConfigManipulatorInterface $import_config_manipulator, JsonapiHelperInterface $jsonapi_helper) {
$this->entityTypeManager = $entity_type_manager;
$this->logger = $logger;
$this->messenger = $messenger;
$this->remoteManager = $remote_manager;
$this->importConfigManipulator = $import_config_manipulator;
$this->jsonapiHelper = $jsonapi_helper;
$this->runtimeImportContext = new RuntimeImportContext();
}
/**
* {@inheritdoc}
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/
public function importEntities(ImportContext $context, array $uuids, bool $is_batched = TRUE) {
if (!$this
->prepareImport($context)) {
return [];
}
// Add the selected UUIDs to the URL.
// We do not handle offset or limit as we provide a maximum of 50 UUIDs.
$url = $this->runtimeImportContext
->getChannelUrl();
$prepared_url = EntityShareUtility::prepareUuidsFilteredUrl($url, $uuids);
if ($is_batched) {
$batch = [
'title' => $this
->t('Import entities'),
'operations' => [
[
'\\Drupal\\entity_share_client\\ImportBatchHelper::importUrlBatch',
[
$context,
$prepared_url,
],
],
],
'finished' => '\\Drupal\\entity_share_client\\ImportBatchHelper::importUrlBatchFinished',
];
batch_set($batch);
}
else {
return $this
->importFromUrl($prepared_url);
}
}
/**
* {@inheritdoc}
*/
public function importChannel(ImportContext $context) {
if (!$this
->prepareImport($context)) {
return;
}
$log_variables = [
'@remote_id' => $context
->getRemoteId(),
'@channel_id' => $context
->getChannelId(),
];
$channel_count = $context
->getRemoteChannelCount();
// Check how much content is in the channel if the count has not been
// provided before.
if (empty($channel_count)) {
$url_uuid = $this->runtimeImportContext
->getChannelUrlUuid();
$response = $this->remoteManager
->jsonApiRequest($this->runtimeImportContext
->getRemote(), 'GET', $url_uuid);
$json = Json::decode((string) $response
->getBody());
if (isset($json['errors'])) {
$this->logger
->error('An error occurred while requesting the UUID URL for the remote website @remote_id and channel @channel_id', $log_variables);
$this->messenger
->addError($this
->t('An error occurred while requesting the UUID URL for the remote website @remote_id and channel @channel_id', $log_variables));
return;
}
elseif (!isset($json['meta']['count'])) {
$this->logger
->error('There is no count of the number of entities to import for the remote website @remote_id and channel @channel_id', $log_variables);
$this->messenger
->addError($this
->t('There is no count of the number of entities to import for the remote website @remote_id and channel @channel_id', $log_variables));
return;
}
$channel_count = $json['meta']['count'];
}
if ($channel_count == 0) {
$this->logger
->info('Nothing to import for the remote website @remote_id and channel @channel_id', $log_variables);
$this->messenger
->addMessage($this
->t('Nothing to import for the remote website @remote_id and channel @channel_id', $log_variables));
return;
}
// Using the number of entities on the channel, we can generate all the
// urls of the channel's pages, and so prepare all the operations.
$step = 50;
$url = $this->runtimeImportContext
->getChannelUrl();
$parsed_url = UrlHelper::parse($url);
$parsed_url['query']['page']['limit'] = $step;
// If the count is a multiple of the step, the last offset is equal to the
// number of content and so the last offset will have no data.
// So we remove 1.
$operations = [];
if ($channel_count >= $step) {
$offsets = range(0, $channel_count - 1, $step);
foreach ($offsets as $offset) {
$parsed_url['query']['page']['offset'] = $offset;
$query = UrlHelper::buildQuery($parsed_url['query']);
$prepared_url = $parsed_url['path'] . '?' . $query;
$operations[] = [
'\\Drupal\\entity_share_client\\ImportBatchHelper::importUrlBatch',
[
$context,
$prepared_url,
],
];
}
}
else {
$operations[] = [
'\\Drupal\\entity_share_client\\ImportBatchHelper::importUrlBatch',
[
$context,
$url,
],
];
}
$batch = [
'title' => $this
->t('Import channel'),
'operations' => $operations,
'progress_message' => $this
->t('Imported pages: @current of @total.'),
'finished' => '\\Drupal\\entity_share_client\\ImportBatchHelper::importUrlBatchFinished',
];
batch_set($batch);
}
/**
* {@inheritdoc}
*/
public function importFromUrl(string $url) {
$response = $this
->jsonApiRequest('GET', $url);
$json = Json::decode((string) $response
->getBody());
if (!isset($json['data'])) {
return [];
}
$entity_list_data = EntityShareUtility::prepareData($json['data']);
return $this
->importEntityListData($entity_list_data);
}
/**
* {@inheritdoc}
*/
public function importEntityListData(array $entity_list_data) {
$imported_entity_ids = [];
foreach (EntityShareUtility::prepareData($entity_list_data) as $entity_data) {
foreach ($this->importProcessors[ImportProcessorInterface::STAGE_PREPARE_ENTITY_DATA] as $import_processor) {
$import_processor
->prepareEntityData($this->runtimeImportContext, $entity_data);
}
foreach ($this->importProcessors[ImportProcessorInterface::STAGE_IS_ENTITY_IMPORTABLE] as $import_processor) {
if (!$import_processor
->isEntityImportable($this->runtimeImportContext, $entity_data)) {
// Skip the import process for this entity data.
continue 2;
}
}
foreach ($this->importProcessors[ImportProcessorInterface::STAGE_PREPARE_IMPORTABLE_ENTITY_DATA] as $import_processor) {
$import_processor
->prepareImportableEntityData($this->runtimeImportContext, $entity_data);
}
$processed_entity = $this
->getProcessedEntity($entity_data);
$imported_entity_ids[$processed_entity
->uuid()] = $processed_entity
->id();
// Prevent infinite loop.
// Check if we try to import an already imported entity translation.
// We can't check this in the STAGE_IS_ENTITY_IMPORTABLE stage because we
// need to obtain the entity ID to return it for entity reference fields.
$processed_entity_langcode = $processed_entity
->language()
->getId();
$processed_entity_uuid = $processed_entity
->uuid();
if ($this->runtimeImportContext
->isEntityTranslationImported($processed_entity_langcode, $processed_entity_uuid)) {
continue;
}
else {
// Store data to prevent the entity of being re-imported.
$this->runtimeImportContext
->addImportedEntity($processed_entity_langcode, $processed_entity_uuid);
}
foreach ($this->importProcessors[ImportProcessorInterface::STAGE_PROCESS_ENTITY] as $import_processor) {
$import_processor
->processEntity($this->runtimeImportContext, $processed_entity, $entity_data);
}
$processed_entity
->save();
foreach ($this->importProcessors[ImportProcessorInterface::STAGE_POST_ENTITY_SAVE] as $import_processor) {
$import_processor
->postEntitySave($this->runtimeImportContext, $processed_entity);
}
}
return $imported_entity_ids;
}
/**
* {@inheritdoc}
*/
public function prepareImport(ImportContext $context) {
$remote_id = $context
->getRemoteId();
$channel_id = $context
->getChannelId();
$import_config_id = $context
->getImportConfigId();
$remote = NULL;
$import_config = NULL;
$log_variables = [];
$log_variables['@remote_id'] = $remote_id;
$log_variables['@channel_id'] = $channel_id;
$log_variables['@import_config_id'] = $import_config_id;
// Prepare import processors.
if (is_null($import_config_id)) {
$this->logger
->error('No import config ID provided.');
$this->messenger
->addError($this
->t('No import config ID provided.'));
return FALSE;
}
try {
/** @var \Drupal\entity_share_client\Entity\ImportConfigInterface $import_config */
$import_config = $this->entityTypeManager
->getStorage('import_config')
->load($import_config_id);
} catch (\Exception $exception) {
$this->logger
->error('Impossible to load the import config with the ID: @import_config_id', $log_variables);
$this->messenger
->addError($this
->t('Impossible to load the import config with the ID: @import_config_id', $log_variables));
}
if (is_null($import_config)) {
$this->logger
->error('Impossible to load the import config with the ID: @import_config_id', $log_variables);
$this->messenger
->addError($this
->t('Impossible to load the import config with the ID: @import_config_id', $log_variables));
return FALSE;
}
$this->importProcessors = $this->importConfigManipulator
->getImportProcessorsByStages($import_config);
// Prepare runtimeImportContext.
try {
/** @var \Drupal\entity_share_client\Entity\RemoteInterface $remote */
$remote = $this->entityTypeManager
->getStorage('remote')
->load($remote_id);
} catch (\Exception $exception) {
$this->logger
->error('Impossible to load the remote website with the ID: @remote_id', $log_variables);
$this->messenger
->addError($this
->t('Impossible to load the remote website with the ID: @remote_id', $log_variables));
}
// Check that the remote exists.
if (is_null($remote)) {
return FALSE;
}
$this->runtimeImportContext
->setRemote($remote);
// Check that the channel exists and that we can get the channel
// information.
$channels_info = $this->remoteManager
->getChannelsInfos($remote);
if (!isset($channels_info[$channel_id])) {
$this->logger
->error('Impossible to obtain the channel @channel_id on the remote website with the ID: @remote_id', $log_variables);
$this->messenger
->addError($this
->t('Impossible to obtain the channel @channel_id on the remote website with the ID: @remote_id', $log_variables));
return FALSE;
}
$this->runtimeImportContext
->setChannelId($channel_id);
$this->runtimeImportContext
->setChannelLabel($channels_info[$channel_id]['label']);
$this->runtimeImportContext
->setChannelUrl($channels_info[$channel_id]['url']);
$this->runtimeImportContext
->setChannelUrlUuid($channels_info[$channel_id]['url_uuid']);
$this->runtimeImportContext
->setChannelEntityType($channels_info[$channel_id]['channel_entity_type']);
$this->runtimeImportContext
->setChannelBundle($channels_info[$channel_id]['channel_bundle']);
$this->runtimeImportContext
->setChannelSearchConfiguration($channels_info[$channel_id]['search_configuration']);
// Get field mappings.
$this->runtimeImportContext
->setFieldMappings($this->remoteManager
->getfieldMappings($remote));
$this->runtimeImportContext
->setImportService($this);
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getRuntimeImportContext() {
return $this->runtimeImportContext;
}
/**
* {@inheritdoc}
*/
public function request($method, $url) {
return $this->remoteManager
->request($this->runtimeImportContext
->getRemote(), $method, $url);
}
/**
* {@inheritdoc}
*/
public function jsonApiRequest($method, $url) {
return $this->remoteManager
->jsonApiRequest($this->runtimeImportContext
->getRemote(), $method, $url);
}
/**
* Helper function.
*
* Encapsulates the logic of detection of which content entity to manipulate.
*
* @param array $entity_data
* JSON:API data for an entity.
*
* @return \Drupal\Core\Entity\ContentEntityInterface
* The entity to be processed.
*/
protected function getProcessedEntity(array $entity_data) {
// @todo Avoid duplicated code (and duplicate execution?) with
// DefaultDataProcessor.
$field_mappings = $this->runtimeImportContext
->getFieldMappings();
$parsed_type = explode('--', $entity_data['type']);
$entity_type_id = $parsed_type[0];
$entity_bundle = $parsed_type[1];
$entity_storage = $this->entityTypeManager
->getStorage($entity_type_id);
$entity_keys = $entity_storage
->getEntityType()
->getKeys();
$data_uuid = $entity_data['id'];
$langcode_public_name = FALSE;
if (!empty($entity_keys['langcode']) && isset($field_mappings[$entity_type_id][$entity_bundle][$entity_keys['langcode']])) {
$langcode_public_name = $field_mappings[$entity_type_id][$entity_bundle][$entity_keys['langcode']];
}
$data_langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
if ($langcode_public_name && !empty($entity_data['attributes'][$langcode_public_name])) {
$data_langcode = $entity_data['attributes'][$langcode_public_name];
}
// Check if an entity already exists.
// JSON:API no longer includes uuid in attributes so we're using id
// instead. See https://www.drupal.org/node/2984247.
$existing_entities = $entity_storage
->loadByProperties([
'uuid' => $data_uuid,
]);
// Here is the supposition that we are importing a list of content
// entities. Currently this is ensured by the fact that it is not possible
// to make a channel on config entities and on users. And that in the
// relationshipHandleable() method we prevent handling config entities and
// users relationships.
// We can't create a placeholder entity manually and then populating using
// the JSON values directly because otherwise we would lose all the
// denormalization processes. Especially those created for JSON:API
// Extras.
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$remote_entity = $this->jsonapiHelper
->extractEntity($entity_data);
// New entity.
if (empty($existing_entities)) {
// Save the entity to have an ID for entity reference management.
// If A -> B -> A, and A is not saved, when recursively importing A, B
// will not be able to reference A.
$remote_entity
->save();
$processed_entity = $remote_entity;
}
else {
/** @var \Drupal\Core\Entity\ContentEntityInterface $existing_entity */
$existing_entity = array_shift($existing_entities);
$has_translation = $existing_entity
->hasTranslation($data_langcode);
// Update the existing translation.
if ($has_translation) {
$existing_translation = $existing_entity
->getTranslation($data_langcode);
// Need to set those field values now with the denormalized remote
// entity, so that we have data processed by denormalization processes.
// For example, JSON:API extras field enhancers plugins.
foreach (array_keys($entity_data['attributes']) as $field_public_name) {
$field_internal_name = array_search($field_public_name, $field_mappings[$entity_type_id][$entity_bundle]);
if ($field_internal_name && $existing_translation
->hasField($field_internal_name)) {
$existing_translation
->set($field_internal_name, $remote_entity
->get($field_internal_name)
->getValue());
}
else {
$this->logger
->notice('Error during import. The field @field does not exist.', [
'@field' => $field_internal_name,
]);
}
}
$processed_entity = $existing_translation;
}
else {
$remote_entity_as_array = $remote_entity
->toArray();
$existing_entity
->addTranslation($data_langcode, $remote_entity_as_array);
$processed_entity = $existing_entity
->getTranslation($data_langcode);
}
}
return $processed_entity;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ImportService:: |
protected | property | The entity type manager. | |
ImportService:: |
protected | property | The import config manipulator service. | |
ImportService:: |
protected | property | The import processors instances by stages. | |
ImportService:: |
protected | property | The JSON:API helper service. | |
ImportService:: |
protected | property | Logger. | |
ImportService:: |
protected | property | The messenger service. | |
ImportService:: |
protected | property | The remote manager service. | |
ImportService:: |
protected | property | The runtime import context. | |
ImportService:: |
protected | function | Helper function. | |
ImportService:: |
public | function |
Getter. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Import all the entities on a channel. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Plugin annotation
@SuppressWarnings(PHPMD . BooleanArgumentFlag); Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Use data from the JSON:API to import content. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Import the entities from a prepared JSON:API URL. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Performs a HTTP request on a JSON:API endpoint. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Prepare runtime import context and import processors. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function |
Performs a HTTP request. Overrides ImportServiceInterface:: |
|
ImportService:: |
public | function | RemoteManager constructor. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |