Initial Drupal 11 with DDEV setup
This commit is contained in:
70
web/core/modules/workflows/src/Annotation/WorkflowType.php
Normal file
70
web/core/modules/workflows/src/Annotation/WorkflowType.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Workflow type annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\WorkflowType
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\content_moderation\Plugin\Workflow\ContentModerate
|
||||
*
|
||||
* @see \Drupal\workflows\WorkflowTypeInterface
|
||||
* @see \Drupal\workflows\WorkflowTypeManager
|
||||
* @see workflow_type_info_alter()
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class WorkflowType extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The label of the workflow.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $label = '';
|
||||
|
||||
/**
|
||||
* States required to exist.
|
||||
*
|
||||
* Normally supplied by WorkflowType::defaultConfiguration().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $required_states = [];
|
||||
|
||||
/**
|
||||
* A list of optional form classes implementing PluginFormInterface.
|
||||
*
|
||||
* Forms which will be used for the workflow UI are:
|
||||
* - 'configure' (\Drupal\workflows\WorkflowTypeInterface::PLUGIN_FORM_KEY)
|
||||
* - 'state' (\Drupal\workflows\StateInterface::PLUGIN_FORM_KEY)
|
||||
* - 'transition' (\Drupal\workflows\TransitionInterface::PLUGIN_FORM_KEY)
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see \Drupal\Core\Plugin\PluginWithFormsInterface
|
||||
* @see \Drupal\Core\Plugin\PluginFormInterface
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeConfigureFormBase
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeStateFormBase
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeTransitionFormBase
|
||||
* @see \Drupal\workflows\WorkflowTypeInterface::PLUGIN_FORM_KEY
|
||||
* @see \Drupal\workflows\StateInterface::PLUGIN_FORM_KEY
|
||||
* @see \Drupal\workflows\TransitionInterface::PLUGIN_FORM_KEY
|
||||
*/
|
||||
public $forms = [];
|
||||
|
||||
}
|
||||
72
web/core/modules/workflows/src/Attribute/WorkflowType.php
Normal file
72
web/core/modules/workflows/src/Attribute/WorkflowType.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines a Workflow type attribute object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\WorkflowType
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\content_moderation\Plugin\Workflow\ContentModerate
|
||||
*
|
||||
* @see \Drupal\workflows\WorkflowTypeInterface
|
||||
* @see \Drupal\workflows\WorkflowTypeManager
|
||||
* @see workflow_type_info_alter()
|
||||
* @see plugin_api
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class WorkflowType extends Plugin {
|
||||
|
||||
/**
|
||||
* States required to exist.
|
||||
*
|
||||
* Normally supplied by WorkflowType::defaultConfiguration().
|
||||
*/
|
||||
public array $required_states = [];
|
||||
|
||||
/**
|
||||
* A list of optional form classes implementing PluginFormInterface.
|
||||
*
|
||||
* Forms which will be used for the workflow UI are:
|
||||
* - 'configure' (\Drupal\workflows\WorkflowTypeInterface::PLUGIN_FORM_KEY)
|
||||
* - 'state' (\Drupal\workflows\StateInterface::PLUGIN_FORM_KEY)
|
||||
* - 'transition' (\Drupal\workflows\TransitionInterface::PLUGIN_FORM_KEY)
|
||||
*
|
||||
* @see \Drupal\Core\Plugin\PluginWithFormsInterface
|
||||
* @see \Drupal\Core\Plugin\PluginFormInterface
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeConfigureFormBase
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeStateFormBase
|
||||
* @see \Drupal\workflows\Plugin\WorkflowTypeTransitionFormBase
|
||||
* @see \Drupal\workflows\WorkflowTypeInterface::PLUGIN_FORM_KEY
|
||||
* @see \Drupal\workflows\StateInterface::PLUGIN_FORM_KEY
|
||||
* @see \Drupal\workflows\TransitionInterface::PLUGIN_FORM_KEY
|
||||
*/
|
||||
public array $forms = [];
|
||||
|
||||
/**
|
||||
* Constructs an Action attribute.
|
||||
*
|
||||
* @param string $id
|
||||
* The plugin ID.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
|
||||
* The label of the action.
|
||||
* @param string[] $forms
|
||||
* A list of optional form classes implementing PluginFormInterface.
|
||||
* @param string[] $required_states
|
||||
* States required to exist.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly ?TranslatableMarkup $label = NULL,
|
||||
array $forms = [],
|
||||
array $required_states = [],
|
||||
) {
|
||||
$this->forms = $forms;
|
||||
$this->required_states = $required_states;
|
||||
}
|
||||
|
||||
}
|
||||
190
web/core/modules/workflows/src/Entity/Workflow.php
Normal file
190
web/core/modules/workflows/src/Entity/Workflow.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ConfigEntityType;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
|
||||
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
|
||||
use Drupal\workflows\Exception\RequiredStateMissingException;
|
||||
use Drupal\workflows\Form\WorkflowAddForm;
|
||||
use Drupal\workflows\Form\WorkflowDeleteForm;
|
||||
use Drupal\workflows\Form\WorkflowEditForm;
|
||||
use Drupal\workflows\Form\WorkflowStateAddForm;
|
||||
use Drupal\workflows\Form\WorkflowStateDeleteForm;
|
||||
use Drupal\workflows\Form\WorkflowStateEditForm;
|
||||
use Drupal\workflows\Form\WorkflowTransitionAddForm;
|
||||
use Drupal\workflows\Form\WorkflowTransitionDeleteForm;
|
||||
use Drupal\workflows\Form\WorkflowTransitionEditForm;
|
||||
use Drupal\workflows\WorkflowAccessControlHandler;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\workflows\WorkflowListBuilder;
|
||||
|
||||
/**
|
||||
* Defines the workflow entity.
|
||||
*/
|
||||
#[ConfigEntityType(
|
||||
id: 'workflow',
|
||||
label: new TranslatableMarkup('Workflow'),
|
||||
label_collection: new TranslatableMarkup('Workflows'),
|
||||
label_singular: new TranslatableMarkup('workflow'),
|
||||
label_plural: new TranslatableMarkup('workflows'),
|
||||
config_prefix: 'workflow',
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'label' => 'label',
|
||||
'uuid' => 'uuid',
|
||||
],
|
||||
handlers: [
|
||||
'access' => WorkflowAccessControlHandler::class,
|
||||
'list_builder' => WorkflowListBuilder::class,
|
||||
'form' => [
|
||||
'add' => WorkflowAddForm::class,
|
||||
'edit' => WorkflowEditForm::class,
|
||||
'delete' => WorkflowDeleteForm::class,
|
||||
'add-state' => WorkflowStateAddForm::class,
|
||||
'edit-state' => WorkflowStateEditForm::class,
|
||||
'delete-state' => WorkflowStateDeleteForm::class,
|
||||
'add-transition' => WorkflowTransitionAddForm::class,
|
||||
'edit-transition' => WorkflowTransitionEditForm::class,
|
||||
'delete-transition' => WorkflowTransitionDeleteForm::class,
|
||||
],
|
||||
'route_provider' => ['html' => AdminHtmlRouteProvider::class],
|
||||
],
|
||||
links: [
|
||||
'add-form' => '/admin/config/workflow/workflows/add',
|
||||
'edit-form' => '/admin/config/workflow/workflows/manage/{workflow}',
|
||||
'delete-form' => '/admin/config/workflow/workflows/manage/{workflow}/delete',
|
||||
'add-state-form' => '/admin/config/workflow/workflows/manage/{workflow}/add_state',
|
||||
'add-transition-form' => '/admin/config/workflow/workflows/manage/{workflow}/add_transition',
|
||||
'collection' => '/admin/config/workflow/workflows',
|
||||
],
|
||||
admin_permission: 'administer workflows',
|
||||
label_count: [
|
||||
'singular' => '@count workflow',
|
||||
'plural' => '@count workflows',
|
||||
],
|
||||
config_export: [
|
||||
'id',
|
||||
'label',
|
||||
'type',
|
||||
'type_settings',
|
||||
],
|
||||
)]
|
||||
class Workflow extends ConfigEntityBase implements WorkflowInterface, EntityWithPluginCollectionInterface {
|
||||
|
||||
/**
|
||||
* The Workflow ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The workflow label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The workflow type plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see \Drupal\workflows\WorkflowTypeManager
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The configuration for the workflow type plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $type_settings = [];
|
||||
|
||||
/**
|
||||
* The workflow type plugin collection.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\LazyPluginCollection
|
||||
*/
|
||||
protected $pluginCollection;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
$workflow_type = $this->getTypePlugin();
|
||||
$missing_states = array_diff($workflow_type->getRequiredStates(), array_keys($this->getTypePlugin()->getStates()));
|
||||
if (!empty($missing_states)) {
|
||||
throw new RequiredStateMissingException(sprintf("Workflow type '{$workflow_type->label()}' requires states with the ID '%s' in workflow '{$this->id()}'", implode("', '", $missing_states)));
|
||||
}
|
||||
parent::preSave($storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypePlugin() {
|
||||
return $this->getPluginCollection()->get($this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPluginCollections() {
|
||||
return ['type_settings' => $this->getPluginCollection()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the creation of the workflow's plugin collection.
|
||||
*
|
||||
* @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
|
||||
* The workflow's plugin collection.
|
||||
*/
|
||||
protected function getPluginCollection() {
|
||||
if (!$this->pluginCollection && $this->type) {
|
||||
$this->pluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.workflows.type'), $this->type, $this->type_settings);
|
||||
}
|
||||
return $this->pluginCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all workflows of the provided type.
|
||||
*
|
||||
* @param string $type
|
||||
* The workflow type to load all workflows for.
|
||||
*
|
||||
* @return static[]
|
||||
* An array of workflow objects of the provided workflow type, indexed by
|
||||
* their IDs.
|
||||
*
|
||||
* @see \Drupal\workflows\Annotation\WorkflowType
|
||||
*/
|
||||
public static function loadMultipleByType($type) {
|
||||
return self::loadMultiple(\Drupal::entityQuery('workflow')->condition('type', $type)->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function status() {
|
||||
// In order for a workflow to be usable it must have at least one state.
|
||||
return !empty($this->status) && !empty($this->getTypePlugin()->getStates());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// Give the parent method and the workflow type plugin a chance to react
|
||||
// to removed dependencies and report if either of these two made a change.
|
||||
$parent_changed_entity = parent::onDependencyRemoval($dependencies);
|
||||
$plugin_changed_entity = $this->getTypePlugin()->onDependencyRemoval($dependencies);
|
||||
return $plugin_changed_entity || $parent_changed_entity;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Exception;
|
||||
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
|
||||
/**
|
||||
* Indicates that a workflow does not contain a required state.
|
||||
*/
|
||||
class RequiredStateMissingException extends ConfigException {
|
||||
}
|
||||
116
web/core/modules/workflows/src/Form/WorkflowAddForm.php
Normal file
116
web/core/modules/workflows/src/Form/WorkflowAddForm.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form for adding workflows.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowAddForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The workflow type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $workflowTypePluginManager;
|
||||
|
||||
/**
|
||||
* WorkflowAddForm constructor.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_plugin_manager
|
||||
* The workflow type plugin manager.
|
||||
*/
|
||||
public function __construct(PluginManagerInterface $workflow_type_plugin_manager) {
|
||||
$this->workflowTypePluginManager = $workflow_type_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.workflows.type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $workflow->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $workflow->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [Workflow::class, 'load'],
|
||||
],
|
||||
];
|
||||
|
||||
$workflow_types = array_column($this->workflowTypePluginManager->getDefinitions(), 'label', 'id');
|
||||
|
||||
$form['workflow_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Workflow type'),
|
||||
'#required' => TRUE,
|
||||
'#options' => $workflow_types,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$return = $workflow->save();
|
||||
if (empty($workflow->getTypePlugin()->getStates())) {
|
||||
$this->messenger()->addStatus($this->t('Created the %label Workflow. In order for the workflow to be enabled there needs to be at least one state.', [
|
||||
'%label' => $workflow->label(),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('add-state-form'));
|
||||
}
|
||||
else {
|
||||
$this->messenger()->addStatus($this->t('Created the %label Workflow.', [
|
||||
'%label' => $workflow->label(),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('edit-form'));
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
// This form can only set the workflow's ID, label and the weights for each
|
||||
// state.
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$entity->set('label', $values['label']);
|
||||
$entity->set('id', $values['id']);
|
||||
$entity->set('type', $values['workflow_type']);
|
||||
}
|
||||
|
||||
}
|
||||
64
web/core/modules/workflows/src/Form/WorkflowDeleteForm.php
Normal file
64
web/core/modules/workflows/src/Form/WorkflowDeleteForm.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Builds the form to delete Workflow entities.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
if ($this->entity->getTypePlugin()->workflowHasData($this->entity)) {
|
||||
$form['#title'] = $this->getQuestion();
|
||||
$form['description'] = ['#markup' => $this->t('This workflow is in use. You cannot remove this workflow until you have removed all content using it.')];
|
||||
return $form;
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.workflow.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->delete();
|
||||
|
||||
$this->messenger()->addStatus($this->t(
|
||||
'Workflow %label deleted.',
|
||||
['%label' => $this->entity->label()]
|
||||
));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
283
web/core/modules/workflows/src/Form/WorkflowEditForm.php
Normal file
283
web/core/modules/workflows/src/Form/WorkflowEditForm.php
Normal file
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workflows\WorkflowTypeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The form for editing workflows.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowEditForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The plugin form factory.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
|
||||
*/
|
||||
protected $pluginFormFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance of WorkflowStateEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $pluginFormFactory
|
||||
* The plugin form factory.
|
||||
*/
|
||||
public function __construct(PluginFormFactoryInterface $pluginFormFactory) {
|
||||
$this->pluginFormFactory = $pluginFormFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin_form.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$form['#title'] = $this->t('Edit %label workflow', ['%label' => $workflow->label()]);
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $workflow->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $workflow->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [Workflow::class, 'load'],
|
||||
],
|
||||
'#disabled' => TRUE,
|
||||
];
|
||||
|
||||
$header = [
|
||||
'state' => $this->t('State'),
|
||||
'weight' => $this->t('Weight'),
|
||||
'operations' => $this->t('Operations'),
|
||||
];
|
||||
$form['states_container'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('States'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['states_container']['states'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#title' => $this->t('States'),
|
||||
'#empty' => $this->t('There are no states yet.'),
|
||||
'#tabledrag' => [
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'state-weight',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$states = $workflow->getTypePlugin()->getStates();
|
||||
|
||||
// Warn the user if there are no states.
|
||||
if (empty($states)) {
|
||||
$this->messenger()->addWarning(
|
||||
$this->t(
|
||||
'This workflow has no states and will be disabled until there is at least one, <a href=":add-state">add a new state.</a>',
|
||||
[':add-state' => $workflow->toUrl('add-state-form')->toString()]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$state_weight_delta = round(count($states) / 2);
|
||||
foreach ($states as $state) {
|
||||
$links = [
|
||||
'edit' => [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.workflow.edit_state_form', ['workflow' => $workflow->id(), 'workflow_state' => $state->id()]),
|
||||
],
|
||||
];
|
||||
if ($this->entity->access('delete-state:' . $state->id())) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.workflow.delete_state_form', [
|
||||
'workflow' => $workflow->id(),
|
||||
'workflow_state' => $state->id(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
$form['states_container']['states'][$state->id()] = [
|
||||
'#attributes' => ['class' => ['draggable']],
|
||||
'state' => ['#markup' => $state->label()],
|
||||
'#weight' => $state->weight(),
|
||||
'weight' => [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for @title', ['@title' => $state->label()]),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $state->weight(),
|
||||
'#attributes' => ['class' => ['state-weight']],
|
||||
'#delta' => $state_weight_delta,
|
||||
],
|
||||
'operations' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
}
|
||||
$form['states_container']['state_add'] = [
|
||||
'#markup' => $workflow->toLink($this->t('Add a new state'), 'add-state-form')->toString(),
|
||||
];
|
||||
|
||||
$header = [
|
||||
'label' => $this->t('Label'),
|
||||
'weight' => $this->t('Weight'),
|
||||
'from' => $this->t('From'),
|
||||
'to' => $this->t('To'),
|
||||
'operations' => $this->t('Operations'),
|
||||
];
|
||||
$form['transitions_container'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Transitions'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['transitions_container']['transitions'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#title' => $this->t('Transitions'),
|
||||
'#empty' => $this->t('There are no transitions yet.'),
|
||||
'#tabledrag' => [
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'transition-weight',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$transitions = $workflow->getTypePlugin()->getTransitions();
|
||||
$transition_weight_delta = round(count($transitions) / 2);
|
||||
foreach ($transitions as $transition) {
|
||||
$links['edit'] = [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.workflow.edit_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
|
||||
];
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.workflow.delete_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
|
||||
];
|
||||
$form['transitions_container']['transitions'][$transition->id()] = [
|
||||
'#attributes' => ['class' => ['draggable']],
|
||||
'label' => ['#markup' => $transition->label()],
|
||||
'#weight' => $transition->weight(),
|
||||
'weight' => [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for @title', ['@title' => $transition->label()]),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $transition->weight(),
|
||||
'#attributes' => ['class' => ['transition-weight']],
|
||||
'#delta' => $transition_weight_delta,
|
||||
],
|
||||
'from' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => array_map([State::class, 'labelCallback'], $transition->from()),
|
||||
'#context' => ['list_style' => 'comma-list'],
|
||||
],
|
||||
'to' => ['#markup' => $transition->to()->label()],
|
||||
'operations' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
}
|
||||
$form['transitions_container']['transition_add'] = [
|
||||
'#markup' => $workflow->toLink($this->t('Add a new transition'), 'add-transition-form')->toString(),
|
||||
];
|
||||
|
||||
if ($workflow_type->hasFormClass(WorkflowTypeInterface::PLUGIN_FORM_KEY)) {
|
||||
$form['type_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$form['type_settings'] += $this->pluginFormFactory
|
||||
->createInstance($workflow_type, WorkflowTypeInterface::PLUGIN_FORM_KEY)
|
||||
->buildConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
if ($workflow_type->hasFormClass(WorkflowTypeInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, WorkflowTypeInterface::PLUGIN_FORM_KEY)
|
||||
->validateConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
if ($workflow_type->hasFormClass(WorkflowTypeInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, WorkflowTypeInterface::PLUGIN_FORM_KEY)
|
||||
->submitConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$workflow->save();
|
||||
$this->messenger()->addStatus($this->t('Saved the %label Workflow.', ['%label' => $workflow->label()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
// This form can only set the workflow's ID, label and the weights for each
|
||||
// state.
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$entity->set('label', $values['label']);
|
||||
$entity->set('id', $values['id']);
|
||||
foreach ($values['states'] as $state_id => $state_values) {
|
||||
$entity->getTypePlugin()->setStateWeight($state_id, $state_values['weight']);
|
||||
}
|
||||
foreach ($values['transitions'] as $transition_id => $transition_values) {
|
||||
$entity->getTypePlugin()->setTransitionWeight($transition_id, $transition_values['weight']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
181
web/core/modules/workflows/src/Form/WorkflowStateAddForm.php
Normal file
181
web/core/modules/workflows/src/Form/WorkflowStateAddForm.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
|
||||
use Drupal\workflows\StateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Entity form variant for adding workflow states.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowStateAddForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The plugin form factory.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
|
||||
*/
|
||||
protected $pluginFormFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance of WorkflowStateEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $pluginFormFactory
|
||||
* The plugin form factory.
|
||||
*/
|
||||
public function __construct(PluginFormFactoryInterface $pluginFormFactory) {
|
||||
$this->pluginFormFactory = $pluginFormFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin_form.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_state_add_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('State label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => '',
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
],
|
||||
];
|
||||
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$form['type_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$form['type_settings'] += $this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->buildConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the workflow state already exists.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The workflow state ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow state exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($state_id) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $original_workflow */
|
||||
$original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id());
|
||||
return $original_workflow->getTypePlugin()->hasState($state_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies top-level form values to entity properties.
|
||||
*
|
||||
* This form can only change values for a state, which is part of workflow.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity the current form should operate upon.
|
||||
* @param array $form
|
||||
* A nested array of form elements comprising the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
if (!$form_state->isValidationComplete()) {
|
||||
// Only do something once form validation is complete.
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$entity->getTypePlugin()->addState($values['id'], $values['label']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
/** @var \Drupal\workflows\WorkflowTypeInterface $workflow_type */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->validateConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$state = $workflow_type->getState($form_state->getValue('id'));
|
||||
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('state', $state);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->submitConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$workflow->save();
|
||||
$this->messenger()->addStatus($this->t('Created %label state.', [
|
||||
'%label' => $workflow->getTypePlugin()->getState($form_state->getValue('id'))->label(),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('edit-form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#submit' => ['::submitForm', '::save'],
|
||||
];
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
109
web/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
Normal file
109
web/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Builds the form to delete states from Workflow entities.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowStateDeleteForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The workflow entity the state being deleted belongs to.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowInterface
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* The state being deleted.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stateId;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_state_delete_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete %state from %workflow?', ['%state' => $this->workflow->getTypePlugin()->getState($this->stateId)->label(), '%workflow' => $this->workflow->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->workflow->toUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow entity being edited.
|
||||
* @param string|null $workflow_state
|
||||
* The workflow state being deleted.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?WorkflowInterface $workflow = NULL, $workflow_state = NULL) {
|
||||
if (!$workflow->getTypePlugin()->hasState($workflow_state)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$this->workflow = $workflow;
|
||||
$this->stateId = $workflow_state;
|
||||
|
||||
if ($this->workflow->getTypePlugin()->workflowStateHasData($this->workflow, $this->workflow->getTypePlugin()->getState($this->stateId))) {
|
||||
$form['#title'] = $this->getQuestion();
|
||||
$form['description'] = ['#markup' => $this->t('This workflow state is in use. You cannot remove this workflow state until you have removed all content using it.')];
|
||||
return $form;
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$workflow_label = $this->workflow->getTypePlugin()->getState($this->stateId)->label();
|
||||
$this->workflow
|
||||
->getTypePlugin()
|
||||
->deleteState($this->stateId);
|
||||
$this->workflow->save();
|
||||
|
||||
$this->messenger()->addStatus($this->t(
|
||||
'State %label deleted.',
|
||||
['%label' => $workflow_label]
|
||||
));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
240
web/core/modules/workflows/src/Form/WorkflowStateEditForm.php
Normal file
240
web/core/modules/workflows/src/Form/WorkflowStateEditForm.php
Normal file
@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workflows\StateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Entity form variant for editing workflow states.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowStateEditForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The ID of the state that is being edited.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stateId;
|
||||
|
||||
/**
|
||||
* The plugin form factory.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
|
||||
*/
|
||||
protected $pluginFormFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance of WorkflowStateEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $pluginFormFactory
|
||||
* The plugin form factory.
|
||||
*/
|
||||
public function __construct(PluginFormFactoryInterface $pluginFormFactory) {
|
||||
$this->pluginFormFactory = $pluginFormFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin_form.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_state_edit_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $workflow_state = NULL) {
|
||||
$this->stateId = $workflow_state;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$state = $workflow->getTypePlugin()->getState($this->stateId);
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('State label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $state->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $this->stateId,
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
],
|
||||
'#disabled' => TRUE,
|
||||
];
|
||||
|
||||
// Add additional form fields from the workflow type plugin.
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$form['type_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('state', $state);
|
||||
$form['type_settings'] += $this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->buildConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$header = [
|
||||
'label' => $this->t('Transition'),
|
||||
'state' => $this->t('To'),
|
||||
'operations' => $this->t('Operations'),
|
||||
];
|
||||
$form['transitions'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#empty' => $this->t('There are no transitions to or from this state yet.'),
|
||||
];
|
||||
foreach ($state->getTransitions() as $transition) {
|
||||
$links['edit'] = [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.workflow.edit_transition_form', [
|
||||
'workflow' => $workflow->id(),
|
||||
'workflow_transition' => $transition->id(),
|
||||
]),
|
||||
];
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.workflow.delete_transition_form', [
|
||||
'workflow' => $workflow->id(),
|
||||
'workflow_transition' => $transition->id(),
|
||||
]),
|
||||
];
|
||||
$form['transitions'][$transition->id()] = [
|
||||
'label' => [
|
||||
'#markup' => $transition->label(),
|
||||
],
|
||||
'state' => [
|
||||
'#markup' => $transition->to()->label(),
|
||||
],
|
||||
'operations' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies top-level form values to entity properties.
|
||||
*
|
||||
* This form can only change values for a state, which is part of workflow.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity the current form should operate upon.
|
||||
* @param array $form
|
||||
* A nested array of form elements comprising the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
if (!$form_state->isValidationComplete()) {
|
||||
// Only do something once form validation is complete.
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$entity->getTypePlugin()->setStateLabel($values['id'], $values['label']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
/** @var \Drupal\workflows\WorkflowTypeInterface $workflow_type */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('state', $workflow_type->getState($this->stateId));
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->validateConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
if ($workflow_type->hasFormClass(StateInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('state', $workflow_type->getState($this->stateId));
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, StateInterface::PLUGIN_FORM_KEY)
|
||||
->submitConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$workflow->save();
|
||||
$this->messenger()->addStatus($this->t('Saved %label state.', [
|
||||
'%label' => $workflow->getTypePlugin()->getState($this->stateId)->label(),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('edit-form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#submit' => ['::submitForm', '::save'],
|
||||
];
|
||||
|
||||
$actions['delete'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Delete'),
|
||||
'#access' => $this->entity->access('delete-state:' . $this->stateId),
|
||||
'#attributes' => [
|
||||
'class' => ['button', 'button--danger'],
|
||||
],
|
||||
'#url' => Url::fromRoute('entity.workflow.delete_state_form', [
|
||||
'workflow' => $this->entity->id(),
|
||||
'workflow_state' => $this->stateId,
|
||||
]),
|
||||
];
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\workflows\TransitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Entity form variant for adding workflow transitions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowTransitionAddForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The plugin form factory.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
|
||||
*/
|
||||
protected $pluginFormFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance of WorkflowStateEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $pluginFormFactory
|
||||
* The plugin form factory.
|
||||
*/
|
||||
public function __construct(PluginFormFactoryInterface $pluginFormFactory) {
|
||||
$this->pluginFormFactory = $pluginFormFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin_form.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_transition_add_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Transition label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => '',
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
],
|
||||
];
|
||||
|
||||
// @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that
|
||||
// only valid transitions are selectable.
|
||||
$states = array_map([State::class, 'labelCallback'], $workflow->getTypePlugin()->getStates());
|
||||
$form['from'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('From'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => [],
|
||||
'#options' => $states,
|
||||
];
|
||||
$form['to'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('To'),
|
||||
'#required' => TRUE,
|
||||
'#options' => $states,
|
||||
];
|
||||
|
||||
// Add additional form fields from the workflow type plugin.
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$form['type_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$form['type_settings'] += $this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->buildConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the workflow transition already exists.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The workflow transition ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow transition exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($transition_id) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $original_workflow */
|
||||
$original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id());
|
||||
return $original_workflow->getTypePlugin()->hasTransition($transition_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies top-level form values to entity properties.
|
||||
*
|
||||
* This form can only change values for a state, which is part of workflow.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity the current form should operate upon.
|
||||
* @param array $form
|
||||
* A nested array of form elements comprising the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
if (!$form_state->isValidationComplete()) {
|
||||
// Only do something once form validation is complete.
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$entity->getTypePlugin()->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
$values = $form_state->getValues();
|
||||
foreach (array_filter($values['from']) as $from_state_id) {
|
||||
if ($workflow->getTypePlugin()->hasTransitionFromStateToState($from_state_id, $values['to'])) {
|
||||
$form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [
|
||||
'%from' => $workflow->getTypePlugin()->getState($from_state_id)->label(),
|
||||
'%to' => $workflow->getTypePlugin()->getState($values['to'])->label(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->validateConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$transition = $workflow_type->getTransition($form_state->getValue('id'));
|
||||
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('transition', $transition);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->submitConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$workflow->save();
|
||||
$this->messenger()->addStatus($this->t('Created %label transition.', [
|
||||
'%label' => $form_state->getValue('label'),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('edit-form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#submit' => ['::submitForm', '::save'],
|
||||
];
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Builds the form to delete transitions from Workflow entities.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowTransitionDeleteForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The workflow entity the transition being deleted belongs to.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowInterface
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* The workflow transition being deleted.
|
||||
*
|
||||
* @var \Drupal\workflows\TransitionInterface
|
||||
*/
|
||||
protected $transition;
|
||||
|
||||
/**
|
||||
* The transition being deleted.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $transitionId;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_transition_delete_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete %transition from %workflow?', ['%transition' => $this->transition->label(), '%workflow' => $this->workflow->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->workflow->toUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow entity being edited.
|
||||
* @param string|null $workflow_transition
|
||||
* The workflow transition being deleted.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?WorkflowInterface $workflow = NULL, $workflow_transition = NULL) {
|
||||
try {
|
||||
$this->transition = $workflow->getTypePlugin()->getTransition($workflow_transition);
|
||||
}
|
||||
catch (\InvalidArgumentException) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$this->workflow = $workflow;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->workflow
|
||||
->getTypePlugin()
|
||||
->deleteTransition($this->transition->id());
|
||||
$this->workflow->save();
|
||||
|
||||
$this->messenger()->addStatus($this->t('%transition transition deleted.', ['%transition' => $this->transition->label()]));
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Plugin\PluginFormFactoryInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\workflows\TransitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Entity form variant for editing workflow transitions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class WorkflowTransitionEditForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The ID of the transition that is being edited.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $transitionId;
|
||||
|
||||
/**
|
||||
* The plugin form factory.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
|
||||
*/
|
||||
protected $pluginFormFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance of WorkflowStateEditForm.
|
||||
*
|
||||
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $pluginFormFactory
|
||||
* The plugin form factory.
|
||||
*/
|
||||
public function __construct(PluginFormFactoryInterface $pluginFormFactory) {
|
||||
$this->pluginFormFactory = $pluginFormFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin_form.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_transition_edit_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $workflow_transition = NULL) {
|
||||
$this->transitionId = $workflow_transition;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$transition = $workflow->getTypePlugin()->getTransition($this->transitionId);
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Transition label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $transition->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $this->transitionId,
|
||||
];
|
||||
|
||||
// @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that
|
||||
// only valid transitions are selectable.
|
||||
$states = array_map([State::class, 'labelCallback'], $workflow->getTypePlugin()->getStates());
|
||||
$form['from'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('From'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => array_keys($transition->from()),
|
||||
'#options' => $states,
|
||||
];
|
||||
$form['to'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('To'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $transition->to()->id(),
|
||||
'#options' => $states,
|
||||
'#disabled' => TRUE,
|
||||
];
|
||||
|
||||
// Add additional form fields from the workflow type plugin.
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$form['type_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('transition', $transition);
|
||||
$form['type_settings'] += $this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->buildConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->getEntity();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$transition = $workflow_type->getTransition($this->transitionId);
|
||||
|
||||
$values = $form_state->getValues();
|
||||
foreach (array_filter($values['from']) as $from_state_id) {
|
||||
if ($workflow_type->hasTransitionFromStateToState($from_state_id, $values['to'])) {
|
||||
$existing_transition = $workflow_type->getTransitionFromStateToState($from_state_id, $values['to']);
|
||||
if ($existing_transition->id() !== $values['id']) {
|
||||
$form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [
|
||||
'%from' => $workflow->getTypePlugin()->getState($from_state_id)->label(),
|
||||
'%to' => $workflow->getTypePlugin()->getState($values['to'])->label(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('transition', $transition);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->validateConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies top-level form values to entity properties.
|
||||
*
|
||||
* This form can only change values for a state, which is part of workflow.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity the current form should operate upon.
|
||||
* @param array $form
|
||||
* A nested array of form elements comprising the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
if (!$form_state->isValidationComplete()) {
|
||||
// Only do something once form validation is complete.
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$values = $form_state->getValues();
|
||||
$form_state->set('created_transition', FALSE);
|
||||
$entity->getTypePlugin()->setTransitionLabel($values['id'], $values['label']);
|
||||
$entity->getTypePlugin()->setTransitionFromStates($values['id'], array_filter($values['from']));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = $this->entity;
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
$transition = $workflow_type->getTransition($this->transitionId);
|
||||
|
||||
if ($workflow_type->hasFormClass(TransitionInterface::PLUGIN_FORM_KEY)) {
|
||||
$subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
|
||||
$subform_state->set('transition', $transition);
|
||||
$this->pluginFormFactory
|
||||
->createInstance($workflow_type, TransitionInterface::PLUGIN_FORM_KEY)
|
||||
->submitConfigurationForm($form['type_settings'], $subform_state);
|
||||
}
|
||||
|
||||
$workflow->save();
|
||||
$this->messenger()->addStatus($this->t('Saved %label transition.', [
|
||||
'%label' => $workflow->getTypePlugin()->getTransition($this->transitionId)->label(),
|
||||
]));
|
||||
$form_state->setRedirectUrl($workflow->toUrl('edit-form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#submit' => ['::submitForm', '::save'],
|
||||
];
|
||||
|
||||
$actions['delete'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Delete'),
|
||||
// Deleting a transition is editing a workflow.
|
||||
'#access' => $this->entity->access('edit'),
|
||||
'#attributes' => [
|
||||
'class' => ['button', 'button--danger'],
|
||||
],
|
||||
'#url' => Url::fromRoute('entity.workflow.delete_transition_form', [
|
||||
'workflow' => $this->entity->id(),
|
||||
'workflow_transition' => $this->transitionId,
|
||||
]),
|
||||
];
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
66
web/core/modules/workflows/src/Hook/WorkflowsHooks.php
Normal file
66
web/core/modules/workflows/src/Hook/WorkflowsHooks.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Hook;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for workflows.
|
||||
*/
|
||||
class WorkflowsHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.workflows':
|
||||
$content_moderation_url = NULL;
|
||||
if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
|
||||
$content_moderation_url = Url::fromRoute('help.page', ['name' => 'content_moderation'])->toString();
|
||||
}
|
||||
$output = '<h2>' . $this->t('About') . '</h2>';
|
||||
if ($content_moderation_url) {
|
||||
$output .= '<p>' . $this->t('The Workflows module provides an API and an interface to create workflows with transitions between different states (for example publication or user status). These have to be provided by other modules such as the <a href=":moderation">Content Moderation module</a>. For more information, see the <a href=":workflow">online documentation for the Workflows module</a>.', [
|
||||
':moderation' => $content_moderation_url,
|
||||
':workflow' => 'https://www.drupal.org/documentation/modules/workflows',
|
||||
]) . '</p>';
|
||||
}
|
||||
else {
|
||||
$output .= '<p>' . $this->t('The Workflows module provides an API and an interface to create workflows with transitions between different states (for example publication or user status). These have to be provided by other modules such as the Content Moderation module. For more information, see the <a href=":workflow">online documentation for the Workflows module</a>.', [':workflow' => 'https://www.drupal.org/documentation/modules/workflows']) . '</p>';
|
||||
}
|
||||
$output .= '<h3>' . $this->t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Adding workflows') . '</dt>';
|
||||
if ($content_moderation_url) {
|
||||
$output .= '<dd>' . $this->t('You can <em>only</em> add workflows on the <a href=":workflows">Workflows page</a>, after you have installed a module that leverages the API such as the <a href=":moderation">Content Moderation module</a>.', [
|
||||
':moderation' => $content_moderation_url,
|
||||
':workflows' => Url::fromRoute('entity.workflow.collection')->toString(),
|
||||
]) . '</dd>';
|
||||
}
|
||||
else {
|
||||
$output .= '<dd>' . $this->t('You can <em>only</em> add workflows on the <a href=":workflows">Workflows page</a>, after you have installed a module that leverages the API such as the Content Moderation module.', [':workflow' => 'https://www.drupal.org/documentation/modules/workflows']) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . $this->t('Adding states') . '<dt>';
|
||||
$output .= '<dd>' . $this->t('A workflow requires at least two states. States can be added when you add or edit a workflow on the <a href=":workflows">Workflows page</a>.', [
|
||||
':workflows' => Url::fromRoute('entity.workflow.collection')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Adding transitions') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('A transition defines in which state an item can be save as next. It has one destination state, but can have several states <em>from</em> which the transition can be applied. Transitions can be added when you add or edit a workflow on the <a href=":workflows">Workflows page</a>.', [
|
||||
':workflows' => Url::fromRoute('entity.workflow.collection')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Configuring workflows further') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('Depending on the installed workflow type, additional configuration can be available in the edit form of a workflow.') . '</dd>';
|
||||
$output .= '<dl>';
|
||||
return $output;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
464
web/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
Normal file
464
web/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
Normal file
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\PluginWithFormsTrait;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\workflows\StateInterface;
|
||||
use Drupal\workflows\Transition;
|
||||
use Drupal\workflows\TransitionInterface;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\workflows\WorkflowTypeInterface;
|
||||
|
||||
/**
|
||||
* A base class for Workflow type plugins.
|
||||
*
|
||||
* @see \Drupal\workflows\Annotation\WorkflowType
|
||||
*/
|
||||
abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterface {
|
||||
|
||||
use PluginWithFormsTrait;
|
||||
|
||||
/**
|
||||
* A regex for matching a valid state/transition machine name.
|
||||
*/
|
||||
const VALID_ID_REGEX = '/[^a-z0-9_]+/';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
$definition = $this->getPluginDefinition();
|
||||
// The label can be an object.
|
||||
// @see \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
return $definition['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function workflowHasData(WorkflowInterface $workflow) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function workflowStateHasData(WorkflowInterface $workflow, StateInterface $state) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration + $this->defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequiredStates() {
|
||||
return $this->getPluginDefinition()['required_states'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'states' => [],
|
||||
'transitions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInitialState() {
|
||||
$ordered_states = $this->getStates();
|
||||
return reset($ordered_states);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addState($state_id, $label) {
|
||||
if ($this->hasState($state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' already exists in workflow.");
|
||||
}
|
||||
if (preg_match(static::VALID_ID_REGEX, $state_id)) {
|
||||
throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores");
|
||||
}
|
||||
$this->configuration['states'][$state_id] = [
|
||||
'label' => $label,
|
||||
'weight' => $this->getNextWeight($this->configuration['states']),
|
||||
];
|
||||
ksort($this->configuration['states']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasState($state_id) {
|
||||
return isset($this->configuration['states'][$state_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStates($state_ids = NULL) {
|
||||
if ($state_ids === NULL) {
|
||||
$state_ids = array_keys($this->configuration['states']);
|
||||
}
|
||||
/** @var \Drupal\workflows\StateInterface[] $states */
|
||||
$states = array_combine($state_ids, array_map([$this, 'getState'], $state_ids));
|
||||
return static::labelWeightMultisort($states);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState($state_id) {
|
||||
if (!isset($this->configuration['states'][$state_id])) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
|
||||
}
|
||||
return new State(
|
||||
$this,
|
||||
$state_id,
|
||||
$this->configuration['states'][$state_id]['label'],
|
||||
$this->configuration['states'][$state_id]['weight']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStateLabel($state_id, $label) {
|
||||
if (!$this->hasState($state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
|
||||
}
|
||||
$this->configuration['states'][$state_id]['label'] = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStateWeight($state_id, $weight) {
|
||||
if (!$this->hasState($state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
|
||||
}
|
||||
if (!is_numeric($weight)) {
|
||||
$label = $this->getState($state_id)->label();
|
||||
throw new \InvalidArgumentException("The weight '$weight' must be numeric for state '$label'.");
|
||||
}
|
||||
$this->configuration['states'][$state_id]['weight'] = $weight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteState($state_id) {
|
||||
if (!$this->hasState($state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
|
||||
}
|
||||
if (count($this->configuration['states']) === 1) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' can not be deleted from workflow as it is the only state.");
|
||||
}
|
||||
|
||||
foreach ($this->configuration['transitions'] as $transition_id => $transition) {
|
||||
if ($transition['to'] === $state_id) {
|
||||
$this->deleteTransition($transition_id);
|
||||
continue;
|
||||
}
|
||||
$from_key = array_search($state_id, $transition['from'], TRUE);
|
||||
if ($from_key !== FALSE) {
|
||||
// Remove state from the from array.
|
||||
unset($transition['from'][$from_key]);
|
||||
if (empty($transition['from'])) {
|
||||
// There are no more 'from' entries, remove the transition.
|
||||
$this->deleteTransition($transition_id);
|
||||
continue;
|
||||
}
|
||||
// We changed the from state, update the transition.
|
||||
$this->setTransitionFromStates($transition_id, $transition['from']);
|
||||
}
|
||||
}
|
||||
unset($this->configuration['states'][$state_id]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTransition($transition_id, $label, array $from_state_ids, $to_state_id) {
|
||||
if ($this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow.");
|
||||
}
|
||||
if (preg_match(static::VALID_ID_REGEX, $transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition ID '$transition_id' must contain only lowercase letters, numbers, and underscores.");
|
||||
}
|
||||
|
||||
if (!$this->hasState($to_state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$to_state_id' does not exist in workflow.");
|
||||
}
|
||||
$this->configuration['transitions'][$transition_id] = [
|
||||
'label' => $label,
|
||||
'from' => [],
|
||||
'to' => $to_state_id,
|
||||
// Always add to the end.
|
||||
'weight' => $this->getNextWeight($this->configuration['transitions']),
|
||||
];
|
||||
|
||||
try {
|
||||
$this->setTransitionFromStates($transition_id, $from_state_ids);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
unset($this->configuration['transitions'][$transition_id]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
ksort($this->configuration['transitions']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitions(?array $transition_ids = NULL) {
|
||||
if ($transition_ids === NULL) {
|
||||
$transition_ids = array_keys($this->configuration['transitions']);
|
||||
}
|
||||
/** @var \Drupal\workflows\TransitionInterface[] $transitions */
|
||||
$transitions = array_combine($transition_ids, array_map([$this, 'getTransition'], $transition_ids));
|
||||
return static::labelWeightMultisort($transitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort states or transitions by weight, label, and key.
|
||||
*
|
||||
* @param \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[] $objects
|
||||
* An array of state or transition objects to multi-sort, keyed by the
|
||||
* state or transition ID.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[]
|
||||
* An array of sorted transitions or states, keyed by the state or
|
||||
* transition ID.
|
||||
*/
|
||||
protected static function labelWeightMultisort($objects) {
|
||||
if (count($objects) > 1) {
|
||||
// Separate weights, labels, and keys into arrays.
|
||||
$weights = $labels = [];
|
||||
$keys = array_keys($objects);
|
||||
foreach ($objects as $id => $object) {
|
||||
$weights[$id] = $object->weight();
|
||||
$labels[$id] = $object->label();
|
||||
}
|
||||
// Sort weights, labels, and keys in the same order as each other.
|
||||
array_multisort(
|
||||
// Use the numerical weight as the primary sort.
|
||||
$weights, SORT_NUMERIC, SORT_ASC,
|
||||
// When objects have the same weight, sort them alphabetically by label.
|
||||
$labels, SORT_NATURAL, SORT_ASC,
|
||||
// Ensure that the keys (the object IDs) are sorted in the same order as
|
||||
// the weights.
|
||||
$keys
|
||||
);
|
||||
// Combine keys and weights to make sure the weights are keyed with the
|
||||
// correct keys.
|
||||
$weights = array_combine($keys, $weights);
|
||||
// Return the objects sorted by weight.
|
||||
return array_replace($weights, $objects);
|
||||
}
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransition($transition_id) {
|
||||
if (!$this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
|
||||
}
|
||||
return new Transition(
|
||||
$this,
|
||||
$transition_id,
|
||||
$this->configuration['transitions'][$transition_id]['label'],
|
||||
$this->configuration['transitions'][$transition_id]['from'],
|
||||
$this->configuration['transitions'][$transition_id]['to'],
|
||||
$this->configuration['transitions'][$transition_id]['weight']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasTransition($transition_id) {
|
||||
return isset($this->configuration['transitions'][$transition_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionsForState($state_id, $direction = TransitionInterface::DIRECTION_FROM) {
|
||||
$transition_ids = array_keys(array_filter($this->configuration['transitions'], function ($transition) use ($state_id, $direction) {
|
||||
return in_array($state_id, (array) $transition[$direction], TRUE);
|
||||
}));
|
||||
return $this->getTransitions($transition_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionFromStateToState($from_state_id, $to_state_id) {
|
||||
$transition_id = $this->getTransitionIdFromStateToState($from_state_id, $to_state_id);
|
||||
if (empty($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition from '$from_state_id' to '$to_state_id' does not exist in workflow.");
|
||||
}
|
||||
return $this->getTransition($transition_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasTransitionFromStateToState($from_state_id, $to_state_id) {
|
||||
return $this->getTransitionIdFromStateToState($from_state_id, $to_state_id) !== NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transition ID from state to state.
|
||||
*
|
||||
* @param string $from_state_id
|
||||
* The state ID to transition from.
|
||||
* @param string $to_state_id
|
||||
* The state ID to transition to.
|
||||
*
|
||||
* @return string|null
|
||||
* The transition ID, or NULL if no transition exists.
|
||||
*/
|
||||
protected function getTransitionIdFromStateToState($from_state_id, $to_state_id) {
|
||||
foreach ($this->configuration['transitions'] as $transition_id => $transition) {
|
||||
if (in_array($from_state_id, $transition['from'], TRUE) && $transition['to'] === $to_state_id) {
|
||||
return $transition_id;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTransitionLabel($transition_id, $label) {
|
||||
if (!$this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
|
||||
}
|
||||
$this->configuration['transitions'][$transition_id]['label'] = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTransitionWeight($transition_id, $weight) {
|
||||
if (!$this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
|
||||
}
|
||||
if (!is_numeric($weight)) {
|
||||
$label = $this->getTransition($transition_id)->label();
|
||||
throw new \InvalidArgumentException("The weight '$weight' must be numeric for transition '$label'.");
|
||||
}
|
||||
$this->configuration['transitions'][$transition_id]['weight'] = $weight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTransitionFromStates($transition_id, array $from_state_ids) {
|
||||
if (!$this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
|
||||
}
|
||||
|
||||
// Ensure that the states exist.
|
||||
foreach ($from_state_ids as $from_state_id) {
|
||||
if (!$this->hasState($from_state_id)) {
|
||||
throw new \InvalidArgumentException("The state '$from_state_id' does not exist in workflow.");
|
||||
}
|
||||
if ($this->hasTransitionFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to'])) {
|
||||
$existing_transition_id = $this->getTransitionIdFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to']);
|
||||
if ($transition_id !== $existing_transition_id) {
|
||||
throw new \InvalidArgumentException("The '$existing_transition_id' transition already allows '$from_state_id' to '{$this->configuration['transitions'][$transition_id]['to']}' transitions in workflow.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the order of the state IDs in the from value and don't save any
|
||||
// keys.
|
||||
$from_state_ids = array_values($from_state_ids);
|
||||
sort($from_state_ids);
|
||||
$this->configuration['transitions'][$transition_id]['from'] = $from_state_ids;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteTransition($transition_id) {
|
||||
if (!$this->hasTransition($transition_id)) {
|
||||
throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
|
||||
}
|
||||
unset($this->configuration['transitions'][$transition_id]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the weight for a new state or transition.
|
||||
*
|
||||
* @param array $items
|
||||
* An array of states or transitions information where each item has a
|
||||
* 'weight' key with a numeric value.
|
||||
*
|
||||
* @return int
|
||||
* The weight for a new item in the array so that it has the highest weight.
|
||||
*/
|
||||
protected function getNextWeight(array $items) {
|
||||
return array_reduce($items, function ($carry, $item) {
|
||||
return max($carry, $item['weight'] + 1);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginAwareInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* A base class for workflow type configuration forms.
|
||||
*/
|
||||
abstract class WorkflowTypeConfigureFormBase implements PluginFormInterface, PluginAwareInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The workflow type.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowTypeInterface
|
||||
*/
|
||||
protected $workflowType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPlugin(PluginInspectionInterface $plugin) {
|
||||
$this->workflowType = $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginAwareInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* A base class for workflow type state forms.
|
||||
*/
|
||||
abstract class WorkflowTypeStateFormBase implements PluginFormInterface, PluginAwareInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The workflow type.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowTypeInterface
|
||||
*/
|
||||
protected $workflowType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPlugin(PluginInspectionInterface $plugin) {
|
||||
$this->workflowType = $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$state = $form_state->get('state');
|
||||
$configuration = $this->workflowType->getConfiguration();
|
||||
$configuration['states'][$state->id()] = $values + $configuration['states'][$state->id()];
|
||||
$this->workflowType->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginAwareInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* A base class for workflow type transition forms.
|
||||
*/
|
||||
abstract class WorkflowTypeTransitionFormBase implements PluginFormInterface, PluginAwareInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The workflow type.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowTypeInterface
|
||||
*/
|
||||
protected $workflowType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPlugin(PluginInspectionInterface $plugin) {
|
||||
$this->workflowType = $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$transition = $form_state->get('transition');
|
||||
$configuration = $this->workflowType->getConfiguration();
|
||||
$configuration['transitions'][$transition->id()] = $values + $configuration['transitions'][$transition->id()];
|
||||
$this->workflowType->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
}
|
||||
115
web/core/modules/workflows/src/State.php
Normal file
115
web/core/modules/workflows/src/State.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
/**
|
||||
* A value object representing a workflow state.
|
||||
*/
|
||||
class State implements StateInterface {
|
||||
|
||||
/**
|
||||
* The workflow the state is attached to.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowTypeInterface
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* The state's ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The state's label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The state's weight.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* State constructor.
|
||||
*
|
||||
* @param \Drupal\workflows\WorkflowTypeInterface $workflow
|
||||
* The workflow the state is attached to.
|
||||
* @param string $id
|
||||
* The state's ID.
|
||||
* @param string $label
|
||||
* The state's label.
|
||||
* @param int $weight
|
||||
* The state's weight.
|
||||
*/
|
||||
public function __construct(WorkflowTypeInterface $workflow, $id, $label, $weight = 0) {
|
||||
$this->workflow = $workflow;
|
||||
$this->id = $id;
|
||||
$this->label = $label;
|
||||
$this->weight = $weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function weight() {
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function canTransitionTo($to_state_id) {
|
||||
return $this->workflow->hasTransitionFromStateToState($this->id, $to_state_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionTo($to_state_id) {
|
||||
if (!$this->canTransitionTo($to_state_id)) {
|
||||
throw new \InvalidArgumentException("Can not transition to '$to_state_id' state");
|
||||
}
|
||||
return $this->workflow->getTransitionFromStateToState($this->id(), $to_state_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitions() {
|
||||
return $this->workflow->getTransitionsForState($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert a State value object to a label.
|
||||
*
|
||||
* @param \Drupal\workflows\StateInterface $state
|
||||
* The state.
|
||||
*
|
||||
* @return string
|
||||
* The label of the state.
|
||||
*/
|
||||
public static function labelCallback(StateInterface $state) {
|
||||
return $state->label();
|
||||
}
|
||||
|
||||
}
|
||||
76
web/core/modules/workflows/src/StateInterface.php
Normal file
76
web/core/modules/workflows/src/StateInterface.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
/**
|
||||
* An interface for state value objects.
|
||||
*
|
||||
* @internal
|
||||
* The StateInterface should only be used by Workflows and Content Moderation.
|
||||
* @todo Revisit the need for this in https://www.drupal.org/node/2902309.
|
||||
*/
|
||||
interface StateInterface {
|
||||
|
||||
/**
|
||||
* The key of the state plugin form.
|
||||
*/
|
||||
const PLUGIN_FORM_KEY = 'state';
|
||||
|
||||
/**
|
||||
* Gets the state's ID.
|
||||
*
|
||||
* @return string
|
||||
* The state's ID.
|
||||
*/
|
||||
public function id();
|
||||
|
||||
/**
|
||||
* Gets the state's label.
|
||||
*
|
||||
* @return string
|
||||
* The state's label.
|
||||
*/
|
||||
public function label();
|
||||
|
||||
/**
|
||||
* Gets the state's weight.
|
||||
*
|
||||
* @return int
|
||||
* The state's weight.
|
||||
*/
|
||||
public function weight();
|
||||
|
||||
/**
|
||||
* Determines if the state can transition to the provided state ID.
|
||||
*
|
||||
* @param string $to_state_id
|
||||
* The state to transition to.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the state can transition to the provided state ID. FALSE, if not.
|
||||
*/
|
||||
public function canTransitionTo($to_state_id);
|
||||
|
||||
/**
|
||||
* Gets the Transition object for the provided state ID.
|
||||
*
|
||||
* @param string $to_state_id
|
||||
* The state to transition to.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface
|
||||
* The Transition object for the provided state ID.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Exception thrown when the provided state ID can not be transitioned to.
|
||||
*/
|
||||
public function getTransitionTo($to_state_id);
|
||||
|
||||
/**
|
||||
* Gets all the possible transition objects for the state.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface[]
|
||||
* All the possible transition objects for the state.
|
||||
*/
|
||||
public function getTransitions();
|
||||
|
||||
}
|
||||
112
web/core/modules/workflows/src/Transition.php
Normal file
112
web/core/modules/workflows/src/Transition.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
/**
|
||||
* A transition value object that describes the transition between states.
|
||||
*/
|
||||
class Transition implements TransitionInterface {
|
||||
|
||||
/**
|
||||
* The workflow that this transition is attached to.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowTypeInterface
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* The transition's ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The transition's label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The transition's from state IDs.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fromStateIds;
|
||||
|
||||
/**
|
||||
* The transition's to state ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $toStateId;
|
||||
|
||||
/**
|
||||
* The transition's weight.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* Transition constructor.
|
||||
*
|
||||
* @param \Drupal\workflows\WorkflowTypeInterface $workflow
|
||||
* The workflow the state is attached to.
|
||||
* @param string $id
|
||||
* The transition's ID.
|
||||
* @param string $label
|
||||
* The transition's label.
|
||||
* @param array $from_state_ids
|
||||
* A list of from state IDs.
|
||||
* @param string $to_state_id
|
||||
* The to state ID.
|
||||
* @param int $weight
|
||||
* (optional) The transition's weight. Defaults to 0.
|
||||
*/
|
||||
public function __construct(WorkflowTypeInterface $workflow, $id, $label, array $from_state_ids, $to_state_id, $weight = 0) {
|
||||
$this->workflow = $workflow;
|
||||
$this->id = $id;
|
||||
$this->label = $label;
|
||||
$this->fromStateIds = $from_state_ids;
|
||||
$this->toStateId = $to_state_id;
|
||||
$this->weight = $weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function from() {
|
||||
return $this->workflow->getStates($this->fromStateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function to() {
|
||||
return $this->workflow->getState($this->toStateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function weight() {
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
}
|
||||
71
web/core/modules/workflows/src/TransitionInterface.php
Normal file
71
web/core/modules/workflows/src/TransitionInterface.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
/**
|
||||
* A transition value object that describes the transition between two states.
|
||||
*
|
||||
* @internal
|
||||
* The TransitionInterface should only be used by Workflows and Content
|
||||
* Moderation.
|
||||
*
|
||||
* @todo Revisit the need for this in https://www.drupal.org/node/2902309.
|
||||
*/
|
||||
interface TransitionInterface {
|
||||
|
||||
/**
|
||||
* The key of the transition plugin form.
|
||||
*/
|
||||
const PLUGIN_FORM_KEY = 'transition';
|
||||
|
||||
/**
|
||||
* The transition direction from.
|
||||
*/
|
||||
const DIRECTION_FROM = 'from';
|
||||
|
||||
/**
|
||||
* The transition direction to.
|
||||
*/
|
||||
const DIRECTION_TO = 'to';
|
||||
|
||||
/**
|
||||
* Gets the transition's ID.
|
||||
*
|
||||
* @return string
|
||||
* The transition's ID.
|
||||
*/
|
||||
public function id();
|
||||
|
||||
/**
|
||||
* Gets the transition's label.
|
||||
*
|
||||
* @return string
|
||||
* The transition's label.
|
||||
*/
|
||||
public function label();
|
||||
|
||||
/**
|
||||
* Gets the transition's from states.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface[]
|
||||
* The transition's from states.
|
||||
*/
|
||||
public function from();
|
||||
|
||||
/**
|
||||
* Gets the transition's to state.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface
|
||||
* The transition's to state.
|
||||
*/
|
||||
public function to();
|
||||
|
||||
/**
|
||||
* Gets the transition's weight.
|
||||
*
|
||||
* @return string
|
||||
* The transition's weight.
|
||||
*/
|
||||
public function weight();
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Access controller for the Workflow entity.
|
||||
*
|
||||
* @see \Drupal\workflows\Entity\Workflow.
|
||||
*/
|
||||
class WorkflowAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* The workflow type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $workflowTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('plugin.manager.workflows.type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the workflow access control handler instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_manager
|
||||
* The workflow type plugin manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, PluginManagerInterface $workflow_type_manager) {
|
||||
parent::__construct($entity_type);
|
||||
$this->workflowTypeManager = $workflow_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
/** @var \Drupal\workflows\Entity\Workflow $entity */
|
||||
$workflow_type = $entity->getTypePlugin();
|
||||
if (str_starts_with($operation, 'delete-state')) {
|
||||
[, $state_id] = explode(':', $operation, 2);
|
||||
// Deleting a state is editing a workflow, but also we should forbid
|
||||
// access if there is only one state.
|
||||
return AccessResult::allowedIf(count($entity->getTypePlugin()->getStates()) > 1)
|
||||
->andIf(parent::checkAccess($entity, 'edit', $account))
|
||||
->andIf(AccessResult::allowedIf(!in_array($state_id, $workflow_type->getRequiredStates(), TRUE)))
|
||||
->addCacheableDependency($entity);
|
||||
}
|
||||
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
$workflow_types_count = count($this->workflowTypeManager->getDefinitions());
|
||||
$admin_access = parent::checkCreateAccess($account, $context, $entity_bundle);
|
||||
// Allow access if there is at least one workflow type. Since workflow types
|
||||
// are provided by modules this is cacheable until extensions change.
|
||||
return $admin_access
|
||||
->andIf(AccessResult::allowedIf($workflow_types_count > 0));
|
||||
}
|
||||
|
||||
}
|
||||
20
web/core/modules/workflows/src/WorkflowInterface.php
Normal file
20
web/core/modules/workflows/src/WorkflowInterface.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining workflow entities.
|
||||
*/
|
||||
interface WorkflowInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Gets the workflow type plugin.
|
||||
*
|
||||
* @return \Drupal\workflows\WorkflowTypeInterface
|
||||
* The workflow type plugin.
|
||||
*/
|
||||
public function getTypePlugin();
|
||||
|
||||
}
|
||||
102
web/core/modules/workflows/src/WorkflowListBuilder.php
Normal file
102
web/core/modules/workflows/src/WorkflowListBuilder.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a listing of Workflow entities.
|
||||
*/
|
||||
class WorkflowListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* The workflow type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $workflowTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type->id()),
|
||||
$container->get('plugin.manager.workflows.type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new WorkflowListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage class.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_manager
|
||||
* The workflow type plugin manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, PluginManagerInterface $workflow_type_manager) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
$this->workflowTypeManager = $workflow_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_admin_overview_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Workflow');
|
||||
$header['type'] = $this->t('Type');
|
||||
$header['states'] = $this->t('States');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $entity */
|
||||
$row['label'] = $entity->label();
|
||||
|
||||
$row['type']['data'] = [
|
||||
'#markup' => $entity->getTypePlugin()->label(),
|
||||
];
|
||||
|
||||
$items = array_map([State::class, 'labelCallback'], $entity->getTypePlugin()->getStates());
|
||||
$row['states']['data'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#context' => ['list_style' => 'comma-list'],
|
||||
'#items' => $items,
|
||||
];
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$workflow_types_count = count($this->workflowTypeManager->getDefinitions());
|
||||
if ($workflow_types_count === 0) {
|
||||
$build['table']['#empty'] = $this->t('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the <a href=":content-moderation">Content Moderation</a> module provides a workflow type that enables workflows for content entities.', [':content-moderation' => Url::fromRoute('system.modules_list', [], ['fragment' => 'module-content-moderation'])->toString()]);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides an access check for state and transition operations.
|
||||
*/
|
||||
class WorkflowStateTransitionOperationsAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access for operations of workflow states and transitions.
|
||||
*
|
||||
* The value of '_workflow_access' is used to check to kind of access that
|
||||
* should be applied to a route in the context of a workflow and a state or
|
||||
* transition. States and transitions can individually have access control
|
||||
* applied to them for 'add', 'update' and 'delete'. By default workflows will
|
||||
* use the admin permission 'administer workflows' for all of these
|
||||
* operations, except for delete-state which checks there is at least one
|
||||
* state, a state does not have data and it's not a required state.
|
||||
*
|
||||
* For the update and delete operations, a workflow and a state or transition
|
||||
* is required in the route for the access check to be applied. For the "add"
|
||||
* operation, only a workflow is required. The '_workflow_access' requirement
|
||||
* translates into access checks on the workflow entity type in the formats:
|
||||
* - "$operation-state:$state_id"
|
||||
* - "$operation-transition:$transition_id"
|
||||
*
|
||||
* For example the following route definition with the path
|
||||
* "/test-workflow/foo-state/delete" the 'delete-state:foo-state' operation
|
||||
* will be checked:
|
||||
* @code
|
||||
* path: '/{workflow}/{workflow_state}/delete'
|
||||
* requirements:
|
||||
* _workflow_access: 'delete-state'
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The parametrized route.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* An access result.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Throws an exception when a route is defined with an invalid operation.
|
||||
*/
|
||||
public function access(RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$workflow_operation = $this->getOperation($route_match);
|
||||
if (!preg_match('/^(?<operation>add|update|delete)-(?<type>state|transition)$/', $workflow_operation, $matches)) {
|
||||
throw new \Exception("Invalid _workflow_access operation '$workflow_operation' specified for route '{$route_match->getRouteName()}'.");
|
||||
}
|
||||
|
||||
$parameters = $route_match->getParameters();
|
||||
$workflow = $parameters->get('workflow');
|
||||
if ($workflow && $matches['operation'] === 'add') {
|
||||
return $workflow->access($workflow_operation, $account, TRUE);
|
||||
}
|
||||
if ($workflow && $type = $parameters->get(sprintf('workflow_%s', $matches['type']))) {
|
||||
return $workflow->access(sprintf('%s:%s', $workflow_operation, $type), $account, TRUE);
|
||||
}
|
||||
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the operation that will be used for the access check.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The parametrized route.
|
||||
*
|
||||
* @return string
|
||||
* The access operation.
|
||||
*/
|
||||
protected function getOperation(RouteMatchInterface $route_match) {
|
||||
return $route_match->getRouteObject()->getRequirement('_workflow_access');
|
||||
}
|
||||
|
||||
}
|
||||
352
web/core/modules/workflows/src/WorkflowTypeInterface.php
Normal file
352
web/core/modules/workflows/src/WorkflowTypeInterface.php
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurableInterface;
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
|
||||
|
||||
/**
|
||||
* An interface for Workflow type plugins.
|
||||
*/
|
||||
interface WorkflowTypeInterface extends PluginWithFormsInterface, DerivativeInspectionInterface, ConfigurableInterface, DependentPluginInterface {
|
||||
|
||||
/**
|
||||
* The key of the global workflow plugin form.
|
||||
*/
|
||||
const PLUGIN_FORM_KEY = 'configure';
|
||||
|
||||
/**
|
||||
* Gets the label for the workflow type.
|
||||
*
|
||||
* @return string
|
||||
* The workflow type label.
|
||||
*/
|
||||
public function label();
|
||||
|
||||
/**
|
||||
* Determines if the workflow is being has data associated with it.
|
||||
*
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow is being used, FALSE if not.
|
||||
*
|
||||
* @internal
|
||||
* Marked as internal until it's validated this should form part of the
|
||||
* public API in https://www.drupal.org/node/2897148.
|
||||
*/
|
||||
public function workflowHasData(WorkflowInterface $workflow);
|
||||
|
||||
/**
|
||||
* Determines if the workflow state has data associated with it.
|
||||
*
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow to check.
|
||||
* @param \Drupal\workflows\StateInterface $state
|
||||
* The workflow state to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow state is being used, FALSE if not.
|
||||
*
|
||||
* @internal
|
||||
* Marked as internal until it's validated this should form part of the
|
||||
* public API in https://www.drupal.org/node/2897148.
|
||||
*/
|
||||
public function workflowStateHasData(WorkflowInterface $workflow, StateInterface $state);
|
||||
|
||||
/**
|
||||
* Gets the initial state for the workflow.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface
|
||||
* The initial state.
|
||||
*/
|
||||
public function getInitialState();
|
||||
|
||||
/**
|
||||
* Gets the required states of workflow type.
|
||||
*
|
||||
* This is usually specified in the workflow type annotation.
|
||||
*
|
||||
* @return string[]
|
||||
* The required states.
|
||||
*
|
||||
* @see \Drupal\workflows\Annotation\WorkflowType
|
||||
*/
|
||||
public function getRequiredStates();
|
||||
|
||||
/**
|
||||
* Informs the plugin that a dependency of the workflow will be deleted.
|
||||
*
|
||||
* @param array $dependencies
|
||||
* An array of dependencies that will be deleted keyed by dependency type.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow settings have been changed, FALSE if not.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigEntityInterface::onDependencyRemoval()
|
||||
*
|
||||
* @todo https://www.drupal.org/node/2579743 make part of a generic interface.
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies);
|
||||
|
||||
/**
|
||||
* Adds a state to the workflow.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state's ID.
|
||||
* @param string $label
|
||||
* The state's label.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if a state already exists or state ID is invalid.
|
||||
*/
|
||||
public function addState($state_id, $label);
|
||||
|
||||
/**
|
||||
* Determines if the workflow has a state with the provided ID.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state's ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow has a state with the provided ID, FALSE if not.
|
||||
*/
|
||||
public function hasState($state_id);
|
||||
|
||||
/**
|
||||
* Gets state objects for the provided state IDs.
|
||||
*
|
||||
* @param string[] $state_ids
|
||||
* A list of state IDs to get. If NULL then all states will be returned.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface[]
|
||||
* An array of workflow states, keyed by state IDs.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $state_ids contains a state ID that does not exist.
|
||||
*/
|
||||
public function getStates($state_ids = NULL);
|
||||
|
||||
/**
|
||||
* Gets a workflow state.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state's ID.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface
|
||||
* The workflow state.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $state_id does not exist.
|
||||
*/
|
||||
public function getState($state_id);
|
||||
|
||||
/**
|
||||
* Sets a state's label.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state ID to set the label for.
|
||||
* @param string $label
|
||||
* The state's label.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStateLabel($state_id, $label);
|
||||
|
||||
/**
|
||||
* Sets a state's weight value.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state ID to set the weight for.
|
||||
* @param int $weight
|
||||
* The state's weight.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStateWeight($state_id, $weight);
|
||||
|
||||
/**
|
||||
* Deletes a state from the workflow.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state ID to delete.
|
||||
*
|
||||
* @return $this
|
||||
* The workflow type plugin.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $state_id does not exist.
|
||||
*/
|
||||
public function deleteState($state_id);
|
||||
|
||||
/**
|
||||
* Adds a transition to the workflow.
|
||||
*
|
||||
* @param string $id
|
||||
* The transition ID.
|
||||
* @param string $label
|
||||
* The transition's label.
|
||||
* @param array $from_state_ids
|
||||
* The state IDs to transition from.
|
||||
* @param string $to_state_id
|
||||
* The state ID to transition to.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if either state does not exist.
|
||||
*/
|
||||
public function addTransition($id, $label, array $from_state_ids, $to_state_id);
|
||||
|
||||
/**
|
||||
* Gets a transition object for the provided transition ID.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* A transition ID.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface
|
||||
* The transition.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $transition_id does not exist.
|
||||
*/
|
||||
public function getTransition($transition_id);
|
||||
|
||||
/**
|
||||
* Determines if a transition exists.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the transition exists, FALSE if not.
|
||||
*/
|
||||
public function hasTransition($transition_id);
|
||||
|
||||
/**
|
||||
* Gets transition objects for the provided transition IDs.
|
||||
*
|
||||
* @param string[] $transition_ids
|
||||
* A list of transition IDs to get. If NULL then all transitions will be
|
||||
* returned.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface[]
|
||||
* An array of transition objects.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $transition_ids contains a transition ID that does not exist.
|
||||
*/
|
||||
public function getTransitions(?array $transition_ids = NULL);
|
||||
|
||||
/**
|
||||
* Gets the transitions for a state for the provided direction.
|
||||
*
|
||||
* @param string $state_id
|
||||
* The state to get transitions for.
|
||||
* @param string $direction
|
||||
* (optional) The direction of the transition, defaults to
|
||||
* TransitionInterface::DIRECTION_FROM. Possible values are:
|
||||
* TransitionInterface::DIRECTION_FROM or TransitionInterface::DIRECTION_TO.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface[]
|
||||
* An array of the transition objects for the state in the given direction,
|
||||
* keyed by transition ID.
|
||||
*
|
||||
* @see \Drupal\workflows\TransitionInterface::DIRECTION_FROM
|
||||
* @see \Drupal\workflows\TransitionInterface::DIRECTION_TO
|
||||
*/
|
||||
public function getTransitionsForState($state_id, $direction = TransitionInterface::DIRECTION_FROM);
|
||||
|
||||
/**
|
||||
* Gets a transition from state to state.
|
||||
*
|
||||
* @param string $from_state_id
|
||||
* The state ID to transition from.
|
||||
* @param string $to_state_id
|
||||
* The state ID to transition to.
|
||||
*
|
||||
* @return \Drupal\workflows\TransitionInterface
|
||||
* The transitions.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if the transition does not exist.
|
||||
*/
|
||||
public function getTransitionFromStateToState($from_state_id, $to_state_id);
|
||||
|
||||
/**
|
||||
* Determines if a transition from state to state exists.
|
||||
*
|
||||
* @param string $from_state_id
|
||||
* The state ID to transition from.
|
||||
* @param string $to_state_id
|
||||
* The state ID to transition to.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the transition exists, FALSE if not.
|
||||
*/
|
||||
public function hasTransitionFromStateToState($from_state_id, $to_state_id);
|
||||
|
||||
/**
|
||||
* Sets a transition's label.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
* @param string $label
|
||||
* The transition's label.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if the transition does not exist.
|
||||
*/
|
||||
public function setTransitionLabel($transition_id, $label);
|
||||
|
||||
/**
|
||||
* Sets a transition's weight.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
* @param int $weight
|
||||
* The transition's weight.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if the transition does not exist.
|
||||
*/
|
||||
public function setTransitionWeight($transition_id, $weight);
|
||||
|
||||
/**
|
||||
* Sets a transition's from states.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
* @param array $from_state_ids
|
||||
* The state IDs to transition from.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if the transition does not exist or the states do not exist.
|
||||
*/
|
||||
public function setTransitionFromStates($transition_id, array $from_state_ids);
|
||||
|
||||
/**
|
||||
* Deletes a transition.
|
||||
*
|
||||
* @param string $transition_id
|
||||
* The transition ID.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if the transition does not exist.
|
||||
*/
|
||||
public function deleteTransition($transition_id);
|
||||
|
||||
}
|
||||
36
web/core/modules/workflows/src/WorkflowTypeManager.php
Normal file
36
web/core/modules/workflows/src/WorkflowTypeManager.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\workflows\Attribute\WorkflowType;
|
||||
|
||||
/**
|
||||
* Provides a Workflow type plugin manager.
|
||||
*
|
||||
* @see \Drupal\workflows\Annotation\WorkflowType
|
||||
* @see \Drupal\workflows\WorkflowTypeInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class WorkflowTypeManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/WorkflowType', $namespaces, $module_handler, WorkflowTypeInterface::class, WorkflowType::class, 'Drupal\workflows\Annotation\WorkflowType');
|
||||
$this->alterInfo('workflow_type_info');
|
||||
$this->setCacheBackend($cache_backend, 'workflow_type_info');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user