You are here

class ScheduledTransitionsRunner in Scheduled Transitions 2.x

Same name and namespace in other branches
  1. 8 src/ScheduledTransitionsRunner.php \Drupal\scheduled_transitions\ScheduledTransitionsRunner

Executes transitions.

Hierarchy

Expanded class hierarchy of ScheduledTransitionsRunner

1 string reference to 'ScheduledTransitionsRunner'
scheduled_transitions.services.yml in ./scheduled_transitions.services.yml
scheduled_transitions.services.yml
1 service uses ScheduledTransitionsRunner
scheduled_transitions.runner in ./scheduled_transitions.services.yml
Drupal\scheduled_transitions\ScheduledTransitionsRunner

File

src/ScheduledTransitionsRunner.php, line 29

Namespace

Drupal\scheduled_transitions
View source
class ScheduledTransitionsRunner implements ScheduledTransitionsRunnerInterface {
  use StringTranslationTrait;
  protected const LOCK_DURATION = 1800;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * System time.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * General service for moderation-related questions about Entity API.
   *
   * @var \Drupal\content_moderation\ModerationInformationInterface
   */
  protected $moderationInformation;

  /**
   * The token replacement system.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * Utilities for Scheduled Transitions module.
   *
   * @var \Drupal\scheduled_transitions\ScheduledTransitionsUtilityInterface
   */
  protected $scheduledTransitionsUtility;

  /**
   * Constructs a new ScheduledTransitionsRunner.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The configuration factory.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   System time.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\content_moderation\ModerationInformationInterface $moderationInformation
   *   General service for moderation-related questions about Entity API.
   * @param \Drupal\Core\Utility\Token $token
   *   The token replacement system.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   The event dispatcher.
   * @param \Drupal\scheduled_transitions\ScheduledTransitionsUtilityInterface $scheduledTransitionsUtility
   *   Utilities for Scheduled Transitions module.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory, TimeInterface $time, LoggerInterface $logger, ModerationInformationInterface $moderationInformation, Token $token, EventDispatcherInterface $eventDispatcher, ScheduledTransitionsUtilityInterface $scheduledTransitionsUtility) {
    $this->entityTypeManager = $entityTypeManager;
    $this->configFactory = $configFactory;
    $this->time = $time;
    $this->logger = $logger;
    $this->moderationInformation = $moderationInformation;
    $this->token = $token;
    $this->eventDispatcher = $eventDispatcher;
    $this->scheduledTransitionsUtility = $scheduledTransitionsUtility;
  }

  /**
   * {@inheritdoc}
   */
  public function runTransition(ScheduledTransitionInterface $scheduledTransition) : void {
    $scheduledTransitionId = $scheduledTransition
      ->id();
    $targs = [
      '@id' => $scheduledTransitionId,
    ];
    $entity = $scheduledTransition
      ->getEntity();
    if (!$entity) {
      $this->logger
        ->info('Entity does not exist for scheduled transition #@id', $targs);
      throw new ScheduledTransitionMissingEntity(sprintf('Entity does not exist for scheduled transition #%s', $scheduledTransitionId));
    }
    $event = new ScheduledTransitionsNewRevisionEvent($scheduledTransition);
    $this->eventDispatcher
      ->dispatch(ScheduledTransitionsEvents::NEW_REVISION, $event);
    $newRevision = $event
      ->getNewRevision();
    if (!$newRevision) {
      throw new ScheduledTransitionMissingEntity(sprintf('No revision could be determined to transition to for scheduled transition #%s', $scheduledTransitionId));
    }

    /** @var \Drupal\Core\Entity\EntityStorageInterface|\Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */
    $entityStorage = $this->entityTypeManager
      ->getStorage($entity
      ->getEntityTypeId());
    $latestRevisionId = $entityStorage
      ->getLatestRevisionId($entity
      ->id());
    if ($latestRevisionId) {

      /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $latest */
      $latest = $entityStorage
        ->loadRevision($latestRevisionId);
    }
    if (!isset($latest)) {
      $this->logger
        ->info('Latest revision does not exist for scheduled transition #@id', $targs);
      throw new ScheduledTransitionMissingEntity(sprintf('Latest revision does not exist for scheduled transition #%s', $scheduledTransitionId));
    }
    $this
      ->transitionEntity($scheduledTransition, $newRevision, $latest);
    $this->logger
      ->info('Deleted scheduled transition #@id', $targs);
    $scheduledTransition
      ->delete();
  }

  /**
   * Transition a revision.
   *
   * This method is responsible for saving new revision, and any intermediate
   * revisions if applicable.
   *
   * @param \Drupal\scheduled_transitions\Entity\ScheduledTransitionInterface $scheduledTransition
   *   A scheduled transition entity.
   * @param \Drupal\Core\Entity\EntityInterface $newRevision
   *   A new default revision.
   * @param \Drupal\Core\Entity\EntityInterface $latest
   *   The latest current revision.
   */
  protected function transitionEntity(ScheduledTransitionInterface $scheduledTransition, EntityInterface $newRevision, EntityInterface $latest) : void {

    /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $newRevision */

    /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $latest */

    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */
    $entityStorage = $this->entityTypeManager
      ->getStorage($newRevision
      ->getEntityTypeId());
    $settings = $this->configFactory
      ->get('scheduled_transitions.settings');

    // Check this now before any new saves.
    // Remove if Scheduled Transitions supports non CM workflows in the future.
    $isLatestRevisionPublished = NULL;
    $originalLatestState = NULL;
    $newState = NULL;
    if ($latest instanceof ContentEntityInterface) {
      $isLatestRevisionPublished = $this->moderationInformation
        ->isLiveRevision($latest);
      $workflow = $this->moderationInformation
        ->getWorkflowForEntity($latest);
      $workflowPlugin = $workflow
        ->getTypePlugin();
      $states = $workflowPlugin
        ->getStates();
      $originalLatestState = $states[$latest->moderation_state->value ?? ''] ?? NULL;
      $newState = $states[$scheduledTransition
        ->getState()] ?? NULL;
    }
    $replacements = new ScheduledTransitionsTokenReplacements($scheduledTransition, $newRevision, $latest);

    // Start the transition process.
    // Determine if latest before calling setNewRevision on $newRevision.
    $newIsLatest = $newRevision
      ->getRevisionId() === $latest
      ->getRevisionId();
    $revisionLog = $newRevision instanceof RevisionLogInterface ? $this->scheduledTransitionsUtility
      ->generateRevisionLog($scheduledTransition, $newRevision) : NULL;

    // Creating revisions via createRevision to invoke
    // setRevisionTranslationAffected and whatever other logic doesn't happen
    // automatically by simply setting setNewRevision on its own.
    // 'default' param: will be changed by content moderation anyway, and
    // ->setNewRevision() is called.
    $newRevision = $entityStorage
      ->createRevision($newRevision, FALSE);
    $newRevision->moderation_state = $newState
      ->id();
    if ($newRevision instanceof EntityChangedInterface) {
      $newRevision
        ->setChangedTime($this->time
        ->getRequestTime());
    }

    // If publishing the latest revision, then only set moderation state.
    if ($newIsLatest) {
      $this
        ->log(LogLevel::INFO, 'Transitioning latest revision from @original_state to @new_state', $replacements);
      if ($newRevision instanceof RevisionLogInterface && $revisionLog) {
        $newRevision
          ->setRevisionLogMessage($revisionLog)
          ->setRevisionCreationTime($this->time
          ->getRequestTime());
      }
      $newRevision
        ->save();
    }
    else {
      $this
        ->log(LogLevel::INFO, 'Copied revision #@revision_id and changed from @original_state to @new_state', $replacements);
      if ($newRevision instanceof RevisionLogInterface && $revisionLog) {
        $newRevision
          ->setRevisionLogMessage($revisionLog)
          ->setRevisionCreationTime($this->time
          ->getRequestTime());
      }
      $newRevision
        ->save();
      $options = $scheduledTransition
        ->getOptions();

      // If the new revision is now a default, and the old latest was not a
      // default (e.g Draft), then pull it back on top.
      if (!empty($options[ScheduledTransition::OPTION_RECREATE_NON_DEFAULT_HEAD])) {

        // To republish, this revision cannot be published, and the state for
        // this revision must still exist.
        if (!$isLatestRevisionPublished && $originalLatestState) {
          $latest = $entityStorage
            ->createRevision($latest, FALSE);
          $this
            ->log(LogLevel::INFO, 'Reverted @original_latest_state revision #@original_revision_id back to top', $replacements);
          if ($latest instanceof RevisionLogInterface) {
            $template = $settings
              ->get('message_transition_copy_latest_draft');
            $latest
              ->setRevisionLogMessage($this
              ->tokenReplace($template, $replacements))
              ->setRevisionCreationTime($this->time
              ->getRequestTime());
          }
          $latest
            ->save();
        }
      }
    }
  }

  /**
   * Logs a message and adds context.
   *
   * @param mixed $level
   *   Log level.
   * @param string $message
   *   A log message.
   * @param \Drupal\scheduled_transitions\ScheduledTransitionsTokenReplacements $replacements
   *   A replacements object.
   */
  protected function log($level, string $message, ScheduledTransitionsTokenReplacements $replacements) : void {
    $variables = $replacements
      ->getReplacements();
    $targs = [
      '@new_state' => $variables['to-state'],
      '@original_state' => $variables['from-state'],
      '@revision_id' => $variables['from-revision-id'],
      '@original_latest_state' => $variables['latest-state'],
      '@original_revision_id' => $variables['latest-revision-id'],
    ];
    $this->logger
      ->log($level, $message, $targs);
  }

  /**
   * Replaces all tokens in a given string with appropriate values.
   *
   * @param string $text
   *   A string containing replaceable tokens.
   * @param \Drupal\scheduled_transitions\ScheduledTransitionsTokenReplacements $replacements
   *   A replacements object.
   *
   * @return string
   *   The string with the tokens replaced.
   */
  protected function tokenReplace(string $text, ScheduledTransitionsTokenReplacements $replacements) : string {
    $tokenData = [
      'scheduled-transitions' => $replacements
        ->getReplacements(),
    ];
    return $this->token
      ->replace($text, $tokenData);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ScheduledTransitionsRunner::$configFactory protected property The configuration factory.
ScheduledTransitionsRunner::$entityTypeManager protected property The entity type manager.
ScheduledTransitionsRunner::$eventDispatcher protected property The event dispatcher.
ScheduledTransitionsRunner::$logger protected property A logger instance.
ScheduledTransitionsRunner::$moderationInformation protected property General service for moderation-related questions about Entity API.
ScheduledTransitionsRunner::$scheduledTransitionsUtility protected property Utilities for Scheduled Transitions module.
ScheduledTransitionsRunner::$time protected property System time.
ScheduledTransitionsRunner::$token protected property The token replacement system.
ScheduledTransitionsRunner::LOCK_DURATION protected constant
ScheduledTransitionsRunner::log protected function Logs a message and adds context.
ScheduledTransitionsRunner::runTransition public function Executes a transition. Overrides ScheduledTransitionsRunnerInterface::runTransition
ScheduledTransitionsRunner::tokenReplace protected function Replaces all tokens in a given string with appropriate values.
ScheduledTransitionsRunner::transitionEntity protected function Transition a revision.
ScheduledTransitionsRunner::__construct public function Constructs a new ScheduledTransitionsRunner.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.