You are here

class PullEntity in CMS Content Sync 2.0.x

Same name and namespace in other branches
  1. 8 src/Plugin/rest/resource/PullEntity.php \Drupal\cms_content_sync\Plugin\rest\resource\PullEntity
  2. 2.1.x src/Plugin/rest/resource/PullEntity.php \Drupal\cms_content_sync\Plugin\rest\resource\PullEntity

Provides entity interfaces for Content Sync, allowing the manual pull dashboard to query preview entities and to pull them to the site.

Plugin annotation


@RestResource(
  id = "cms_content_sync_import_entity",
  label = @Translation("Content Sync Pull"),
  uri_paths = {
    "canonical" = "/rest/cms-content-sync-import/{pool_id}",
    "create" = "/rest/cms-content-sync-import/{pool_id}/{entity_type_name}/{bundle_name}/{shared_entity_id}"
  }
)

Hierarchy

Expanded class hierarchy of PullEntity

File

src/Plugin/rest/resource/PullEntity.php, line 33

Namespace

Drupal\cms_content_sync\Plugin\rest\resource
View source
class PullEntity extends ResourceBase {

  /**
   * @var int CODE_INVALID_DATA The provided data could not be interpreted
   */
  public const CODE_INVALID_DATA = 401;

  /**
   * @var int CODE_NOT_FOUND The entity doesn't exist or can't be accessed
   */
  public const CODE_NOT_FOUND = 404;

  /**
   * @var int CODE_UNEXPECTED_ERROR The Sync Core wasn't able to synchronize this entity
   */
  public const CODE_UNEXPECTED_ERROR = 500;

  /**
   * @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'), $container
      ->get('logger.factory')
      ->get('rest'), $container
      ->get('entity_type.bundle.info'), $container
      ->get('entity_type.manager'), $container
      ->get('renderer'), $container
      ->get('entity.repository'));
  }

  /**
   * Responds to entity GET requests.
   *
   * @param string $pool_id
   *                        The ID of the selected flow
   *
   * @throws \Exception
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *                                                    A list of entities of the given type and bundle
   */
  public function get(string $pool_id) {
    $cache_build = [
      '#cache' => [
        'max-age' => 0,
      ],
    ];

    // Loaded directly from the Sync Core, so we just need to provide the status for them.
    if (!empty($_GET['entities'])) {
      $items = [];
      $types = explode(';', $_GET['entities']);
      foreach ($types as $type) {
        list($type, $ids) = explode(':', $type);
        $ids = explode(',', $ids);
        foreach ($ids as $id) {
          list(, , $entity_type_name, $bundle_name) = explode('-', $type);
          $items[] = array_merge([
            'entity_type_id' => $type,
            'id' => $id,
          ], $this
            ->getPreviewItemData($entity_type_name, $bundle_name, $id));
        }
      }
      $resource_response = new ResourceResponse([
        'items' => $items,
      ]);
      $resource_response
        ->addCacheableDependency($cache_build);
      return $resource_response;
    }
    $pool = Pool::getAll()[$pool_id];
    if (!$pool) {
      $resource_response = new ResourceResponse([
        'message' => "Unknown pool {$pool_id}.",
      ], self::CODE_NOT_FOUND);
      $resource_response
        ->addCacheableDependency($cache_build);
      return $resource_response;
    }
    $entity_type_ids = [];
    $entity_type_name = isset($_GET['entity_type_name']) ? $_GET['entity_type_name'] : null;
    $bundle_name = isset($_GET['bundle_name']) ? $_GET['bundle_name'] : null;
    $page = isset($_GET['page']) ? intval($_GET['page']) : 0;
    $sync_core_settings = $pool
      ->getClient()
      ->getSyndicationService()
      ->configurePullDashboard();
    if (!$sync_core_settings) {
      throw new \Exception('Invalid pull usage. Please refresh the page.');
    }

    /**
     * @var \Drupal\Core\Entity\EntityFieldManager $entityFieldManager
     */
    $entityFieldManager = \Drupal::service('entity_field.manager');
    foreach (Flow::getAll() as $flow) {
      foreach ($flow
        ->getEntityTypeConfig() as $definition) {
        if (!$flow
          ->canPullEntity($definition['entity_type_name'], $definition['bundle_name'], PullIntent::PULL_MANUALLY)) {
          continue;
        }
        if ($entity_type_name && $definition['entity_type_name'] != $entity_type_name) {
          continue;
        }
        if ($bundle_name && $definition['bundle_name'] != $bundle_name) {
          continue;
        }
        if (empty($definition['import_pools'][$pool->id]) || Pool::POOL_USAGE_ALLOW != $definition['import_pools'][$pool->id]) {
          continue;
        }
        if (isset($entity_type_ids[$pool_id][$definition['entity_type_name']][$definition['bundle_name']])) {
          continue;
        }
        if (EntityHandlerPluginManager::isEntityTypeFieldable($definition['entity_type_name'])) {

          /**
           * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
           */
          $fields = $entityFieldManager
            ->getFieldDefinitions($definition['entity_type_name'], $definition['bundle_name']);
          foreach ($fields as $key => $field) {
            $field_config = $flow
              ->getFieldHandlerConfig($definition['entity_type_name'], $definition['bundle_name'], $key);
            if (empty($field_config)) {
              continue;
            }
            if (empty($field_config['handler_settings']['subscribe_only_to'])) {
              continue;
            }
            $allowed = [];
            foreach ($field_config['handler_settings']['subscribe_only_to'] as $ref) {
              $allowed[] = $ref['uuid'];
            }
            $sync_core_settings
              ->ifTaggedWith($pool_id, $definition['entity_type_name'], $definition['bundle_name'], $key, $allowed);
          }
        }
        $sync_core_settings
          ->forEntityType($pool_id, $definition['entity_type_name'], $definition['bundle_name']);
        $entity_type_ids[$pool_id][$definition['entity_type_name']][$definition['bundle_name']] = true;
      }
    }
    if (empty($entity_type_ids)) {
      $resource_response = new ResourceResponse([
        'message' => 'No previews available.',
      ], self::CODE_NOT_FOUND);
      $resource_response
        ->addCacheableDependency($cache_build);
      return $resource_response;
    }
    if (!empty($_GET['filter_title'])) {
      $sync_core_settings
        ->searchInTitle($_GET['filter_title']);
    }
    if (!empty($_GET['filter_preview'])) {
      $sync_core_settings
        ->searchInPreview($_GET['filter_preview']);
    }
    $sync_core_settings
      ->publishedBetween(empty($_GET['filter_published_from']) ? null : (int) $_GET['filter_published_from'], empty($_GET['filter_published_to']) ? null : (int) $_GET['filter_published_to'] + 24 * 60 * 60);
    try {
      $order_by_title = isset($_GET['order_by']) && 'title' == $_GET['order_by'];
      $order_ascending = isset($_GET['order_direction']) && 'asc' == $_GET['order_direction'];
      $response = $sync_core_settings
        ->runSearch($order_by_title, $order_ascending, $page);
    } catch (SyncCoreException $e) {
      $body = $e
        ->getResponseBody();
      $code = $e
        ->getStatusCode();
      $resource_response = new ResourceResponse($body ? $body : json_encode([
        'message' => $e
          ->getMessage(),
      ]), $code ? $code : 500);
      $resource_response
        ->addCacheableDependency($cache_build);
      return $resource_response;
    }
    foreach ($response
      ->getItems() as $item) {
      $item
        ->extend($this
        ->getPreviewItemData($item
        ->getType(), $item
        ->getBundle(), $item
        ->getId()));
    }
    $resource_response = new ResourceResponse($response
      ->toArray());
    $resource_response
      ->addCacheableDependency($cache_build);
    return $resource_response;
  }

  /**
   * Responds to entity POST requests.
   *
   * @throws \Exception
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *                                                    -
   */
  public function post(string $pool_id, string $entity_type_name, string $bundle_name, string $shared_entity_id) {
    $pool = Pool::getAll()[$pool_id];
    if (!$pool) {
      return new ResourceResponse([
        'message' => "Unknown pool ID {$pool_id}.",
      ], self::CODE_NOT_FOUND);
    }
    $preview_item = null;
    foreach (Flow::getAll() as $flow) {
      foreach ($flow
        ->getEntityTypeConfig() as $definition) {
        if (!$flow
          ->canPullEntity($definition['entity_type_name'], $definition['bundle_name'], PullIntent::PULL_MANUALLY)) {
          continue;
        }
        if ($definition['entity_type_name'] != $entity_type_name) {
          continue;
        }
        if ($definition['bundle_name'] != $bundle_name) {
          continue;
        }
        if (Pool::POOL_USAGE_ALLOW != $definition['import_pools'][$pool->id]) {
          continue;
        }
        try {
          $preview_item = $pool
            ->getClient()
            ->getSyndicationService()
            ->pullSingle($flow->id, $entity_type_name, $bundle_name, $shared_entity_id)
            ->fromPool($pool->id)
            ->manually(true)
            ->execute()
            ->getPullDashboardSearchResultItem();
          break 2;
        } catch (SyncCoreException $e) {
          return new ResourceResponse([
            'message' => 'Failed to pull entity: ' . $e
              ->getMessage(),
          ], self::CODE_UNEXPECTED_ERROR);
        }
      }
    }
    if (!$preview_item) {
      return new ResourceResponse([
        'message' => "Missing flow for pool {$pool_id}.",
      ], self::CODE_NOT_FOUND);
    }
    $data = array_merge($preview_item
      ->toArray(), $this
      ->getPreviewItemData($preview_item
      ->getType(), $preview_item
      ->getBundle(), $preview_item
      ->getId()));

    // Entity was ignored by the Sync Core for some reason or the update was rejected by Drupal.
    if (empty($data['last_import'])) {
      return new ResourceResponse([
        'message' => 'Failed to pull entity. Pull was triggered but there\'s no new status entity.',
      ], self::CODE_UNEXPECTED_ERROR);
    }
    return new ResourceResponse($data);
  }

  /**
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @return array
   */
  protected function getPreviewItemData(string $entity_type_name, string $bundle_name, string $shared_entity_id) {
    $data = [];
    $data['entity_type_name'] = $entity_type_name;
    $data['bundle_name'] = $bundle_name;
    $data['entity_status'] = [];
    $data['last_import'] = null;
    $data['last_export'] = null;
    $data['deleted'] = false;
    $data['is_source'] = false;

    /**
     * @var \Drupal\Core\Entity\EntityInterface $entity
     */
    if (EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_name)) {
      $entity = \Drupal::entityTypeManager()
        ->getStorage($entity_type_name)
        ->load($shared_entity_id);
      $entity_uuid = null;
    }
    else {
      $entity = \Drupal::service('entity.repository')
        ->loadEntityByUuid($entity_type_name, $shared_entity_id);
      $entity_uuid = $shared_entity_id;
    }
    if ($entity) {
      if ($entity
        ->hasLinkTemplate('canonical')) {
        try {
          $url = $entity
            ->toUrl('canonical', [
            'absolute' => true,
          ])
            ->toString(true)
            ->getGeneratedUrl();
          $data['local_url'] = $url;
        } catch (\Exception $e) {
        }
      }
      $entity_uuid = $entity
        ->uuid();
    }
    if (!$entity_uuid) {
      return $data;
    }
    $entity_status = EntityStatus::getInfosForEntity($entity_type_name, $entity_uuid);
    foreach ($entity_status as $info) {
      $data['entity_status'][] = [
        'flow_id' => $info
          ->get('flow')->value,
        'pool_id' => $info
          ->get('pool')->value,
        'last_import' => $info
          ->getLastPull(),
        'last_export' => $info
          ->getLastPush(),
      ];
      if (!$data['last_import'] || $data['last_import'] < $info
        ->getLastPull()) {
        $data['last_import'] = $info
          ->getLastPull();
      }
      if (!$data['last_export'] || $data['last_export'] < $info
        ->getLastPush()) {
        $data['last_export'] = $info
          ->getLastPush();
      }
      if ($info
        ->isDeleted()) {
        $data['deleted'] = true;
      }
      if ($info
        ->isSourceEntity()) {
        $data['is_source'] = true;
      }
    }

    // We had a bug before where the "deleted" flag wasn't set correctly locally. So we're mitigating for that here.
    if ($data['last_import'] && !$entity) {
      $data['deleted'] = true;
    }
    return $data;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PullEntity::$entityRepository protected property
PullEntity::$entityTypeBundleInfo protected property
PullEntity::$entityTypeManager protected property
PullEntity::$renderedManager protected property
PullEntity::CODE_INVALID_DATA public constant
PullEntity::CODE_NOT_FOUND public constant
PullEntity::CODE_UNEXPECTED_ERROR public constant
PullEntity::create public static function Creates an instance of the plugin. Overrides ResourceBase::create
PullEntity::get public function Responds to entity GET requests.
PullEntity::getPreviewItemData protected function
PullEntity::post public function Responds to entity POST requests.
PullEntity::__construct public function Constructs an object. Overrides ResourceBase::__construct
ResourceBase::$logger protected property A logger instance.
ResourceBase::$serializerFormats protected property The available serialization formats.
ResourceBase::availableMethods public function Returns the available HTTP request methods on this plugin. Overrides ResourceInterface::availableMethods 1
ResourceBase::getBaseRoute protected function Gets the base route for a particular method. 2
ResourceBase::getBaseRouteRequirements protected function Gets the base route requirements for a particular method. 1
ResourceBase::permissions public function Implements ResourceInterface::permissions(). Overrides ResourceInterface::permissions 2
ResourceBase::requestMethods protected function Provides predefined HTTP request methods.
ResourceBase::routes public function Returns a collection of routes with URL path information for the resource. Overrides ResourceInterface::routes
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.