You are here

private function SyncCoreEntityItemResource::handleIncomingEntity in CMS Content Sync 2.1.x

Same name and namespace in other branches
  1. 2.0.x src/Plugin/rest/resource/SyncCoreEntityItemResource.php \Drupal\cms_content_sync\Plugin\rest\resource\SyncCoreEntityItemResource::handleIncomingEntity()
2 calls to SyncCoreEntityItemResource::handleIncomingEntity()
SyncCoreEntityItemResource::delete in src/Plugin/rest/resource/SyncCoreEntityItemResource.php
SyncCoreEntityItemResource::post in src/Plugin/rest/resource/SyncCoreEntityItemResource.php

File

src/Plugin/rest/resource/SyncCoreEntityItemResource.php, line 328

Class

SyncCoreEntityItemResource
Provides entity interfaces for Content Sync, allowing Sync Core v2 to request and manipulate entities.

Namespace

Drupal\cms_content_sync\Plugin\rest\resource

Code

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;
  }

  // Delete doesn't come with pools
  $pool_machine_names = $operation
    ->getPoolIds();
  if (empty($pool_machine_names)) {
    $pool_machine_names = [];
    $statuses = EntityStatus::getInfosForEntity($operation
      ->getEntityTypeNamespaceMachineName(), $entity_uuid, [
      'flow' => $flow_id,
    ]);

    // Maybe the entity type is overloaded (multiple Flows for the same type) and the Sync Core uses a
    // different Flow for the delete request because none of the Flows matches.
    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,
    ]));
  }

  // TODO: Handle multiple Pools at once.
  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();
    }

    // Delete menu items that no longer exist.
    if ($parent) {
      $menu_link_manager = \Drupal::service('plugin.manager.menu.link');

      /**
       * @var Drupal\menu_link_content\Plugin\Menu\MenuLinkContent[] $menu_items
       */
      $menu_items = $menu_link_manager
        ->loadLinksByRoute('entity.' . $parent_type . '.canonical', [
        $parent_type => $parent
          ->id(),
      ]);
      foreach ($menu_items as $plugin_item) {

        /**
         * @var Drupal\menu_link_content\Entity\MenuLinkContent $item
         */

        // We need to get an Entity at this point,
        // but 'getEntity' is protected for some reason.
        // So we don't have other choice here but use a reflection.
        $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) {

            /**
             * @var Drupal\menu_link_content\Entity\MenuLinkContent
             */
            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);

    // If we send data for DELETE requests, the Drupal Serializer will throw
    // a random error. So we just leave the body empty then.
    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);
}