Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
|
@ -0,0 +1,44 @@
|
|||
workflows.workflow.*:
|
||||
type: config_entity
|
||||
label: 'Workflow'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
type:
|
||||
type: string
|
||||
label: 'Workflow type'
|
||||
type_settings:
|
||||
type: workflow.type_settings.[%parent.type]
|
||||
|
||||
workflows.state:
|
||||
type: mapping
|
||||
mapping:
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
|
||||
workflows.transition:
|
||||
type: mapping
|
||||
mapping:
|
||||
label:
|
||||
type: label
|
||||
label: 'Transition label'
|
||||
from:
|
||||
type: sequence
|
||||
label: 'From state IDs'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'From state ID'
|
||||
to:
|
||||
type: string
|
||||
label: 'To state ID'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflows\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an 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)
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $forms = [];
|
||||
|
||||
}
|
179
2017/web/core/modules/workflows/src/Entity/Workflow.php
Normal file
179
2017/web/core/modules/workflows/src/Entity/Workflow.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflows\Entity;
|
||||
|
||||
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\WorkflowInterface;
|
||||
|
||||
/**
|
||||
* Defines the workflow entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "workflow",
|
||||
* label = @Translation("Workflow"),
|
||||
* label_collection = @Translation("Workflows"),
|
||||
* label_singular = @Translation("workflow"),
|
||||
* label_plural = @Translation("workflows"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count workflow",
|
||||
* plural = "@count workflows",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "access" = "Drupal\workflows\WorkflowAccessControlHandler",
|
||||
* "list_builder" = "Drupal\workflows\WorkflowListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\workflows\Form\WorkflowAddForm",
|
||||
* "edit" = "Drupal\workflows\Form\WorkflowEditForm",
|
||||
* "delete" = "Drupal\workflows\Form\WorkflowDeleteForm",
|
||||
* "add-state" = "Drupal\workflows\Form\WorkflowStateAddForm",
|
||||
* "edit-state" = "Drupal\workflows\Form\WorkflowStateEditForm",
|
||||
* "delete-state" = "Drupal\workflows\Form\WorkflowStateDeleteForm",
|
||||
* "add-transition" = "Drupal\workflows\Form\WorkflowTransitionAddForm",
|
||||
* "edit-transition" = "Drupal\workflows\Form\WorkflowTransitionEditForm",
|
||||
* "delete-transition" = "Drupal\workflows\Form\WorkflowTransitionDeleteForm",
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* config_prefix = "workflow",
|
||||
* admin_permission = "administer workflows",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "uuid" = "uuid",
|
||||
* },
|
||||
* 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",
|
||||
* },
|
||||
* 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.
|
||||
*
|
||||
* @see \Drupal\workflows\WorkflowTypeManager
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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
2017/web/core/modules/workflows/src/Form/WorkflowAddForm.php
Normal file
116
2017/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']);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
284
2017/web/core/modules/workflows/src/Form/WorkflowEditForm.php
Normal file
284
2017/web/core/modules/workflows/src/Form/WorkflowEditForm.php
Normal file
|
@ -0,0 +1,284 @@
|
|||
<?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,
|
||||
'#collapsible' => 'FALSE',
|
||||
];
|
||||
$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' => 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' => 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' => 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' => 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']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* Class WorkflowStateAddForm.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* Class WorkflowStateEditForm.
|
||||
*
|
||||
* @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' => 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;
|
||||
|
||||
/**
|
||||
* Class WorkflowTransitionAddForm.
|
||||
*
|
||||
* @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 $e) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class WorkflowTransitionEditForm.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
464
2017/web/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
Normal file
464
2017/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);
|
||||
}
|
||||
|
||||
}
|
114
2017/web/core/modules/workflows/src/State.php
Normal file
114
2017/web/core/modules/workflows/src/State.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?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\WorkflowInterface
|
||||
*/
|
||||
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\WorkflowInterface $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
|
||||
*
|
||||
* @return string
|
||||
* The label of the state.
|
||||
*/
|
||||
public static function labelCallback(StateInterface $state) {
|
||||
return $state->label();
|
||||
}
|
||||
|
||||
}
|
76
2017/web/core/modules/workflows/src/StateInterface.php
Normal file
76
2017/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 $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 $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
2017/web/core/modules/workflows/src/Transition.php
Normal file
112
2017/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\WorkflowInterface
|
||||
*/
|
||||
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\WorkflowInterface $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
2017/web/core/modules/workflows/src/TransitionInterface.php
Normal file
71
2017/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,83 @@
|
|||
<?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 (strpos($operation, 'delete-state') === 0) {
|
||||
list(, $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))
|
||||
->addCacheTags(['workflow_type_plugins']);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides a access checker for deleting a workflow state.
|
||||
*
|
||||
* @internal
|
||||
* Marked as internal until it's validated this should form part of the public
|
||||
* API in https://www.drupal.org/node/2897148.
|
||||
*/
|
||||
class WorkflowDeleteAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access to deleting a workflow state for a particular route.
|
||||
*
|
||||
* The value of '_workflow_state_delete_access' is ignored. The route must
|
||||
* have the parameters 'workflow' and 'workflow_state'. For example:
|
||||
* @code
|
||||
* pattern: '/foo/{workflow}/bar/{workflow_state}/delete'
|
||||
* requirements:
|
||||
* _workflow_state_delete_access: 'true'
|
||||
* @endcode
|
||||
* @see \Drupal\Core\ParamConverter\EntityConverter
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @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
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
// If there is valid entity of the given entity type, check its access.
|
||||
$parameters = $route_match->getParameters();
|
||||
if ($parameters->has('workflow') && $parameters->has('workflow_state')) {
|
||||
$entity = $parameters->get('workflow');
|
||||
if ($entity instanceof EntityInterface) {
|
||||
return $entity->access('delete-state:' . $parameters->get('workflow_state'), $account, TRUE);
|
||||
}
|
||||
}
|
||||
// No opinion, so other access checks should decide if access should be
|
||||
// allowed or not.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
}
|
20
2017/web/core/modules/workflows/src/WorkflowInterface.php
Normal file
20
2017/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();
|
||||
|
||||
}
|
101
2017/web/core/modules/workflows/src/WorkflowListBuilder.php
Normal file
101
2017/web/core/modules/workflows/src/WorkflowListBuilder.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?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 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' => '/admin/modules#module-content-moderation']);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
350
2017/web/core/modules/workflows/src/WorkflowTypeInterface.php
Normal file
350
2017/web/core/modules/workflows/src/WorkflowTypeInterface.php
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflows;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
|
||||
|
||||
/**
|
||||
* An interface for Workflow type plugins.
|
||||
*/
|
||||
interface WorkflowTypeInterface extends PluginWithFormsInterface, DerivativeInspectionInterface, ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @internal
|
||||
* Marked as internal until it's validated this should form part of the
|
||||
* public API in https://www.drupal.org/node/2897148.
|
||||
*
|
||||
* @param \Drupal\workflows\WorkflowInterface $workflow
|
||||
* The workflow to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the workflow is being used, FALSE if not.
|
||||
*/
|
||||
public function workflowHasData(WorkflowInterface $workflow);
|
||||
|
||||
/**
|
||||
* Determines if the workflow state has data associated with it.
|
||||
*
|
||||
* @internal
|
||||
* Marked as internal until it's validated this should form part of the
|
||||
* public API in https://www.drupal.org/node/2897148.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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 \Drupal\workflows\WorkflowTypeInterface
|
||||
* 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 transition IDs for a state for the provided direction.
|
||||
*
|
||||
* @param $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 array
|
||||
* The transition IDs for a state for the provided direction.
|
||||
*
|
||||
* @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
2017/web/core/modules/workflows/src/WorkflowTypeManager.php
Normal file
36
2017/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\Annotation\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);
|
||||
$this->alterInfo('workflow_type_info');
|
||||
$this->setCacheBackend($cache_backend, 'workflow_type_info', ['workflow_type_plugins']);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
workflows.workflow.*.third_party.workflow_third_party_settings_test:
|
||||
type: ignore
|
|
@ -0,0 +1,8 @@
|
|||
name: 'Workflow Third Party Settings Test'
|
||||
type: module
|
||||
description: 'Allows third party settings on workflows to be tested.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:workflows
|
|
@ -0,0 +1,72 @@
|
|||
workflow.type_settings.workflow_type_test:
|
||||
type: mapping
|
||||
label: 'Workflow test type settings'
|
||||
mapping:
|
||||
states:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: ignore
|
||||
transitions:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: ignore
|
||||
|
||||
workflow.type_settings.workflow_type_required_state_test:
|
||||
type: mapping
|
||||
label: 'Workflow test type settings'
|
||||
mapping:
|
||||
states:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: ignore
|
||||
transitions:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: ignore
|
||||
|
||||
# @todo, inline this straight into "workflow.type_settings.workflow_type_complex_test"
|
||||
# after https://www.drupal.org/node/2871746 is resolved.
|
||||
workflows.state.complex_test_state:
|
||||
type: workflows.state
|
||||
mapping:
|
||||
extra:
|
||||
type: string
|
||||
label: 'Extra information'
|
||||
|
||||
workflows.state.complex_test_transition:
|
||||
type: workflows.transition
|
||||
mapping:
|
||||
extra:
|
||||
type: string
|
||||
label: 'Extra information'
|
||||
|
||||
workflow.type_settings.workflow_type_complex_test:
|
||||
type: mapping
|
||||
label: 'Workflow complex test type settings'
|
||||
mapping:
|
||||
example_setting:
|
||||
type: string
|
||||
label: 'Example setting'
|
||||
states:
|
||||
type: sequence
|
||||
label: 'States'
|
||||
sequence:
|
||||
type: workflows.state.complex_test_state
|
||||
label: 'States'
|
||||
transitions:
|
||||
type: sequence
|
||||
label: 'Transitions'
|
||||
sequence:
|
||||
label: 'Transitions'
|
||||
type: workflows.state.complex_test_transition
|
||||
|
||||
workflow.type_settings.predefined_states_workflow_test_type:
|
||||
type: mapping
|
||||
label: 'Predefined states workflow test type'
|
||||
mapping:
|
||||
transitions:
|
||||
type: sequence
|
||||
label: 'Transitions'
|
||||
sequence:
|
||||
label: 'Transitions'
|
||||
type: workflows.transition
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeConfigureFormBase;
|
||||
|
||||
/**
|
||||
* Form to configure the complex test workflow type.
|
||||
*
|
||||
* @see \Drupal\workflow_type_test\Plugin\WorkflowType\ComplexTestType
|
||||
*/
|
||||
class ComplexTestTypeConfigureForm extends WorkflowTypeConfigureFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$type_configuration = $this->workflowType->getConfiguration();
|
||||
$form['example_setting'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Example global workflow setting'),
|
||||
'#description' => $this->t('Extra information added to the workflow'),
|
||||
'#default_value' => $type_configuration['example_setting'],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$type_configuration = $this->workflowType->getConfiguration();
|
||||
$type_configuration['example_setting'] = $form_state->getValue('example_setting');
|
||||
$this->workflowType->setConfiguration($type_configuration);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeStateFormBase;
|
||||
|
||||
/**
|
||||
* Form to configure the complex test workflow states.
|
||||
*
|
||||
* @see \Drupal\workflow_type_test\Plugin\WorkflowType\ComplexTestType
|
||||
*/
|
||||
class ComplexTestTypeStateForm extends WorkflowTypeStateFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$state = $form_state->get('state');
|
||||
$configuration = $this->workflowType->getConfiguration();
|
||||
$form['extra'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Extra'),
|
||||
'#description' => $this->t('Extra information added to state'),
|
||||
'#default_value' => $state && isset($configuration['states'][$state->id()]['extra']) ? $configuration['states'][$state->id()]['extra'] : '',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeTransitionFormBase;
|
||||
|
||||
/**
|
||||
* Form to configure the complex test workflow states.
|
||||
*
|
||||
* @see \Drupal\workflow_type_test\Plugin\WorkflowType\ComplexTestType
|
||||
*/
|
||||
class ComplexTestTypeTransitionForm extends WorkflowTypeTransitionFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$transition = $form_state->get('transition');
|
||||
$configuration = $this->workflowType->getConfiguration();
|
||||
$form['extra'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Extra'),
|
||||
'#description' => $this->t('Extra information added to transition'),
|
||||
'#default_value' => $transition && isset($configuration['transitions'][$transition->id()]['extra']) ? $configuration['transitions'][$transition->id()]['extra'] : '',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeBase;
|
||||
|
||||
/**
|
||||
* Test workflow type.
|
||||
*
|
||||
* @WorkflowType(
|
||||
* id = "workflow_type_complex_test",
|
||||
* label = @Translation("Workflow Type Complex Test"),
|
||||
* forms = {
|
||||
* "configure" = "\Drupal\workflow_type_test\Form\ComplexTestTypeConfigureForm",
|
||||
* "state" = "\Drupal\workflow_type_test\Form\ComplexTestTypeStateForm",
|
||||
* "transition" = "\Drupal\workflow_type_test\Form\ComplexTestTypeTransitionForm",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ComplexTestType extends WorkflowTypeBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// Always return TRUE to allow the logic in
|
||||
// \Drupal\workflows\Entity\Workflow::onDependencyRemoval() to be tested.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return parent::defaultConfiguration() + [
|
||||
'example_setting' => '',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
|
||||
|
||||
use Drupal\workflows\Plugin\WorkflowTypeBase;
|
||||
use Drupal\workflows\State;
|
||||
|
||||
/**
|
||||
* Test workflow type.
|
||||
*
|
||||
* @WorkflowType(
|
||||
* id = "predefined_states_workflow_test_type",
|
||||
* label = @Translation("Predefined States Workflow Test Type"),
|
||||
* required_states = {
|
||||
* "pay_blinds",
|
||||
* "bet",
|
||||
* "raise",
|
||||
* "fold",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class PredefinedStatesWorkflowTestType extends WorkflowTypeBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStates($state_ids = NULL) {
|
||||
return array_filter([
|
||||
'pay_blinds' => new State($this, 'pay_blinds', 'Pay Blinds'),
|
||||
'bet' => new State($this, 'bet', 'Bet'),
|
||||
'raise' => new State($this, 'raise', 'Raise'),
|
||||
'fold' => new State($this, 'fold', 'Fold'),
|
||||
], function ($state) use ($state_ids) {
|
||||
return is_array($state_ids) ? in_array($state->id(), $state_ids) : TRUE;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState($state_id) {
|
||||
$states = $this->getStates();
|
||||
if (!isset($states[$state_id])) {
|
||||
throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.'");
|
||||
}
|
||||
return $states[$state_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasState($state_id) {
|
||||
$states = $this->getStates();
|
||||
return isset($states[$state_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addState($state_id, $label) {
|
||||
// States cannot be added on this workflow.
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStateLabel($state_id, $label) {
|
||||
// States cannot be altered on this workflow.
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStateWeight($state_id, $weight) {
|
||||
// States cannot be altered on this workflow.
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteState($state_id) {
|
||||
// States cannot be deleted on this workflow.
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'transitions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\workflows\Plugin\WorkflowTypeBase;
|
||||
|
||||
/**
|
||||
* Test workflow type.
|
||||
*
|
||||
* @WorkflowType(
|
||||
* id = "workflow_type_required_state_test",
|
||||
* label = @Translation("Required State Type Test"),
|
||||
* required_states = {
|
||||
* "fresh",
|
||||
* "rotten",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class RequiredStateTestType extends WorkflowTypeBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'states' => [
|
||||
'fresh' => [
|
||||
'label' => 'Fresh',
|
||||
'weight' => 0,
|
||||
],
|
||||
'rotten' => [
|
||||
'label' => 'Rotten',
|
||||
'weight' => 1,
|
||||
],
|
||||
],
|
||||
'transitions' => [
|
||||
'rot' => [
|
||||
'label' => 'Rot',
|
||||
'to' => 'rotten',
|
||||
'weight' => 0,
|
||||
'from' => [
|
||||
'fresh',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
|
||||
|
||||
use Drupal\workflows\Plugin\WorkflowTypeBase;
|
||||
|
||||
/**
|
||||
* Test workflow type.
|
||||
*
|
||||
* @WorkflowType(
|
||||
* id = "workflow_type_test",
|
||||
* label = @Translation("Workflow Type Test"),
|
||||
* )
|
||||
*/
|
||||
class TestType extends WorkflowTypeBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequiredStates() {
|
||||
// Normally this is obtained from the annotation but we get from state to
|
||||
// allow dynamic testing.
|
||||
return \Drupal::state()->get('workflow_type_test.required_states', []);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
name: 'Workflow Type Test'
|
||||
type: module
|
||||
description: 'Provides a workflow type plugin for testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:workflows
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module file for workflow_type_test.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_type_info_alter().
|
||||
*/
|
||||
function workflow_type_test_workflow_type_info_alter(&$definitions) {
|
||||
// Allow tests to override the workflow type definitions.
|
||||
$state = \Drupal::state();
|
||||
if ($state->get('workflow_type_test.plugin_definitions') !== NULL) {
|
||||
$definitions = $state->get('workflow_type_test.plugin_definitions');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type plugin definitions override and clear the cache.
|
||||
*
|
||||
* @param array $definitions
|
||||
* Definitions to set.
|
||||
*/
|
||||
function workflow_type_test_set_definitions($definitions) {
|
||||
\Drupal::state()->set('workflow_type_test.plugin_definitions', $definitions);
|
||||
\Drupal::service('plugin.manager.workflows.type')->clearCachedDefinitions();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\workflows\Functional\Rest\WorkflowResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class WorkflowHalJsonAnonTest extends WorkflowResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class WorkflowHalJsonBasicAuthTest extends WorkflowHalJsonAnonTest {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class WorkflowHalJsonCookieTest extends WorkflowHalJsonAnonTest {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowJsonAnonTest extends WorkflowResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowJsonBasicAuthTest extends WorkflowResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowJsonCookieTest extends WorkflowResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* ResourceTestBase for Workflow entity.
|
||||
*/
|
||||
abstract class WorkflowResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'workflow';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [];
|
||||
|
||||
/**
|
||||
* The Workflow entity.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer workflows']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$workflow = Workflow::create([
|
||||
'id' => 'rest_workflow',
|
||||
'label' => 'REST Worklow',
|
||||
'type' => 'workflow_type_complex_test',
|
||||
]);
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published');
|
||||
$configuration = $workflow->getTypePlugin()->getConfiguration();
|
||||
$configuration['example_setting'] = 'foo';
|
||||
$configuration['states']['draft']['extra'] = 'bar';
|
||||
$workflow->getTypePlugin()->setConfiguration($configuration);
|
||||
$workflow->save();
|
||||
return $workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'dependencies' => [
|
||||
'module' => [
|
||||
'workflow_type_test',
|
||||
],
|
||||
],
|
||||
'id' => 'rest_workflow',
|
||||
'label' => 'REST Worklow',
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'type' => 'workflow_type_complex_test',
|
||||
'type_settings' => [
|
||||
'states' => [
|
||||
'draft' => [
|
||||
'extra' => 'bar',
|
||||
'label' => 'Draft',
|
||||
'weight' => 0,
|
||||
],
|
||||
'published' => [
|
||||
'label' => 'Published',
|
||||
'weight' => 1,
|
||||
],
|
||||
],
|
||||
'transitions' => [],
|
||||
'example_setting' => 'foo',
|
||||
],
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowXmlAnonTest extends WorkflowResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowXmlBasicAuthTest extends WorkflowResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class WorkflowXmlCookieTest extends WorkflowResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests workflow UI when there are no types.
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class WorkflowUiNoTypeTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['workflows', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// We're testing local actions.
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the creation of a workflow through the UI.
|
||||
*/
|
||||
public function testWorkflowUiWithNoType() {
|
||||
$this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
|
||||
$this->drupalGet('admin/config/workflow/workflows/add');
|
||||
// There are no workflow types so this should be a 403.
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$this->drupalGet('admin/config/workflow/workflows');
|
||||
$this->assertSession()->pageTextContains('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 Content Moderation module provides a workflow type that enables workflows for content entities.');
|
||||
$this->assertSession()->linkExists('Content Moderation');
|
||||
$this->assertSession()->pageTextNotContains('Add workflow');
|
||||
|
||||
$this->container->get('module_installer')->install(['workflow_type_test']);
|
||||
// The render cache needs to be cleared because although the cache tags are
|
||||
// correctly set the render cache does not pick it up.
|
||||
\Drupal::cache('render')->deleteAll();
|
||||
|
||||
$this->drupalGet('admin/config/workflow/workflows');
|
||||
$this->assertSession()->pageTextNotContains('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 Content Moderation module provides a workflow type that enables workflows for content entities.');
|
||||
$this->assertSession()->linkExists('Add workflow');
|
||||
$this->assertSession()->pageTextContains('There are no workflows yet.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests workflow creation UI.
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class WorkflowUiTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['workflows', 'workflow_type_test', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// We're testing local actions.
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests route access/permissions.
|
||||
*/
|
||||
public function testAccess() {
|
||||
// Create a minimal workflow for testing.
|
||||
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_test']);
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft', 'published'], 'published');
|
||||
$workflow->save();
|
||||
|
||||
$paths = [
|
||||
'admin/config/workflow/workflows',
|
||||
'admin/config/workflow/workflows/add',
|
||||
'admin/config/workflow/workflows/manage/test',
|
||||
'admin/config/workflow/workflows/manage/test/delete',
|
||||
'admin/config/workflow/workflows/manage/test/add_state',
|
||||
'admin/config/workflow/workflows/manage/test/state/published',
|
||||
'admin/config/workflow/workflows/manage/test/state/published/delete',
|
||||
'admin/config/workflow/workflows/manage/test/add_transition',
|
||||
'admin/config/workflow/workflows/manage/test/transition/publish',
|
||||
'admin/config/workflow/workflows/manage/test/transition/publish/delete',
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// No access.
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
$this->drupalLogin($this->createUser(['administer workflows']));
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
// User has access.
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
// Ensure that default states can not be deleted.
|
||||
\Drupal::state()->set('workflow_type_test.required_states', ['published']);
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
\Drupal::state()->set('workflow_type_test.required_states', []);
|
||||
|
||||
// Delete one of the states and ensure the other test cannot be deleted.
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/draft/delete');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the machine name validation of the state add form.
|
||||
*/
|
||||
public function testStateMachineNameValidation() {
|
||||
Workflow::create([
|
||||
'id' => 'test_workflow',
|
||||
'type' => 'workflow_type_test',
|
||||
])->save();
|
||||
|
||||
$this->drupalLogin($this->createUser(['administer workflows']));
|
||||
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/test_workflow/add_state', [
|
||||
'label' => 'Test State',
|
||||
'id' => 'Invalid ID',
|
||||
], 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
|
||||
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/test_workflow/add_transition', [
|
||||
'label' => 'Test Transition',
|
||||
'id' => 'Invalid ID',
|
||||
], 'Save');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the creation of a workflow through the UI.
|
||||
*/
|
||||
public function testWorkflowCreation() {
|
||||
$workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
|
||||
$this->drupalGet('admin/config/workflow');
|
||||
$this->assertSession()->linkByHrefExists('admin/config/workflow/workflows');
|
||||
$this->clickLink('Workflows');
|
||||
$this->assertSession()->pageTextContains('Workflows');
|
||||
$this->assertSession()->pageTextContains('There are no workflows yet.');
|
||||
$this->clickLink('Add workflow');
|
||||
$this->submitForm(['label' => 'Test', 'id' => 'test', 'workflow_type' => 'workflow_type_test'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created the Test Workflow.');
|
||||
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test/add_state');
|
||||
$this->drupalGet('/admin/config/workflow/workflows/manage/test');
|
||||
$this->assertSession()->pageTextContains('This workflow has no states and will be disabled until there is at least one, add a new state.');
|
||||
$this->assertSession()->pageTextContains('There are no states yet.');
|
||||
$this->clickLink('Add a new state');
|
||||
$this->submitForm(['label' => 'Published', 'id' => 'published'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Published state.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertFalse($workflow->getTypePlugin()->getState('published')->canTransitionTo('published'), 'No default transition from published to published exists.');
|
||||
|
||||
$this->clickLink('Add a new state');
|
||||
// Don't create a draft to draft transition by default.
|
||||
$this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Draft state.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertFalse($workflow->getTypePlugin()->getState('draft')->canTransitionTo('draft'), 'Can not transition from draft to draft');
|
||||
|
||||
$this->clickLink('Add a new transition');
|
||||
$this->submitForm(['id' => 'publish', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Publish transition.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertTrue($workflow->getTypePlugin()->getState('draft')->canTransitionTo('published'), 'Can transition from draft to published');
|
||||
|
||||
$this->clickLink('Add a new transition');
|
||||
$this->assertCount(2, $this->cssSelect('input[name="to"][type="radio"]'));
|
||||
$this->assertCount(0, $this->cssSelect('input[name="to"][checked="checked"][type="radio"]'));
|
||||
$this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Create new draft transition.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertTrue($workflow->getTypePlugin()->getState('draft')->canTransitionTo('draft'), 'Can transition from draft to draft');
|
||||
|
||||
// The fist state to edit on the page should be published.
|
||||
$this->clickLink('Edit');
|
||||
$this->assertSession()->fieldValueEquals('label', 'Published');
|
||||
// Change the label.
|
||||
$this->submitForm(['label' => 'Live'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Saved Live state.');
|
||||
|
||||
// Allow published to draft.
|
||||
$this->clickLink('Edit', 3);
|
||||
$this->submitForm(['from[published]' => 'published'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Saved Create new draft transition.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertTrue($workflow->getTypePlugin()->getState('published')->canTransitionTo('draft'), 'Can transition from published to draft');
|
||||
|
||||
// Try creating a duplicate transition.
|
||||
$this->clickLink('Add a new transition');
|
||||
$this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
|
||||
// Try creating a transition which duplicates the states of another.
|
||||
$this->submitForm(['id' => 'create_new_draft2', 'label' => 'Create new draft again', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('The transition from Live to Draft already exists.');
|
||||
|
||||
// Create a new transition.
|
||||
$this->submitForm(['id' => 'save_and_publish', 'label' => 'Save and publish', 'from[published]' => 'published', 'to' => 'published'], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created Save and publish transition.');
|
||||
// Edit the new transition and try to add an existing transition.
|
||||
$this->clickLink('Edit', 4);
|
||||
$this->submitForm(['from[draft]' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('The transition from Draft to Live already exists.');
|
||||
|
||||
// Delete the transition.
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('published', 'published'), 'Can transition from published to published');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete Save and publish from Test?');
|
||||
$this->submitForm([], 'Delete');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('published', 'published'), 'Cannot transition from published to published');
|
||||
|
||||
// Try creating a duplicate state.
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test');
|
||||
$this->clickLink('Add a new state');
|
||||
$this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
|
||||
|
||||
// Ensure that weight changes the state ordering.
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertEquals('published', $workflow->getTypePlugin()->getInitialState()->id());
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test');
|
||||
$this->submitForm(['states[draft][weight]' => '-1'], 'Save');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertEquals('draft', $workflow->getTypePlugin()->getInitialState()->id());
|
||||
|
||||
// Verify that we are still on the workflow edit page.
|
||||
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test');
|
||||
|
||||
// Ensure that weight changes the transition ordering.
|
||||
$this->assertEquals(['publish', 'create_new_draft'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test');
|
||||
$this->submitForm(['transitions[create_new_draft][weight]' => '-1'], 'Save');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
|
||||
// Verify that we are still on the workflow edit page.
|
||||
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test');
|
||||
|
||||
// Ensure that a delete link for the published state exists before deleting
|
||||
// the draft state.
|
||||
$published_delete_link = Url::fromRoute('entity.workflow.delete_state_form', [
|
||||
'workflow' => $workflow->id(),
|
||||
'workflow_state' => 'published',
|
||||
])->toString();
|
||||
$draft_delete_link = Url::fromRoute('entity.workflow.delete_state_form', [
|
||||
'workflow' => $workflow->id(),
|
||||
'workflow_state' => 'draft',
|
||||
])->toString();
|
||||
$this->assertSession()->elementContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
|
||||
$this->assertSession()->linkByHrefExists($published_delete_link);
|
||||
$this->assertSession()->linkByHrefExists($draft_delete_link);
|
||||
|
||||
// Make the published state a default state and ensure it is no longer
|
||||
// linked.
|
||||
\Drupal::state()->set('workflow_type_test.required_states', ['published']);
|
||||
$this->getSession()->reload();
|
||||
$this->assertSession()->linkByHrefNotExists($published_delete_link);
|
||||
$this->assertSession()->linkByHrefExists($draft_delete_link);
|
||||
$this->assertSession()->elementNotContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
|
||||
\Drupal::state()->set('workflow_type_test.required_states', []);
|
||||
$this->getSession()->reload();
|
||||
$this->assertSession()->elementContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
|
||||
$this->assertSession()->linkByHrefExists($published_delete_link);
|
||||
$this->assertSession()->linkByHrefExists($draft_delete_link);
|
||||
|
||||
// Delete the Draft state.
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete Draft from Test?');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains('State Draft deleted.');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasState('draft'), 'Draft state deleted');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('published'), 'Workflow still has published state');
|
||||
|
||||
// The last state cannot be deleted so the only delete link on the page will
|
||||
// be for the workflow.
|
||||
$this->assertSession()->linkByHrefNotExists($published_delete_link);
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete Test?');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains('Workflow Test deleted.');
|
||||
$this->assertSession()->pageTextContains('There are no workflows yet.');
|
||||
$this->assertNull($workflow_storage->loadUnchanged('test'), 'The test workflow has been deleted');
|
||||
|
||||
// Ensure that workflow types with default configuration are initialized
|
||||
// correctly.
|
||||
$this->drupalGet('admin/config/workflow/workflows');
|
||||
$this->clickLink('Add workflow');
|
||||
$this->submitForm(['label' => 'Test 2', 'id' => 'test2', 'workflow_type' => 'workflow_type_required_state_test'], 'Save');
|
||||
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test2');
|
||||
$workflow = $workflow_storage->loadUnchanged('test2');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('fresh'), 'The workflow has the "fresh" state');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('rotten'), 'The workflow has the "rotten" state');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransition('rot'), 'The workflow has the "rot" transition');
|
||||
$this->assertSession()->pageTextContains('Fresh');
|
||||
$this->assertSession()->pageTextContains('Rotten');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the workflow configuration form.
|
||||
*/
|
||||
public function testWorkflowConfigurationForm() {
|
||||
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test', 'label' => 'Test']);
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['published'], 'published');
|
||||
$workflow->save();
|
||||
|
||||
$this->drupalLogin($this->createUser(['administer workflows']));
|
||||
|
||||
// Add additional information to the workflow via the configuration form.
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test');
|
||||
$this->assertSession()->pageTextContains('Example global workflow setting');
|
||||
$this->submitForm(['type_settings[example_setting]' => 'Extra global settings'], 'Save');
|
||||
|
||||
$workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
|
||||
$workflow = $workflow_storage->loadUnchanged('test');
|
||||
$this->assertEquals('Extra global settings', $workflow->getTypePlugin()->getConfiguration()['example_setting']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a workflow, state, and transition can have a numeric ID and label.
|
||||
*/
|
||||
public function testNumericIds() {
|
||||
$this->drupalLogin($this->createUser(['administer workflows']));
|
||||
$this->drupalGet('admin/config/workflow/workflows');
|
||||
$this->clickLink('Add workflow');
|
||||
$this->submitForm(['label' => 123, 'id' => 123, 'workflow_type' => 'workflow_type_complex_test'], 'Save');
|
||||
|
||||
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/123/add_state');
|
||||
|
||||
$this->submitForm(['label' => 456, 'id' => 456], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created 456 state.');
|
||||
|
||||
$this->clickLink('Add a new state');
|
||||
$this->submitForm(['label' => 789, 'id' => 789], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created 789 state.');
|
||||
|
||||
$this->clickLink('Add a new transition');
|
||||
$this->submitForm(['id' => 101112, 'label' => 101112, 'from[456]' => 456, 'to' => 789], 'Save');
|
||||
$this->assertSession()->pageTextContains('Created 101112 transition.');
|
||||
|
||||
$workflow = $this->container->get('entity_type.manager')->getStorage('workflow')->loadUnchanged(123);
|
||||
$this->assertEquals(123, $workflow->id());
|
||||
$this->assertEquals(456, $workflow->getTypePlugin()->getState(456)->id());
|
||||
$this->assertEquals(101112, $workflow->getTypePlugin()->getTransition(101112)->id());
|
||||
$this->assertEquals(789, $workflow->getTypePlugin()->getTransition(101112)->to()->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the sorting of states and transitions by weight and label.
|
||||
*/
|
||||
public function testSorting() {
|
||||
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test', 'label' => 'Test']);
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->setConfiguration([
|
||||
'states' => [
|
||||
'twoa' => [
|
||||
'label' => 'twoa',
|
||||
'weight' => 2,
|
||||
],
|
||||
'three' => [
|
||||
'label' => 'three',
|
||||
'weight' => 3,
|
||||
],
|
||||
'twob' => [
|
||||
'label' => 'twob',
|
||||
'weight' => 2,
|
||||
],
|
||||
'one' => [
|
||||
'label' => 'one',
|
||||
'weight' => 1,
|
||||
],
|
||||
],
|
||||
'transitions' => [
|
||||
'three' => [
|
||||
'label' => 'three',
|
||||
'from' => ['three'],
|
||||
'to' => 'three',
|
||||
'weight' => 3,
|
||||
],
|
||||
'twoa' => [
|
||||
'label' => 'twoa',
|
||||
'from' => ['twoa'],
|
||||
'to' => 'twoa',
|
||||
'weight' => 2,
|
||||
],
|
||||
'one' => [
|
||||
'label' => 'one',
|
||||
'from' => ['one'],
|
||||
'to' => 'one',
|
||||
'weight' => 1,
|
||||
],
|
||||
'twob' => [
|
||||
'label' => 'twob',
|
||||
'from' => ['twob'],
|
||||
'to' => 'twob',
|
||||
'weight' => 2,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$workflow->save();
|
||||
|
||||
$this->drupalLogin($this->createUser(['administer workflows']));
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/test');
|
||||
$expected_states = ['one', 'twoa', 'twob', 'three'];
|
||||
$elements = $this->xpath('//details[@id="edit-states-container"]//table/tbody/tr');
|
||||
foreach ($elements as $key => $element) {
|
||||
$this->assertEquals($expected_states[$key], $element->find('xpath', 'td')->getText());
|
||||
}
|
||||
$expected_transitions = ['one', 'twoa', 'twob', 'three'];
|
||||
$elements = $this->xpath('//details[@id="edit-transitions-container"]//table/tbody/tr');
|
||||
foreach ($elements as $key => $element) {
|
||||
$this->assertEquals($expected_transitions[$key], $element->find('xpath', 'td')->getText());
|
||||
}
|
||||
|
||||
// Ensure that there are enough weights to satisfy the potential number of
|
||||
// states and transitions.
|
||||
$this->assertSession()
|
||||
->selectExists('states[three][weight]')
|
||||
->selectOption('2');
|
||||
$this->assertSession()
|
||||
->selectExists('states[three][weight]')
|
||||
->selectOption('-2');
|
||||
$this->assertSession()
|
||||
->selectExists('transitions[three][weight]')
|
||||
->selectOption('2');
|
||||
$this->assertSession()
|
||||
->selectExists('transitions[three][weight]')
|
||||
->selectOption('-2');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Workflow entity tests that require modules or storage.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workflow_type_test\Plugin\WorkflowType\ComplexTestType
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class ComplexWorkflowTypeTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['workflows', 'workflow_type_test'];
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workflows\Entity\Workflow::loadMultipleByType
|
||||
*/
|
||||
public function testLoadMultipleByType() {
|
||||
$workflow1 = new Workflow(['id' => 'test1', 'type' => 'workflow_type_complex_test'], 'workflow');
|
||||
$workflow1->save();
|
||||
$workflow2 = new Workflow(['id' => 'test2', 'type' => 'workflow_type_complex_test'], 'workflow');
|
||||
$workflow2->save();
|
||||
$workflow3 = new Workflow(['id' => 'test3', 'type' => 'workflow_type_test'], 'workflow');
|
||||
$workflow3->save();
|
||||
|
||||
$this->assertEquals(['test1', 'test2'], array_keys(Workflow::loadMultipleByType('workflow_type_complex_test')));
|
||||
$this->assertEquals(['test3'], array_keys(Workflow::loadMultipleByType('workflow_type_test')));
|
||||
$this->assertEquals([], Workflow::loadMultipleByType('a_type_that_does_not_exist'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Test a predefined workflow based on something other than configuration.
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class PredefinedWorkflowTypeTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['workflows', 'workflow_type_test'];
|
||||
|
||||
/**
|
||||
* Test a predefined workflow type.
|
||||
*/
|
||||
public function testPredefinedWorkflowType() {
|
||||
$workflow = Workflow::create([
|
||||
'id' => 'aces',
|
||||
'label' => 'Aces Workflow',
|
||||
'type' => 'predefined_states_workflow_test_type',
|
||||
'transitions' => [
|
||||
'bet' => [
|
||||
'label' => 'Bet',
|
||||
'from' => [
|
||||
'pay_blinds',
|
||||
],
|
||||
'to' => 'bet',
|
||||
],
|
||||
'raise' => [
|
||||
'label' => 'Raise',
|
||||
'from' => [
|
||||
'pay_blinds',
|
||||
],
|
||||
'to' => 'raise',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$workflow->save();
|
||||
|
||||
// No states configuration is stored for this workflow.
|
||||
$configuration = $workflow->getTypePlugin()->getConfiguration();
|
||||
$this->assertFalse(isset($configuration['states']));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\workflows\Exception\RequiredStateMissingException;
|
||||
|
||||
/**
|
||||
* Tests Workflow type's required states and configuration initialization.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workflows\Plugin\WorkflowTypeBase
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class RequiredStatesTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['workflows', 'workflow_type_test'];
|
||||
|
||||
/**
|
||||
* @covers ::getRequiredStates
|
||||
* @covers ::__construct
|
||||
*/
|
||||
public function testGetRequiredStates() {
|
||||
$workflow = new Workflow([
|
||||
'id' => 'test',
|
||||
'type' => 'workflow_type_required_state_test',
|
||||
], 'workflow');
|
||||
$workflow->save();
|
||||
$this->assertEquals(['fresh', 'rotten'], $workflow->getTypePlugin()
|
||||
->getRequiredStates());
|
||||
|
||||
// Ensure that the workflow has the default configuration.
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('rotten'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('fresh'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('fresh', 'rotten'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workflows\Entity\Workflow::preSave
|
||||
*/
|
||||
public function testDeleteRequiredStateAPI() {
|
||||
$workflow = new Workflow([
|
||||
'id' => 'test',
|
||||
'type' => 'workflow_type_required_state_test',
|
||||
], 'workflow');
|
||||
$workflow->save();
|
||||
// Ensure that required states can't be deleted.
|
||||
$this->setExpectedException(RequiredStateMissingException::class, "Required State Type Test' requires states with the ID 'fresh' in workflow 'test'");
|
||||
$workflow->getTypePlugin()->deleteState('fresh');
|
||||
$workflow->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workflows\Entity\Workflow::preSave
|
||||
*/
|
||||
public function testNoStatesRequiredStateAPI() {
|
||||
$workflow = new Workflow([
|
||||
'id' => 'test',
|
||||
'type' => 'workflow_type_required_state_test',
|
||||
'type_settings' => [
|
||||
'states' => [],
|
||||
],
|
||||
], 'workflow');
|
||||
$this->setExpectedException(RequiredStateMissingException::class, "Required State Type Test' requires states with the ID 'fresh', 'rotten' in workflow 'test'");
|
||||
$workflow->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that initialized configuration can be changed.
|
||||
*/
|
||||
public function testChangeRequiredStateAPI() {
|
||||
$workflow = new Workflow([
|
||||
'id' => 'test',
|
||||
'type' => 'workflow_type_required_state_test',
|
||||
], 'workflow');
|
||||
$workflow->save();
|
||||
|
||||
// Ensure states added by default configuration can be changed.
|
||||
$this->assertEquals('Fresh', $workflow->getTypePlugin()->getState('fresh')->label());
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->setStateLabel('fresh', 'Fresher');
|
||||
$workflow->save();
|
||||
$this->assertEquals('Fresher', $workflow->getTypePlugin()->getState('fresh')->label());
|
||||
|
||||
// Ensure transitions can be altered.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('cooked', 'Cooked')
|
||||
->setTransitionFromStates('rot', ['fresh', 'cooked']);
|
||||
$workflow->save();
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('fresh', 'rotten'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('cooked', 'rotten'));
|
||||
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->setTransitionFromStates('rot', ['cooked']);
|
||||
$workflow->save();
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('fresh', 'rotten'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('cooked', 'rotten'));
|
||||
|
||||
// Ensure the default configuration does not cause ordering issues.
|
||||
$workflow->getTypePlugin()->addTransition('cook', 'Cook', ['fresh'], 'cooked');
|
||||
$workflow->save();
|
||||
$this->assertSame([
|
||||
'cooked',
|
||||
'fresh',
|
||||
'rotten',
|
||||
], array_keys($workflow->getTypePlugin()->getConfiguration()['states']));
|
||||
$this->assertSame([
|
||||
'cook',
|
||||
'rot',
|
||||
], array_keys($workflow->getTypePlugin()->getConfiguration()['transitions']));
|
||||
|
||||
// Ensure that transitions can be deleted.
|
||||
$workflow->getTypePlugin()->deleteTransition('rot');
|
||||
$workflow->save();
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransition('rot'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Kernel;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\simpletest\UserCreationTrait;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workflows\WorkflowAccessControlHandler
|
||||
* @group workflows
|
||||
*/
|
||||
class WorkflowAccessControlHandlerTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* The workflow access control handler.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowAccessControlHandler
|
||||
*/
|
||||
protected $accessControlHandler;
|
||||
|
||||
/**
|
||||
* A test admin user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A non-privileged user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('workflow');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('system', ['sequences']);
|
||||
|
||||
$this->accessControlHandler = $this->container->get('entity_type.manager')->getAccessControlHandler('workflow');
|
||||
|
||||
// Create and discard user 1, which is special and bypasses all access
|
||||
// checking.
|
||||
$this->createUser([]);
|
||||
$this->user = $this->createUser([]);
|
||||
$this->adminUser = $this->createUser(['administer workflows']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::checkCreateAccess
|
||||
*/
|
||||
public function testCheckCreateAccess() {
|
||||
// A user must have the correct permission to create a workflow.
|
||||
$this->assertEquals(
|
||||
AccessResult::neutral()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required.")
|
||||
->addCacheTags(['workflow_type_plugins']),
|
||||
$this->accessControlHandler->createAccess(NULL, $this->user, [], TRUE)
|
||||
);
|
||||
$this->assertEquals(
|
||||
AccessResult::allowed()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->addCacheTags(['workflow_type_plugins']),
|
||||
$this->accessControlHandler->createAccess(NULL, $this->adminUser, [], TRUE)
|
||||
);
|
||||
|
||||
// Remove all plugin types and ensure not even the admin user is allowed to
|
||||
// create a workflow.
|
||||
workflow_type_test_set_definitions([]);
|
||||
$this->accessControlHandler->resetCache();
|
||||
$this->assertEquals(
|
||||
AccessResult::neutral()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->addCacheTags(['workflow_type_plugins']),
|
||||
$this->accessControlHandler->createAccess(NULL, $this->adminUser, [], TRUE)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::checkAccess
|
||||
* @dataProvider checkAccessProvider
|
||||
*/
|
||||
public function testCheckAccess($user, $operation, $result, $states_to_create = []) {
|
||||
$workflow = Workflow::create([
|
||||
'type' => 'workflow_type_test',
|
||||
'id' => 'test_workflow',
|
||||
]);
|
||||
$workflow->save();
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
foreach ($states_to_create as $state_id => $is_required) {
|
||||
$workflow_type->addState($state_id, $this->randomString());
|
||||
}
|
||||
\Drupal::state()->set('workflow_type_test.required_states', array_filter($states_to_create));
|
||||
$this->assertEquals($result, $this->accessControlHandler->access($workflow, $operation, $this->{$user}, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testCheckAccess.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkAccessProvider() {
|
||||
$container = new ContainerBuilder();
|
||||
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
|
||||
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
|
||||
$cache_contexts_manager->reveal();
|
||||
$container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
return [
|
||||
'Admin view' => [
|
||||
'adminUser',
|
||||
'view',
|
||||
AccessResult::allowed()->addCacheContexts(['user.permissions']),
|
||||
],
|
||||
'Admin update' => [
|
||||
'adminUser',
|
||||
'update',
|
||||
AccessResult::allowed()->addCacheContexts(['user.permissions']),
|
||||
],
|
||||
'Admin delete' => [
|
||||
'adminUser',
|
||||
'delete',
|
||||
AccessResult::allowed()->addCacheContexts(['user.permissions']),
|
||||
],
|
||||
'Admin delete only state' => [
|
||||
'adminUser',
|
||||
'delete-state:foo',
|
||||
AccessResult::neutral()->addCacheTags(['config:workflows.workflow.test_workflow']),
|
||||
['foo' => FALSE],
|
||||
],
|
||||
'Admin delete one of two states' => [
|
||||
'adminUser',
|
||||
'delete-state:foo',
|
||||
AccessResult::allowed()
|
||||
->addCacheTags(['config:workflows.workflow.test_workflow'])
|
||||
->addCacheContexts(['user.permissions']),
|
||||
['foo' => FALSE, 'bar' => FALSE],
|
||||
],
|
||||
'Admin delete required state when there are >1 states' => [
|
||||
'adminUser',
|
||||
'delete-state:foo',
|
||||
AccessResult::allowed()
|
||||
->addCacheTags(['config:workflows.workflow.test_workflow'])
|
||||
->addCacheContexts(['user.permissions']),
|
||||
['foo' => TRUE, 'bar' => FALSE],
|
||||
],
|
||||
'User view' => [
|
||||
'user',
|
||||
'view',
|
||||
AccessResult::neutral()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required."),
|
||||
],
|
||||
'User update' => [
|
||||
'user',
|
||||
'update',
|
||||
AccessResult::neutral()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required."),
|
||||
],
|
||||
'User delete' => [
|
||||
'user',
|
||||
'delete',
|
||||
AccessResult::neutral()
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required."),
|
||||
],
|
||||
'User delete only state' => [
|
||||
'user',
|
||||
'delete-state:foo',
|
||||
AccessResult::neutral()->addCacheTags(['config:workflows.workflow.test_workflow']),
|
||||
['foo' => FALSE],
|
||||
],
|
||||
'User delete one of two states' => [
|
||||
'user',
|
||||
'delete-state:foo',
|
||||
AccessResult::neutral()
|
||||
->addCacheTags(['config:workflows.workflow.test_workflow'])
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required."),
|
||||
['foo' => FALSE, 'bar' => FALSE],
|
||||
],
|
||||
'User delete required state when there are >1 states' => [
|
||||
'user',
|
||||
'delete-state:foo',
|
||||
AccessResult::neutral()
|
||||
->addCacheTags(['config:workflows.workflow.test_workflow'])
|
||||
->addCacheContexts(['user.permissions'])
|
||||
->setReason("The 'administer workflows' permission is required."),
|
||||
['foo' => TRUE, 'bar' => FALSE],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests configuration dependencies in workflows.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workflows\Entity\Workflow
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class WorkflowDependenciesTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'workflows', 'workflow_type_test', 'workflow_third_party_settings_test'];
|
||||
|
||||
/**
|
||||
* Tests \Drupal\workflows\Entity\Workflow::onDependencyRemoval().
|
||||
*/
|
||||
public function testOnDependencyRemoval() {
|
||||
// Create a workflow that has a dependency on a third party setting.
|
||||
$workflow = Workflow::create(['id' => 'test3', 'type' => 'workflow_type_complex_test']);
|
||||
$workflow->setThirdPartySetting('workflow_third_party_settings_test', 'key', 'value');
|
||||
$workflow->save();
|
||||
$this->assertSame(['workflow_third_party_settings_test', 'workflow_type_test'], $workflow->getDependencies()['module']);
|
||||
|
||||
// Uninstall workflow_third_party_settings_test to ensure
|
||||
// \Drupal\workflows\Entity\Workflow::onDependencyRemoval() works as
|
||||
// expected.
|
||||
\Drupal::service('module_installer')->uninstall(['node', 'workflow_third_party_settings_test']);
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
$workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($workflow->id());
|
||||
$this->assertSame(['workflow_type_test'], $workflow->getDependencies()['module']);
|
||||
}
|
||||
|
||||
}
|
108
2017/web/core/modules/workflows/tests/src/Unit/StateTest.php
Normal file
108
2017/web/core/modules/workflows/tests/src/Unit/StateTest.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Unit;
|
||||
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workflow_type_test\Plugin\WorkflowType\TestType;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\workflows\WorkflowTypeInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workflows\State
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class StateTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::id
|
||||
* @covers ::label
|
||||
* @covers ::weight
|
||||
*/
|
||||
public function testGetters() {
|
||||
$state = new State(
|
||||
$this->prophesize(WorkflowTypeInterface::class)->reveal(),
|
||||
'draft',
|
||||
'Draft',
|
||||
3
|
||||
);
|
||||
$this->assertEquals('draft', $state->id());
|
||||
$this->assertEquals('Draft', $state->label());
|
||||
$this->assertEquals(3, $state->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::canTransitionTo
|
||||
*/
|
||||
public function testCanTransitionTo() {
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$state = $workflow_type->getState('draft');
|
||||
$this->assertTrue($state->canTransitionTo('published'));
|
||||
$this->assertFalse($state->canTransitionTo('some_other_state'));
|
||||
|
||||
$workflow_type->deleteTransition('publish');
|
||||
$this->assertFalse($state->canTransitionTo('published'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitionTo
|
||||
*/
|
||||
public function testGetTransitionTo() {
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$state = $workflow_type->getState('draft');
|
||||
$transition = $state->getTransitionTo('published');
|
||||
$this->assertEquals('Publish', $transition->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitionTo
|
||||
*/
|
||||
public function testGetTransitionToException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "Can not transition to 'published' state");
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type->addState('draft', 'Draft');
|
||||
$state = $workflow_type->getState('draft');
|
||||
$state->getTransitionTo('published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitions
|
||||
*/
|
||||
public function testGetTransitions() {
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published')
|
||||
->addTransition('archive', 'Archive', ['published'], 'archived');
|
||||
$state = $workflow_type->getState('draft');
|
||||
$transitions = $state->getTransitions();
|
||||
$this->assertCount(2, $transitions);
|
||||
$this->assertEquals('Create new draft', $transitions['create_new_draft']->label());
|
||||
$this->assertEquals('Publish', $transitions['publish']->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::labelCallback
|
||||
*/
|
||||
public function testLabelCallback() {
|
||||
$workflow_type = $this->prophesize(WorkflowTypeInterface::class)->reveal();
|
||||
$states = [
|
||||
new State($workflow_type, 'draft', 'Draft'),
|
||||
new State($workflow_type, 'published', 'Published'),
|
||||
];
|
||||
$this->assertEquals(['Draft', 'Published'], array_map([State::class, 'labelCallback'], $states));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Unit;
|
||||
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workflow_type_test\Plugin\WorkflowType\TestType;
|
||||
use Drupal\workflows\Transition;
|
||||
use Drupal\workflows\WorkflowTypeInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workflows\Transition
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class TransitionTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::id
|
||||
* @covers ::label
|
||||
*/
|
||||
public function testGetters() {
|
||||
$state = new Transition(
|
||||
$this->prophesize(WorkflowTypeInterface::class)->reveal(),
|
||||
'draft_published',
|
||||
'Publish',
|
||||
['draft'],
|
||||
'published'
|
||||
);
|
||||
$this->assertEquals('draft_published', $state->id());
|
||||
$this->assertEquals('Publish', $state->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::from
|
||||
* @covers ::to
|
||||
*/
|
||||
public function testFromAndTo() {
|
||||
$workflow = new TestType([], '', []);
|
||||
$workflow
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$state = $workflow->getState('draft');
|
||||
$transition = $state->getTransitionTo('published');
|
||||
$this->assertEquals($state, $transition->from()['draft']);
|
||||
$this->assertEquals($workflow->getState('published'), $transition->to());
|
||||
}
|
||||
|
||||
}
|
687
2017/web/core/modules/workflows/tests/src/Unit/WorkflowTest.php
Normal file
687
2017/web/core/modules/workflows/tests/src/Unit/WorkflowTest.php
Normal file
|
@ -0,0 +1,687 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workflows\Unit;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workflow_type_test\Plugin\WorkflowType\TestType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\workflows\State;
|
||||
use Drupal\workflows\Transition;
|
||||
use Drupal\workflows\WorkflowTypeManager;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workflows\Plugin\WorkflowTypeBase
|
||||
*
|
||||
* @group workflows
|
||||
*/
|
||||
class WorkflowTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Create a container so that the plugin manager and workflow type can be
|
||||
// mocked.
|
||||
$container = new ContainerBuilder();
|
||||
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
|
||||
$workflow_manager->createInstance('test_type', Argument::any())->willReturn(new TestType([], '', []));
|
||||
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addState
|
||||
* @covers ::hasState
|
||||
*/
|
||||
public function testAddAndHasState() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasState('draft'));
|
||||
|
||||
// By default states are ordered in the order added.
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasState('draft'));
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasState('published'));
|
||||
$this->assertEquals(0, $workflow->getTypePlugin()->getState('draft')->weight());
|
||||
// Adding a state does not set up a transition to itself.
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('draft', 'draft'));
|
||||
|
||||
// New states are added with a new weight 1 more than the current highest
|
||||
// weight.
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$this->assertEquals(1, $workflow->getTypePlugin()->getState('published')->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addState
|
||||
*/
|
||||
public function testAddStateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addState
|
||||
*/
|
||||
public function testAddStateInvalidIdException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state ID 'draft-draft' must contain only lowercase letters, numbers, and underscores");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft-draft', 'Draft');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getStates
|
||||
*/
|
||||
public function testGetStates() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
|
||||
// Getting states works when there are none.
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getStates()));
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getStates([])));
|
||||
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived');
|
||||
|
||||
// States are stored in alphabetical key order.
|
||||
$this->assertArrayEquals([
|
||||
'archived',
|
||||
'draft',
|
||||
'published',
|
||||
], array_keys($workflow->getTypePlugin()->getConfiguration()['states']));
|
||||
|
||||
// Ensure we're returning state objects.
|
||||
$this->assertInstanceOf(State::class, $workflow->getTypePlugin()->getStates()['draft']);
|
||||
|
||||
// Passing in no IDs returns all states.
|
||||
$this->assertArrayEquals(['draft', 'published', 'archived'], array_keys($workflow->getTypePlugin()->getStates()));
|
||||
|
||||
// The order of states is by weight.
|
||||
$workflow->getTypePlugin()->setStateWeight('published', -1);
|
||||
$this->assertArrayEquals(['published', 'draft', 'archived'], array_keys($workflow->getTypePlugin()->getStates()));
|
||||
|
||||
// The label is also used for sorting if weights are equal.
|
||||
$workflow->getTypePlugin()->setStateWeight('archived', 0);
|
||||
$this->assertArrayEquals(['published', 'archived', 'draft'], array_keys($workflow->getTypePlugin()->getStates()));
|
||||
|
||||
// You can limit the states returned by passing in states IDs.
|
||||
$this->assertArrayEquals(['archived', 'draft'], array_keys($workflow->getTypePlugin()->getStates(['draft', 'archived'])));
|
||||
|
||||
// An empty array does not load all states.
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getStates([])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test numeric IDs when added to a workflow.
|
||||
*/
|
||||
public function testNumericIdSorting() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
|
||||
$workflow_type->addState('1', 'One');
|
||||
$workflow_type->addState('2', 'Two');
|
||||
$workflow_type->addState('3', 'ZZZ');
|
||||
$workflow_type->addState('4', 'AAA');
|
||||
|
||||
$workflow_type->setStateWeight('1', 1);
|
||||
$workflow_type->setStateWeight('2', 2);
|
||||
$workflow_type->setStateWeight('3', 3);
|
||||
$workflow_type->setStateWeight('4', 3);
|
||||
|
||||
// Ensure numeric states are correctly sorted by weight first, label second.
|
||||
$this->assertEquals([1, 2, 4, 3], array_keys($workflow_type->getStates()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getStates
|
||||
*/
|
||||
public function testGetStatesException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->getStates(['state_that_does_not_exist']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getState
|
||||
*/
|
||||
public function testGetState() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
|
||||
// Ensure we're returning state objects and they are set up correctly
|
||||
$this->assertInstanceOf(State::class, $workflow->getTypePlugin()->getState('draft'));
|
||||
$this->assertEquals('archived', $workflow->getTypePlugin()->getState('archived')->id());
|
||||
$this->assertEquals('Archived', $workflow->getTypePlugin()->getState('archived')->label());
|
||||
|
||||
$draft = $workflow->getTypePlugin()->getState('draft');
|
||||
$this->assertTrue($draft->canTransitionTo('draft'));
|
||||
$this->assertTrue($draft->canTransitionTo('published'));
|
||||
$this->assertFalse($draft->canTransitionTo('archived'));
|
||||
$this->assertEquals('Publish', $draft->getTransitionTo('published')->label());
|
||||
$this->assertEquals(0, $draft->weight());
|
||||
$this->assertEquals(1, $workflow->getTypePlugin()->getState('published')->weight());
|
||||
$this->assertEquals(2, $workflow->getTypePlugin()->getState('archived')->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getState
|
||||
*/
|
||||
public function testGetStateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->getState('state_that_does_not_exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStateLabel
|
||||
*/
|
||||
public function testSetStateLabel() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$this->assertEquals('Draft', $workflow->getTypePlugin()->getState('draft')->label());
|
||||
$workflow->getTypePlugin()->setStateLabel('draft', 'Unpublished');
|
||||
$this->assertEquals('Unpublished', $workflow->getTypePlugin()->getState('draft')->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStateLabel
|
||||
*/
|
||||
public function testSetStateLabelException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->setStateLabel('draft', 'Draft');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStateWeight
|
||||
*/
|
||||
public function testSetStateWeight() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$this->assertEquals(0, $workflow->getTypePlugin()->getState('draft')->weight());
|
||||
$workflow->getTypePlugin()->setStateWeight('draft', -10);
|
||||
$this->assertEquals(-10, $workflow->getTypePlugin()->getState('draft')->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStateWeight
|
||||
*/
|
||||
public function testSetStateWeightException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->setStateWeight('draft', 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStateWeight
|
||||
*/
|
||||
public function testSetStateWeightNonNumericException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The weight 'foo' must be numeric for state 'Published'.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->setStateWeight('published', 'foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteState
|
||||
*/
|
||||
public function testDeleteState() {
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft', 'published'], 'draft')
|
||||
->addTransition('archive', 'Archive', ['draft', 'published'], 'archived');
|
||||
$this->assertCount(3, $workflow_type->getStates());
|
||||
$this->assertCount(3, $workflow_type->getState('published')->getTransitions());
|
||||
$workflow_type->deleteState('draft');
|
||||
$this->assertFalse($workflow_type->hasState('draft'));
|
||||
$this->assertCount(2, $workflow_type->getStates());
|
||||
$this->assertCount(2, $workflow_type->getState('published')->getTransitions());
|
||||
$workflow_type->deleteState('published');
|
||||
$this->assertCount(0, $workflow_type->getTransitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteState
|
||||
*/
|
||||
public function testDeleteStateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->deleteState('draft');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteState
|
||||
*/
|
||||
public function testDeleteOnlyStateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' can not be deleted from workflow as it is the only state");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$workflow->getTypePlugin()->deleteState('draft');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
* @covers ::hasTransition
|
||||
*/
|
||||
public function testAddTransition() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published');
|
||||
|
||||
$this->assertFalse($workflow->getTypePlugin()->getState('draft')->canTransitionTo('published'));
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$this->assertTrue($workflow->getTypePlugin()->getState('draft')->canTransitionTo('published'));
|
||||
$this->assertEquals(0, $workflow->getTypePlugin()->getTransition('publish')->weight());
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransition('publish'));
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransition('draft'));
|
||||
|
||||
$workflow->getTypePlugin()->addTransition('save_publish', 'Save', ['published'], 'published');
|
||||
$this->assertEquals(1, $workflow->getTypePlugin()->getTransition('save_publish')->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionDuplicateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['published'], 'published');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['published'], 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionInvalidIdException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition ID 'publish-publish' must contain only lowercase letters, numbers, and underscores");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->addTransition('publish-publish', 'Publish', ['published'], 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionMissingFromException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionDuplicateTransitionStatesException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The 'publish' transition already allows 'draft' to 'published' transitions in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['draft', 'published'], 'published');
|
||||
$workflow->getTypePlugin()->addTransition('draft_to_published', 'Publish a draft', ['draft'], 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionConsistentAfterFromCatch() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
try {
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
// Ensure that the workflow is not left in an inconsistent state after an
|
||||
// exception is thrown from Workflow::setTransitionFromStates() whilst
|
||||
// calling Workflow::addTransition().
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransition('publish'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addTransition
|
||||
*/
|
||||
public function testAddTransitionMissingToException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitions
|
||||
* @covers ::setTransitionWeight
|
||||
*/
|
||||
public function testGetTransitions() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
|
||||
// Getting transitions works when there are none.
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getTransitions([])));
|
||||
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('a', 'A')
|
||||
->addState('b', 'B')
|
||||
->addTransition('a_b', 'A to B', ['a'], 'b')
|
||||
->addTransition('a_a', 'A to A', ['a'], 'a');
|
||||
|
||||
// Transitions are stored in alphabetical key order in configuration.
|
||||
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTypePlugin()->getConfiguration()['transitions']));
|
||||
|
||||
// Ensure we're returning transition objects.
|
||||
$this->assertInstanceOf(Transition::class, $workflow->getTypePlugin()->getTransitions()['a_a']);
|
||||
|
||||
// Passing in no IDs returns all transitions.
|
||||
$this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
|
||||
// The order of states is by weight.
|
||||
$workflow->getTypePlugin()->setTransitionWeight('a_a', -1);
|
||||
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
|
||||
// If all weights are equal it will fallback to labels.
|
||||
$workflow->getTypePlugin()->setTransitionWeight('a_a', 0);
|
||||
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
$workflow->getTypePlugin()->setTransitionLabel('a_b', 'A B');
|
||||
$this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTypePlugin()->getTransitions()));
|
||||
|
||||
// You can limit the states returned by passing in states IDs.
|
||||
$this->assertArrayEquals(['a_a'], array_keys($workflow->getTypePlugin()->getTransitions(['a_a'])));
|
||||
|
||||
// An empty array does not load all states.
|
||||
$this->assertArrayEquals([], array_keys($workflow->getTypePlugin()->getTransitions([])));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransition
|
||||
*/
|
||||
public function testGetTransition() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
|
||||
// Ensure we're returning state objects and they are set up correctly
|
||||
$this->assertInstanceOf(Transition::class, $workflow->getTypePlugin()->getTransition('create_new_draft'));
|
||||
$this->assertEquals('publish', $workflow->getTypePlugin()->getTransition('publish')->id());
|
||||
$this->assertEquals('Publish', $workflow->getTypePlugin()->getTransition('publish')->label());
|
||||
|
||||
$transition = $workflow->getTypePlugin()->getTransition('publish');
|
||||
$this->assertEquals($workflow->getTypePlugin()->getState('draft'), $transition->from()['draft']);
|
||||
$this->assertEquals($workflow->getTypePlugin()->getState('published'), $transition->to());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransition
|
||||
*/
|
||||
public function testGetTransitionException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'transition_that_does_not_exist' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->getTransition('transition_that_does_not_exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitionsForState
|
||||
*/
|
||||
public function testGetTransitionsForState() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
|
||||
->addTransition('archive', 'Archive', ['published'], 'archived');
|
||||
|
||||
$this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTypePlugin()->getTransitionsForState('draft')));
|
||||
$this->assertEquals(['create_new_draft'], array_keys($workflow->getTypePlugin()->getTransitionsForState('draft', 'to')));
|
||||
$this->assertEquals(['publish', 'archive'], array_keys($workflow->getTypePlugin()->getTransitionsForState('published')));
|
||||
$this->assertEquals(['publish'], array_keys($workflow->getTypePlugin()->getTransitionsForState('published', 'to')));
|
||||
$this->assertEquals(['create_new_draft'], array_keys($workflow->getTypePlugin()->getTransitionsForState('archived', 'from')));
|
||||
$this->assertEquals(['archive'], array_keys($workflow->getTypePlugin()->getTransitionsForState('archived', 'to')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitionFromStateToState
|
||||
* @covers ::hasTransitionFromStateToState
|
||||
*/
|
||||
public function testGetTransitionFromStateToState() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
|
||||
->addTransition('archive', 'Archive', ['published'], 'archived');
|
||||
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('draft', 'published'));
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('archived', 'archived'));
|
||||
$transition = $workflow->getTypePlugin()->getTransitionFromStateToState('published', 'archived');
|
||||
$this->assertEquals('Archive', $transition->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getTransitionFromStateToState
|
||||
*/
|
||||
public function testGetTransitionFromStateToStateException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition from 'archived' to 'archived' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
// By default states are ordered in the order added.
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
|
||||
->addTransition('archive', 'Archive', ['published'], 'archived');
|
||||
|
||||
$workflow->getTypePlugin()->getTransitionFromStateToState('archived', 'archived');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionLabel
|
||||
*/
|
||||
public function testSetTransitionLabel() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$this->assertEquals('Publish', $workflow->getTypePlugin()->getTransition('publish')->label());
|
||||
$workflow->getTypePlugin()->setTransitionLabel('publish', 'Publish!');
|
||||
$this->assertEquals('Publish!', $workflow->getTypePlugin()->getTransition('publish')->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionLabel
|
||||
*/
|
||||
public function testSetTransitionLabelException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->setTransitionLabel('draft-published', 'Publish');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionWeight
|
||||
*/
|
||||
public function testSetTransitionWeight() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$this->assertEquals(0, $workflow->getTypePlugin()->getTransition('publish')->weight());
|
||||
$workflow->getTypePlugin()->setTransitionWeight('publish', 10);
|
||||
$this->assertEquals(10, $workflow->getTypePlugin()->getTransition('publish')->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionWeight
|
||||
*/
|
||||
public function testSetTransitionWeightException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->setTransitionWeight('draft-published', 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionWeight
|
||||
*/
|
||||
public function testSetTransitionWeightNonNumericException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The weight 'foo' must be numeric for transition 'Publish'.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->addTransition('publish', 'Publish', [], 'published');
|
||||
$workflow->getTypePlugin()->setTransitionWeight('publish', 'foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionFromStates
|
||||
*/
|
||||
public function testSetTransitionFromStates() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('test', 'Test', ['draft'], 'draft');
|
||||
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('draft', 'draft'));
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('published', 'draft'));
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('archived', 'draft'));
|
||||
$workflow->getTypePlugin()->setTransitionFromStates('test', ['draft', 'published', 'archived']);
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('draft', 'draft'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('published', 'draft'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('archived', 'draft'));
|
||||
$workflow->getTypePlugin()->setTransitionFromStates('test', ['published', 'archived']);
|
||||
$this->assertFalse($workflow->getTypePlugin()->hasTransitionFromStateToState('draft', 'draft'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('published', 'draft'));
|
||||
$this->assertTrue($workflow->getTypePlugin()->hasTransitionFromStateToState('archived', 'draft'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionFromStates
|
||||
*/
|
||||
public function testSetTransitionFromStatesMissingTransition() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
|
||||
|
||||
$workflow->getTypePlugin()->setTransitionFromStates('test', ['draft', 'published', 'archived']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionFromStates
|
||||
*/
|
||||
public function testSetTransitionFromStatesMissingState() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('archived', 'Archived')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
|
||||
|
||||
$workflow->getTypePlugin()->setTransitionFromStates('create_new_draft', ['draft', 'published', 'archived']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setTransitionFromStates
|
||||
*/
|
||||
public function testSetTransitionFromStatesAlreadyExists() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The 'create_new_draft' transition already allows 'draft' to 'draft' transitions in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow
|
||||
->getTypePlugin()
|
||||
->addState('draft', 'Draft')
|
||||
->addState('archived', 'Archived')
|
||||
->addState('needs_review', 'Needs Review')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
|
||||
->addTransition('needs_review', 'Needs review', ['needs_review'], 'draft');
|
||||
|
||||
$workflow->getTypePlugin()->setTransitionFromStates('needs_review', ['draft']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteTransition
|
||||
*/
|
||||
public function testDeleteTransition() {
|
||||
$workflow_type = new TestType([], '', []);
|
||||
$workflow_type
|
||||
->addState('draft', 'Draft')
|
||||
->addState('published', 'Published')
|
||||
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
|
||||
->addTransition('publish', 'Publish', ['draft'], 'published');
|
||||
$this->assertTrue($workflow_type->getState('draft')->canTransitionTo('published'));
|
||||
$workflow_type->deleteTransition('publish');
|
||||
$this->assertFalse($workflow_type->getState('draft')->canTransitionTo('published'));
|
||||
$this->assertTrue($workflow_type->getState('draft')->canTransitionTo('draft'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteTransition
|
||||
*/
|
||||
public function testDeleteTransitionException() {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow.");
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$workflow->getTypePlugin()->deleteTransition('draft-published');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workflows\Entity\Workflow::status
|
||||
*/
|
||||
public function testStatus() {
|
||||
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
|
||||
$this->assertFalse($workflow->status());
|
||||
$workflow->getTypePlugin()->addState('published', 'Published');
|
||||
$this->assertTrue($workflow->status());
|
||||
}
|
||||
|
||||
}
|
17
2017/web/core/modules/workflows/workflows.api.php
Normal file
17
2017/web/core/modules/workflows/workflows.api.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API documentation for Workflows module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup workflow_type_plugins Workflow Type Plugins
|
||||
* @{
|
||||
* Any module harnessing Workflows module must define a Workflow Type Plugin.
|
||||
* This allows the module to tailor the workflow to its specific need. For
|
||||
* example, Content Moderation module uses its the Workflow Type Plugin to link
|
||||
* workflows to entities. On their own, workflows are a stand-alone concept. It
|
||||
* takes a module such as Content Moderation to give the workflow context.
|
||||
* @}
|
||||
*/
|
7
2017/web/core/modules/workflows/workflows.info.yml
Normal file
7
2017/web/core/modules/workflows/workflows.info.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: 'Workflows'
|
||||
type: module
|
||||
description: 'Provides UI and API for managing workflows. This module can be used with the Content moderation module to add highly customizable workflows to content.'
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
package: Core
|
||||
configure: entity.workflow.collection
|
|
@ -0,0 +1,8 @@
|
|||
# Workflows extension relation types.
|
||||
# See https://tools.ietf.org/html/rfc5988#section-4.2.
|
||||
add-state-form:
|
||||
uri: https://drupal.org/link-relations/add-state-form
|
||||
description: A form where a state can be created.
|
||||
add-transition-form:
|
||||
uri: https://drupal.org/link-relations/add-transition-form
|
||||
description: A form where a transition can be created.
|
|
@ -0,0 +1,5 @@
|
|||
entity.workflow.add_form:
|
||||
route_name: 'entity.workflow.add_form'
|
||||
title: 'Add workflow'
|
||||
appears_on:
|
||||
- entity.workflow.collection
|
5
2017/web/core/modules/workflows/workflows.links.menu.yml
Normal file
5
2017/web/core/modules/workflows/workflows.links.menu.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
entity.workflow.collection:
|
||||
title: 'Workflows'
|
||||
route_name: entity.workflow.collection
|
||||
description: 'Configure workflows.'
|
||||
parent: system.admin_config_workflow
|
27
2017/web/core/modules/workflows/workflows.module
Normal file
27
2017/web/core/modules/workflows/workflows.module
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides hook implementations for the Workflows module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function workflows_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.workflows':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Workflows module provides a UI and an API for creating workflows content. This lets site admins define workflows and their states, and then define transitions between those states. 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 .= '<h4>' . t('Workflow') . '</h4>';
|
||||
$output .= '<p>' . t('A collection of states and transitions between those states.') . '</p>';
|
||||
$output .= '<h4>' . t('State') . '</h4>';
|
||||
$output .= '<p>' . t('A particular condition that something is in at a specific time. The usage of the state is determined by a module that harnesses the Workflows module. For example, Content Moderation allows a state to be used for moderation of content by assigning a given state to a content item.') . '</p>';
|
||||
$output .= '<h4>' . t('Transition') . '</h4>';
|
||||
$output .= '<p>' . t('The process of changing from one state to another. A transition can occur from multiple states, but only to one state.') . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
administer workflows:
|
||||
title: 'Administer workflows'
|
||||
description: 'Create and edit workflows.'
|
||||
restrict access: TRUE
|
47
2017/web/core/modules/workflows/workflows.routing.yml
Normal file
47
2017/web/core/modules/workflows/workflows.routing.yml
Normal file
|
@ -0,0 +1,47 @@
|
|||
entity.workflow.add_state_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/add_state'
|
||||
defaults:
|
||||
_entity_form: 'workflow.add-state'
|
||||
_title: 'Add state'
|
||||
requirements:
|
||||
_entity_access: 'workflow.edit'
|
||||
|
||||
entity.workflow.edit_state_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}'
|
||||
defaults:
|
||||
_entity_form: 'workflow.edit-state'
|
||||
_title: 'Edit state'
|
||||
requirements:
|
||||
_entity_access: 'workflow.edit'
|
||||
|
||||
entity.workflow.delete_state_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\workflows\Form\WorkflowStateDeleteForm'
|
||||
_title: 'Delete state'
|
||||
requirements:
|
||||
_workflow_state_delete_access: 'true'
|
||||
|
||||
entity.workflow.add_transition_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/add_transition'
|
||||
defaults:
|
||||
_entity_form: 'workflow.add-transition'
|
||||
_title: 'Add transition'
|
||||
requirements:
|
||||
_entity_access: 'workflow.edit'
|
||||
|
||||
entity.workflow.edit_transition_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}'
|
||||
defaults:
|
||||
_entity_form: 'workflow.edit-transition'
|
||||
_title: 'Edit transition'
|
||||
requirements:
|
||||
_entity_access: 'workflow.edit'
|
||||
|
||||
entity.workflow.delete_transition_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\workflows\Form\WorkflowTransitionDeleteForm'
|
||||
_title: 'Delete transition'
|
||||
requirements:
|
||||
_entity_access: 'workflow.edit'
|
10
2017/web/core/modules/workflows/workflows.services.yml
Normal file
10
2017/web/core/modules/workflows/workflows.services.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
plugin.manager.workflows.type:
|
||||
class: Drupal\workflows\WorkflowTypeManager
|
||||
parent: default_plugin_manager
|
||||
tags:
|
||||
- { name: plugin_manager_cache_clear }
|
||||
workflows.access_check.delete_state:
|
||||
class: \Drupal\workflows\WorkflowDeleteAccessCheck
|
||||
tags:
|
||||
- { name: access_check, applies_to: _workflow_state_delete_access }
|
Reference in a new issue