cms_content_sync-3.0.x-dev/src/MissingDependencyManager.php

src/MissingDependencyManager.php
<?php

namespace Drupal\cms_content_sync;

use Drupal\cms_content_sync\Controller\LoggerProxy;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use EdgeBox\SyncCore\Exception\SyncCoreException;

/**
 * Class MissingDependencyManagement.
 *
 * Manage dependencies that couldn't be resolved. So if Content A references Content B and Content A is pulled before
 * Content B, then the reference can't be resolved. This class ensures that as soon as Content B becomes available,
 * Content A is updated as well.
 *
 * This can have multiple causes:
 * - If your Flow is configured to push ALL of a specific entity type, that push is not ordered. So for taxonomies
 *   for example the child term may be pulled before the parent term.
 * - If you don't use the "Push referenced entities automatically" functionality (e.g. with content that references
 *   other content), that content will also not arrive at the destination site in the required order (if ever).
 */
class MissingDependencyManager {
  /**
   * @var string COLLECTION_NAME
   *             The KeyValue store to use for saving unresolved dependencies
   */
  public const COLLECTION_NAME = 'cms_content_sync_dependency';

  public const INDEX_ENTITY_TYPE = 'entity_type';
  public const INDEX_ENTITY_ID = 'id';
  public const INDEX_PULL_REASON = 'reason';
  public const INDEX_SET_FIELD = 'field';
  public const INDEX_DATA = 'data';

  /**
   * Get a shorter version of the given ID by hashing it.
   *
   * @param string $referenced_entity_shared_id
   *
   * @return string
   */
  public static function getShortenedId(string $referenced_entity_shared_id) {
    // Let regular IDs and UUIDs pass. But file URIs for example need to be
    // shortened because the DatabaseStorage for KeyValue's only allows 128
    // characters which means longer file names would lead to exceptions.
    if (strlen($referenced_entity_shared_id) > 36) {
      return md5($referenced_entity_shared_id);
    }
  }

  /**
   * Get a unique ID for the given type + ID. Will try to use the provided ID if
   * possible but fallback to a hash of the same if it's too long for the key
   * value databse storage that limits the key length to 128 bytes.
   *
   * @param string $referenced_entity_type
   * @param string $referenced_entity_shared_id
   *
   * @return string
   */
  public static function getReferenceId(string $referenced_entity_type, string $referenced_entity_shared_id) {
    $id = $referenced_entity_type . ':' . $referenced_entity_shared_id;
    if (strlen($id) > 128) {
      $id = $referenced_entity_type . ':' . self::getShortenedId($referenced_entity_shared_id);
    }
    return $id;
  }

  /**
   * Save that an entity dependency could not be resolved so it triggers its pull automatically whenever it can be
   * resolved.
   *
   * @param string $referenced_entity_type
   * @param string $referenced_entity_shared_id
   * @param \Drupal\Core\Entity\EntityInterface $entity
   * @param string $reason
   * @param null|string $field
   * @param null|array $custom_data
   */
  public static function saveUnresolvedDependency(string $referenced_entity_type, string $referenced_entity_shared_id, $entity, $reason, $field = NULL, $custom_data = NULL) {
    $storage = \Drupal::keyValue(self::COLLECTION_NAME);

    $id = self::getReferenceId($referenced_entity_type, $referenced_entity_shared_id);

    $missing = $storage->get($id);
    if (empty($missing)) {
      $missing = [];
    }

    // Skip if that entity has already been added (referencing the same entity multiple times)
    foreach ($missing as $sync) {
      if ($sync[self::INDEX_ENTITY_TYPE] === $entity->getEntityTypeId() && $sync[self::INDEX_ENTITY_ID] === $entity->uuid() && ($sync[self::INDEX_SET_FIELD] ?? NULL) === $field) {
        return;
      }
    }

    $data = [
      self::INDEX_ENTITY_TYPE => $entity->getEntityTypeId(),
      self::INDEX_ENTITY_ID => $entity->uuid(),
      self::INDEX_PULL_REASON => $reason,
    ];

    if ($field) {
      $data[self::INDEX_SET_FIELD] = $field;
    }

    if ($custom_data) {
      $data[self::INDEX_DATA] = $custom_data;
    }

    $missing[] = $data;

    $storage->set($id, $missing);
  }

  /**
   * Resolve any dependencies that were missing before for the given entity that is now available.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public static function resolveDependencies($entity) {
    $storage = \Drupal::keyValue(self::COLLECTION_NAME);

    if ('file' === $entity->getEntityTypeId()) {
      /**
       * @var \Drupal\file\Entity\File $entity
       */
      $id = self::getReferenceId($entity->getEntityTypeId(), $entity->getFileUri());

      $missing = $storage->get($id);

      if (!empty($missing)) {
        self::saveResolvedDependencies($entity, $missing);

        $storage->delete($id);
      }
    }

    $shared_entity_id = EntityHandlerPluginManager::getSharedId($entity);

    $id = self::getReferenceId($entity->getEntityTypeId(), $shared_entity_id);

    $missing = $storage->get($id);
    if (empty($missing)) {
      return;
    }

    self::saveResolvedDependencies($entity, $missing);

    $storage->delete($id);
  }

  /**
   * @param $entity
   * @param $missing
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected static function saveResolvedDependencies($entity, $missing) {
    foreach ($missing as $sync) {
      $infos = EntityStatus::getInfosForEntity($sync[self::INDEX_ENTITY_TYPE], $sync[self::INDEX_ENTITY_ID]);
      foreach ($infos as $info) {
        if ($info->isDeleted()) {
          break;
        }

        if (!$info->getPool() || !$info->getFlow()) {
          continue;
        }

        $referenced_entity = $info->getEntity();

        // Maybe the entity that declared this as missing has been deleted in the meantime.
        if (empty($referenced_entity)) {
          continue;
        }

        $flow = $info->getFlow();
        if (!$flow->getController()->canPullEntity($referenced_entity->getEntityTypeId(), $referenced_entity->bundle(), PullIntent::PULL_FORCED)) {
          continue;
        }

        if (!empty($sync[self::INDEX_SET_FIELD])) {
          /**
           * @var \Drupal\Core\Entity\FieldableEntityInterface $referenced_entity
           */
          if ('link' === $sync[self::INDEX_SET_FIELD] && 'menu_link_content' === $referenced_entity->getEntityTypeId()) {
            if (isset($sync[self::INDEX_DATA]['enabled']) && $sync[self::INDEX_DATA]['enabled']) {
              $referenced_entity->set('enabled', [['value' => 1]]);
            }

            $data = 'entity:' . $entity->getEntityTypeId() . '/' . $entity->id();
          }
          elseif ('crop' === $referenced_entity->getEntityTypeId() && 'entity_id' === $sync[self::INDEX_SET_FIELD]) {
            $data = [
              'value' => $entity->id(),
            ];
          }
          else {
            $data = [
              'target_id' => $entity->id(),
            ];
          }

          $referenced_entity->set($sync[self::INDEX_SET_FIELD], $data);
          $referenced_entity->save();

          break;
        }

        try {
          $info
            ->getPool()
            ->getClient()
            ->getSyndicationService()
            ->pullSingle($flow->id, $referenced_entity->getEntityTypeId(), $referenced_entity->bundle(), $referenced_entity->uuid())
            ->fromPool($info->getPool()->id)
            ->asDependency(PullIntent::PULL_AS_DEPENDENCY === $sync[self::INDEX_PULL_REASON])
            ->manually(PullIntent::PULL_MANUALLY === $sync[self::INDEX_PULL_REASON])
            ->execute();
        }
        catch (SyncCoreException $e) {
          LoggerProxy::get()->warning('Failed to pull %type.%bundle %entity_id through the missing dependency manager: @message<br>Flow: @flow_id | Pool: @pool_id', [
            '%type' => $referenced_entity->getEntityTypeId(),
            '%bundle' => $referenced_entity->bundle(),
            '%entity_id' => $referenced_entity->uuid(),
            '@message' => $e->getMessage(),
            '@flow_id' => $flow->id,
            '@pool_id' => $info->getPool()->id,
          ]);
        }
      }
    }
  }

}

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

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