lightning_scheduler-8.x-1.x-dev/src/TransitionManager.php

src/TransitionManager.php
<?php

namespace Drupal\lightning_scheduler;

use Drupal\Component\Serialization\Json;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;

/**
 * @internal
 *   This is an internal part of Lightning Scheduler and may be changed or
 *   removed at any time without warning. It should not be used by external
 *   code in any way.
 */
final class TransitionManager {

  use StringTranslationTrait;

  /**
   * The moderation information service.
   *
   * @var \Drupal\content_moderation\ModerationInformationInterface
   */
  private $moderationInformation;

  /**
   * The currently logged-in user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  private $currentUser;

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

  /**
   * The logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  private $logger;

  /**
   * State storage service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  private $state;

  /**
   * TransitionManager constructor.
   *
   * @param ModerationInformationInterface $moderation_information
   *   The moderation information service.
   * @param AccountInterface $current_user
   *   The currently logged-in user.
   * @param EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param LoggerChannelInterface $logger
   *   The logger channel.
   * @param TranslationInterface $translation
   *   The string translation service.
   * @param StateInterface $state
   *   State storage service.
   */
  public function __construct(
    ModerationInformationInterface $moderation_information,
    AccountInterface $current_user,
    EntityTypeManagerInterface $entity_type_manager,
    LoggerChannelInterface $logger,
    TranslationInterface $translation,
    StateInterface $state
  ) {
    $this->moderationInformation = $moderation_information;
    $this->currentUser = $current_user;
    $this->entityTypeManager = $entity_type_manager;
    $this->logger = $logger;
    $this->setStringTranslation($translation);
    $this->state = $state;
  }

  /**
   * Validates incoming transition data.
   *
   * @param array $element
   *   The form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @see lightning_scheduler_form_alter()
   */
  public static function validate(array $element, FormStateInterface $form_state) {
    $data = Json::decode($element['#value']);

    if (json_last_error() !== JSON_ERROR_NONE) {
      $variables = [
        '%error' => json_last_error_msg(),
      ];
      $form_state->setError($element, t('Invalid transition data: %error', $variables));
      return;
    }

    if (! is_array($data)) {
      $form_state->setError($element, t('Expected scheduled transitions to be an array.'));
      return;
    }

    $minimum_date = NULL;

    if (!\Drupal::config('lightning_scheduler.settings')->get('allow_past_dates')) {
      $minimum_date = \Drupal::time()->getRequestTime();
    }

    foreach ($data as $transition) {
      if (empty($transition['when'])) {
        $form_state->setError($element, t('Scheduled transitions must have a date and time.'));
        return;
      }

      if (! preg_match('/^[0-9]+$/', $transition['when'])) {
        $variables = [
          '%when' => $transition['when'],
        ];
        $form_state->setError($element, t('"%when" is not a valid date and time.', $variables));
        return;
      }

      // The transition must take place after $minimum_date.
      if (isset($minimum_date) && $transition['when'] < $minimum_date) {
        $form_state->setError($element, t('You cannot schedule a transition to take place before @date.', [
          '@date' => self::renderTranslatableDate($minimum_date, 'long'),
        ]));
        return;
      }
      $minimum_date = $transition['when'];
    }
  }

  /**
   * Executes all scheduled transitions for a particular entity type.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param DrupalDateTime $now
   *   The time that processing began.
   */
  public function process($entity_type_id, DrupalDateTime $now) {
    /** @var ContentEntityInterface $entity */
    foreach ($this->getTransitionable($entity_type_id, $now) as $entity) {
      $error_context = [
        'entity_type' => (string) $entity->getEntityType()->getSingularLabel(),
        'entity' => $entity->label(),
      ];

      $workflow = $this->moderationInformation->getWorkflowForEntity($entity);
      // If the entity hasn't got a workflow, what are we doing here?
      if (empty($workflow)) {
        $message = $this->t('Could not execute scheduled transition(s) for {entity_type} "{entity}" because no workflow is assigned to it.');
        $this->logger->error($message, $error_context);
        continue;
      }

      $transition_set = new TransitionSet(
        $entity->get('scheduled_transition_date'),
        $entity->get('scheduled_transition_state')
      );

      $to_state = $transition_set->getExpectedState($now);
      // If no workflow state is targeted, there's nothing to transition to.
      if (empty($to_state)) {
        continue;
      }

      $from_state = $entity->moderation_state->value;
      $plugin = $workflow->getTypePlugin();

      if ($plugin->hasTransitionFromStateToState($from_state, $to_state)) {
        $entity->set('moderation_state', $to_state);
      }
      else {
        $error_context += [
          'from_state' => $plugin->getState($from_state)->label(),
          'to_state' => $plugin->getState($to_state)->label(),
          'workflow' => $workflow->label(),
        ];
        $message = $this->t('Could not transition {entity_type} "{entity}" from {from_state} to {to_state} because no such transition exists in the "{workflow}" workflow.');
        $this->logger->warning($message, $error_context);
      }
      $transition_set->trim($now);
      $entity->save();
    }
  }

  /**
   * Returns all transitionable entities of a given type.
   *
   * The entity type is assumed to have the scheduled_transition_date field.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param DrupalDateTime $now
   *   The time that processing began.
   *
   * @return \Generator
   *   An iterable of the latest revisions of all transitionable entities of the
   *   given type.
   */
  private function getTransitionable($entity_type_id, DrupalDateTime $now) {
    $storage = $this->entityTypeManager->getStorage($entity_type_id);

    $sql_timezone = DateTimeItemInterface::STORAGE_TIMEZONE;
    $storage_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
    $now = $now->format($storage_format, ['timezone' => $sql_timezone]);

    $time_ago = 0;
    $cron_last = $this->state->get('system.cron_last');
    // Three days before cron last.
    if (!empty($cron_last)) {
      $time_ago = $cron_last - 259200;
    }
    $time_ago_in_drupal_datetime = new DrupalDateTime(date('Y-m-d\TH:i:s', $time_ago), $sql_timezone);
    $time_ago_in_drupal_datetime_for_query = $time_ago_in_drupal_datetime->format($storage_format, ['timezone' => $sql_timezone]);

    // Entities are transitionable if their latest revision has any transitions
    // scheduled now or going back to a little bit before the last cron run.
    // We should not reprocess going back to 1970 beginning of unix time.
    // Limit past re-processing for performance and scalability reasons.
    // Only go back in time as far as the last successful cron.
    $ids = $storage->getQuery()
        ->latestRevision()
        ->accessCheck(FALSE)
        ->condition('scheduled_transition_date.value', $now, '<=')
        ->condition('scheduled_transition_date.value', $time_ago_in_drupal_datetime_for_query, '>=')
        ->execute();

    foreach (array_keys($ids) as $revision_id) {
        yield $storage->loadRevision($revision_id);
    }
  }

  /**
   * Render a date from epoch using a translatable date format in Drupal.
   *
   * @param int $timestamp
   *   The epoch timestamp.
   * @param string $format_id
   *   The date format ID to use.
   *
   * @return string
   *   The formatted date string.
   */
  private static function renderTranslatableDate($timestamp, $format_id) {
    // Load the date format storage service
    $date_format_storage = \Drupal::entityTypeManager()->getStorage('date_format');

    // Check if the date format ID exists
    $dateFormat = $date_format_storage->load($format_id);

    // Convert epoch to DateTime object
    $dateTime = new \DateTime();
    $dateTime->setTimestamp($timestamp);

    // Load the date formatter service
    /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
    $date_formatter = \Drupal::service('date.formatter');

    // Render the date using the format ID
    return (!empty($dateFormat)) ?
      $date_formatter->format($dateTime->getTimestamp(), $format_id) :
      $date_formatter->format($dateTime->getTimestamp(), 'custom', 'F j, Y g:i A');
  }
}

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

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