You are here

ModerationStateWidget.php in Lightning Scheduler 8

File

src/Plugin/Field/FieldWidget/ModerationStateWidget.php
View source
<?php

namespace Drupal\lightning_scheduler\Plugin\Field\FieldWidget;

use Drupal\Component\Serialization\Json;
use Drupal\content_moderation\Plugin\Field\FieldWidget\ModerationStateWidget as BaseModerationStateWidget;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\lightning_scheduler\TransitionManager;
use Drupal\lightning_scheduler\TransitionSet;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Scheduler extension of Content Moderation's widget.
 *
 * @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 ModerationStateWidget extends BaseModerationStateWidget {

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $configuration['third_party_settings'] += [
      'lightning_scheduler' => [
        'time_step' => $container
          ->get('config.factory')
          ->get('lightning_scheduler.settings')
          ->get('time_step'),
      ],
    ];
    return parent::create($container, $configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);
    $entity = $items
      ->getEntity();
    assert($entity instanceof ContentEntityInterface);

    // The entity must have the proper fields.
    $has_fields = $entity
      ->hasField('scheduled_transition_date') && $entity
      ->hasField('scheduled_transition_state');
    if (!$has_fields) {
      return $element;
    }
    $states = $this
      ->getStates($entity);

    // The latest revision, if there is one, is the canonical source of truth
    // regarding scheduled transitions.

    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
    $storage = $this->entityTypeManager
      ->getStorage($entity
      ->getEntityTypeId());
    if (!$entity
      ->isNew() && $storage
      ->getEntityType()
      ->isRevisionable() && ($latest_revision_id = $storage
      ->getLatestRevisionId($entity
      ->id()))) {
      $latest_revision = $storage
        ->loadRevision($latest_revision_id) ?: $entity;
    }
    else {
      $latest_revision = $entity;
    }
    $transition_set = new TransitionSet($latest_revision
      ->get('scheduled_transition_date'), $latest_revision
      ->get('scheduled_transition_state'));
    $element['scheduled_transitions'] = [
      '#type' => 'html_tag',
      '#tag' => 'TransitionSet',
      '#attributes' => [
        'states' => Json::encode($states),
        'step' => $this
          ->getThirdPartySetting('lightning_scheduler', 'time_step', 60),
      ],
      '#attached' => [
        'library' => [
          'lightning_scheduler/widget',
        ],
      ],
      'data' => [
        '#type' => 'hidden',
        '#entity_uuid' => $entity
          ->uuid(),
        '#element_validate' => [
          [
            TransitionManager::class,
            'validate',
          ],
          [
            $this,
            'storeValue',
          ],
        ],
        '#default_value' => $transition_set
          ->toJSON(),
        '#process' => [
          [
            $this,
            'processComponentInput',
          ],
        ],
      ],
    ];
    return $element;
  }

  /**
   * #process callback for the scheduler component's input element.
   *
   * @param array $element
   *   The unprocessed element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @return array
   *   The processed element.
   */
  public function processComponentInput(array $element, FormStateInterface $form_state) {
    $key = $element['#parents'];
    if ($form_state
      ->hasValue($key)) {
      $element['#default_value'] = $form_state
        ->getValue($key);
    }
    return $element;
  }

  /**
   * Validation method that accesses the hidden input element, and stores its
   * value in the form state.
   *
   * @param array $element
   *   The hidden input.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state to update.
   */
  public function storeValue(array $element, FormStateInterface $form_state) {
    if ($form_state
      ->getErrors()) {
      return;
    }
    assert(!empty($element['#entity_uuid']));
    $decoded = Json::decode($element['#value']);
    assert(is_array($decoded));
    $transition_storage = $form_state
      ->getValue('transition_storage') ?: [];

    // Support multiple widgets on one form (e.g. Inline Entity Form).
    $uuid = $element['#entity_uuid'];
    $transition_storage[$uuid] = $decoded;
    $form_state
      ->setValue('transition_storage', $transition_storage);
  }

  /**
   * {@inheritdoc}
   */
  public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
    parent::extractFormValues($items, $form, $form_state);
    $transitions = $form_state
      ->getValue('transition_storage');
    $entity = $items
      ->getEntity();
    $uuid = $entity
      ->uuid();

    // Do not use empty() here, because it's possible that the user is trying to
    // clear all scheduled transitions, which means $transitions[$uuid] will
    // be an empty array.
    if (!isset($transitions[$uuid])) {
      return;
    }
    $states = array_map(function (array $transition) {
      assert(!empty($transition['state']) && is_string($transition['state']));
      return [
        'value' => $transition['state'],
      ];
    }, $transitions[$uuid]);
    $dates = array_map(function (array $transition) {
      return [
        'value' => gmdate(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $transition['when']),
      ];
    }, $transitions[$uuid]);
    assert(count($states) === count($dates));
    $entity
      ->set('scheduled_transition_state', $states)
      ->set('scheduled_transition_date', $dates);
  }

  /**
   * Returns an array of available workflow states for an entity.
   *
   * A workflow state is considered "available" if the current user has
   * permission to use or schedule it.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity which has the workflow.
   *
   * @return array
   *   An associative array where the keys are the workflow state IDs, and the
   *   values are the states' human-readable labels.
   */
  private function getStates(ContentEntityInterface $entity) {
    $states = [];
    $workflow = $this->moderationInformation
      ->getWorkflowForEntity($entity);
    foreach ($workflow
      ->getTypePlugin()
      ->getTransitions() as $transition) {
      $base_permission = $workflow
        ->id() . ' transition ' . $transition
        ->id();
      if ($this->currentUser
        ->hasPermission("schedule {$base_permission}") || $this->currentUser
        ->hasPermission("use {$base_permission}")) {
        $to_state = $transition
          ->to();
        $states[$to_state
          ->id()] = $to_state
          ->label();
      }
    }
    return $states;
  }

}

Classes

Namesort descending Description
ModerationStateWidget Scheduler extension of Content Moderation's widget.