You are here

class PullIntent in CMS Content Sync 8

Same name and namespace in other branches
  1. 2.1.x src/PullIntent.php \Drupal\cms_content_sync\PullIntent
  2. 2.0.x src/PullIntent.php \Drupal\cms_content_sync\PullIntent

Hierarchy

Expanded class hierarchy of PullIntent

35 files declare their use of PullIntent
AfterEntityPull.php in src/Event/AfterEntityPull.php
BeforeEntityPull.php in src/Event/BeforeEntityPull.php
cms_content_sync.module in ./cms_content_sync.module
Module file for cms_content_sync.
cms_content_sync_migrate_acquia_content_hub.drush.inc in modules/cms_content_sync_migrate_acquia_content_hub/cms_content_sync_migrate_acquia_content_hub.drush.inc
Contains Drush commands for Content Sync.
CopyRemoteFlow.php in src/Form/CopyRemoteFlow.php

... See full list

File

src/PullIntent.php, line 13

Namespace

Drupal\cms_content_sync
View source
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';
  protected $mergeChanges;

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

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

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

  /**
   * SyncIntent constructor.
   *
   * @param \Drupal\cms_content_sync\Entity\Flow                    $flow
   *                                                                             {@see SyncIntent::$sync}
   * @param \Drupal\cms_content_sync\Entity\Pool                    $pool
   *                                                                             {@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
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(Flow $flow, Pool $pool, $reason, $action, $entity_type, $bundle, $operation, $parent_type = null, $parent_uuid = null) {
    $this->operation = $operation;
    if (EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type)) {
      $entity_id = $this->operation
        ->getId();
    }
    else {
      $entity_id = null;
    }
    parent::__construct($flow, $pool, $reason, $action, $entity_type, $bundle, $this->operation
      ->getUuid(), $entity_id, $this->operation
      ->getSourceUrl());
    if ($this->operation
      ->getSourceUrl()) {
      $this->entity_status
        ->set('source_url', $this->operation
        ->getSourceUrl());
    }
    if ($parent_type && $parent_uuid) {
      $this->entity_status
        ->wasPulledEmbedded(true);
      $this->entity_status
        ->setParentEntity($parent_type, $parent_uuid);
    }
    else {
      $this->entity_status
        ->wasPulledEmbedded(false);
    }
    $this->mergeChanges = PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN == $this->flow
      ->getEntityTypeConfig($this->entityType, $this->bundle)['import_updates'] && $this->entity_status
      ->isOverriddenLocally();
  }

  /**
   * @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() {
    return $this->mergeChanges;
  }

  /**
   * 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() {
    $pull = $this->pool
      ->getNewestTimestamp($this->entityType, $this->uuid, true);
    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
      ->getEntityTypeConfig($this->entityType, $this->bundle);
    $handler = $this->flow
      ->getEntityTypeHandler($config);
    self::entityHasBeenPulledFromRemoteSite($this->entityType, $this->uuid, true);
    $result = $handler
      ->pull($this);
    \Drupal::logger('cms_content_sync')
      ->info('@not PULL @action @entity_type:@bundle @uuid @reason: @message<br>Flow: @flow_id | Pool: @pool_id', [
      '@reason' => $this->reason,
      '@action' => $this->action,
      '@entity_type' => $this->entityType,
      '@bundle' => $this->bundle,
      '@uuid' => $this->uuid,
      '@not' => $result ? '' : 'NO',
      '@message' => $result ? t('The entity has been pulled.') : t('The entity handler denied to pull this entity.'),
      '@flow_id' => $this
        ->getFlow()
        ->id(),
      '@pool_id' => $this
        ->getPool()
        ->id(),
    ]);

    // Don't save entity_status entity if entity wasn't pulled anyway.
    if (!$result) {
      return false;
    }

    // Need to save after setting timestamp to prevent exception.
    $this->entity_status
      ->setLastPull($pull);
    $this->pool
      ->setTimestamp($this->entityType, $this->uuid, $pull, true);
    $this->entity_status
      ->isDeleted(SyncIntent::ACTION_DELETE == $this->action);
    $this->entity_status
      ->save();
    if (SyncIntent::ACTION_DELETE == $this->action) {
      $this->pool
        ->markDeleted($this->entityType, $this->uuid);
    }
    $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(AfterEntityPull::EVENT_NAME, new AfterEntityPull($entity, $this));
    }

    // Handle Extended Entity Import logging.
    $settings = ContentSyncSettings::getInstance();
    if ($settings
      ->getExtendedEntityImportLogging()) {
      $url = null;
      if ($entity && $entity
        ->hasLinkTemplate('canonical') && !$entity instanceof FieldCollectionItem) {
        $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]);
  }

  /**
   * 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
   *
   * @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 = $this->operation
      ->loadReference($definition);
    $expected_version = Flow::getEntityTypeVersion($reference
      ->getType(), $reference
      ->getBundle());
    if ($expected_version != $reference
      ->getVersion()) {
      \Drupal::logger('cms_content_sync')
        ->warning('Outdated reference to @entity_type:@bundle: Remote version @remote_version doesn\'t match local version @local_version<br>Flow: @flow_id | Pool: @pool_id', [
        '@entity_type' => $reference
          ->getType(),
        '@bundle' => $reference
          ->getBundle(),
        '@remote_version' => $reference
          ->getVersion(),
        '@local_version' => $expected_version,
        '@flow_id' => $this
          ->getFlow()
          ->id(),
        '@pool_id' => $this
          ->getPool()
          ->id(),
      ]);
    }
    if ($reference
      ->isEmbedded()) {
      $embedded_entity = $reference
        ->getEmbeddedEntity();
      if (empty($embedded_entity)) {
        return null;
      }
      $pool_id = $reference
        ->getPoolId();
      $pool = Pool::getAll()[$pool_id];
      if (empty($pool)) {
        return null;
      }
      $entity_type_name = $reference
        ->getType();
      $entity_bundle = $reference
        ->getBundle();
      $flow = Flow::getFlowForApiAndEntityType($pool, $entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE);
      if (!$flow) {
        return null;
      }
      $intent = new PullIntent($flow, $pool, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE, $entity_type_name, $entity_bundle, $embedded_entity, $this->entityType, $this->uuid);
      $status = $intent
        ->execute();
      if (!$status) {
        return null;
      }
      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
        ->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 isset($overridden[$name]) ? $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();
  }

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

}

Members

Namesort descending Modifiers Type Description Overrides
PullIntent::$mergeChanges protected property
PullIntent::$operation protected property
PullIntent::$overriddenProperties protected property
PullIntent::$overriddenTranslatedProperties protected property
PullIntent::entityHasBeenPulledFromRemoteSite public static function 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…
PullIntent::execute public function Pull the provided entity. Overrides SyncIntent::execute
PullIntent::getEmbeddedEntityData public function Get all embedded entity data besides the predefined keys. Images for example have "alt" and "title" in addition to the file reference.
PullIntent::getOperation public function
PullIntent::getOverriddenProperties public function Get all field values at once for the currently active language.
PullIntent::getProperty public function Provide the value of a field you stored when pushing by using.
PullIntent::getTranslationLanguages public function Get all languages for field translations that are currently used.
PullIntent::loadEmbeddedEntity public function Restore an entity that was added via {{
PullIntent::overwriteProperty public function Overwrite the value for the given field to preprocess the values or access them at a later stage.
PullIntent::PULL_AS_DEPENDENCY public constant Pull only some of these entities, pulled if other pulled entities use it.
PullIntent::PULL_AUTOMATICALLY public constant Automatically pull all entities of this entity type.
PullIntent::PULL_DISABLED public constant Disable pull completely for this entity type, unless forced.
PullIntent::PULL_FAILED_CONTENT_SYNC_ERROR public constant An internal Content Sync error occurred when trying to pull the entity
PullIntent::PULL_FAILED_DIFFERENT_VERSION public constant The remote entity type version is different to the local entity type version
PullIntent::PULL_FAILED_HANDLER_DENIED public constant Soft: The pull failed because the handler returned FALSE when executing the pull
PullIntent::PULL_FAILED_INTERNAL_ERROR public constant An unexpected error occurred when trying to pull the entity
PullIntent::PULL_FAILED_NO_FLOW public constant Soft: No Flow is configured to pull this entity
PullIntent::PULL_FAILED_UNKNOWN_POOL public constant Soft: The provided Pool doesn't exist
PullIntent::PULL_FORCED public constant Force the entity to be pulled (as long as a handler is also selected). Can be used programmatically for custom workflows.
PullIntent::PULL_MANUALLY public constant Pull only some of these entities, chosen manually.
PullIntent::PULL_UPDATE_FORCE public constant Overwrite any local changes on all updates
PullIntent::PULL_UPDATE_FORCE_AND_FORBID_EDITING public constant Pull all changes and forbid local editors to change the content
PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN public constant 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.
PullIntent::PULL_UPDATE_IGNORE public constant Ignore all incoming updates
PullIntent::PULL_UPDATE_UNPUBLISHED public constant Pull all changes and as an unpublished revision. Only available for nodes that are revisionable.
PullIntent::resolveMissingDependencies protected function Resolve all references to the entity that has just been pulled if they're missing at other content.
PullIntent::saveUnresolvedDependency public function Mark the given dependency as missing so it's automatically resolved whenever it gets pulled.
PullIntent::shouldMergeChanges public function
PullIntent::__construct public function SyncIntent constructor. Overrides SyncIntent::__construct
SyncIntent::$action protected property
SyncIntent::$activeLanguage protected property
SyncIntent::$bundle protected property
SyncIntent::$entity protected property
SyncIntent::$entityType protected property
SyncIntent::$entity_status protected property
SyncIntent::$flow protected property
SyncIntent::$id protected property
SyncIntent::$pool protected property The synchronization this request spawned at @var string entity type of the processed entity @var string bundle of the processed entity @var string UUID of the…
SyncIntent::$reason protected property
SyncIntent::$uuid protected property
SyncIntent::ACTION_CREATE public constant push/pull the creation of this entity
SyncIntent::ACTION_DELETE public constant push/pull the deletion of this entity
SyncIntent::ACTION_DELETE_TRANSLATION public constant Drupal doesn't update the ->getTranslationStatus($langcode) to TRANSLATION_REMOVED before calling hook_entity_translation_delete, so we need to use a custom action to circumvent deletions of translations of entities not being handled. This is…
SyncIntent::ACTION_UPDATE public constant push/pull the update of this entity
SyncIntent::changeTranslationLanguage public function Change the language used for provided field values. If you want to add a translation of an entity, the same SyncIntent is used. First, you add your fields using self::setField() for the untranslated version. After that you call…
SyncIntent::getAction public function
SyncIntent::getActiveLanguage public function Return the language that's currently used.
SyncIntent::getBundle public function
SyncIntent::getEntity public function
SyncIntent::getEntityStatus public function Returns the entity status.
SyncIntent::getEntityType public function
SyncIntent::getFlow public function
SyncIntent::getId public function
SyncIntent::getPool public function
SyncIntent::getReason public function
SyncIntent::getStatusData public function Retrieve a value you stored before via ::setstatusData().
SyncIntent::getUuid public function
SyncIntent::setEntity public function Set the entity when pulling (may not be saved yet then).
SyncIntent::setStatusData public function Store a key=>value pair for later retrieval.