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

src/SyncIntent.php
<?php

namespace Drupal\cms_content_sync;

use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\Core\Entity\EntityInterface;

/**
 * Class SyncIntent.
 *
 * For every pull and push of every entity, an instance of this class is
 * created and passed through the entity and field handlers. When pushing,
 * you can set field values and embed entities. When pushing, you can
 * receive these values back and resolve the entity references you saved.
 *
 * The same class is used for push and pull to allow adjusting values
 * with hook integration.
 */
abstract class SyncIntent {
  /**
   * @var string ACTION_CREATE
   *             push/pull the creation of this entity
   */
  public const ACTION_CREATE = 'create';

  /**
   * @var string ACTION_UPDATE
   *             push/pull the update of this entity
   */
  public const ACTION_UPDATE = 'update';

  /**
   * @var string ACTION_DELETE
   *             push/pull the deletion of this entity
   */
  public const ACTION_DELETE = 'delete';

  /**
   * @var string ACTION_DELETE_TRANSLATION
   *             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 only used for calling the
   *             ->pushEntity function. It will then be replaced by a simple
   *             ::ACTION_UPDATE.
   */
  public const ACTION_DELETE_TRANSLATION = 'delete translation';
  /**
   * @var \Drupal\cms_content_sync\Entity\Flow
   * @var \Drupal\cms_content_sync\Entity\Pool[]
   * @var string                               entity type of the processed entity
   * @var string                               bundle of the processed entity
   * @var string                               UUID of the processed entity
   * @var array                                the field values for the untranslated entity
   * @var array                                The entities that should be processed along with this entity. Each entry is an array consisting of all SyncIntent::_*KEY entries.
   * @var string                               the currently active language
   * @var array                                the field values for the translation of the entity per language as key
   */
  protected $flow;
  protected $pools;
  protected $reason;
  protected $action;
  protected $entity;
  protected $entityType;
  protected $bundle;
  protected $uuid;
  protected $id;
  protected $activeLanguage;
  protected $entityStatuses;
  protected $individualTranslation;

  /**
   * @var \Drupal\cms_content_sync\Entity\EntityStatusProxyInterface
   */
  protected $entity_status;

  /**
   * Ignore the given properties/fields completely.
   *
   * @var array
   */
  protected $ignoreProperties = [];

  /**
   * @var float
   */
  protected $start;

  /**
   * @var float
   */
  protected $end = 0;

  /**
   * @var float
   */
  protected $childTime = 0;

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

  /**
   * 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 string $uuid
   *   {@see SyncIntent::$uuid}.
   * @param null $id
   * @param string $source_url
   *   The source URL if pulled or NULL if pushed from this site.
   * @param bool $individual_translation
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(Flow $flow, array $pools, $reason, $action, $entity_type, $bundle, $uuid, $id = NULL, $source_url = NULL, $individual_translation = FALSE) {
    $this->start = microtime(TRUE);

    $this->flow = $flow;
    $this->pools = $pools;
    $this->reason = $reason;
    $this->action = $action;
    $this->entityType = $entity_type;
    $this->bundle = $bundle;
    $this->uuid = $uuid;
    $this->id = $id;
    $this->entityStatuses = [];
    $this->individualTranslation = $individual_translation;

    foreach ($pools as $pool) {
      $status = EntityStatus::getInfoForEntity($entity_type, $uuid, $flow, $pool);

      if (!$status) {
        $status = EntityStatus::create([
          'flow' => $this->flow->id,
          'pool' => $pool->id,
          'entity_type' => $entity_type,
          'entity_uuid' => $uuid,
          'entity_type_version' => Flow::getEntityTypeVersion($entity_type, $bundle),
          'flags' => 0,
          'source_url' => $source_url,
        ]);
      }

      $this->entityStatuses[$pool->id] = $status;
    }

    if (1 === count($this->entityStatuses)) {
      $this->entity_status = reset($this->entityStatuses);
    }
    else {
      $this->entity_status = new EntityStatusProxy($this->entityStatuses);
    }
  }

  /**
   *
   */
  public function startTimer($name) {
    $this->timers[$name] = [
      'start' => microtime(TRUE),
      'end' => NULL,
      'duration' => NULL,
    ];
  }

  /**
   *
   */
  public function stopTimer($name) {
    $this->timers[$name]['end'] = microtime(TRUE);
    $this->timers[$name]['duration'] = $this->timers[$name]['end'] - $this->timers[$name]['start'];
  }

  /**
   * Whether or not only the current translation should be pushed or pulled.
   * By default, all translations are sent and received in the same request
   * to keep the number of requests low and performance up. But if we are
   * dealing with a lot of translations or very nested entities then we want
   * to instead use one request per translation.
   *
   * @return bool
   */
  public function isIndividualTranslation() {
    return $this->individualTranslation;
  }

  /**
   * Ignore the provided property or field completely when pushing / pulling.
   * This allows you to programmatically exclude specific properties e.g. if
   * you want to customize path alias settings / creation per site.
   */
  public function ignoreProperty(string $name) {
    $this->ignoreProperties[] = $name;
  }

  /**
   * Check whether the given property / field should be ignored. Used by the
   * entity handlers.
   *
   * @return bool
   */
  public function shouldIgnoreProperty(string $name) {
    return in_array($name, $this->ignoreProperties);
  }

  /**
   * Execute the intent.
   *
   * @return bool
   */
  abstract public function execute();

  /**
   * @return string
   */
  public function getReason() {
    return $this->reason;
  }

  /**
   * @return string
   */
  public function getAction() {
    return $this->action;
  }

  /**
   * @return \Drupal\cms_content_sync\Entity\Flow
   */
  public function getFlow() {
    return $this->flow;
  }

  /**
   * @return string[]
   */
  public function getPoolIds() {
    $ids = [];
    foreach ($this->pools as $pool) {
      $ids[] = $pool->id;
    }

    return $ids;
  }

  /**
   * @return \Drupal\Core\Entity\EntityInterface
   *   The entity of the intent, if it already exists locally
   */
  public function getEntity($fresh = FALSE) {
    if ($fresh) {
      $this->entity = NULL;
    }

    if (!$this->entity) {
      if ($this->id) {
        $entity = \Drupal::entityTypeManager()
          ->getStorage($this->entityType)
          ->load($this->id);
      }
      else {
        $entity = \Drupal::service('entity.repository')
          ->loadEntityByUuid($this->entityType, $this->uuid);
        // If the paragraph was initially created in  the wrong language, it may
        // not have any default language assigned.
        // The query above implicitly adds a filter for the default language if
        // not explicitly disabled, so we're trying again but without caring
        // about that flag.
        if (!$entity && $this->entityType === 'paragraph') {
          $entities = \Drupal::entityTypeManager()
            ->getStorage($this->entityType)
            ->loadByProperties(['uuid' => $this->uuid, 'default_langcode' => NULL]);
          if (count($entities)) {
            $entity = reset($entities);
          }
        }
      }

      if ($entity) {
        $this->setEntity($entity);
      }
    }

    return $this->entity;
  }

  /**
   * Returns the entity status per pool.
   */
  public function getAllEntityStatuses() {
    return $this->entityStatuses;
  }

  /**
   * Returns the entity status proxy for all pools.
   *
   * @return \Drupal\cms_content_sync\Entity\EntityStatusProxyInterface
   */
  public function getEntityStatus() {
    return $this->entity_status;
  }

  /**
   * Set the entity when pulling (may not be saved yet then).
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity you just created.
   *
   * @throws \Drupal\cms_content_sync\Exception\SyncException
   *
   * @return $this|EntityInterface|TranslatableInterface
   */
  public function setEntity(EntityInterface $entity) {
    if ($entity === $this->entity) {
      return $this->entity;
    }

    if ($this->entity && $this->entity->id() !== $entity->id()) {
      throw new SyncException(SyncException::CODE_INTERNAL_ERROR, NULL, 'Attempting to re-set existing entity.');
    }

    /**
     * @var \Drupal\Core\Entity\EntityInterface       $entity
     * @var \Drupal\Core\Entity\TranslatableInterface $entity
     */
    $this->entity = $entity;
    if ($this->entity) {
      if ($this->activeLanguage) {
        $this->entity = $this->entity->getTranslation($this->activeLanguage);
      }
    }

    return $this->entity;
  }

  /**
   * Retrieve a value you stored before via ::setstatusData().
   *
   * @see EntityStatus::getData()
   *
   * @param string|string[] $key
   *   The key to retrieve.
   *
   * @return mixed whatever you previously stored here
   */
  public function getStatusData($key) {
    return $this->entity_status ? $this->entity_status->getData($key) : NULL;
  }

  /**
   * Store a key=>value pair for later retrieval.
   *
   * @see EntityStatus::setData()
   *
   * @param string|string[] $key
   *   The key to store the data against. Especially
   *                               field handlers should use nested keys like ['field','[name]','[key]'].
   * @param mixed $value
   *   Whatever simple value you'd like to store.
   *
   * @return bool
   */
  public function setStatusData($key, $value) {
    if (!$this->entity_status) {
      return FALSE;
    }
    $this->entity_status->setData($key, $value);

    return TRUE;
  }

  /**
   * 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 self::changeTranslationLanguage() with the language
   * identifier for the translation in question. Then you perform all the
   * self::setField() updates for that language and eventually return to the
   * untranslated entity by using self::changeTranslationLanguage() without
   * arguments.
   *
   * @param string $language
   *   The identifier of the language to switch to or NULL to reset.
   */
  public function changeTranslationLanguage($language = NULL) {
    $this->activeLanguage = $language;
    if ($this->entity) {
      if ($language) {
        $this->entity = $this->entity->getTranslation($language);
      }
      else {
        $this->entity = $this->entity->getUntranslated();
      }
    }
  }

  /**
   * Return the language that's currently used.
   *
   * @see SyncIntent::changeTranslationLanguage() for a detailed explanation.
   */
  public function getActiveLanguage() {
    return $this->activeLanguage;
  }

  /**
   * @see SyncIntent::$entityType
   *
   * @return string
   */
  public function getEntityType() {
    return $this->entityType;
  }

  /**
   * @see SyncIntent::$bundle
   */
  public function getBundle() {
    return $this->bundle;
  }

  /**
   * @return null|string
   *
   * @see SyncIntent::$uuid
   */
  public function getUuid() {
    return $this->uuid;
  }

  /**
   * @return null|string
   *
   * @see SyncIntent::$id
   */
  public function getId() {
    return $this->id;
  }

  /**
   * @return string
   */
  public function getSharedId() {
    return $this->getId() ?? $this->getUuid();
  }

  /**
   *
   */
  protected function formatDuration() {
    if (!$this->end) {
      $this->end = microtime(TRUE);
    }

    $duration = $this->end - $this->start;
    $duration_formatted = number_format($duration, 3) . 's';
    if ($this->childTime) {
      $duration_without_children = $duration - $this->childTime;
      if ($duration_without_children < 0) {
        $duration_without_children = 0;
      }
      $duration_without_children_formatted = number_format($duration_without_children, 3) . 's';
      $duration_formatted .= '/' . $duration_without_children_formatted;
    }

    return $duration_formatted;
  }

  /**
   *
   */
  protected function formatTimers() {
    $formatted = [];
    foreach ($this->timers as $name => $properties) {
      if (NULL === $properties['duration']) {
        continue;
      }
      $formatted[] = $name . ': ' . number_format($properties['duration'], 3) . 's';
    }
    if (!count($formatted)) {
      return '-';
    }

    return implode(', ', $formatted);
  }

}

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

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