cms_content_sync-3.0.x-dev/src/Plugin/rest/resource/SyncCoreEntityListResource.php
src/Plugin/rest/resource/SyncCoreEntityListResource.php
<?php
namespace Drupal\cms_content_sync\Plugin\rest\resource;
use Drupal\cms_content_sync\Controller\LoggerProxy;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Render\Renderer;
use Drupal\rest\Plugin\ResourceBase;
use EdgeBox\SyncCore\Interfaces\IApplicationInterface;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteEntityListRequestMode;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteEntityListResponse;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteRequestQueryParamsEntityList;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides entity interfaces for Content Sync, allowing Sync Core v2 to
* list entities.
*
* @RestResource(
* id = "cms_content_sync_sync_core_entity_list",
* label = @Translation("Content Sync: Sync Core: Entity list"),
* uri_paths = {
* "canonical" = "/rest/cms-content-sync/v2/{flow_id}"
* }
* )
*/
class SyncCoreEntityListResource extends ResourceBase implements ContentSyncRestInterface {
use ContentSyncRestTrait;
protected const NONE = 'null';
/**
* @var \Drupal\Core\Entity\EntityTypeBundleInfo
*/
protected $entityTypeBundleInfo;
/**
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* @var \Drupal\Core\Render\Renderer
*/
protected $renderedManager;
/**
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* Constructs an object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param array $serializer_formats
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info
* An entity type bundle info instance.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* An entity type manager instance.
* @param \Drupal\Core\Render\Renderer $render_manager
* A rendered instance.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository interface.
*/
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;
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
LoggerProxy::get(),
$container->get('entity_type.bundle.info'),
$container->get('entity_type.manager'),
$container->get('renderer'),
$container->get('entity.repository')
);
}
/**
*
*/
public function get($flow_id) {
$query = \Drupal::request()->query->all();
$queryObject = new RemoteRequestQueryParamsEntityList($query);
$entity_type = $queryObject->getNamespaceMachineName();
$bundle = $queryObject->getMachineName();
$flow = Flow::getAll()[$flow_id] ?? NULL;
if (empty($flow)) {
if (IApplicationInterface::FLOW_NONE === $flow_id) {
$flow = new Flow([
'id' => $flow_id,
'name' => 'Virtual',
'type' => Flow::TYPE_PUSH,
'variant' => Flow::VARIANT_SIMPLE,
'simple_settings' => [
'poolAssignment' => 'force',
'mode' => 'automatically',
'deletions' => FALSE,
'updateBehavior' => 'ignore',
'ignoreUnpublishedChanges' => FALSE,
'allowExplicitUnpublishing' => FALSE,
'pushMenuItems' => FALSE,
'pushPreviews' => FALSE,
'mergeLocalChanges' => FALSE,
'resolveUserReferences' => 'name',
'poolSelectionWidget' => 'checkboxes',
'entityTypeSettings' => [
$entity_type => [
'perBundle' => [
$bundle => [
'mode' => 'default',
],
],
],
],
],
], 'cms_content_sync_flow');
$flow->getController()->isVirtual(TRUE);
}
else {
$message = t("The flow @flow_id doesn't exist.", ['@flow_id' => $flow_id])->render();
$this->logger->notice('@not LIST: @message', [
'@not' => 'NO',
'@message' => $message,
]);
return $this->respondWith(
['message' => $message],
self::
CODE_NOT_FOUND,
FALSE
);
}
}
$mode = $queryObject->getMode();
if (!$mode) {
return $this->returnError(t('The mode query parameter is required.')->render());
}
if (RemoteEntityListRequestMode::ALL === $mode) {
if (!$entity_type || !$bundle) {
return $this->returnError(t("The type and bundle query parameters are required for mode 'all'.")->render());
}
}
$page = (int) $queryObject->getPage();
if (!$page) {
$page = 0;
}
$items_per_page = (int) $queryObject->getItemsPerPage();
if (!$items_per_page) {
$items_per_page = 0;
}
$mode = $queryObject->getMode();
// Need to convert miliseconds to seconds.
$changed_after = $queryObject->getChangedAfter() ? floor((int) $queryObject->getChangedAfter() / 1000) : NULL;
$skip = $page * $items_per_page;
$database = \Drupal::database();
/**
* @var \EdgeBox\SyncCore\V2\Raw\Model\RemoteEntitySummary[] $items
*/
$items = [];
try {
// If ALL entities are requested, we can't rely on the status entity.
// Instead, we query for these entities by their type's table directly.
if (RemoteEntityListRequestMode::ALL === $mode) {
$config = $flow->getController()->getEntityTypeConfig($entity_type, $bundle);
$handler = $flow->getController()->getEntityTypeHandler($entity_type, $bundle, $config);
$result = $handler->getSyncCoreList($flow, $queryObject);
}
else {
$query = $database->select('cms_content_sync_entity_status', 'cses');
$base_table = NULL;
if ($entity_type && $bundle) {
$entity_type_storage = \Drupal::entityTypeManager()->getStorage($entity_type);
if ($entity_type_storage instanceof SqlContentEntityStorage) {
$bundle_key = $entity_type_storage->getEntityType()->getKey('bundle');
$base_table = $entity_type_storage->getBaseTable();
if ($base_table) {
$query->leftJoin($base_table, 'bt', 'bt.uuid = cses.entity_uuid');
}
}
}
$query
->condition('cses.flow', $flow_id);
$changed_field = RemoteEntityListRequestMode::PULLED === $mode ? 'last_import' : 'last_export';
if ($changed_after) {
$query->condition('cses.' . $changed_field, $changed_after, '>');
}
elseif (RemoteEntityListRequestMode::PULLED === $mode || RemoteEntityListRequestMode::PUSHED === $mode) {
$query->condition('cses.' . $changed_field, 0, '>');
}
if ($entity_type) {
$query
->condition('cses.entity_type', $entity_type);
if ($bundle && !empty($bundle_key) && $base_table) {
$or = $query->orConditionGroup()
->where('flags&:deleted_flag=:deleted_flag', [':deleted_flag' => EntityStatus::FLAG_DELETED])
->condition('bt.' . $bundle_key, $bundle);
$query->condition($or);
}
}
if (RemoteEntityListRequestMode::PUSH_FAILED === $mode) {
$query
->where('flags&:push_failed_flag=:push_failed_flag', [':push_failed_flag' => EntityStatus::FLAG_PUSH_FAILED]);
}
$query->addExpression('MIN(cses.id)', 'min_id');
$query
->orderBy('min_id', 'ASC')
->fields('cses', ['entity_type', 'entity_uuid']);
$query->groupBy('cses.entity_type');
$query->groupBy('cses.entity_uuid');
$total_number_of_items = (int) $query->countQuery()->execute()->fetchField();
if ($total_number_of_items && $items_per_page) {
$query->range($skip, $items_per_page);
$ids = $query->execute()->fetchAll(\PDO::FETCH_ASSOC);
foreach ($ids as $id) {
$entity = \Drupal::service('entity.repository')->loadEntityByUuid(
$id['entity_type'],
$id['entity_uuid']
);
$handler_type = $entity_type ?? $id['entity_type'];
$handler_bundle = $bundle ?? ($entity ? $entity->bundle() : '*');
$config = $flow->getController()->getEntityTypeConfig($handler_type, $handler_bundle);
if (empty($config)) {
continue;
}
$handler = $flow->getController()->getEntityTypeHandler($handler_type, $handler_bundle, $config);
if (empty($handler)) {
continue;
}
$items[] = $handler->getSyncCoreListItem($flow, $entity, EntityStatus::getInfosForEntity($id['entity_type'], $id['entity_uuid'], ['flow' => $flow_id]));
}
}
if (!$items_per_page) {
$number_of_pages = $total_number_of_items;
}
else {
$number_of_pages = ceil($total_number_of_items / $items_per_page);
}
$result = new RemoteEntityListResponse();
$result->setPage($page);
$result->setNumberOfPages($number_of_pages);
$result->setItemsPerPage($items_per_page);
$result->setTotalNumberOfItems($total_number_of_items);
$result->setItems($items);
}
$body = $result->jsonSerialize();
// Turn objects into arrays.
return $this->respondWith(json_decode(json_encode($body), TRUE), self::CODE_OK, FALSE);
}
catch (SyncException $e) {
$message = $e->getSyncExceptionMessage();
$this->logger->notice('@not LIST: @message', [
'@not' => 'NO',
'@message' => $message,
]);
return $this->respondWith(
$e->serialize(),
self::
CODE_INTERNAL_SERVER_ERROR,
FALSE
);
}
catch (\Exception $e) {
$this->logger->notice('@not LIST: @message', [
'@not' => 'NO',
'@message' => $e->getMessage(),
]);
return $this->respondWith(
[
'message' => 'Unexpected error: ' . $e->getMessage(),
'stack' => $e->getTraceAsString(),
],
self::
CODE_INTERNAL_SERVER_ERROR,
FALSE
);
}
}
/**
*
*/
protected function returnError($message, $code = self::CODE_BAD_REQUEST) {
$this->logger->notice('@not LIST: @message', [
'@not' => 'NO',
'@message' => $message,
]);
return $this->respondWith(
['message' => $message],
$code,
FALSE
);
}
}
