View source
<?php
namespace Drupal\cms_content_sync\Plugin\rest\resource;
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\SyncCoreInterface\DrupalApplication;
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 Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class EntityResource extends ResourceBase {
public const CODE_INVALID_DATA = 401;
public const CODE_NOT_FOUND = 404;
public const TYPE_HAS_NOT_BEEN_FOUND = 'The entity type has not been found.';
public const TYPE_HAS_INCOMPATIBLE_VERSION = 'The entity type has an incompatible version.';
public const READ_LIST_ENTITY_ID = '0';
public const PING_PARAMETER = 'ping';
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 static function getInternalPingUrl($method) {
return DrupalApplication::get()
->getRestUrl(self::PING_PARAMETER, self::PING_PARAMETER, self::PING_PARAMETER, self::PING_PARAMETER, 'POST' === $method ? null : self::PING_PARAMETER);
}
public function get($entity_type, $entity_bundle, $entity_uuid) {
return new ResourceResponse([
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
public function patch($api, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid, array $data) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, $data, SyncIntent::ACTION_UPDATE);
}
public function delete($api, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, [
'uuid' => $entity_uuid,
'id' => $entity_uuid,
], SyncIntent::ACTION_DELETE);
}
public function post($api, $entity_type, $entity_bundle, $entity_type_version, array $data) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, $data, SyncIntent::ACTION_CREATE);
}
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($api, $entity_type_name, $entity_bundle, $entity_type_version, array $data, $action) {
if (self::PING_PARAMETER === $api) {
return new ResourceResponse(SyncIntent::ACTION_DELETE === $action ? null : [
'pong' => true,
]);
}
$entity_types = $this->entityTypeBundleInfo
->getAllBundleInfo();
if (empty($entity_types[$entity_type_name])) {
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$is_dependency = isset($_GET['is_dependency']) && 'true' == $_GET['is_dependency'];
$is_manual = isset($_GET['is_manual']) && 'true' == $_GET['is_manual'];
$reason = $is_dependency ? PullIntent::PULL_AS_DEPENDENCY : ($is_manual ? PullIntent::PULL_MANUALLY : PullIntent::PULL_AUTOMATICALLY);
$entity_uuid = null;
$pool = Pool::getAll()[$api];
if (empty($pool)) {
\Drupal::logger('cms_content_sync')
->warning('@not PULL @action @entity_type:@bundle @uuid @reason: @message<br>Flow: @flow_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $api,
'@message' => t('No pool config matches this request (@api).', [
'@api' => $api,
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_UNKNOWN_POOL, $action, $reason);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$flow = Flow::getFlowForApiAndEntityType($pool, $entity_type_name, $entity_bundle, $reason, $action);
if (empty($flow) && SyncIntent::ACTION_DELETE == $action && PullIntent::PULL_AS_DEPENDENCY != $reason) {
$flow = Flow::getFlowForApiAndEntityType($pool, $entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, $action);
if (!empty($flow)) {
$reason = PullIntent::PULL_AS_DEPENDENCY;
}
}
if (empty($flow)) {
\Drupal::logger('cms_content_sync')
->notice('@not PULL @action @entity_type:@bundle @uuid @reason: @message', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@message' => t('No synchronization config matches this request (dependency: @dependency, manual: @manual).', [
'@dependency' => $is_dependency ? 'YES' : 'NO',
'@manual' => $is_manual ? 'YES' : 'NO',
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_NO_FLOW, $action, $reason);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$operation = $pool
->getClient()
->getSyndicationService()
->handlePull($flow->id, $entity_type_name, $entity_bundle, $data, SyncIntent::ACTION_DELETE == $action);
if (SyncIntent::ACTION_DELETE === $action && EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_name)) {
$entity_uuid = \Drupal::entityTypeManager()
->getStorage($entity_type_name)
->load($operation
->getId())
->uuid();
}
else {
$entity_uuid = $operation
->getUuid();
}
$local_version = Flow::getEntityTypeVersion($entity_type_name, $entity_bundle);
if ($entity_type_version != $local_version && SyncIntent::ACTION_DELETE != $action) {
\Drupal::logger('cms_content_sync')
->warning('@not PULL @action @entity_type:@bundle @uuid @reason: @message<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' => $api,
'@pool_id' => $pool
->id(),
'@message' => t('The requested entity type version @requested doesn\'t match the local entity type version @local.', [
'@requested' => $entity_type_version,
'@local' => $local_version,
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_DIFFERENT_VERSION, $action, $reason, $flow->id);
return new ResourceResponse([
'message' => t(self::TYPE_HAS_INCOMPATIBLE_VERSION)
->render(),
], self::CODE_NOT_FOUND);
}
try {
$intent = new PullIntent($flow, $pool, $reason, $action, $entity_type_name, $entity_bundle, $operation);
$status = $intent
->execute();
} 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' => $api,
'@pool_id' => $pool
->id(),
'@message' => $message,
'@trace' => ($e->parentException ? $e->parentException
->getTraceAsString() . "\n\n\n" : '') . $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_CONTENT_SYNC_ERROR, $action, $reason, $flow->id);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('SyncException @code: @message', [
'@code' => $e->errorCode,
'@message' => $e
->getMessage(),
])
->render(),
'code' => $e->errorCode,
], 500);
} 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' => $api,
'@pool_id' => $pool
->id(),
'@message' => $message,
'@trace' => $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_INTERNAL_ERROR, $action, $reason, $flow->id);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('Unexpected error: @message', [
'@message' => $e
->getMessage(),
])
->render(),
], 500);
}
if (!$status) {
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_HANDLER_DENIED, $action, $reason, $flow->id);
}
if ($status) {
$entity = $intent
->getEntity();
$url = null;
if ($entity && $entity
->hasLinkTemplate('canonical')) {
try {
$url = $entity
->toUrl('canonical', [
'absolute' => true,
])
->toString(true)
->getGeneratedUrl();
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_UNEXPECTED_EXCEPTION, $e);
}
}
$response_body = $operation
->getResponseBody($url);
return new ModifiedResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : $response_body);
}
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('Entity is not configured to be pulled yet.')
->render(),
], 404);
}
}