class ImportEntityManager in Acquia Content Hub 8
Provides a service for managing imported entities' actions.
Hierarchy
- class \Drupal\acquia_contenthub\ImportEntityManager uses StringTranslationTrait
Expanded class hierarchy of ImportEntityManager
7 files declare their use of ImportEntityManager
- acquia_contenthub_subscriber.module in acquia_contenthub_subscriber/
acquia_contenthub_subscriber.module - Handles Content Hub Content Subscriptions and Updates.
- ContentHubEntityImportController.php in src/
Controller/ ContentHubEntityImportController.php - ContentHubImportQueueBase.php in src/
Plugin/ QueueWorker/ ContentHubImportQueueBase.php - ContentHubImportQueueBaseTest.php in tests/
src/ Unit/ Plugin/ QueueWorker/ ContentHubImportQueueBaseTest.php - ImportEntityManagerTest.php in tests/
src/ Unit/ ImportEntityManagerTest.php
1 string reference to 'ImportEntityManager'
1 service uses ImportEntityManager
File
- src/
ImportEntityManager.php, line 28
Namespace
Drupal\acquia_contenthubView source
class ImportEntityManager {
use StringTranslationTrait;
/**
* Format for the cdf.
*
* @var string
*/
private $format = 'acquia_contenthub_cdf';
/**
* The Database Connection.
*
* @var \Drupal\Core\Database\Connection
*/
private $database;
/**
* Logger Factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
private $loggerFactory;
/**
* The Serializer.
*
* @var \Symfony\Component\Serializer\SerializerInterface
*/
private $serializer;
/**
* Entity Repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
private $entityRepository;
/**
* Content Hub Client Manager.
*
* @var \Drupal\acquia_contenthub\Client\ClientManager
*/
private $clientManager;
/**
* The Content Hub Entities Tracking Service.
*
* @var \Drupal\acquia_contenthub\ContentHubEntitiesTracking
*/
private $contentHubEntitiesTracking;
/**
* Diff module's entity comparison service.
*
* @var \Drupal\diff\DiffEntityComparison
*/
private $diffEntityComparison;
/**
* The content hub entity manager.
*
* @var \Drupal\acquia_contenthub\EntityManager
*/
private $entityManager;
/**
* The queue factory.
*
* @var \Drupal\Core\Queue\QueueFactory
*/
private $queue;
/**
* Language Manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Implements the static interface create method.
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('database'), $container
->get('logger.factory'), $container
->get('serializer'), $container
->get('entity.repository'), $container
->get('acquia_contenthub.client_manager'), $container
->get('acquia_contenthub.acquia_contenthub_entities_tracking'), $container
->get('diff.entity_comparison'), $container
->get('acquia_contenthub.entity_manager'), $container
->get('string_translation'), $container
->get('queue'), $container
->get('language_manager'));
}
/**
* Constructor.
*
* @param \Drupal\Core\Database\Connection $database
* Database connection.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The Logger Factory.
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The Serializer.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The Entity Repository.
* @param \Drupal\acquia_contenthub\Client\ClientManagerInterface $client_manager
* The client manager.
* @param \Drupal\acquia_contenthub\ContentHubEntitiesTracking $entities_tracking
* The Content Hub Entities Tracking Service.
* @param \Drupal\diff\DiffEntityComparison $entity_comparison
* The Diff module's Entity Comparison service.
* @param \Drupal\acquia_contenthub\EntityManager $entity_manager
* The entity manager for Content Hub.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Queue\QueueFactory $queue_factory
* The Queue Factory.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The Language Manager.
*/
public function __construct(Connection $database, LoggerChannelFactoryInterface $logger_factory, SerializerInterface $serializer, EntityRepositoryInterface $entity_repository, ClientManagerInterface $client_manager, ContentHubEntitiesTracking $entities_tracking, DiffEntityComparison $entity_comparison, EntityManager $entity_manager, TranslationInterface $string_translation, QueueFactory $queue_factory, LanguageManagerInterface $language_manager) {
$this->database = $database;
$this->loggerFactory = $logger_factory;
$this->serializer = $serializer;
$this->entityRepository = $entity_repository;
$this->clientManager = $client_manager;
$this->contentHubEntitiesTracking = $entities_tracking;
$this->diffEntityComparison = $entity_comparison;
$this->entityManager = $entity_manager;
$this->stringTranslation = $string_translation;
$this->queue = $queue_factory;
$this->languageManager = $language_manager;
}
/**
* Compare entities by checking if the entities referenced by it has changed.
*
* Note: It needs to be a changed entity (has $entity->original).
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check for differences.
*
* @return bool
* TRUE if it finds differences, FALSE otherwise.
*/
private function compareReferencedEntities(EntityInterface $entity) {
$new_references = $entity
->referencedEntities();
$old_references = $entity->original
->referencedEntities();
$new_uuids = array_map([
$this,
'excludeCompareReferencedEntities',
], $new_references);
$old_uuids = array_map([
$this,
'excludeCompareReferencedEntities',
], $old_references);
$changes = array_diff($new_uuids, $old_uuids);
if (!empty($changes)) {
return TRUE;
}
return FALSE;
}
/**
* Excludes entity from comparison.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check for differences.
*
* @return null|string
* Entity uuid if entity is not excluded from comparison, NULL otherwise.
*/
private function excludeCompareReferencedEntities(EntityInterface $entity) {
// Currently Content Hub does not support configuration entities.
if ($entity
->getEntityType()
->entityClassImplements('\\Drupal\\Core\\Config\\Entity\\ConfigEntityInterface')) {
return FALSE;
}
return $entity
->uuid();
}
/**
* Compare entities by checking if the fields information has changed.
*
* Note: It needs to be a changed entity (has $entity->original).
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check for differences.
*
* @return bool
* TRUE if it finds differences, FALSE otherwise.
*/
private function compareRevisions(EntityInterface $entity) {
// Check if the entity has introduced any local changes.
$field_comparisons = $this->diffEntityComparison
->compareRevisions($entity->original, $entity);
foreach ($field_comparisons as $field_comparison => $field_comparison_value) {
list($entity_id, $field_comparison_data) = explode(':', $field_comparison);
list($entity_type_id, $field_comparison_name) = explode('.', $field_comparison_data);
if ($entity_id == $entity
->id() && $entity_type_id == $entity
->getEntityTypeId() && $this
->isFieldReferencedToSubclassOf($entity, $field_comparison_name)) {
continue;
}
if ($field_comparison_value['#data']['#left'] !== $field_comparison_value['#data']['#right']) {
return TRUE;
}
}
return FALSE;
}
/**
* Determine if field have link to ConfigEntityInterface entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check for differences.
* @param string $field_name
* The machine name of the field.
* @param string $subclass
* The class or interface to check.
*
* @return bool
* TRUE if field have link to ConfigEntityInterface entity, FALSE otherwise.
*/
private function isFieldReferencedToSubclassOf(EntityInterface $entity, $field_name, $subclass = '\\Drupal\\Core\\Config\\Entity\\ConfigEntityInterface') {
if (empty($entity) || empty($field_name)) {
return FALSE;
}
$field_type = $entity
->getFieldDefinition($field_name)
->getType();
if ($field_type == 'entity_reference') {
$field_references = $entity
->get($field_name)
->referencedEntities();
foreach ($field_references as $field_reference) {
if ($field_reference
->getEntityType()
->entityClassImplements($subclass)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Act on the entity's presave action.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that is being saved.
*/
public function entityPresave(EntityInterface $entity) {
// Early return, if the entity doesn't have an older version or if it is
// already sync'ing.
if (!isset($entity->original) || !empty($entity->__contenthub_entity_syncing)) {
return;
}
// Find the top-level host entity's import entity.
$imported_entity = $this
->findRootAncestorImportEntity($entity);
// Early return, if the entity is not an imported entity, or it is not
// "pending sync" or "has local change".
if (!$imported_entity || $imported_entity
->isPendingSync() || $imported_entity
->hasLocalChange()) {
return;
}
$has_local_change = $this
->compareRevisions($entity) || $this
->compareReferencedEntities($entity);
// Don't do anything else if there is no local change.
if (!$has_local_change) {
return;
}
// Set and store the imported entity as having local changes.
$imported_entity
->setLocalChange();
$imported_entity
->save();
}
/**
* Loads the Remote Content Hub Entity.
*
* @param string $uuid
* The Remote Entity UUID.
*
* @return \Drupal\acquia_contenthub\ContentHubEntityDependency|bool
* The Content Hub Entity Dependency if found, FALSE otherwise.
*/
private function loadRemoteEntity($uuid) {
$entity = $this->clientManager
->createRequest('readEntity', [
$uuid,
]);
if (!$entity) {
return FALSE;
}
return new ContentHubEntityDependency($entity);
}
/**
* Obtains all dependencies for the current Content Hub Entity.
*
* It collects dependencies on all levels, flattening out the dependency array
* to avoid looping circular dependencies.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $content_hub_entity
* The Content Hub Entity.
* @param array $dependencies
* An array of \Drupal\acquia_contenthub\ContentHubEntityDependency.
* @param bool|true $use_chain
* If the dependencies should be unique to the dependency chain or not.
*
* @return array
* An array of \Drupal\acquia_contenthub\ContentHubEntityDependency.
*/
private function getAllRemoteDependencies(ContentHubEntityDependency $content_hub_entity, array &$dependencies, $use_chain = TRUE) {
// Obtaining dependencies of this entity.
$dep_dependencies = $this
->getRemoteDependencies($content_hub_entity, $use_chain);
/** @var \Drupal\acquia_contenthub\ContentHubEntityDependency $content_hub_dependency */
foreach ($dep_dependencies as $uuid => $content_hub_dependency) {
if (isset($dependencies[$uuid])) {
continue;
}
// Also check if this dependency has been 1) previously imported, 2) is an
// independent entity, and 3) has the same modified timestamp. If the
// 'modified' timestamp matches, then we know we are trying to import an
// entity that has no change, then it does not need to be imported again.
$imported_entity = $this->contentHubEntitiesTracking
->loadImportedByUuid($uuid);
if ($imported_entity && !$imported_entity
->isDependent() && $imported_entity
->getModified() === $content_hub_dependency
->getRawEntity()
->getModified()) {
continue;
}
$dependencies[$uuid] = $content_hub_dependency;
$this
->getAllRemoteDependencies($content_hub_dependency, $dependencies, $use_chain);
}
return array_reverse($dependencies, TRUE);
}
/**
* Obtains First-level remote dependencies for the current Content Hub Entity.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $content_hub_entity
* The Content Hub Entity.
* @param bool|true $use_chain
* If the dependencies should be unique to the dependency chain or not.
*
* @return array
* An array of \Drupal\acquia_contenthub\ContentHubEntityDependency.
*/
private function getRemoteDependencies(ContentHubEntityDependency $content_hub_entity, $use_chain = TRUE) {
$dependencies = [];
$uuids = $content_hub_entity
->getRemoteDependencies();
foreach ($uuids as $uuid) {
$content_hub_dependent_entity = $this
->loadRemoteEntity($uuid);
if ($content_hub_dependent_entity === FALSE) {
continue;
}
// If this dependency is already tracked in the dependency chain
// then we don't need to consider it a dependency unless we're not using
// the chain.
if ($content_hub_entity
->isInDependencyChain($content_hub_dependent_entity) && $use_chain) {
$content_hub_dependent_entity
->setParent($content_hub_entity);
continue;
}
$content_hub_dependent_entity
->setParent($content_hub_entity);
$dependencies[$uuid] = $content_hub_dependent_entity;
}
return $dependencies;
}
/**
* Import an entity.
*
* @param string $uuid
* The UUID of the Entity to save.
* @param bool $include_dependencies
* TRUE if we want to save all its dependencies, FALSE otherwise.
* @param string $author
* The UUID of the author (user) that will own the entity.
* @param int $status
* The publishing status of the entity (Applies to nodes).
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON Response.
*/
public function import($uuid, $include_dependencies = TRUE, $author = NULL, $status = 0) {
if (\Drupal::config('acquia_contenthub.entity_config')
->get('import_with_queue')) {
return $this
->addEntityToImportQueue($uuid, $include_dependencies, $author, $status);
}
return $this
->importRemoteEntity($uuid, $include_dependencies, $author, $status);
}
/**
* Saves a Content Hub Entity into a Drupal Entity, given its UUID.
*
* This method accepts a parameter if we want to save all its dependencies.
* Note that dependencies could be of 2 different types:
* - pre-dependency or Entity Independent:
* Has to be created before the host-entity and referenced from it.
* - post-dependency or Entity Dependent:
* Has to be created after the host-entity and referenced from it.
* This is a recursive method, and will also create dependencies of the
* dependencies.
*
* @param string $uuid
* The UUID of the Entity to save.
* @param bool $include_dependencies
* TRUE if we want to save all its dependencies, FALSE otherwise.
* @param string $author
* The UUID of the author (user) that will own the entity.
* @param int $status
* The publishing status of the entity (Applies to nodes).
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON Response.
*/
public function importRemoteEntity($uuid, $include_dependencies = TRUE, $author = NULL, $status = 0) {
// Checking that the parameter given is a UUID.
if (!Uuid::isValid($uuid)) {
// We will just show a standard "access denied" page in this case.
throw new AccessDeniedHttpException();
}
// If the Entity is not found in Content Hub then return a 404 Not Found.
$contenthub_entity = $this
->loadRemoteEntity($uuid);
if (!$contenthub_entity) {
$message = $this
->t('Entity with UUID = @uuid not found.', [
'@uuid' => $uuid,
]);
return $this
->jsonErrorResponseMessage($message, FALSE, 404);
}
$origin = $contenthub_entity
->getRawEntity()
->getOrigin();
$site_origin = $this->contentHubEntitiesTracking
->getSiteOrigin();
// Checking that the entity origin is different than this site's origin.
if ($origin === $site_origin) {
$args = [
'@type' => $contenthub_entity
->getRawEntity()
->getType(),
'@uuid' => $contenthub_entity
->getRawEntity()
->getUuid(),
'@origin' => $origin,
];
$message = $this
->t('Cannot save "@type" entity with uuid="@uuid". It has the same origin as this site: "@origin"', $args);
$this->loggerFactory
->get('acquia_contenthub')
->warning($message);
$result = FALSE;
return $this
->jsonErrorResponseMessage($message, $result, 403);
}
// Checking if bundle exists.
$allowed_entity_types = $this->entityManager
->getAllowedEntityTypes();
$contenthub_entity_attribute = $contenthub_entity
->getRawEntity()
->getAttribute('type')['value'] ?? NULL;
$contenthub_entity_bundle = $contenthub_entity_attribute ? reset($contenthub_entity_attribute) : NULL;
if ($contenthub_entity_bundle && !array_key_exists($contenthub_entity_bundle, $allowed_entity_types[$contenthub_entity
->getRawEntity()
->getType()])) {
$args = [
'@type' => $contenthub_entity
->getRawEntity()
->getType(),
'@uuid' => $contenthub_entity
->getRawEntity()
->getUuid(),
'@bundle' => $contenthub_entity_bundle,
];
$message = $this
->t('Cannot save "@type" entity with uuid="@uuid". Missing "@type" entity with bundle "@bundle"', $args);
$this->loggerFactory
->get('acquia_contenthub')
->warning($message);
$result = FALSE;
return $this
->jsonErrorResponseMessage($message, $result, 403);
}
// Checking that the entity has a language that is supported by this site.
if (!$this
->verifyLanguageSupportability($contenthub_entity)) {
$args = [
'@type' => $contenthub_entity
->getRawEntity()
->getType(),
'@uuid' => $contenthub_entity
->getRawEntity()
->getUuid(),
];
$message = $this
->t('Cannot save "@type" entity with uuid="@uuid". The site does not support any of the languages available for this entity.', $args);
$this->loggerFactory
->get('acquia_contenthub')
->warning($message);
return $this
->jsonErrorResponseMessage($message, FALSE, 403);
}
// Collect and flat out all dependencies.
$dependencies = [];
if ($include_dependencies) {
$dependencies = $this
->getAllRemoteDependencies($contenthub_entity, $dependencies, TRUE);
}
// Obtaining the Status of the parent entity, if it is a node and
// setting the publishing status of that entity.
$contenthub_entity
->setStatus($status);
// Assigning author to this entity and dependencies.
$contenthub_entity
->setAuthor($author);
foreach ($dependencies as $uuid => $dependency) {
$dependencies[$uuid]
->setAuthor($author);
// Only change the Node status of dependent entities if they are nodes,
// if the status flag is set and if they haven't been imported before.
$entity_type = $dependency
->getEntityType();
if (isset($status) && $entity_type === 'node' && !$this->contentHubEntitiesTracking
->loadImportedByUuid($uuid)) {
$dependencies[$uuid]
->setStatus($status);
}
}
// Save this entity and all its dependencies.
return $this
->importRemoteEntityDependencies($contenthub_entity, $dependencies);
}
/**
* Verifies that the sites supports at least a language defined in the entity.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $contenthub_entity_dependency
* The Content Hub Entity.
*
* @return bool
* TRUE if the site supports at least a language defined in the entity,
* FALSE otherwise.
*/
public function verifyLanguageSupportability(ContentHubEntityDependency $contenthub_entity_dependency) {
$contenthub_entity = $contenthub_entity_dependency
->getRawEntity();
$entity_type = $contenthub_entity
->getType();
$attributes = $contenthub_entity
->getAttributes();
if ($entity_type === 'file') {
$langcodes = array_keys($attributes['url']['value']);
}
elseif ($langcode = $contenthub_entity
->getAttribute('langcode')) {
$langcodes = $langcode['value'];
}
else {
$langcodes = array_keys($contenthub_entity
->getAttribute('default_langcode')['value'] ?? []);
}
$site_langcodes = array_keys($this->languageManager
->getLanguages());
$supported_languages = array_intersect($site_langcodes, $langcodes);
return !empty($supported_languages);
}
/**
* Saves the current Drupal Entity and all its dependencies.
*
* This method is not to be used alone but to be used from
* importRemoteEntity() method, which is why it is private.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $contenthub_entity
* The Content Hub Entity.
* @param array $dependencies
* An array of ContentHubEntityDependency objects.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|null
* The Drupal entity being created.
*/
private function importRemoteEntityDependencies(ContentHubEntityDependency $contenthub_entity, array &$dependencies) {
// Create pre-dependencies.
foreach ($contenthub_entity
->getDependencyChain() as $uuid) {
$content_hub_entity_dependency = isset($dependencies[$uuid]) ? $dependencies[$uuid] : FALSE;
if ($content_hub_entity_dependency && !isset($content_hub_entity_dependency->__processed) && $content_hub_entity_dependency
->getRelationship() == ContentHubEntityDependency::RELATIONSHIP_INDEPENDENT) {
$dependencies[$uuid]->__processed = TRUE;
$this
->importRemoteEntityDependencies($content_hub_entity_dependency, $dependencies);
}
}
// Check already imported entity, that auto import is available or not.
$trackingEntity = $this->contentHubEntitiesTracking
->loadImportedByUuid($contenthub_entity
->getRawEntity()
->getUuid());
if ($trackingEntity && $trackingEntity
->isAutoUpdateDisabled()) {
return new JsonResponse(NULL);
}
// Now that we have created all its pre-dependencies, create the current
// Drupal entity.
$response = $this
->importRemoteEntityNoDependencies($contenthub_entity);
// Create post-dependencies.
foreach ($contenthub_entity
->getDependencyChain() as $uuid) {
$content_hub_entity_dependency = isset($dependencies[$uuid]) ? $dependencies[$uuid] : FALSE;
if ($content_hub_entity_dependency && !isset($content_hub_entity_dependency->__processed) && $content_hub_entity_dependency
->getRelationship() == ContentHubEntityDependency::RELATIONSHIP_DEPENDENT) {
$dependencies[$uuid]->__processed = TRUE;
$this
->importRemoteEntityDependencies($content_hub_entity_dependency, $dependencies);
}
}
return $response;
}
/**
* Saves an Entity without taking care of dependencies.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $contenthub_entity
* The Content Hub Entity.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The Response.
*
* @throws \Exception
* Throws exception in certain cases.
*/
private function importRemoteEntityNoDependencies(ContentHubEntityDependency $contenthub_entity) {
// Import the entity.
$entity_type = $contenthub_entity
->getRawEntity()
->getType();
$class = \Drupal::entityTypeManager()
->getDefinition($entity_type)
->getClass();
// Check if this dependency has originated in this site or not.
$site_origin = $this->contentHubEntitiesTracking
->getSiteOrigin();
if ($contenthub_entity
->getRawEntity()
->getOrigin() == $site_origin) {
$args = [
'@type' => $contenthub_entity
->getRawEntity()
->getType(),
'@uuid' => $contenthub_entity
->getRawEntity()
->getUuid(),
'@origin' => $contenthub_entity
->getRawEntity()
->getOrigin(),
];
$message = $this
->t('Cannot save "@type" entity with uuid="@uuid". It has the same origin as this site: "@origin"', $args);
$this->loggerFactory
->get('acquia_contenthub')
->debug($message);
return $this
->jsonErrorResponseMessage($message, FALSE, 400);
}
try {
$entity = $this->serializer
->deserialize($contenthub_entity
->getRawEntity()
->json(), $class, $this->format);
} catch (\UnexpectedValueException $e) {
$error = $e
->getMessage();
return $this
->jsonErrorResponseMessage($error, FALSE, 400);
}
// If the entity cannot be serialized (we detected it cannot be saved
// because it has missing information) then we should not try to save it.
if (empty($entity)) {
$message = $this
->t('Entity (type = "%type", uuid = "%uuid") cannot be saved.', [
'%type' => $entity_type,
'%uuid' => $contenthub_entity
->getUuid(),
]);
$this->loggerFactory
->get('acquia_contenthub')
->debug($message);
return new JsonResponse(NULL);
}
// Finally Save the Entity.
$transaction = $this->database
->startTransaction();
try {
// Add synchronization flag.
$entity->__contenthub_entity_syncing = TRUE;
if ($entity instanceof ContentEntityInterface && $entity
->hasField('path') && !$this
->pathAliasMovedToSeparateModule()) {
$languages = $entity
->getTranslationLanguages();
foreach ($languages as $language) {
$entity = $entity
->getTranslation($language
->getId());
$path = $entity
->get('path')
->first()
->getValue();
if (!empty($path['pid']) || !isset($path['alias'])) {
continue;
}
$raw_path = $entity
->id() ? '/' . $entity
->toUrl()
->getInternalPath() : $this
->getPathByAlias($path['alias'], $path['langcode']);
if (!$raw_path) {
continue;
}
$query = \Drupal::database()
->select('url_alias', 'ua')
->fields('ua', [
'pid',
]);
$query
->condition('ua.source', $raw_path);
$query
->condition('ua.langcode', $language
->getId());
$alias = $query
->execute()
->fetchObject();
if (!isset($alias->pid)) {
continue;
}
$path['pid'] = $alias->pid;
$path['source'] = $raw_path;
$entity
->set('path', [
$path,
]);
}
}
// Check if there exists a local "redirect" entity with the same hash.
if ($entity
->getEntityTypeId() === 'redirect' && $entity
->getHash()) {
/** @var \Drupal\redirect\Entity\Redirect $local_redirect_entity */
$local_redirect_entity = reset(\Drupal::entityTypeManager()
->getStorage('redirect')
->loadByProperties([
'hash' => $entity
->getHash(),
]));
if ($local_redirect_entity && $local_redirect_entity
->uuid() !== $entity
->uuid()) {
$this->loggerFactory
->get('acquia_contenthub')
->debug('An existing redirect entity with the same "hash" was found and overwritten by the one coming from Content Hub. Old UUID = "%old_uuid", New UUID = "%new_uuid", source = "%source", old destination = "%old_destination", new destination = "%new_destination".', [
'%old_uuid' => $local_redirect_entity
->uuid(),
'%new_uuid' => $entity
->uuid(),
'%source' => $local_redirect_entity
->getSourceUrl(),
'%old_destination' => $local_redirect_entity
->getRedirectUrl()
->toUriString(),
'%new_destination' => $entity
->getRedirectUrl()
->toUriString(),
]);
$local_redirect_entity
->delete();
}
}
// Save the entity.
$entity
->save();
// Remove synchronization flag.
unset($entity->__contenthub_entity_syncing);
// Save this entity in the tracking for importing entities.
$is_new_entity = $this
->trackImportedEntity($contenthub_entity);
// If this is a post-dependency (paragraphs or field collections), then
// we will need to update the host entity ONLY if we are creating this
// entity for the first time.
// If we are updating a paragraph, the reference is already set.
if ($is_new_entity && $contenthub_entity
->isEntityDependent()) {
$this
->updateHostEntity($entity);
}
// Do we need to create an path alias?
$moduleHandler = \Drupal::service('module_handler');
if ($moduleHandler
->moduleExists('pathauto')) {
/** @var \Drupal\Core\Session\AccountSwitcherInterface $accountSwitcher */
$accountSwitcher = \Drupal::service('account_switcher');
$roles = \Drupal::entityTypeManager()
->getStorage('user_role')
->loadByProperties([
'is_admin' => TRUE,
]);
$role = reset($roles);
if ($role) {
$renderUser = new ContentHubUserSession($role
->id());
$accountSwitcher
->switchTo($renderUser);
try {
/** @var \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper */
$alias_storage_helper = \Drupal::service('pathauto.alias_storage_helper');
$languages = $entity
->getTranslationLanguages();
foreach ($languages as $language) {
$entity = $entity
->getTranslation($language
->getId());
if ($entity && $entity
->hasField('path')) {
$path = $entity
->get('path')
->getValue();
if (!$this
->pathAliasMovedToSeparateModule() && ($path = reset($path))) {
$path['source'] = empty($path['source']) ? '/' . $entity
->toUrl()
->getInternalPath() : $path['source'];
$path['language'] = isset($path['langcode']) ? $path['langcode'] : $language
->getId();
$existing_alias = $alias_storage_helper
->loadBySource($path['source'], $language
->getId());
$existing_alias = !empty($existing_alias) ? $existing_alias : NULL;
$alias_storage_helper
->save($path, $existing_alias);
}
}
}
} catch (\Exception $e) {
$this->loggerFactory
->get('acquia_contenthub')
->debug('Could not generate path alias for (%entity_type, %entity_id). Error message: %message', [
'%entity_type' => $entity
->getEntityTypeId(),
'%entity_id' => $entity
->id(),
'%message' => $e
->getMessage(),
]);
}
$accountSwitcher
->switchBack();
}
}
} catch (\Exception $e) {
$transaction
->rollback();
$this->loggerFactory
->get('acquia_contenthub')
->error($e
->getMessage());
throw $e;
}
$serialized_entity = $this->serializer
->normalize($entity, 'json');
return new JsonResponse($serialized_entity);
}
/**
* Updates the reference in the host entity to point to the dependent entity.
*
* In cases of dependent entities (paragraphs and field collections), we need
* to update the reference in the host entity to point to the dependent entity
* after the dependent entity has been saved.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* A Drupal entity interface.
*/
private function updateHostEntity(EntityInterface $entity) {
switch ($entity
->getEntityTypeId()) {
case 'paragraph':
$host_entity = $entity
->getParentEntity();
// Assuming single parenthood.
$field_paragraph = $entity
->get('parent_field_name')
->getString();
$host_entity->{$field_paragraph}
->appendItem($entity);
// Add synchronization flag.
$host_entity->__contenthub_entity_syncing = TRUE;
// Save the host entity.
$host_entity
->save();
// Remove synchronization flag.
unset($host_entity->__contenthub_entity_syncing);
break;
}
}
/**
* Provides a JSON Response Message.
*
* @param string $message
* The message to print.
* @param bool $status
* The status message.
* @param int $status_code
* The HTTP Status code.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON Response.
*/
private function jsonErrorResponseMessage($message, $status, $status_code = 400) {
// If the Entity is not found in Content Hub then return a 404 Not Found.
$json = [
'status' => $status,
'message' => $message,
];
return new JsonResponse($json, $status_code);
}
/**
* Save this entity in the Tracking table.
*
* @param \Drupal\acquia_contenthub\ContentHubEntityDependency $contenthub_entity
* The Content Hub Entity.
*
* @return bool
* TRUE if entity is new, FALSE otherwise.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
private function trackImportedEntity(ContentHubEntityDependency $contenthub_entity) {
$cdf = (array) $contenthub_entity
->getRawEntity();
$imported_entity = $this->contentHubEntitiesTracking
->loadImportedByUuid($cdf['uuid']);
// If already exist, update some fields and exit.
if ($imported_entity) {
// Set status to "auto-update" only when the entity is independent.
if (!$imported_entity
->isDependent()) {
$imported_entity
->setAutoUpdate();
}
$imported_entity
->setModified($cdf['modified']);
$this
->saveImportedEntity();
return FALSE;
}
// If the entity is new, create a new imported entity.
$entity = $this->entityRepository
->loadEntityByUuid($cdf['type'], $cdf['uuid']);
if ($entity) {
$this->contentHubEntitiesTracking
->setImportedEntity($cdf['type'], $entity
->id(), $cdf['uuid'], $cdf['modified'], $cdf['origin']);
if ($contenthub_entity
->isEntityDependent()) {
$this->contentHubEntitiesTracking
->setDependent();
}
$this
->saveImportedEntity();
return TRUE;
}
// We should never reach this far.
$this->loggerFactory
->get('acquia_contenthub')
->error('Error trying to track imported entity with uuid=%uuid, type=%type. Check if the entity exists and is being tracked properly.', [
'%type' => $cdf['type'],
'%uuid' => $cdf['uuid'],
]);
return FALSE;
}
/**
* Save the imported entity.
*/
private function saveImportedEntity() {
// Now save the entity.
$success = $this->contentHubEntitiesTracking
->save();
// Log event.
if (!$success) {
$args = [
'%type' => $this->contentHubEntitiesTracking
->getEntityType(),
'%uuid' => $this->contentHubEntitiesTracking
->getUuid(),
];
$message = $this
->t('Imported entity type = %type with uuid=%uuid could not be saved in the tracking table.', $args);
}
else {
$args = [
'%type' => $this->contentHubEntitiesTracking
->getEntityType(),
'%uuid' => $this->contentHubEntitiesTracking
->getUuid(),
];
$message = $this
->t('Saving %type entity with uuid=%uuid. Tracking imported entity with auto updates.', $args);
}
$this->loggerFactory
->get('acquia_contenthub')
->debug($message);
}
/**
* Act on the entity's update action.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that is being updated.
*/
public function entityUpdate(EntityInterface $entity) {
// Early return, if already sync'ing.
if (!empty($entity->__contenthub_entity_syncing)) {
return;
}
$imported_entity = $this->contentHubEntitiesTracking
->loadImportedByDrupalEntity($entity
->getEntityTypeId(), $entity
->id());
// Early return, if not an imported entity or not pending sync.
if (!$imported_entity || !$imported_entity
->isPendingSync()) {
return;
}
// Otherwise, re-import the entity.
$this
->importRemoteEntity($imported_entity
->getUuid(), $entity);
}
/**
* Returns the entity's root ancestor's imported entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The current (child) entity.
*
* @return \Drupal\acquia_contenthub\ContentHubEntitiesTracking|bool
* The root ancestor's ContentHubEntitiesTracking object.
*/
private function findRootAncestorImportEntity(EntityInterface $entity) {
$imported_entity = $this->contentHubEntitiesTracking
->loadImportedByDrupalEntity($entity
->getEntityTypeId(), $entity
->id());
if (!$imported_entity || !$imported_entity
->isDependent()) {
return $imported_entity;
}
return $this
->findRootAncestorImportEntity($this
->getParentEntity($entity));
}
/**
* Returns the parent entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to get the parent from.
*
* @return mixed
* The parent entity.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
private function getParentEntity(EntityInterface $entity) {
if ($entity
->getEntityTypeId() == 'path_alias') {
$route_params = Url::fromUserInput($entity
->getPath())
->getRouteParameters();
foreach ($route_params as $entity_type_id => $entity_id) {
if (!\Drupal::entityTypeManager()
->hasDefinition($entity_type_id)) {
// Skip in case of unknown entity type.
continue;
}
$entity_from_route = \Drupal::entityTypeManager()
->getStorage($entity_type_id)
->load($entity_id);
if ($entity
->getPath() !== "/{$entity_from_route->toUrl()->getInternalPath()}") {
// Skip mismatched entities.
continue;
}
return $entity_from_route;
}
}
return $entity
->getParentEntity();
}
/**
* Act on the entity's delete action.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that is being deleted.
*/
public function entityDelete(EntityInterface $entity) {
$imported_entity = $this->contentHubEntitiesTracking
->loadImportedByDrupalEntity($entity
->getEntityTypeId(), $entity
->id());
if (!$imported_entity) {
return;
}
$imported_entity
->delete();
}
/**
* Add the UUID to the import queue.
*
* @param string $uuid
* The UUID of the Entity to save.
* @param bool $include_dependencies
* TRUE if we want to save all its dependencies, FALSE otherwise.
* @param string $author
* The UUID of the author (user) that will own the entity.
* @param int $status
* The publishing status of the entity (Applies to nodes).
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A valid response.
*/
public function addEntityToImportQueue($uuid, $include_dependencies = TRUE, $author = NULL, $status = 0) {
$item = (object) [
'data' => [],
];
$item->data[] = new ImportQueueItem($uuid, $include_dependencies, $author, $status);
$queue = $this->queue
->get('acquia_contenthub_import_queue');
if ($queue
->createItem($item)) {
return new JsonResponse([
'status' => 200,
'message' => $uuid . ' added to the queue',
], 200);
}
return $this
->jsonErrorResponseMessage('Unable to add ' . $uuid . ' to the import queue', FALSE);
}
/**
* Returns path by alias.
*
* Since Drupal 8.8.0 the path alias core subsystem has been moved
* to the "path_alias" module and "path.alias_manager" marked as deprecated.
*
* @param string $alias
* Alias string.
* @param string|null $langcode
* An optional language code to look up the path in.
*
* @return string
* Path.
*/
private function getPathByAlias($alias, $langcode = NULL) {
$service_id = $this
->pathAliasMovedToSeparateModule() ? 'path_alias.manager' : 'path.alias_manager';
return \Drupal::service($service_id)
->getPathByAlias($alias, $langcode);
}
/**
* Checks that path alias subsystem has been moved to the separate module.
*
* @see https://www.drupal.org/node/3092086
*
* @return bool
* TRUE in the case when Drupal version higher than 8.8.0.
*/
private function pathAliasMovedToSeparateModule() {
return version_compare(\Drupal::VERSION, '8.8.0', '>=');
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ImportEntityManager:: |
private | property | Content Hub Client Manager. | |
ImportEntityManager:: |
private | property | The Content Hub Entities Tracking Service. | |
ImportEntityManager:: |
private | property | The Database Connection. | |
ImportEntityManager:: |
private | property | Diff module's entity comparison service. | |
ImportEntityManager:: |
private | property | The content hub entity manager. | |
ImportEntityManager:: |
private | property | Entity Repository. | |
ImportEntityManager:: |
private | property | Format for the cdf. | |
ImportEntityManager:: |
protected | property | Language Manager. | |
ImportEntityManager:: |
private | property | Logger Factory. | |
ImportEntityManager:: |
private | property | The queue factory. | |
ImportEntityManager:: |
private | property | The Serializer. | |
ImportEntityManager:: |
public | function | Add the UUID to the import queue. | |
ImportEntityManager:: |
private | function | Compare entities by checking if the entities referenced by it has changed. | |
ImportEntityManager:: |
private | function | Compare entities by checking if the fields information has changed. | |
ImportEntityManager:: |
public static | function | Implements the static interface create method. | |
ImportEntityManager:: |
public | function | Act on the entity's delete action. | |
ImportEntityManager:: |
public | function | Act on the entity's presave action. | |
ImportEntityManager:: |
public | function | Act on the entity's update action. | |
ImportEntityManager:: |
private | function | Excludes entity from comparison. | |
ImportEntityManager:: |
private | function | Returns the entity's root ancestor's imported entity. | |
ImportEntityManager:: |
private | function | Obtains all dependencies for the current Content Hub Entity. | |
ImportEntityManager:: |
private | function | Returns the parent entity. | |
ImportEntityManager:: |
private | function | Returns path by alias. | |
ImportEntityManager:: |
private | function | Obtains First-level remote dependencies for the current Content Hub Entity. | |
ImportEntityManager:: |
public | function | Import an entity. | |
ImportEntityManager:: |
public | function | Saves a Content Hub Entity into a Drupal Entity, given its UUID. | |
ImportEntityManager:: |
private | function | Saves the current Drupal Entity and all its dependencies. | |
ImportEntityManager:: |
private | function | Saves an Entity without taking care of dependencies. | |
ImportEntityManager:: |
private | function | Determine if field have link to ConfigEntityInterface entity. | |
ImportEntityManager:: |
private | function | Provides a JSON Response Message. | |
ImportEntityManager:: |
private | function | Loads the Remote Content Hub Entity. | |
ImportEntityManager:: |
private | function | Checks that path alias subsystem has been moved to the separate module. | |
ImportEntityManager:: |
private | function | Save the imported entity. | |
ImportEntityManager:: |
private | function | Save this entity in the Tracking table. | |
ImportEntityManager:: |
private | function | Updates the reference in the host entity to point to the dependent entity. | |
ImportEntityManager:: |
public | function | Verifies that the sites supports at least a language defined in the entity. | |
ImportEntityManager:: |
public | function | 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. |