cms_content_sync-3.0.x-dev/src/Plugin/rest/resource/SyncCoreEntityItemResource.php

src/Plugin/rest/resource/SyncCoreEntityItemResource.php
<?php

namespace Drupal\cms_content_sync\Plugin\rest\resource;

use Drupal\cms_content_sync\Controller\ContentSyncSettings;
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\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\Cache\Cache;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Render\Renderer;
use Drupal\rest\Plugin\ResourceBase;
use EdgeBox\SyncCore\Interfaces\IApplicationInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use EdgeBox\SyncCore\V2\Raw\Model\EntityOperation;
use EdgeBox\SyncCore\V2\Raw\Model\EntityOperationRequest;
use EdgeBox\SyncCore\V2\Raw\Model\EntityOperationResponse;

/**
 * Provides entity interfaces for Content Sync, allowing Sync Core v2 to
 * request and manipulate entities.
 *
 * @RestResource(
 *   id = "cms_content_sync_sync_core_entity_item",
 *   label = @Translation("Content Sync: Sync Core: Entity item"),
 *   uri_paths = {
 *     "canonical" = "/rest/cms-content-sync/v2/{flow_id}/{entity_type}/{entity_bundle}/{shared_entity_id}",
 *     "create" = "/rest/cms-content-sync/v2/{flow_id}/{entity_type}/{entity_bundle}/{shared_entity_id}"
 *   }
 * )
 */
class SyncCoreEntityItemResource extends ResourceBase implements ContentSyncRestInterface {
  use ContentSyncRestTrait;

  /**
   * @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, $entity_type, $entity_bundle, $shared_entity_id) {
    $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' => [
                  $entity_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 GET @shared_entity_id: @message', [
          '@shared_entity_id' => $shared_entity_id,
          '@not' => 'NO',
          '@message' => $message,
        ]);

        return $this->respondWith(
              ['message' => $message],
              self::CODE_NOT_FOUND,
              FALSE
                );
      }
    }

    $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,
          FALSE
          );
      }
    }

    $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,
        FALSE
        );
    }

    $individual_translation = 'true' === \Drupal::request()->query->get('individualTranslation');
    $language = \Drupal::request()->query->get('language');
    if ($language) {
      $language = preg_replace('@[^a-zA-Z-0-9_]@', '', $language);

      if ($entity instanceof TranslatableInterface && $entity->language()->getId() !== $language) {
        $entity = $entity->getTranslation($language);
      }
    }

    try {
      /**
       * @var \Drupal\cms_content_sync\PushIntent $intent
       */
      $intent = PushIntent::pushEntity($entity, PushIntent::PUSH_ANY, SyncIntent::ACTION_CREATE, $flow, NULL, TRUE, $individual_translation ? $language : NULL);

      if (!$intent) {
        return $this->respondWith(
              [
                'message' => 'This entity is not configured to be pushed.',
                'reason' => PushIntent::getNoPushReason($entity, FALSE),
                'reason_message' => PushIntent::getNoPushReason($entity, TRUE),
              ],
              self::
              CODE_NOT_FOUND,
              FALSE
          );
      }

      /**
       * @var \EdgeBox\SyncCore\V2\Syndication\PushSingle $operation
       */
      $operation = $intent->getOperation();
      $body = $operation->getData();

      $intent->afterPushExecuted(SyncIntent::ACTION_CREATE, $entity);

      return $this->respondWith(
            json_decode(json_encode($body), TRUE),
            self::
        CODE_OK,
        FALSE
        );
    }
    catch (SyncException $e) {
      return $this->respondWith(
            $e->serialize(),
            self::
        CODE_INTERNAL_SERVER_ERROR,
        FALSE
            );
    }
    catch (\Exception $e) {
      return $this->respondWith(
            [
              'message' => 'Unexpected error: ' . $e->getMessage(),
              'stack' => $e->getTraceAsString(),
            ],
            self::
            CODE_INTERNAL_SERVER_ERROR,
            FALSE
            );
    }
  }

  /**
   *
   */
  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);
  }

  /**
   * Save that the pull for the given entity failed.
   *
   * @param string $pool_id
   *   The Pool ID.
   * @param $entity_type
   *   The Entity Type ID
   * @param $entity_bundle
   *   The bundle name
   * @param $entity_type_version
   *   The requested entity type version
   * @param $entity_uuid
   *   The entity UUID
   * @param $failure_reason
   * @param $action
   * @param $reason
   * @param null $flow_id
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function saveFailedPull(?string $pool_id, string $entity_type, ?string $entity_bundle, ?string $entity_type_version, ?string $entity_uuid, string $failure_reason, string $action, string $reason, $flow_id = NULL) {
    if (!$entity_uuid) {
      return;
    }

    $entity_status = EntityStatus::getInfoForEntity($entity_type, $entity_uuid, $flow_id, $pool_id);

    if (!$entity_status) {
      if (!$entity_type_version || !$pool_id) {
        return;
      }

      $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 deleteRemovedEntity($item, Flow $flow, EntityInterface $parent) {
    $menu_pools = [];
    $statuses = EntityStatus::getInfosForEntity($item->getEntityTypeId(), $item->uuid(), [
      'flow' => $flow->id(),
    ]);
    foreach ($statuses as $status) {
      if (!$status->getLastPull()) {
        continue;
      }
      $status_pool = $status->getPool();
      if (!$status_pool) {
        continue;
      }
      // Menu items that were pulled automatically at any point must take
      // precedence over pulls that happened as dependencies. Otherwise
      // if a customer changes configs or has overlapping Flows we delete
      // items we should not delete, so we rather keep them unintentionally
      // than deleting them unintentionally.
      if (!$status->wasPulledEmbedded()) {
        return;
      }
      $menu_pools[] = $status_pool;
    }
    if (empty($menu_pools)) {
      return;
    }

    $operation = new class($item) {
      /**
       * @var \Drupal\Core\Entity\ContentEntityInterface
       */
      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;
        }
      }

    };
    $delete_intent = new PullIntent($flow, $menu_pools, PullIntent::PULL_FORCED, SyncIntent::ACTION_DELETE, $item->getEntityTypeId(), $item->bundle(), $operation, NULL, $parent);
    $delete_intent->execute();
  }

  /**
   *
   */
  private function handleIncomingEntity($flow_id, $entity_type_name, $entity_bundle, $shared_entity_id, array $data, $action) {
    if ($flow_id === IApplicationInterface::FLOW_OPERATION) {
      $request = new EntityOperationRequest($data);
      $operation = $request->getOperation();
      if ($operation === EntityOperation::ENTITY_CLEAR_CACHE) {
        $entity_type_id = $request->getEntityTypeNamespaceMachineName();
        if (EntityHandlerPluginManager::mapById($entity_type_id)) {
          $entity_id = $shared_entity_id;
        }
        else {
          $entity = \Drupal::service('entity.repository')->loadEntityByUuid($entity_type_id, $shared_entity_id);
          if (!$entity) {
            $message = t("The entity @entity_id doesn't exist.", ['@entity_id' => $shared_entity_id])->render();
            $this->logger->notice('@not ENTITY OPERATION @operation @shared_entity_id: @message', [
              '@operation' => $operation,
              '@shared_entity_id' => $shared_entity_id,
              '@not' => 'NO',
              '@message' => $message,
            ]);

            return $this->respondWith(
              ['message' => $message],
              self::CODE_NOT_FOUND,
              SyncIntent::ACTION_DELETE == $action
            );
          }
          $entity_id = $entity->id();
        }

        $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);

        $storage->resetCache([$entity_id]);
        Cache::invalidateTags($entity->getCacheTagsToInvalidate());

        sleep(10);

        $storage->resetCache([$entity_id]);
        Cache::invalidateTags($entity->getCacheTagsToInvalidate());

        $message = t("Cleared cache for @type @id.", ['@type' => $entity_type_id, '@id' => $entity_id])->render();
        $this->logger->notice('@not ENTITY OPERATION @operation @shared_entity_id: @message', [
          '@operation' => $operation,
          '@shared_entity_id' => $shared_entity_id,
          '@not' => '',
          '@message' => $message,
        ]);

        $response = new EntityOperationResponse();
        $response->setSuccess(TRUE);

        return $this->respondWith(
          json_decode(json_encode($response->jsonSerialize()), TRUE),
          self::CODE_OK,
          SyncIntent::ACTION_DELETE == $action
        );
      }
      else {
        $message = t("The operation @operation is not supported.", ['@operation' => $operation])->render();
        $this->logger->notice('@not ENTITY OPERATION @operation @shared_entity_id: @message', [
          '@operation' => $operation,
          '@shared_entity_id' => $shared_entity_id,
          '@not' => 'NO',
          '@message' => $message,
        ]);

        return $this->respondWith(
          ['message' => $message],
          self::CODE_NOT_FOUND,
          SyncIntent::ACTION_DELETE == $action
        );
      }
    }

    $flow = Flow::getAll()[$flow_id];
    if (empty($flow)) {
      $message = t("The flow @flow_id doesn't exist.", ['@flow_id' => $flow_id])->render();
      $this->logger->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
        );
    }

    if (ContentSyncSettings::getInstance()->getExtendedEntityImportLogging()) {
      \Drupal::logger('cms_content_sync_entity_import_log')->debug('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;

    if (!$flow->getController()->canPullEntity($entity_type_name, $entity_bundle, $reason, $action)) {
      $message = t("The flow @flow_id isn't configured to pull this entity.", ['@flow_id' => $flow_id])->render();
      $this->logger->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
        );
    }

    $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() ?? (empty($flow->getController()->getEntityTypeConfig($entity_type_name, $entity_bundle)) ? NULL : $flow->getController()->getEntityTypeConfig($entity_type_name, $entity_bundle)['version']);

    if (EntityHandlerPluginManager::mapById($entity_type_name)) {
      $existing = \Drupal::entityTypeManager()
        ->getStorage($entity_type_name)
        ->load($operation->getId());
      if ($existing) {
        $entity_uuid = $existing->uuid();
      }
      else {
        $entity_uuid = NULL;
      }
    }
    else {
      $entity_uuid = $shared_entity_id;
    }

    // Delete doesn't come with pools.
    $pool_machine_names = $operation->getPoolIds();
    if (empty($pool_machine_names) && $entity_uuid) {
      $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) {
        $status_pool = $status->getPool();
        if ($status_pool) {
          $pool_machine_names[] = $status_pool->id();
        }
      }
    }

    $allowed_pools = $flow->getController()->getUsedPoolsForPulling($entity_type_name, $entity_bundle);

    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();
        $this->logger->notice('@not PULL @action @shared_entity_id: @message', [
          '@action' => $action,
          '@shared_entity_id' => $shared_entity_id,
          '@not' => 'NO',
          '@message' => $message,
        ]);

        if ($entity_uuid) {
          $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
          );
      }

      if (SyncIntent::ACTION_DELETE !== $action && !in_array($all_pools[$machine_name], $allowed_pools)) {
        continue;
      }

      $pools[] = $all_pools[$machine_name];
    }

    // If the content was mapped, it may not have a status entity on this site.
    // But we still want to be able to delete content remotely.
    if (empty($pools) && $action === SyncIntent::ACTION_DELETE) {
      $pools = array_values($allowed_pools);
    }

    if (empty($pools)) {
      return $this->respondWith(['message' => "No pools were given and the entity doesn't exist on this site with any pool."], self::CODE_NOT_FOUND, SyncIntent::ACTION_DELETE == $action);
    }

    $skip_unchanged = 'true' === \Drupal::request()->query->get('skipUnchanged');

    try {
      $individual_translation = 'true' === \Drupal::request()->query->get('individualTranslation');
      $language = \Drupal::request()->query->get('language');
      if ($language) {
        $language = preg_replace('@[^a-zA-Z-0-9_]@', '', $language);

        if ($language) {
          $language_obj = \Drupal::languageManager()->getLanguage($language);
          if (!$language_obj && SyncIntent::ACTION_DELETE !== $action) {
            return $this->respondWith(
                  array_merge(
                      [
                        'message' => 'This entity is not configured to be pulled in the given language: the langue doesn\'t exist.',
                      ],
                  ),
                  self::
                CODE_NOT_FOUND,
                FALSE
              );
          }
        }
      }
      $intent = new PullIntent($flow, $pools, $reason, $action, $entity_type_name, $entity_bundle, $operation, $individual_translation ? $language : NULL);
      $intent->setSkipUnchanged($skip_unchanged);
      $status = $intent->execute();

      $parent = $intent->getEntity();
      if ($parent) {
        $entity_uuid = $parent->uuid();
      }
      $parent_type = $parent ? $parent->getEntityTypeId() : NULL;

      while ($embed = $operation->getNextUnprocessedEmbed()) {
        // Paragraphs require a parent/container, so them not being processed is fine and we can just skip it completely.
        // This can happen if either a handler denies pulling the parent entity OR if the parent entity is skipped as an optimization.
        if ('paragraph' === $embed->getEntityTypeNamespaceMachineName()) {
          continue;
        }
        if (!$flow->getController()->canPullEntity($embed->getEntityTypeNamespaceMachineName(), $embed->getEntityTypeMachineName(), $reason, $action)) {
          continue;
        }

        $embed_pools = [];
        $allowed_pools = $flow->getController()->getUsedPoolsForPulling($embed->getEntityTypeNamespaceMachineName(), $embed->getEntityTypeMachineName());
        foreach ($embed->getPoolIds() as $pool_id) {
          if (isset($all_pools[$pool_id]) && in_array($all_pools[$pool_id], $allowed_pools)) {
            $embed_pools[] = $all_pools[$pool_id];
          }
        }

        if (empty($embed_pools)) {
          continue;
        }

        $embed_intent = new PullIntent($flow, $embed_pools, $reason, $action, $embed->getEntityTypeNamespaceMachineName(), $embed->getEntityTypeMachineName(), $embed, $individual_translation ? $language : NULL, $parent);
        $embed_intent->setSkipUnchanged($skip_unchanged);
        $embed_intent->execute();
      }

      if ($parent) {
        // Delete group<>node references that no longer exist.
        if (\Drupal::moduleHandler()->moduleExists('group') && $parent instanceof ContentEntityInterface) {
          $group_contents = \Drupal::entityTypeManager()
            ->getStorage('group_content')
            ->loadByEntity($parent);
          foreach ($group_contents as $item) {
            if (!$operation->isEmbedded($item->getEntityTypeId(), $item->uuid())) {
              $this->deleteRemovedEntity($item, $flow, $parent);
            }
          }
        }
      }
    }
    // @todo Log explicitly if this was due to an embedded entity.
    catch (SyncException $e) {
      $message = $e->getSyncExceptionMessage();

      $this->logger->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),
      ]);

      if ($entity_uuid) {
        $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(
            $e->serialize(),
            self::CODE_INTERNAL_SERVER_ERROR,
            SyncIntent::ACTION_DELETE == $action
        );
    }
    catch (\Exception $e) {
      $message = $e->getMessage();

      $this->logger->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),
      ]);

      if ($entity_uuid) {
        $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' => 'Unexpected error: ' . $e->getMessage(),
              'stack' => $e->getTraceAsString(),
            ],
            self::CODE_INTERNAL_SERVER_ERROR,
            SyncIntent::ACTION_DELETE == $action
            );
    }

    if (!$status && $entity_uuid) {
      $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 = array_merge($operation->getResponseBody($url), PullIntent::getPullResults());

      // If we send data for DELETE requests, the Drupal Serializer will throw
      // an error. So we just leave the body empty then.
      return $this->respondWith($response_body, self::CODE_OK, SyncIntent::ACTION_DELETE == $action);
    }

    return $this->respondWith(
          array_merge(
              [
                'message' => 'This entity is not configured to be pulled.',
              ],
              PullIntent::getNoPullMessage($entity_type_name, $shared_entity_id) ? [
                'reason' => PullIntent::getNoPullMessage($entity_type_name, $shared_entity_id),
                'possibleReasons' => [
                  'You have not exported the Flow to the Sync Core after making configuration changes.',
                  'You are using a custom entity handler that chose to ignore this entity.',
                  'You are using a custom handler for the BeforeEntityPull event and chose to ignore this entity.',
                ],
              ] : [
                'possibleReasons' => array_merge(
                      // Simple, always possible.
                      [
                        'The entity\'s default language does not exist on this site.',
                        'You have not exported the Flow to the Sync Core after making configuration changes.',
                      ],
                      // Node specific.
                      'node' === $entity_type_name ? [
                        'The node is not published yet but you are only allowing published content to be pulled (default setting).',
                      ] : [],
                      // Menu item specific.
                      'menu_link_content' === $entity_type_name ? [
                        'The menu item is disabled but you are only allowing enabled menu items to be pulled (default setting).',
                        'You are not allowing all menus and the menu of this item is not allowed.',
                      ] : [],
                      // Advanced usage, not likely for most customers.
                      [
                        'You are using a custom entity handler that chose to ignore this entity.',
                        'You are using a custom handler for the BeforeEntityPull event and chose to ignore this entity.',
                      ]
                ),
              ],
          ),
          self::CODE_NOT_FOUND,
          SyncIntent::ACTION_DELETE == $action
      );
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc