View source
<?php
declare (strict_types=1);
namespace Drupal\scheduled_transitions\Form\Entity;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Xss;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\content_moderation\StateTransitionValidationInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Entity\TranslatableRevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\Tableselect;
use Drupal\scheduled_transitions\Entity\ScheduledTransition;
use Drupal\workflows\Transition;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ScheduledTransitionAddForm extends ContentEntityForm {
const LATEST_REVISION = 'latest_revision';
protected $dateFormatter;
protected $moderationInformation;
protected $stateTransitionValidation;
protected $languageManager;
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, DateFormatterInterface $dateFormatter, ModerationInformationInterface $moderationInformation, StateTransitionValidationInterface $stateTransitionValidation, LanguageManagerInterface $languageManager) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->dateFormatter = $dateFormatter;
$this->moderationInformation = $moderationInformation;
$this->stateTransitionValidation = $stateTransitionValidation;
$this->languageManager = $languageManager;
}
public static function create(ContainerInterface $container) {
return new static($container
->get('entity.repository'), $container
->get('entity_type.bundle.info'), $container
->get('datetime.time'), $container
->get('date.formatter'), $container
->get('content_moderation.moderation_information'), $container
->get('content_moderation.state_transition_validation'), $container
->get('language_manager'));
}
public function getBaseFormId() {
return NULL;
}
public function form(array $form, FormStateInterface $form_state) : array {
$account = $this
->currentUser();
$form['scheduled_transitions']['#theme'] = 'scheduled_transitions_form_add';
$entity = $this
->getEntity();
$header = [];
$header['revision_id'] = $this
->t('Revision');
$header['state'] = $this
->t('State');
if ($entity instanceof RevisionLogInterface) {
$header['revision_time'] = $this
->t('Saved on');
$header['revision_author'] = $this
->t('Saved by');
$header['revision_log'] = $this
->t('Log');
}
$newMetaWrapperId = 'new-meta-wrapper';
$input = $form_state
->getUserInput();
$revisionOptions = $this
->getRevisionOptions($entity);
$revision = $input['revision'] ?? (isset($revisionOptions[static::LATEST_REVISION]) ? static::LATEST_REVISION : NULL);
$form['scheduled_transitions']['revision'] = [
'#type' => 'tableselect',
'#header' => $header,
'#caption' => $this
->t('Select which revision you wish to move to a new state.'),
'#options' => $revisionOptions,
'#multiple' => FALSE,
'#footer' => [
[
[
'colspan' => count($header) + 1,
'data' => [
'#plain_text' => $this
->t('Revisions are ordered from newest to oldest.'),
],
],
],
],
'#process' => [
[
Tableselect::class,
'processTableselect',
],
'::revisionProcess',
],
'#new_meta_wrapper_id' => $newMetaWrapperId,
'#default_value' => $revision,
];
$form['scheduled_transitions']['new_meta'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'container-inline',
],
],
'#prefix' => '<div id="' . $newMetaWrapperId . '">',
'#suffix' => '</div>',
];
$workflow = $this->moderationInformation
->getWorkflowForEntity($entity);
$workflowPlugin = $workflow
->getTypePlugin();
if (is_numeric($revision) && $revision > 0) {
$entityStorage = $this->entityTypeManager
->getStorage($entity
->getEntityTypeId());
$entityRevision = $entityStorage
->loadRevision($revision);
$toTransitions = $this->stateTransitionValidation
->getValidTransitions($entityRevision, $this
->currentUser());
}
elseif (is_string($revision)) {
$toTransitions = array_filter($workflowPlugin
->getTransitions(), function (Transition $transition) use ($workflow, $account) {
return $account
->hasPermission('use ' . $workflow
->id() . ' transition ' . $transition
->id());
});
}
if (isset($toTransitions)) {
$transitionOptions = [];
foreach ($toTransitions as $toTransition) {
$transitionOptions[$toTransition
->id()] = $toTransition
->label();
}
$form['scheduled_transitions']['new_meta']['transition_help']['#markup'] = $this
->t('<strong>Execute transition</strong>');
$form['scheduled_transitions']['new_meta']['transition'] = [
'#type' => 'select',
'#options' => $transitionOptions,
'#empty_option' => $this
->t('- Select -'),
'#required' => TRUE,
];
$form['scheduled_transitions']['new_meta']['on_help']['#markup'] = $this
->t('<strong>on date</strong>');
$form['scheduled_transitions']['new_meta']['on'] = [
'#type' => 'datetime',
'#default_value' => new \DateTime(),
'#required' => TRUE,
];
}
else {
$form['scheduled_transitions']['new_meta']['transition_help']['#markup'] = $this
->t('Select a revision above');
}
$form['scheduled_transitions']['to_options'] = [
'#type' => 'container',
];
if (isset($toTransitions) && count($toTransitions) > 0) {
$conditions = [];
foreach ($toTransitions as $transition) {
if ($transition
->to()
->isDefaultRevisionState()) {
$conditions[] = [
':input[name="transition"]' => [
'value' => $transition
->id(),
],
];
}
}
$form['scheduled_transitions']['to_options']['recreate_non_default_head'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Recreate pending revision'),
'#description' => $this
->t('Before creating this revision, check if there is any pending work. If so then recreate it. Regardless of choice, revisions are safely retained in history, and can be reverted manually.'),
'#default_value' => TRUE,
'#states' => [
'visible' => $conditions,
],
];
}
return $form;
}
public function revisionProcess(array &$element, FormStateInterface $form_state, array &$complete_form) : array {
$newMetaWrapperId = $element['#new_meta_wrapper_id'];
foreach (Element::children($element) as $key) {
$element[$key]['#ajax'] = [
'event' => 'change',
'callback' => '::ajaxCallbackNewMeta',
'wrapper' => $newMetaWrapperId,
'progress' => [
'type' => 'fullscreen',
],
'effect' => 'fade',
];
}
return $element;
}
public function ajaxCallbackNewMeta($form, FormStateInterface $form_state) : array {
return $form['scheduled_transitions']['new_meta'];
}
public function validateForm(array &$form, FormStateInterface $form_state) : void {
if (empty($form_state
->getValue('revision'))) {
$form_state
->setError($form['scheduled_transitions']['revision'], $this
->t('Revision must be selected.'));
}
}
public function submitForm(array &$form, FormStateInterface $form_state) : void {
$entity = $this
->getEntity();
$options = [];
if ($form_state
->getValue('recreate_non_default_head')) {
$options[ScheduledTransition::OPTION_RECREATE_NON_DEFAULT_HEAD] = TRUE;
}
$revisionOption = $form_state
->getValue('revision');
$entityRevisionId = 0;
if ($revisionOption === static::LATEST_REVISION) {
$options[ScheduledTransition::OPTION_LATEST_REVISION] = TRUE;
}
else {
$entityRevisionId = $revisionOption;
}
$workflow = $this->moderationInformation
->getWorkflowForEntity($entity);
$transition = $form_state
->getValue([
'transition',
]);
$workflowPlugin = $workflow
->getTypePlugin();
$newState = $workflowPlugin
->getTransition($transition)
->to()
->id();
$onDate = $form_state
->getValue([
'on',
]);
$scheduledTransitionStorage = $this->entityTypeManager
->getStorage('scheduled_transition');
$scheduledTransition = $scheduledTransitionStorage
->create([
'entity' => [
$entity,
],
'entity_revision_id' => $entityRevisionId,
'entity_revision_langcode' => $this->languageManager
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId(),
'author' => [
$this
->currentUser()
->id(),
],
'workflow' => $workflow
->id(),
'moderation_state' => $newState,
'transition_on' => $onDate
->getTimestamp(),
'options' => [
$options,
],
]);
$scheduledTransition
->save();
$this
->messenger()
->addMessage($this
->t('Scheduled a transition for @date', [
'@date' => $this->dateFormatter
->format($onDate
->getTimestamp()),
]));
}
protected function actions(array $form, FormStateInterface $form_state) : array {
$actions['submit']['#attached']['library'][] = 'core/drupal.dialog.ajax';
$actions['submit'] = [
'#type' => 'submit',
'#value' => $this
->t('Schedule transition'),
'#submit' => [
'::submitForm',
],
];
return $actions;
}
public function save(array $form, FormStateInterface $form_state) : void {
}
protected function getRevisionOptions(EntityInterface $entity) : array {
$entityTypeId = $entity
->getEntityTypeId();
$entityDefinition = $this->entityTypeManager
->getDefinition($entityTypeId);
$entityStorage = $this->entityTypeManager
->getStorage($entityTypeId);
$workflow = $this->moderationInformation
->getWorkflowForEntity($entity);
$workflowPlugin = $workflow
->getTypePlugin();
$workflowStates = $workflowPlugin ? $workflowPlugin
->getStates() : [];
$ids = $entityStorage
->getQuery()
->allRevisions()
->condition($entityDefinition
->getKey('id'), $entity
->id())
->condition($entityDefinition
->getKey('langcode'), $this->languageManager
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId())
->sort($entityDefinition
->getKey('revision'), 'DESC')
->execute();
$revisionIds = array_keys($ids);
$entityRevisions = array_map(function (string $revisionId) use ($entityStorage) : EntityInterface {
$revision = $entityStorage
->loadRevision($revisionId);
if ($revision instanceof TranslatableInterface) {
$revision = $revision
->getTranslation($this->languageManager
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId());
}
return $revision;
}, array_combine($revisionIds, $revisionIds));
$entityRevisions = array_filter($entityRevisions, function (EntityInterface $revision) {
return $revision instanceof TranslatableRevisionableInterface ? $revision
->isRevisionTranslationAffected() : TRUE;
});
$options = array_map(function (EntityInterface $entityRevision) use ($workflowStates) : array {
$option = [];
$revisionTArgs = [
'@revision_id' => $entityRevision
->getRevisionId(),
];
$toLinkArgs = [
$this
->t('#@revision_id', $revisionTArgs),
];
if ($entityRevision
->hasLinkTemplate('revision')) {
$toLinkArgs[] = 'revision';
}
$revisionLink = $entityRevision
->toLink(...$toLinkArgs);
$revisionCell = $revisionLink
->toRenderable();
$revisionCell['#attributes'] = [
'target' => '_blank',
];
$option['revision_id']['data'] = $revisionCell;
$moderationState = $workflowStates[$entityRevision->moderation_state->value] ?? NULL;
$option['state']['data'] = $moderationState ? $moderationState
->label() : $this
->t('- Unknown state -');
if ($entityRevision instanceof RevisionLogInterface) {
$option['revision_time']['data']['#plain_text'] = $this->dateFormatter
->format($entityRevision
->getRevisionCreationTime());
$revisionUser = $entityRevision
->getRevisionUser();
if ($revisionUser) {
$option['revision_author']['data'] = $this->moduleHandler
->moduleExists('user') ? [
'#theme' => 'username',
'#account' => $revisionUser,
] : $revisionUser
->toLink();
}
else {
$option['revision_author']['data'] = $this
->t('- Missing user -');
}
if ($revisionLog = $entityRevision
->getRevisionLogMessage()) {
$option['revision_log']['data'] = [
'#markup' => $revisionLog,
'#allowed_tags' => Xss::getHtmlTagList(),
];
}
else {
$option['revision_log']['data'] = $this
->t('<em>- None -</em>');
}
}
return $option;
}, $entityRevisions);
$options = [
static::LATEST_REVISION => [
'revision_id' => [
'data' => $this
->t('Latest revision'),
],
'state' => [
'data' => $this
->t('Automatically determines the latest revision at time of transition.'),
'colspan' => $entity instanceof RevisionLogInterface ? 4 : 1,
],
],
] + $options;
return $options;
}
}