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

src/PullIntent.php
<?php

namespace Drupal\cms_content_sync;

use Drupal\cms_content_sync\Controller\ContentSyncSettings;
use Drupal\cms_content_sync\Controller\LoggerProxy;
use Drupal\cms_content_sync\Controller\UpdateLock;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Event\AfterEntityPull;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\cms_content_sync\Plugin\cms_content_sync\entity_handler\DefaultTaxonomyHandler;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TranslatableInterface;

/**
 *
 */
class PullIntent extends SyncIntent {
  /**
   * @var string PULL_DISABLED
   *             Disable pull completely for this entity type, unless forced.
   *             - used as a configuration option
   *             - not used as $action
   */
  public const PULL_DISABLED = 'disabled';

  /**
   * @var string PULL_AUTOMATICALLY
   *             Automatically pull all entities of this entity type.
   *             - used as a configuration option
   *             - used as $action
   */
  public const PULL_AUTOMATICALLY = 'automatically';

  /**
   * @var string PULL_MANUALLY
   *             Pull only some of these entities, chosen manually.
   *             - used as a configuration option
   *             - used as $action
   */
  public const PULL_MANUALLY = 'manually';

  /**
   * @var string PULL_AS_DEPENDENCY
   *             Pull only some of these entities, pulled if other pulled entities
   *             use it.
   *             - used as a configuration option
   *             - used as $action
   */
  public const PULL_AS_DEPENDENCY = 'dependency';

  /**
   * @var string PULL_FORCED
   *             Force the entity to be pulled (as long as a handler is also selected).
   *             Can be used programmatically for custom workflows.
   *             - not used as a configuration option
   *             - used as $action
   */
  public const PULL_FORCED = 'forced';

  /**
   * @var string PULL_UPDATE_IGNORE
   *             Ignore all incoming updates
   */
  public const PULL_UPDATE_IGNORE = 'ignore';

  /**
   * @var string PULL_UPDATE_FORCE
   *             Overwrite any local changes on all updates
   */
  public const PULL_UPDATE_FORCE = 'force';

  /**
   * @var string PULL_UPDATE_FORCE_AND_FORBID_EDITING
   *             Pull all changes and forbid local editors to change the content
   */
  public const PULL_UPDATE_FORCE_AND_FORBID_EDITING = 'force_and_forbid_editing';

  /**
   * @var string PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN
   *             Pull all changes and forbid local editors to change the content unless
   *             they check the "override" checkbox. As long as that is checked, we
   *             ignore any incoming updates in favor of the local changes.
   */
  public const PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN = 'allow_override';

  /**
   * @var string PULL_UPDATE_UNPUBLISHED
   *             Pull all changes and as an unpublished revision. Only available for nodes
   *             that are revisionable.
   */
  public const PULL_UPDATE_UNPUBLISHED = 'pull_update_unpublished';

  /**
   * @var string PULL_FAILED_DIFFERENT_VERSION
   *             The remote entity type version is different to the local entity type version
   */
  public const PULL_FAILED_DIFFERENT_VERSION = 'import_failed_different_version';

  /**
   * @var string PULL_FAILED_INTERNAL_ERROR
   *             An internal Content Sync error occurred when trying to pull the entity
   */
  public const PULL_FAILED_CONTENT_SYNC_ERROR = 'import_failed_content_sync_error';

  /**
   * @var string PULL_FAILED_INTERNAL_ERROR
   *             An unexpected error occurred when trying to pull the entity
   */
  public const PULL_FAILED_INTERNAL_ERROR = 'import_failed_internal_error';

  /**
   * @var string PULL_FAILED_UNKNOWN_POOL
   *             Soft: The provided Pool doesn't exist
   */
  public const PULL_FAILED_UNKNOWN_POOL = 'import_failed_unknown_pool';

  /**
   * @var string PULL_FAILED_NO_FLOW
   *             Soft: No Flow is configured to pull this entity
   */
  public const PULL_FAILED_NO_FLOW = 'import_failed_no_flow';

  /**
   * @var string PULL_FAILED_HANDLER_DENIED
   *             Soft: The pull failed because the handler returned FALSE when executing the pull
   */
  public const PULL_FAILED_HANDLER_DENIED = 'import_failed_handler_denied';

  /**
   * @var array
   *            Structure:
   *            [ entity_type_id:string ][ entity_uuid:string ] => message
   */
  protected static $noPullMessages = [];

  protected $mergeChanges;

  protected $isNewTranslation = FALSE;

  /**
   * @var array
   */
  protected $overriddenProperties = [];

  /**
   * @var array
   */
  protected $overriddenTranslatedProperties = [];

  /**
   * @var \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation
   */
  protected $operation;

  /**
   * @var \Drupal\cms_content_sync\Plugin\EntityHandlerInterface
   */
  protected $handler;

  /**
   * @var string
   */
  protected $individualLanguage;

  /**
   * Skip unchanged content, based on the last pulled version hash.
   *
   * @var bool
   */
  protected $skipUnchanged = FALSE;

  /**
   * The parent entity for an embedded entity.
   *
   * @var \Drupal\Core\Entity\EntityInterface|null
   */
  protected $parent = NULL;

  /**
   * SyncIntent constructor.
   *
   * @param \Drupal\cms_content_sync\Entity\Flow $flow
   *   {@see SyncIntent::$sync}.
   * @param \Drupal\cms_content_sync\Entity\Pool[] $pools
   *   {@see SyncIntent::$pool}.
   * @param string $reason
   *   {@see Flow::PUSH_*} or {@see Flow::PULL_*}.
   * @param string $action
   *   {@see ::ACTION_*}.
   * @param string $entity_type
   *   {@see SyncIntent::$entityType}.
   * @param string $bundle
   *   {@see SyncIntent::$bundle}.
   * @param \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation $operation
   *   The data provided from Sync Core for pulls.
   *                                                                             Format is the same as in ::getData()
   * @param null $parent_type
   * @param null $parent_uuid
   * @param null|mixed $individual_language
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(Flow $flow, array $pools, $reason, $action, $entity_type, $bundle, $operation, $individual_language = NULL, ?EntityInterface $parent = NULL) {
    $this->operation = $operation;

    // For embedded entities the language may sometimes be different e.g. if
    // the embedded entity has not been translated. Then we want to grab it's
    // langcode property instead as the language passed to this constructor
    // will be the parent entity's language / request language.
    $langcode = $this->operation->getProperty('langcode');
    while (is_array($langcode)) {
      $langcode = reset($langcode);
    }
    if ($individual_language && $langcode && is_string($langcode)) {
      $individual_language = $langcode;
    }

    if (EntityHandlerPluginManager::mapById($entity_type)) {
      $entity_id = $this->operation->getId();
    }
    else {
      $entity_id = NULL;
    }

    parent::__construct(
          $flow,
          $pools,
          $reason,
          $action,
          $entity_type,
          $bundle,
          $this->operation->getUuid(),
          $entity_id,
          $this->operation->getSourceUrl(),
          (bool) $individual_language
      );

    $this->individualLanguage = $individual_language;

    if ($this->operation->getSourceUrl()) {
      $this->entity_status->setSourceUrl($this->operation->getSourceUrl());
    }
    if ($translations = $this->operation->getUsedTranslationLanguages()) {
      $root_url = $this->operation->getSourceUrl();
      foreach ($translations as $language) {
        $url = $this->operation->getSourceUrl($language);
        if (!$url || $url === $root_url) {
          continue;
        }
        $this->entity_status->setTranslationSourceUrl($language, $url);
      }
    }

    if ($parent) {
      $this->parent = $parent;
      $this->entity_status->wasPulledEmbedded(TRUE);
      $this->entity_status->setParentEntity($parent->getEntityTypeId(), $parent->uuid());
    }
    else {
      $this->entity_status->wasPulledEmbedded(FALSE);
    }

    $this->mergeChanges = PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN == $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle)['import_updates']
      && $this->entity_status->isOverriddenLocally();
  }

  /**
   * Get the parent of the embedded entity.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null
   */
  public function getParent() {
    return $this->parent;
  }

  /**
   *
   */
  public function setSkipUnchanged(bool $set) {
    $this->skipUnchanged = $set;
  }

  /**
   *
   */
  public function getSkipUnchanged() {
    return $this->skipUnchanged;
  }

  /**
   *
   */
  public static function setNoPullMessage(string $entity_type, string $shared_entity_id, $message) {
    self::$noPullMessages[$entity_type][$shared_entity_id] = $message;
  }

  /**
   *
   */
  public static function getNoPullMessage(string $entity_type, string $shared_entity_id) {
    return self::$noPullMessages[$entity_type][$shared_entity_id] ?? NULL;
  }

  protected static $pullResults = [];

  /**
   * Increase the update counter.
   */
  public static function addPullResult(string $result) {
    if (empty(self::$pullResults[$result])) {
      self::$pullResults[$result] = 1;
    }
    else {
      return self::$pullResults[$result]++;
    }
  }

  /**
   *
   */
  public static function getPullResults() {
    return self::$pullResults;
  }

  /**
   *
   */
  public function setIgnoreMessage($message) {
    self::setNoPullMessage($this->entityType, $this->getSharedId(), $message);
  }

  /**
   * @return bool
   */
  public function wasPulledEmbedded() {
    return $this->entity_status->wasPulledEmbedded();
  }

  /**
   * @return \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation
   */
  public function getOperation() {
    return $this->operation;
  }

  /**
   * Mark the given dependency as missing so it's automatically resolved whenever it gets pulled.
   *
   * @param array $definition
   * @param null|string $field
   * @param null|array $data
   */
  public function saveUnresolvedDependency($definition, $field = NULL, $data = NULL) {
    $reference = $this->operation->loadReference($definition);

    // User references are ignored.
    if (!$reference->getType() || (!$reference->getId() && !$reference->getUuid())) {
      return;
    }

    MissingDependencyManager::saveUnresolvedDependency(
          $reference->getType(),
          $reference->getId() ? $reference->getId() : $reference->getUuid(),
          $this->getEntity(),
          $this->getReason(),
          $field,
          $data
      );
  }

  /**
   * @return bool
   */
  public function shouldMergeChanges($ignore_new_translation = FALSE) {
    return $this->mergeChanges && ($ignore_new_translation || !$this->isNewTranslation);
  }

  /**
   *
   */
  public function setIsNewTranslation($set) {
    $this->isNewTranslation = $set;
  }

  /**
   *
   */
  public function getIsNewTranslation() {
    return $this->isNewTranslation;
  }

  /**
   *
   */
  public function getAction() {
    $action = parent::getAction();
    if (SyncIntent::ACTION_UPDATE === $action && $this->isNewTranslation) {
      return SyncIntent::ACTION_CREATE;
    }

    return $action;
  }

  /**
   *
   */
  public function getViewUrl() {
    if (!$this->handler) {
      $config = $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle);
      $handler = $this->flow->getController()->getEntityTypeHandler($this->entityType, $this->bundle, $config);

      return $handler->getViewUrl($this->getEntity());
    }

    return $this->handler->getViewUrl($this->getEntity());
  }

  /**
   *
   */
  public function getVersionId(bool $including_translations) {
    return $this->operation->getVersionId($this->getActiveLanguage(), $including_translations);
  }

  /**
   * Pull the provided entity.
   *
   * @throws Exception\SyncException
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   *
   * @return bool
   */
  public function execute() {
    // Skip pulling the entity multiple times. E.g. if an entity is referenced
    // by a parent entity multiple times, we don't need to update it again
    // in the same run.
    if (self::entityHasBeenPulledFromRemoteSite($this->entityType, $this->uuid)) {
      return TRUE;
    }

    $pull = reset($this->pools)->getNewestTimestamp($this->entityType, $this->uuid, TRUE, $this->pools);
    if (!$pull) {
      if (SyncIntent::ACTION_UPDATE == $this->action) {
        $this->action = SyncIntent::ACTION_CREATE;
      }
    }
    elseif (SyncIntent::ACTION_CREATE == $this->action) {
      $this->action = SyncIntent::ACTION_UPDATE;
    }
    $pull = time();

    $config = $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle);
    $handler = $this->flow->getController()->getEntityTypeHandler($this->entityType, $this->bundle, $config);
    $this->handler = $handler;

    self::entityHasBeenPulledFromRemoteSite($this->entityType, $this->uuid, TRUE);

    // As paragraphs are embedded, they are only updated when the parent is
    // updated. So we don't need to add them explicitly becasuse their parent
    // will be locked and fail to update already. This only works for 1:1
    // relationships. E.g. media can be re-used in different content items,
    // so can't be optimized even if it's only ever pulled as a dependency.
    // This saves us some performance during updates.
    $requires_lock = $this->entityType !== 'paragraph';

    if ($requires_lock) {
      if (UpdateLock::isLocked($this->entityType, $this->getSharedId())) {
        $unlocked = FALSE;

        // If this is an embedded it could be e.g. two nodes using the same
        // media item being published simultaneously. An entity update typically
        // takes less than 1 second to complete, so even if it's nested, it
        // should be finished within 2 seconds in most cases.
        // By waiting a little bit and retrying, we can eliminate most update
        // locks for nested entities and avoid retries / increase throughput
        // without sacrificing safety.
        if ($this->getParent()) {
          $this->startTimer('lock-wait');

          // Retry for up to 8 seconds.
          for ($i = 0; $i < 7; $i++) {
            // Wait for 2 seconds the first time, then another second at every
            // consecutive retry.
            sleep($i ? 1 : 2);

            $unlocked = !UpdateLock::isLocked($this->entityType, $this->getSharedId());

            if ($unlocked) {
              break;
            }
          }

          $this->stopTimer('lock-wait');
        }

        if (!$unlocked) {
          throw new SyncException(SyncException::CODE_UPDATE_LOCK, NULL, "The " . $this->entityType . " " . $this->getSharedId() . " is locked for updates, so another update is still running. Please retry in " . UpdateLock::getLockExpiration() . "s.");
        }
      }

      UpdateLock::lock($this->entityType, $this->getSharedId());
    }
    else {
      UpdateLock::renew();
    }

    try {
      $result = $handler->pull($this);
    }
    catch (\Exception $e) {
      if ($requires_lock) {
        UpdateLock::unlock($this->entityType, $this->getSharedId());
      }
      throw $e;
    }

    $new_version_with_translations = $this->getVersionId(TRUE);
    $new_version = $this->getVersionId(FALSE);

    LoggerProxy::get()->info('@not PULL @duration @action @entity_type:@bundle @uuid @ids @reason: @message<br>Flow: @flow_id | Pool: @pool_id | Timers: @timers | Version: @version_with_translations:@version | Language: @language', [
      '@reason' => $this->reason,
      '@action' => $this->action,
      '@duration' => $this->formatDuration(),
      '@timers' => $this->formatTimers(),
      '@version_with_translations' => $new_version_with_translations ?? '-',
      '@version' => $new_version ?? '-',
      '@language' => $this->individualLanguage ?? '-',
      '@entity_type' => $this->entityType,
      '@bundle' => $this->bundle,
      '@uuid' => $this->uuid,
      '@not' => $result ? '' : 'NO',
      '@message' => $result ? t('The entity has been pulled.') : (self::getNoPullMessage($this->entityType, $this->getSharedId()) ?? t('The entity handler denied to pull this entity.')),
      '@flow_id' => $this->getFlow()->id(),
      '@pool_id' => implode(',', $this->getPoolIds()),
      '@ids' => (!empty($this->entity) ? ($this->entity->getEntityType()->isRevisionable() ? "entity_id:{$this->entity->id()} revision_id:{$this->entity->getRevisionId()}" : "entity_id:{$this->entity->id()}") : ('')),
    ]);

    if ($result) {
      self::addPullResult('updateCount');
    }
    else {
      if ($requires_lock) {
        UpdateLock::unlock($this->entityType, $this->getSharedId());
      }

      self::addPullResult('ignoreCount');

      // Don't save entity_status entity if entity wasn't pulled anyway.
      return FALSE;
    }

    // Need to save after setting timestamp to prevent exception.
    $this->entity_status->setLastPull($pull, $this->individualLanguage);
    reset($this->pools)->setTimestamp($this->entityType, $this->uuid, $pull, TRUE, $this->pools, $this->individualLanguage);
    $this->entity_status->isDeleted(SyncIntent::ACTION_DELETE == $this->action);
    $this->entity_status->save();

    if (SyncIntent::ACTION_DELETE == $this->action) {
      reset($this->pools)->markDeleted($this->entityType, $this->uuid, $this->pools);
    }

    $entity = $this->getEntity();

    // Dispatch EntityExport event to give other modules the possibility to react on it.
    // Ignore deleted entities.
    if ($entity) {
      \Drupal::service('event_dispatcher')->dispatch(new AfterEntityPull($entity, $this), AfterEntityPull::EVENT_NAME);
    }

    if ($requires_lock) {
      UpdateLock::unlock($this->entityType, $this->getSharedId());
    }

    // Handle Extended Entity Import logging.
    $settings = ContentSyncSettings::getInstance();
    if ($settings->getExtendedEntityImportLogging()) {
      $url = NULL;
      if ($entity && $entity->hasLinkTemplate('canonical')) {
        $url = $entity->toUrl('canonical', ['absolute' => TRUE])
          ->toString(TRUE)
          ->getGeneratedUrl();
      }

      $serializer = \Drupal::service('serializer');
      $data = $serializer->serialize($this->getOperation()->getResponseBody($url), 'json', ['plugin_id' => 'entity']);

      \Drupal::logger('cms_content_sync_entity_import_log')->debug('%entity_type - %uuid <br>Data: <br><pre><code>%data</code></pre>', [
        '%entity_type' => $entity->getEntityTypeId(),
        '%uuid' => $entity->uuid(),
        '%data' => $data,
      ]);
    }

    $this->resolveMissingDependencies();

    return TRUE;
  }

  /**
   * Check if the provided entity has just been pulled by Sync Core in this
   * very request. In this case it doesn't make sense to perform a remote
   * request telling Sync Core it has been created/updated/deleted
   * (it will know as a result of this current request).
   *
   * @param string $entity_type
   *   The entity type.
   * @param string $entity_uuid
   *   The entity UUID.
   * @param bool $set
   *   If TRUE, this entity will be set to have been pulled at this request.
   *
   * @return bool
   */
  public static function entityHasBeenPulledFromRemoteSite($entity_type = NULL, $entity_uuid = NULL, $set = FALSE) {
    static $entities = [];

    if (!$entity_type) {
      return !empty($entities);
    }

    if ($set) {
      return $entities[$entity_type][$entity_uuid] = TRUE;
    }

    return !empty($entities[$entity_type][$entity_uuid]);
  }

  /**
   * Get the reference meta-data for the given field. If you want to load an
   * entity by reference, use `::loadEmbeddedEntity()` instead.
   *
   * @param array $definition
   *   The definition you saved in a field and gotten
   *                          back when calling one of the mentioned functions above.
   *
   * @return null|\EdgeBox\SyncCore\Interfaces\Syndication\IEntityReference
   */
  public function loadReference($definition) {
    return $this->operation->loadReference($definition);
  }

  /**
   * Restore a file that was added via
   * {@see SyncIntent::embedEntityDefinition} or
   * {@see SyncIntent::embedEntity} but only given the file URI, e.g. because
   * it is embedded within a text field.
   *
   * @param string $uri
   *   The File URI.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null the restored entity
   */
  public function loadEmbeddedFile($uri) {
    $props = [
      'uri' => [
        0 => [
          'value' => $uri,
        ],
      ],
    ];

    $files = $this->operation->loadReferencesByProperties($props);

    if (empty($files)) {
      return NULL;
    }

    $reference = reset($files);

    return $this->loadEmbeddedEntity([], $reference);
  }

  /**
   * Restore an entity that was added via
   * {@see SyncIntent::embedEntityDefinition} or
   * {@see SyncIntent::embedEntity}.
   *
   * @param array $definition
   *   The definition you saved in a field and gotten
   *                          back when calling one of the mentioned functions above.
   * @param null|mixed $reference
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Drupal\cms_content_sync\Exception\SyncException
   *
   * @return \Drupal\Core\Entity\EntityInterface the restored entity
   */
  public function loadEmbeddedEntity($definition, $reference = NULL) {
    $start = microtime(TRUE);
    if (!$reference) {
      $reference = $this->operation->loadReference($definition);
      if (empty($reference) || !$reference->getType() || !$reference->getBundle()) {
        return NULL;
      }
    }

    if ($reference->isEmbedded()) {
      $embedded_entity = $reference->getEmbeddedEntity();
      if (empty($embedded_entity)) {
        return NULL;
      }

      $entity_type_name = $reference->getType();
      $entity_bundle = $reference->getBundle();

      $flow = $this->flow;

      $pool_ids = $reference->getPoolIds();
      $all_pools = Pool::getAll();
      $ideal_pools = [];
      foreach ($pool_ids as $pool_id) {
        if (isset($all_pools[$pool_id])) {
          $ideal_pools[$pool_id] = $all_pools[$pool_id];
        }
      }

      // The pools we actually use for the pull operation below.
      $pools = [];

      // Prefer the current Flow if possible for at least one of the given Pools.
      if (!$flow->getController()->canPullEntity($entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE)) {
        $flow = NULL;
      }
      else {
        $allowed_pools = $flow->getController()->getUsedPoolsForPulling($entity_type_name, $entity_bundle);
        foreach ($pool_ids as $pool_id) {
          if (isset($all_pools[$pool_id]) && in_array($all_pools[$pool_id], $allowed_pools)) {
            $pools[] = $all_pools[$pool_id];
          }
        }
        // Current Flow doesn't match for any of the Pools, so can't be used.
        if (empty($pools)) {
          $flow = NULL;
        }
      }

      // Otherwise fall back to any Flow that can handle any of the given Pools.
      if (!$flow) {
        foreach ($ideal_pools as $pool) {
          $flow = Flow::getFlowForPoolAndEntityType($pool, $entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE);
          if ($flow) {
            $allowed_pools = $flow->getController()->getUsedPoolsForPulling($entity_type_name, $entity_bundle);
            foreach ($pool_ids as $pool_id) {
              if (isset($all_pools[$pool_id]) && in_array($all_pools[$pool_id], $allowed_pools)) {
                $pools[] = $all_pools[$pool_id];
              }
            }

            break;
          }
        }
      }

      if (!$flow || empty($pools)) {
        return NULL;
      }

      $intent = new PullIntent($flow, $pools, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE, $entity_type_name, $entity_bundle, $embedded_entity, $this->individualLanguage, $this->getEntity());
      $intent->setSkipUnchanged($this->skipUnchanged);
      $result = $intent->execute();
      $this->childTime += microtime(TRUE) - $start;

      if ($result) {
        return $intent->getEntity();
      }
    }

    if (empty($reference->getId())) {
      $entity = \Drupal::service('entity.repository')->loadEntityByUuid(
            $reference->getType(),
            $reference->getUuid()
        );
    }
    else {
      $entity = \Drupal::entityTypeManager()->getStorage($reference->getType())->load($reference->getId());
    }

    // Taxonomy terms can be mapped by their name.
    if (!$entity && !empty($reference->getName())) {
      $config = $this->flow->getController()->getEntityTypeConfig($reference->getType(), $reference->getBundle());
      if (!empty($config) && !empty($config['handler_settings'][DefaultTaxonomyHandler::MAP_BY_LABEL_SETTING])) {
        $entity_type = \Drupal::entityTypeManager()->getDefinition($reference->getType());
        $label_property = $entity_type->getKey('label');

        $existing = \Drupal::entityTypeManager()->getStorage($reference->getType())->loadByProperties([
          $label_property => $reference->getName(),
        ]);

        $entity = reset($existing);
      }
    }

    return $entity;
  }

  /**
   * Get all embedded entity data besides the predefined keys.
   * Images for example have "alt" and "title" in addition to the file reference.
   *
   * @param $definition
   *
   * @return array
   */
  public function getEmbeddedEntityData($definition) {
    $reference = $this->operation->loadReference($definition);

    return $reference->getDetails();
  }

  /**
   * Get all field values at once for the currently active language.
   *
   * @return array all field values for the active language
   */
  public function getOverriddenProperties() {
    if ($this->activeLanguage) {
      if (!isset($this->overriddenTranslatedProperties[$this->activeLanguage])) {
        return NULL;
      }

      return $this->overriddenTranslatedProperties[$this->activeLanguage];
    }

    return $this->overriddenProperties;
  }

  /**
   * Provide the value of a field you stored when pushing by using.
   *
   * @see SyncIntent::setField()
   *
   * @param string $name
   *   The name of the field to restore.
   *
   * @return mixed the value you stored for this field
   */
  public function getProperty($name) {
    $overridden = $this->getOverriddenProperties();

    return $overridden[$name] ?? $this->operation->getProperty($name, $this->activeLanguage);
  }

  /**
   * Overwrite the value for the given field to preprocess the values or access
   * them at a later stage.
   *
   * @param string $name
   *   The name of the field in question.
   * @param mixed $value
   *   The value to store.
   */
  public function overwriteProperty($name, $value) {
    if ($this->activeLanguage) {
      $this->overriddenTranslatedProperties[$this->activeLanguage][$name] = $value;

      return;
    }

    $this->overriddenProperties[$name] = $value;
  }

  /**
   * Get all languages for field translations that are currently used.
   */
  public function getTranslationLanguages() {
    return $this->operation->getUsedTranslationLanguages();
  }

  /**
   *
   */
  public function setEntity($entity) {
    parent::setEntity($entity);

    if (!$this->entity) {
      return;
    }

    if ($this->isIndividualTranslation()) {
      if ($this->entity instanceof TranslatableInterface) {
        if ($this->entity->language()->getId() !== $this->individualLanguage) {
          $exists = $this->entity->hasTranslation($this->individualLanguage);
          $this->entity = $exists
                        ? $this->entity->getTranslation($this->individualLanguage)
                        : $this->entity->addTranslation($this->individualLanguage);
          $this->setIsNewTranslation(!$exists);
        }
      }
    }

    return $this->entity;
  }

  /**
   * Resolve all references to the entity that has just been pulled if they're missing at other content.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function resolveMissingDependencies() {
    // Ignore deleted entities.
    if ($this->getEntity()) {
      MissingDependencyManager::resolveDependencies($this->getEntity());
    }
  }

}

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

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