View source
<?php
namespace Drupal\cms_content_sync\Plugin\rest\resource;
use Drupal\cms_content_sync\Controller\Migration;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncCoreInterface\SyncCoreFactory;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Render\Renderer;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use EdgeBox\SyncCore\V2\Syndication\PushSingle;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class SyncCoreEntityItemResource extends ResourceBase {
public const CODE_INVALID_DATA = 401;
public const CODE_NOT_FOUND = 404;
protected $entityTypeBundleInfo;
protected $entityTypeManager;
protected $renderedManager;
protected $entityRepository;
public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, EntityTypeBundleInfo $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, Renderer $render_manager, EntityRepositoryInterface $entity_repository) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityTypeManager = $entity_type_manager;
$this->renderedManager = $render_manager;
$this->entityRepository = $entity_repository;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->getParameter('serializer.formats'), $container
->get('logger.factory')
->get('rest'), $container
->get('entity_type.bundle.info'), $container
->get('entity_type.manager'), $container
->get('renderer'), $container
->get('entity.repository'));
}
public function get($flow_id, $entity_type, $entity_bundle, $shared_entity_id) {
$flow = Flow::getAll()[$flow_id];
if (empty($flow)) {
$message = t("The flow @flow_id doesn't exist.", [
'@flow_id' => $flow_id,
])
->render();
\Drupal::logger('cms_content_sync')
->notice('@not GET @shared_entity_id: @message', [
'@shared_entity_id' => $shared_entity_id,
'@not' => 'NO',
'@message' => $message,
]);
return $this
->respondWith([
'message' => $message,
], self::CODE_NOT_FOUND);
}
$infos = EntityStatus::getInfosForEntity($entity_type, $shared_entity_id, [
'flow' => $flow_id,
]);
foreach ($infos as $info) {
if ($info
->isDeleted()) {
return $this
->respondWith([
'message' => 'This entity has been deleted.',
], self::CODE_NOT_FOUND);
}
}
$entity = \Drupal::service('entity.repository')
->loadEntityByUuid($entity_type, $shared_entity_id);
if (!$entity) {
return $this
->respondWith([
'message' => 'This entity does not exist.',
], self::CODE_NOT_FOUND);
}
$always_v2 = Migration::alwaysUseV2();
if (!$always_v2) {
Migration::useV2(true);
}
$intent = PushIntent::pushEntity($entity, PushIntent::PUSH_ANY, SyncIntent::ACTION_CREATE, $flow, null, true);
if (!$intent) {
return $this
->respondWith([
'message' => 'This entity is not configured to be pushed.',
], self::CODE_NOT_FOUND);
}
$operation = $intent
->getOperation();
$body = $operation
->getData();
$intent
->afterPush(SyncIntent::ACTION_CREATE, $entity);
if (!$always_v2) {
Migration::useV2(false);
}
return $this
->respondWith(json_decode(json_encode($body), true), 200);
}
public function delete($flow_id, $entity_type, $entity_bundle, $shared_entity_id) {
return $this
->handleIncomingEntity($flow_id, $entity_type, $entity_bundle, $shared_entity_id, json_decode(file_get_contents('php://input'), true), SyncIntent::ACTION_DELETE);
}
public function post($flow_id, $entity_type, $entity_bundle, $shared_entity_id, array $data) {
return $this
->handleIncomingEntity($flow_id, $entity_type, $entity_bundle, $shared_entity_id, $data, SyncIntent::ACTION_CREATE);
}
protected function respondWith($body, $status, $serialize = false) {
$response = new ModifiedResourceResponse($serialize ? null : $body, $status);
if ($serialize) {
$response
->setContent(json_encode($body));
}
return $response;
}
protected function saveFailedPull($pool_id, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid, $failure_reason, $action, $reason, $flow_id = null) {
$entity_status = EntityStatus::getInfoForEntity($entity_type, $entity_uuid, $flow_id, $pool_id);
if (!$entity_status) {
$entity_status = EntityStatus::create([
'flow' => $flow_id ? $flow_id : EntityStatus::FLOW_NO_FLOW,
'pool' => $pool_id,
'entity_type' => $entity_type,
'entity_uuid' => $entity_uuid,
'entity_type_version' => $entity_type_version,
'flags' => 0,
'source_url' => null,
]);
}
$soft_fails = [
PullIntent::PULL_FAILED_UNKNOWN_POOL,
PullIntent::PULL_FAILED_NO_FLOW,
PullIntent::PULL_FAILED_HANDLER_DENIED,
];
$soft = in_array($failure_reason, $soft_fails);
$entity_status
->didPullFail(true, $soft, [
'error' => $failure_reason,
'action' => $action,
'reason' => $reason,
'bundle' => $entity_bundle,
]);
$entity_status
->save();
}
private function handleIncomingEntity($flow_id, $entity_type_name, $entity_bundle, $shared_entity_id, array $data, $action) {
$flow = Flow::getAll()[$flow_id];
if (empty($flow)) {
$message = t("The flow @flow_id doesn't exist.", [
'@flow_id' => $flow_id,
])
->render();
\Drupal::logger('cms_content_sync')
->notice('@not PULL @action @shared_entity_id: @message', [
'@action' => $action,
'@shared_entity_id' => $shared_entity_id,
'@not' => 'NO',
'@message' => $message,
]);
return $this
->respondWith([
'message' => $message,
], self::CODE_NOT_FOUND, SyncIntent::ACTION_DELETE == $action);
}
\Drupal::logger('cms_content_sync')
->notice('received @shared_entity_id via @flow_id with @body', [
'@shared_entity_id' => $shared_entity_id,
'@flow_id' => $flow_id,
'@body' => json_encode($data, JSON_PRETTY_PRINT),
]);
$reason = PullIntent::PULL_FORCED;
$core = SyncCoreFactory::getSyncCoreV2();
$all_pools = Pool::getAll();
$pools = [];
$operation = $core
->getSyndicationService()
->handlePull($flow->id, null, null, $data, SyncIntent::ACTION_DELETE === $action);
$entity_type_name = $operation
->getEntityTypeNamespaceMachineName();
$entity_bundle = $operation
->getEntityTypeMachineName();
$entity_type_version = $operation
->getEntityTypeVersionId();
if (EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_name)) {
$entity_uuid = \Drupal::entityTypeManager()
->getStorage($entity_type_name)
->load($operation
->getId())
->uuid();
}
else {
$entity_uuid = $shared_entity_id;
}
$pool_machine_names = $operation
->getPoolIds();
if (empty($pool_machine_names)) {
$pool_machine_names = [];
$statuses = EntityStatus::getInfosForEntity($operation
->getEntityTypeNamespaceMachineName(), $entity_uuid, [
'flow' => $flow_id,
]);
if (empty($statuses)) {
$statuses = EntityStatus::getInfosForEntity($operation
->getEntityTypeNamespaceMachineName(), $entity_uuid);
}
foreach ($statuses as $status) {
$pool_machine_names[] = $status
->getPool()
->id();
}
\Drupal::logger('cms_content_sync')
->notice(json_encode([
$operation
->getEntityTypeNamespaceMachineName(),
$entity_uuid,
$flow_id,
count($statuses),
$pool_machine_names,
]));
}
foreach ($pool_machine_names as $machine_name) {
if (!isset($all_pools[$machine_name])) {
$message = t("The pool @machine_name doesn't exist.", [
'@machine_name' => $machine_name,
])
->render();
\Drupal::logger('cms_content_sync')
->notice('@not PULL @action @shared_entity_id: @message', [
'@action' => $action,
'@shared_entity_id' => $shared_entity_id,
'@not' => 'NO',
'@message' => $message,
]);
$this
->saveFailedPull($machine_name, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_UNKNOWN_POOL, $action, $reason);
return $this
->respondWith([
'message' => $message,
], self::CODE_NOT_FOUND, SyncIntent::ACTION_DELETE == $action);
}
$pools[] = $all_pools[$machine_name];
}
if (empty($pools)) {
return $this
->respondWith([
'message' => "No pools were given and the entity doesn't exist on this site with any pool.",
], 404, SyncIntent::ACTION_DELETE == $action);
}
try {
$intent = new PullIntent($flow, $pools[0], $reason, $action, $entity_type_name, $entity_bundle, $operation);
$status = $intent
->execute();
$parent = $intent
->getEntity();
$parent_type = $parent ? $parent
->getEntityTypeId() : null;
$parent_uuid = $parent ? $parent
->uuid() : null;
while ($embed = $operation
->getNextUnprocessedEmbed()) {
$embed_pool = null;
foreach ($embed
->getPoolIds() as $pool_id) {
if (isset($all_pools[$pool_id])) {
$embed_pool = $all_pools[$pool_id];
break;
}
}
if (!$embed_pool) {
continue;
}
$embed_intent = new PullIntent($flow, $embed_pool, $reason, $action, $embed
->getEntityTypeNamespaceMachineName(), $embed
->getEntityTypeMachineName(), $embed, $parent_type, $parent_uuid);
$embed_intent
->execute();
}
if ($parent) {
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$menu_items = $menu_link_manager
->loadLinksByRoute('entity.' . $parent_type . '.canonical', [
$parent_type => $parent
->id(),
]);
foreach ($menu_items as $plugin_item) {
$menu_link_reflection = new \ReflectionMethod('\\Drupal\\menu_link_content\\Plugin\\Menu\\MenuLinkContent', 'getEntity');
$menu_link_reflection
->setAccessible(true);
$item = $menu_link_reflection
->invoke($plugin_item, 'getEntity');
if (!$operation
->isEmbedded($item
->getEntityTypeId(), $item
->uuid())) {
$menu_pools = [];
$statuses = EntityStatus::getInfosForEntity($item
->getEntityTypeId(), $item
->uuid(), [
'flow' => $flow_id,
]);
foreach ($statuses as $status) {
if (!$status
->getLastPull() || !$status
->wasPulledEmbedded()) {
continue;
}
$menu_pools[] = $status
->getPool();
}
if (empty($menu_pools)) {
continue;
}
$menu_operation = new class($item) {
protected $item;
public function __construct($item) {
$this->item = $item;
}
public function getUuid() {
return $this->item
->uuid();
}
public function getSourceUrl() {
return '';
}
public function getUsedTranslationLanguages() {
return [];
}
public function getName() {
return $this->item
->label();
}
public function getProperty($name) {
try {
return $this->item
->get($name)
->getValue();
} catch (\Exception $e) {
return null;
}
}
};
$menu_intent = new PullIntent($flow, $menu_pools[0], PullIntent::PULL_FORCED, SyncIntent::ACTION_DELETE, $item
->getEntityTypeId(), $item
->bundle(), $menu_operation, $parent_type, $parent_uuid);
$menu_intent
->execute();
}
}
}
if (!Migration::alwaysUseV2()) {
Migration::entityUsedV2($flow->id, $entity_type_name, $entity_bundle, $entity_uuid, EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_name) ? $shared_entity_id : null, false);
}
} catch (SyncException $e) {
$message = $e->parentException ? $e->parentException
->getMessage() : ($e->errorCode == $e
->getMessage() ? '' : $e
->getMessage());
if ($message) {
$message = t('Internal error @code: @message', [
'@code' => $e->errorCode,
'@message' => $message,
])
->render();
}
else {
$message = t('Internal error @code', [
'@code' => $e->errorCode,
])
->render();
}
\Drupal::logger('cms_content_sync')
->error('@not PULL @action @entity_type:@bundle @uuid @reason: @message' . "\n" . '@trace' . "\n" . '@request_body<br>Flow: @flow_id | Pool: @pool_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $flow_id,
'@pool_id' => $pools[0]
->id(),
'@message' => $message,
'@trace' => ($e->parentException ? $e->parentException
->getTraceAsString() . "\n\n\n" : '') . $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($pools[0]
->id(), $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_CONTENT_SYNC_ERROR, $action, $reason, $flow->id);
return $this
->respondWith([
'message' => t('SyncException @code: @message', [
'@code' => $e->errorCode,
'@message' => $e
->getMessage(),
])
->render(),
'code' => $e->errorCode,
], 500, SyncIntent::ACTION_DELETE == $action);
} catch (\Exception $e) {
$message = $e
->getMessage();
\Drupal::logger('cms_content_sync')
->error('@not PULL @action @entity_type:@bundle @uuid @reason: @message' . "\n" . '@trace' . "\n" . '@request_body<br>Flow: @flow_id | Pool: @pool_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $flow_id,
'@pool_id' => $pools[0]
->id(),
'@message' => $message,
'@trace' => $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($pools[0]->id, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_INTERNAL_ERROR, $action, $reason, $flow->id);
return $this
->respondWith([
'message' => t('Unexpected error: @message', [
'@message' => $e
->getMessage(),
])
->render(),
], 500, SyncIntent::ACTION_DELETE == $action);
}
if (!$status) {
$this
->saveFailedPull($pools[0]->id, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_HANDLER_DENIED, $action, $reason, $flow->id);
}
if ($status) {
$url = SyncIntent::ACTION_DELETE === $action ? null : $intent
->getViewUrl();
$response_body = $operation
->getResponseBody($url);
return $this
->respondWith($response_body, 200, SyncIntent::ACTION_DELETE == $action);
}
return $this
->respondWith([
'message' => t('Entity is not configured to be pulled yet.')
->render(),
], 404, SyncIntent::ACTION_DELETE == $action);
}
}