diff --git a/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml b/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml index d8daa5135a..8ebd82782f 100644 --- a/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml +++ b/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml @@ -5,59 +5,55 @@ dependencies: - content_moderation id: editorial label: 'Editorial workflow' -states: - archived: - label: Archived - weight: 5 - draft: - label: Draft - weight: -5 - published: - label: Published - weight: 0 -transitions: - archive: - label: Archive - from: - - published - to: archived - weight: 2 - archived_draft: - label: 'Restore to Draft' - from: - - archived - to: draft - weight: 3 - archived_published: - label: Restore - from: - - archived - to: published - weight: 4 - create_new_draft: - label: 'Create New Draft' - from: - - draft - - published - to: draft - weight: 0 - publish: - label: Publish - from: - - draft - - published - to: published - weight: 1 type: content_moderation type_settings: states: archived: + label: Archived + weight: 5 published: false default_revision: true draft: + label: Draft published: false default_revision: false + weight: -5 published: + label: Published published: true default_revision: true + weight: 0 + transitions: + create_new_draft: + label: 'Create New Draft' + to: draft + weight: 0 + from: + - draft + - published + publish: + label: Publish + to: published + weight: 1 + from: + - draft + - published + archive: + label: Archive + from: + - published + to: archived + weight: 2 + archived_draft: + label: 'Restore to Draft' + from: + - archived + to: draft + weight: 3 + archived_published: + label: Restore + from: + - archived + to: published + weight: 4 entity_types: { } diff --git a/core/modules/content_moderation/config/schema/content_moderation.schema.yml b/core/modules/content_moderation/config/schema/content_moderation.schema.yml index b791b67b80..d38e858496 100644 --- a/core/modules/content_moderation/config/schema/content_moderation.schema.yml +++ b/core/modules/content_moderation/config/schema/content_moderation.schema.yml @@ -6,22 +6,30 @@ views.filter.latest_revision: type: string label: 'Value' +content_moderation.state: + type: workflows.state + mapping: + published: + type: boolean + label: 'Is published' + default_revision: + type: boolean + label: 'Is default revision' + workflow.type_settings.content_moderation: type: mapping mapping: states: type: sequence - label: 'Additional state configuration for content moderation' + label: 'Content moderation states' sequence: - type: mapping + type: content_moderation.state label: 'States' - mapping: - published: - type: boolean - label: 'Is published' - default_revision: - type: boolean - label: 'Is default revision' + transitions: + type: sequence + sequence: + type: workflows.transition + label: 'Transitions' entity_types: type: sequence label: 'Entity types' diff --git a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php index b80ccdda87..679409a882 100644 --- a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php +++ b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php @@ -94,20 +94,6 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function initializeWorkflow(WorkflowInterface $workflow) { - $workflow - ->addState('draft', $this->t('Draft')) - ->setStateWeight('draft', -5) - ->addState('published', $this->t('Published')) - ->setStateWeight('published', 0) - ->addTransition('create_new_draft', $this->t('Create New Draft'), ['draft', 'published'], 'draft') - ->addTransition('publish', $this->t('Publish'), ['draft', 'published'], 'published'); - return $workflow; - } - - /** - * {@inheritdoc} - */ public function checkWorkflowAccess(WorkflowInterface $entity, $operation, AccountInterface $account) { if ($operation === 'view') { return AccessResult::allowedIfHasPermission($account, 'view content moderation'); @@ -243,16 +229,39 @@ public function addEntityTypeAndBundle($entity_type_id, $bundle_id) { * {@inheritdoc} */ public function defaultConfiguration() { - // This plugin does not store anything per transition. return [ 'states' => [ 'draft' => [ + 'label' => 'Draft', 'published' => FALSE, 'default_revision' => FALSE, + 'weight' => 0, ], 'published' => [ + 'label' => 'Published', 'published' => TRUE, 'default_revision' => TRUE, + 'weight' => 1, + ], + ], + 'transitions' => [ + 'create_new_draft' => [ + 'label' => 'Create New Draft', + 'to' => 'draft', + 'weight' => 0, + 'from' => [ + 'draft', + 'published', + ], + ], + 'publish' => [ + 'label' => 'Publish', + 'to' => 'published', + 'weight' => 1, + 'from' => [ + 'draft', + 'published', + ], ], ], 'entity_types' => [], diff --git a/core/modules/content_moderation/tests/src/Functional/ContentModerationWorkflowTypeTest.php b/core/modules/content_moderation/tests/src/Functional/ContentModerationWorkflowTypeTest.php index ff37bd11a9..66065ba9f8 100644 --- a/core/modules/content_moderation/tests/src/Functional/ContentModerationWorkflowTypeTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ContentModerationWorkflowTypeTest.php @@ -50,6 +50,9 @@ public function testNewWorkflow() { $this->assertSession()->pageTextContains('Create New Draft'); $this->assertSession()->pageTextContains('Publish'); + $this->assertSession()->linkByHrefNotExists('/admin/config/workflow/workflows/manage/test_workflow/state/draft/delete'); + $this->assertSession()->linkByHrefNotExists('/admin/config/workflow/workflows/manage/test_workflow/state/published/delete'); + // Ensure after a workflow is created, the bundle information can be // refreshed. $entity_bundle_info->clearCachedBundles(); @@ -63,6 +66,15 @@ public function testNewWorkflow() { 'type_settings[content_moderation][default_revision]' => FALSE, ], 'Save'); $this->assertSession()->pageTextContains('Created Test State state.'); + $this->assertSession()->linkByHrefExists('/admin/config/workflow/workflows/manage/test_workflow/state/test_state/delete'); + + // Check there is a link to delete a default transition. + $this->assertSession()->linkByHrefExists('/admin/config/workflow/workflows/manage/test_workflow/transition/publish/delete'); + // Delete the transition. + $this->drupalGet('/admin/config/workflow/workflows/manage/test_workflow/transition/publish/delete'); + $this->submitForm([], 'Delete'); + // The link to delete the transition should now be gone. + $this->assertSession()->linkByHrefNotExists('/admin/config/workflow/workflows/manage/test_workflow/transition/publish/delete'); // Ensure that the published settings cannot be changed. $this->drupalGet('admin/config/workflow/workflows/manage/test_workflow/state/published'); diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php index 3a19d22d7a..5acf93c7a8 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php @@ -55,37 +55,13 @@ public function permissionsTestCases() { 'id' => 'simple_workflow', 'label' => 'Simple Workflow', 'type' => 'content_moderation', - 'transitions' => [ - 'publish' => [ - 'label' => 'Publish', - 'from' => ['draft'], - 'to' => 'published', - 'weight' => 0, - ], - 'unpublish' => [ - 'label' => 'Unpublish', - 'from' => ['published'], - 'to' => 'draft', - 'weight' => 0, - ], - ], - 'states' => [ - 'draft' => [ - 'label' => 'Draft', - 'weight' => -5, - ], - 'published' => [ - 'label' => 'Published', - 'weight' => 0, - ], - ], ], [ 'use simple_workflow transition publish' => [ 'title' => 'Use Publish transition from Simple Workflow workflow.', ], - 'use simple_workflow transition unpublish' => [ - 'title' => 'Use Unpublish transition from Simple Workflow workflow.', + 'use simple_workflow transition create_new_draft' => [ + 'title' => 'Use Create New Draft transition from Simple Workflow workflow.', ], ], ], diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php index 220ac5d5a6..e10756cf56 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationWorkflowTypeApiTest.php @@ -37,9 +37,6 @@ class ContentModerationWorkflowTypeApiTest extends KernelTestBase { protected function setUp() { parent::setUp(); $this->workflow = Workflow::create(['id' => 'test', 'type' => 'content_moderation']); - $this->workflow - ->addState('draft', 'Draft') - ->addState('published', 'Published'); } /** diff --git a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php index b7c3942d83..3e2e264f7e 100644 --- a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php +++ b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\content_moderation\StateTransitionValidation; use Drupal\Tests\UnitTestCase; +use Drupal\workflow_type_test\Plugin\WorkflowType\TestType; use Drupal\workflows\Entity\Workflow; use Drupal\workflows\WorkflowTypeInterface; use Drupal\workflows\WorkflowTypeManager; @@ -62,14 +63,8 @@ protected function setUpModerationInformation(ContentEntityInterface $entity) { // Create a container so that the plugin manager and workflow type can be // mocked. $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('content_moderation', Argument::any())->willReturn($workflow_type->reveal()); + $workflow_manager->createInstance('content_moderation', Argument::any())->willReturn(new TestType([], '', [])); $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); \Drupal::setContainer($container); diff --git a/core/modules/workflows/config/schema/workflows.schema.yml b/core/modules/workflows/config/schema/workflows.schema.yml index e19eb6b892..12328013ea 100644 --- a/core/modules/workflows/config/schema/workflows.schema.yml +++ b/core/modules/workflows/config/schema/workflows.schema.yml @@ -14,38 +14,32 @@ workflows.workflow.*: type_settings: type: workflow.type_settings.[%parent.type] label: 'Custom settings for workflow type' - states: - type: sequence - label: 'States' - sequence: - type: mapping - label: 'State' - mapping: - label: - type: label - label: 'Label' - weight: - type: integer - label: 'Weight' - transitions: + +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: 'Transitions' + label: 'From state IDs' sequence: - type: mapping - label: 'Transition from state to state' - 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' + type: string + label: 'From state ID' + to: + type: string + label: 'To state ID' + weight: + type: integer + label: 'Weight' diff --git a/core/modules/workflows/src/Entity/Workflow.php b/core/modules/workflows/src/Entity/Workflow.php index 51bdda2b24..6a7d6f7236 100644 --- a/core/modules/workflows/src/Entity/Workflow.php +++ b/core/modules/workflows/src/Entity/Workflow.php @@ -54,8 +54,6 @@ * config_export = { * "id", * "label", - * "states", - * "transitions", * "type", * "type_settings" * }, @@ -82,38 +80,6 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface, EntityWith protected $label; /** - * The states of the workflow. - * - * The array key is the machine name for the state. The structure of each - * array item is: - * @code - * label: {translatable label} - * weight: {integer value} - * @endcode - * - * @var array - */ - protected $states = []; - - /** - * The permitted transitions of the workflow. - * - * The array key is the machine name for the transition. The machine name is - * generated from the machine names of the states. The structure of each array - * item is: - * @code - * from: - * - {state machine name} - * - {state machine name} - * to: {state machine name} - * label: {translatable label} - * @endcode - * - * @var array - */ - protected $transitions = []; - - /** * The workflow type plugin ID. * * @see \Drupal\workflows\WorkflowTypeManager @@ -148,399 +114,206 @@ public function preSave(EntityStorageInterface $storage) { } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function addState($state_id, $label) { - if (isset($this->states[$state_id])) { - throw new \InvalidArgumentException("The state '$state_id' already exists in workflow '{$this->id()}'"); - } - if (preg_match('/[^a-z0-9_]+/', $state_id)) { - throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores"); - } - $this->states[$state_id] = [ - 'label' => $label, - 'weight' => $this->getNextWeight($this->states), - ]; - ksort($this->states); - return $this; + public function getTypePlugin() { + return $this->getPluginCollection()->get($this->type); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function hasState($state_id) { - return isset($this->states[$state_id]); + public function getPluginCollections() { + return ['type_settings' => $this->getPluginCollection()]; } /** - * {@inheritdoc} + * Encapsulates the creation of the workflow's plugin collection. + * + * @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection + * The workflow's plugin collection. */ - public function getStates($state_ids = NULL) { - if ($state_ids === NULL) { - $state_ids = array_keys($this->states); - } - /** @var \Drupal\workflows\StateInterface[] $states */ - $states = array_combine($state_ids, array_map([$this, 'getState'], $state_ids)); - if (count($states) > 1) { - // Sort states by weight and then label. - $weights = $labels = []; - foreach ($states as $id => $state) { - $weights[$id] = $state->weight(); - $labels[$id] = $state->label(); - } - array_multisort( - $weights, SORT_NUMERIC, SORT_ASC, - $labels, SORT_NATURAL, SORT_ASC - ); - $states = array_replace($weights, $states); + protected function getPluginCollection() { + if (!$this->pluginCollection && $this->type) { + $this->pluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.workflows.type'), $this->type, $this->type_settings); } - return $states; + return $this->pluginCollection; } /** - * {@inheritdoc} + * 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 function getState($state_id) { - if (!isset($this->states[$state_id])) { - throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'"); - } - $state = new State( - $this, - $state_id, - $this->states[$state_id]['label'], - $this->states[$state_id]['weight'] - ); - return $this->getTypePlugin()->decorateState($state); + public static function loadMultipleByType($type) { + return self::loadMultiple(\Drupal::entityQuery('workflow')->condition('type', $type)->execute()); } /** * {@inheritdoc} */ - public function setStateLabel($state_id, $label) { - if (!isset($this->states[$state_id])) { - throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'"); - } - $this->states[$state_id]['label'] = $label; - return $this; + 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 setStateWeight($state_id, $weight) { - if (!isset($this->states[$state_id])) { - throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'"); - } - $this->states[$state_id]['weight'] = $weight; - return $this; + public function onDependencyRemoval(array $dependencies) { + $changed = $this->getTypePlugin()->onDependencyRemoval($dependencies); + // Ensure the parent method is called in order to process dependencies that + // affect third party settings. + return parent::onDependencyRemoval($dependencies) || $changed; } /** * {@inheritdoc} */ - public function deleteState($state_id) { - if (!isset($this->states[$state_id])) { - throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'"); - } - if (count($this->states) === 1) { - throw new \InvalidArgumentException("The state '$state_id' can not be deleted from workflow '{$this->id()}' as it is the only state"); - } - - foreach ($this->transitions as $transition_id => $transition) { - $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']) || $transition['to'] === $state_id) { - $this->deleteTransition($transition_id); - } - elseif ($from_key !== FALSE) { - $this->setTransitionFromStates($transition_id, $transition['from']); - } - } - unset($this->states[$state_id]); - $this->getTypePlugin()->deleteState($state_id); + public function addState($state_id, $label) { + $this->getTypePlugin()->addState($state_id, $label); return $this; } /** * {@inheritdoc} */ - public function addTransition($transition_id, $label, array $from_state_ids, $to_state_id) { - if (isset($this->transitions[$transition_id])) { - throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow '{$this->id()}'"); - } - if (preg_match('/[^a-z0-9_]+/', $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->id()}'"); - } - $this->transitions[$transition_id] = [ - 'label' => $label, - 'from' => [], - 'to' => $to_state_id, - // Always add to the end. - 'weight' => $this->getNextWeight($this->transitions), - ]; - - try { - $this->setTransitionFromStates($transition_id, $from_state_ids); - } - catch (\InvalidArgumentException $e) { - unset($this->transitions[$transition_id]); - throw $e; - } - - return $this; + public function hasState($state_id) { + return $this->getTypePlugin()->hasState($state_id); } /** * {@inheritdoc} */ - public function getTransitions(array $transition_ids = NULL) { - if ($transition_ids === NULL) { - $transition_ids = array_keys($this->transitions); - } - /** @var \Drupal\workflows\TransitionInterface[] $transitions */ - $transitions = array_combine($transition_ids, array_map([$this, 'getTransition'], $transition_ids)); - if (count($transitions) > 1) { - // Sort transitions by weights and then labels. - $weights = $labels = []; - foreach ($transitions as $id => $transition) { - $weights[$id] = $transition->weight(); - $labels[$id] = $transition->label(); - } - array_multisort( - $weights, SORT_NUMERIC, SORT_ASC, - $labels, SORT_NATURAL, SORT_ASC - ); - $transitions = array_replace($weights, $transitions); - } - return $transitions; + public function getStates($state_ids = NULL) { + return $this->getTypePlugin()->getStates($state_ids); } /** * {@inheritdoc} */ - public function getTransition($transition_id) { - if (!isset($this->transitions[$transition_id])) { - throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'"); - } - $transition = new Transition( - $this, - $transition_id, - $this->transitions[$transition_id]['label'], - $this->transitions[$transition_id]['from'], - $this->transitions[$transition_id]['to'], - $this->transitions[$transition_id]['weight'] - ); - return $this->getTypePlugin()->decorateTransition($transition); + public function getState($state_id) { + return $this->getTypePlugin()->getState($state_id); } /** * {@inheritdoc} */ - public function hasTransition($transition_id) { - return isset($this->transitions[$transition_id]); + public function setStateLabel($state_id, $label) { + $this->getTypePlugin()->setStateLabel($state_id, $label); + return $this; } /** * {@inheritdoc} */ - public function getTransitionsForState($state_id, $direction = 'from') { - $transition_ids = array_keys(array_filter($this->transitions, function ($transition) use ($state_id, $direction) { - return in_array($state_id, (array) $transition[$direction], TRUE); - })); - return $this->getTransitions($transition_ids); + public function setStateWeight($state_id, $weight) { + $this->getTypePlugin()->setStateWeight($state_id, $weight); + return $this; } /** * {@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 '{$this->id()}'"); - } - return $this->getTransition($transition_id); + public function deleteState($state_id) { + $this->getTypePlugin()->deleteState($state_id); + return $this; } /** * {@inheritdoc} */ - public function hasTransitionFromStateToState($from_state_id, $to_state_id) { - return !empty($this->getTransitionIdFromStateToState($from_state_id, $to_state_id)); - } - - /** - * 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->transitions as $transition_id => $transition) { - if (in_array($from_state_id, $transition['from'], TRUE) && $transition['to'] === $to_state_id) { - return $transition_id; - } - } - return NULL; + public function getInitialState() { + return $this->getTypePlugin()->getInitialState($this); } /** * {@inheritdoc} */ - public function setTransitionLabel($transition_id, $label) { - if (isset($this->transitions[$transition_id])) { - $this->transitions[$transition_id]['label'] = $label; - } - else { - throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'"); - } + public function addTransition($id, $label, array $from_state_ids, $to_state_id) { + $this->getTypePlugin()->addTransition($id, $label, $from_state_ids, $to_state_id); return $this; } /** * {@inheritdoc} */ - public function setTransitionWeight($transition_id, $weight) { - if (isset($this->transitions[$transition_id])) { - $this->transitions[$transition_id]['weight'] = $weight; - } - else { - throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'"); - } - return $this; + public function getTransition($transition_id) { + return $this->getTypePlugin()->getTransition($transition_id); } /** * {@inheritdoc} */ - public function setTransitionFromStates($transition_id, array $from_state_ids) { - if (!isset($this->transitions[$transition_id])) { - throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'"); - } - - // 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 '{$this->id()}'"); - } - if ($this->hasTransitionFromStateToState($from_state_id, $this->transitions[$transition_id]['to'])) { - $transition = $this->getTransitionFromStateToState($from_state_id, $this->transitions[$transition_id]['to']); - if ($transition_id !== $transition->id()) { - throw new \InvalidArgumentException("The '{$transition->id()}' transition already allows '$from_state_id' to '{$this->transitions[$transition_id]['to']}' transitions in workflow '{$this->id()}'"); - } - } - } - - // 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->transitions[$transition_id]['from'] = $from_state_ids; - ksort($this->transitions); - - return $this; + public function hasTransition($transition_id) { + return $this->getTypePlugin()->hasTransition($transition_id); } /** * {@inheritdoc} */ - public function deleteTransition($transition_id) { - if (isset($this->transitions[$transition_id])) { - unset($this->transitions[$transition_id]); - $this->getTypePlugin()->deleteTransition($transition_id); - } - else { - throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'"); - } - return $this; + public function getTransitions(array $transition_ids = NULL) { + return $this->getTypePlugin()->getTransitions($transition_ids); } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getTypePlugin() { - return $this->getPluginCollection()->get($this->type); + public function getTransitionsForState($state_id, $direction = 'from') { + return $this->getTypePlugin()->getTransitionsForState($state_id, $direction); } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getPluginCollections() { - return ['type_settings' => $this->getPluginCollection()]; + public function getTransitionFromStateToState($from_state_id, $to_state_id) { + return $this->getTypePlugin()->getTransitionFromStateToState($from_state_id, $to_state_id); } /** - * Encapsulates the creation of the workflow's plugin collection. - * - * @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection - * The workflow's plugin collection. + * {@inheritdoc} */ - 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; + public function hasTransitionFromStateToState($from_state_id, $to_state_id) { + return $this->getTypePlugin()->hasTransitionFromStateToState($from_state_id, $to_state_id); } /** - * 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 + * {@inheritdoc} */ - public static function loadMultipleByType($type) { - return self::loadMultiple(\Drupal::entityQuery('workflow')->condition('type', $type)->execute()); + public function setTransitionLabel($transition_id, $label) { + $this->getTypePlugin()->setTransitionLabel($transition_id, $label); + 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. + * {@inheritdoc} */ - protected function getNextWeight(array $items) { - return array_reduce($items, function ($carry, $item) { - return max($carry, $item['weight'] + 1); - }, 0); + public function setTransitionWeight($transition_id, $weight) { + $this->getTypePlugin()->setTransitionWeight($transition_id, $weight); + return $this; } /** * {@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->states); + public function setTransitionFromStates($transition_id, array $from_state_ids) { + $this->getTypePlugin()->setTransitionFromStates($transition_id, $from_state_ids); + return $this; } /** * {@inheritdoc} */ - public function onDependencyRemoval(array $dependencies) { - $changed = $this->getTypePlugin()->onDependencyRemoval($dependencies); - // Ensure the parent method is called in order to process dependencies that - // affect third party settings. - return parent::onDependencyRemoval($dependencies) || $changed; + public function deleteTransition($transition_id) { + $this->getTypePlugin()->deleteTransition($transition_id); + return $this; } } diff --git a/core/modules/workflows/src/Form/WorkflowStateAddForm.php b/core/modules/workflows/src/Form/WorkflowStateAddForm.php index 1827dd3e89..8001d44944 100644 --- a/core/modules/workflows/src/Form/WorkflowStateAddForm.php +++ b/core/modules/workflows/src/Form/WorkflowStateAddForm.php @@ -88,7 +88,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, $entity->addState($values['id'], $values['label']); if (isset($values['type_settings'])) { $configuration = $entity->getTypePlugin()->getConfiguration(); - $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; + $configuration['states'][$values['id']] += $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; $entity->set('type_settings', $configuration); } } diff --git a/core/modules/workflows/src/Form/WorkflowStateEditForm.php b/core/modules/workflows/src/Form/WorkflowStateEditForm.php index f21508ff64..dea6f74f6f 100644 --- a/core/modules/workflows/src/Form/WorkflowStateEditForm.php +++ b/core/modules/workflows/src/Form/WorkflowStateEditForm.php @@ -127,7 +127,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, $entity->setStateLabel($values['id'], $values['label']); if (isset($values['type_settings'])) { $configuration = $entity->getTypePlugin()->getConfiguration(); - $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; + $configuration['states'][$values['id']] += $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; $entity->set('type_settings', $configuration); } } diff --git a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php index fe3a40636d..a505e699ef 100644 --- a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php +++ b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php @@ -107,7 +107,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']); if (isset($values['type_settings'])) { $configuration = $entity->getTypePlugin()->getConfiguration(); - $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; + $configuration['transitions'][$values['id']] += $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; $entity->set('type_settings', $configuration); } } diff --git a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php b/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php index 5bbabae6f7..e3ba52d16e 100644 --- a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php +++ b/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php @@ -130,7 +130,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, $entity->setTransitionFromStates($values['id'], array_filter($values['from'])); if (isset($values['type_settings'])) { $configuration = $entity->getTypePlugin()->getConfiguration(); - $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; + $configuration['transitions'][$values['id']] += $values['type_settings'][$entity->getTypePlugin()->getPluginId()]; $entity->set('type_settings', $configuration); } } diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php index 34af735e3d..44a52856f4 100644 --- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php +++ b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php @@ -7,7 +7,9 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\workflows\State; use Drupal\workflows\StateInterface; +use Drupal\workflows\Transition; use Drupal\workflows\TransitionInterface; use Drupal\workflows\WorkflowInterface; use Drupal\workflows\WorkflowTypeInterface; @@ -65,13 +67,6 @@ public function decorateState(StateInterface $state) { /** * {@inheritdoc} */ - public function deleteState($state_id) { - unset($this->configuration['states'][$state_id]); - } - - /** - * {@inheritdoc} - */ public function decorateTransition(TransitionInterface $transition) { return $transition; } @@ -79,13 +74,6 @@ public function decorateTransition(TransitionInterface $transition) { /** * {@inheritdoc} */ - public function deleteTransition($transition_id) { - unset($this->configuration['transitions'][$transition_id]); - } - - /** - * {@inheritdoc} - */ public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) { return []; } @@ -108,10 +96,7 @@ public function getConfiguration() { * {@inheritdoc} */ public function setConfiguration(array $configuration) { - $this->configuration = NestedArray::mergeDeep( - $this->defaultConfiguration(), - $configuration - ); + $this->configuration = $configuration + $this->defaultConfiguration(); } /** @@ -153,4 +138,338 @@ public function getInitialState(WorkflowInterface $workflow) { return reset($ordered_states); } + /** + * {@inheritdoc} + */ + public function addState($state_id, $label) { + if (isset($this->configuration['states'][$state_id])) { + throw new \InvalidArgumentException("The state '$state_id' already exists in workflow."); + } + if (preg_match('/[^a-z0-9_]+/', $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) { + $states = $this->getStates(); + return isset($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)); + if (count($states) > 1) { + // Sort states by weight and then label. + $weights = $labels = []; + foreach ($states as $id => $state) { + $weights[$id] = $state->weight(); + $labels[$id] = $state->label(); + } + array_multisort( + $weights, SORT_NUMERIC, SORT_ASC, + $labels, SORT_NATURAL, SORT_ASC + ); + $states = array_replace($weights, $states); + } + return $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.'"); + } + $state = new State( + $this, + $state_id, + $this->configuration['states'][$state_id]['label'], + $this->configuration['states'][$state_id]['weight'] + ); + return $this->decorateState($state); + } + + /** + * {@inheritdoc} + */ + public function setStateLabel($state_id, $label) { + if (!isset($this->configuration['states'][$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 (!isset($this->configuration['states'][$state_id])) { + throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.'"); + } + $this->configuration['states'][$state_id]['weight'] = $weight; + return $this; + } + + /** + * {@inheritdoc} + */ + public function deleteState($state_id) { + if (!isset($this->configuration['states'][$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) { + $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']) || $transition['to'] === $state_id) { + $this->deleteTransition($transition_id); + } + elseif ($from_key !== FALSE) { + $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 (isset($this->configuration['transitions'][$transition_id])) { + throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow.'"); + } + if (preg_match('/[^a-z0-9_]+/', $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; + } + + 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)); + if (count($transitions) > 1) { + // Sort transitions by weights and then labels. + $weights = $labels = []; + foreach ($transitions as $id => $transition) { + $weights[$id] = $transition->weight(); + $labels[$id] = $transition->label(); + } + array_multisort( + $weights, SORT_NUMERIC, SORT_ASC, + $labels, SORT_NATURAL, SORT_ASC + ); + $transitions = array_replace($weights, $transitions); + } + return $transitions; + } + + /** + * {@inheritdoc} + */ + public function getTransition($transition_id) { + if (!isset($this->configuration['transitions'][$transition_id])) { + throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.'"); + } + $transition = 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'] + ); + return $this->decorateTransition($transition); + } + + /** + * {@inheritdoc} + */ + public function hasTransition($transition_id) { + return isset($this->configuration['transitions'][$transition_id]); + } + + /** + * {@inheritdoc} + */ + public function getTransitionsForState($state_id, $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 !empty($this->getTransitionIdFromStateToState($from_state_id, $to_state_id)); + } + + /** + * 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 (isset($this->configuration['transitions'][$transition_id])) { + $this->configuration['transitions'][$transition_id]['label'] = $label; + } + else { + throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow."); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTransitionWeight($transition_id, $weight) { + if (isset($this->configuration['transitions'][$transition_id])) { + $this->configuration['transitions'][$transition_id]['weight'] = $weight; + } + else { + throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.'"); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTransitionFromStates($transition_id, array $from_state_ids) { + if (!isset($this->configuration['transitions'][$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'])) { + $transition = $this->getTransitionFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to']); + if ($transition_id !== $transition->id()) { + throw new \InvalidArgumentException("The '{$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; + ksort($this->configuration['transitions']); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function deleteTransition($transition_id) { + if (isset($this->configuration['transitions'][$transition_id])) { + unset($this->configuration['transitions'][$transition_id]); + } + else { + throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow."); + } + 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); + } + } diff --git a/core/modules/workflows/src/State.php b/core/modules/workflows/src/State.php index f8b4faea3c..f6a377ba0f 100644 --- a/core/modules/workflows/src/State.php +++ b/core/modules/workflows/src/State.php @@ -51,7 +51,7 @@ class State implements StateInterface { * @param int $weight * The state's weight. */ - public function __construct(WorkflowInterface $workflow, $id, $label, $weight = 0) { + public function __construct(WorkflowTypeInterface $workflow, $id, $label, $weight = 0) { $this->workflow = $workflow; $this->id = $id; $this->label = $label; diff --git a/core/modules/workflows/src/Transition.php b/core/modules/workflows/src/Transition.php index d8c21b6394..38d5dbac03 100644 --- a/core/modules/workflows/src/Transition.php +++ b/core/modules/workflows/src/Transition.php @@ -69,7 +69,7 @@ class Transition implements TransitionInterface { * @param int $weight * (optional) The transition's weight. Defaults to 0. */ - public function __construct(WorkflowInterface $workflow, $id, $label, array $from_state_ids, $to_state_id, $weight = 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; diff --git a/core/modules/workflows/src/WorkflowInterface.php b/core/modules/workflows/src/WorkflowInterface.php index 4f241c34f8..7b106c5e37 100644 --- a/core/modules/workflows/src/WorkflowInterface.php +++ b/core/modules/workflows/src/WorkflowInterface.php @@ -23,6 +23,8 @@ * * @return \Drupal\workflows\WorkflowInterface * The workflow entity. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function addState($state_id, $label); @@ -34,6 +36,8 @@ public function addState($state_id, $label); * * @return bool * TRUE if the workflow has a state with the provided ID, FALSE if not. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function hasState($state_id); @@ -48,6 +52,8 @@ public function hasState($state_id); * * @throws \InvalidArgumentException * Thrown if $state_ids contains a state ID that does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getStates($state_ids = NULL); @@ -62,6 +68,8 @@ public function getStates($state_ids = NULL); * * @throws \InvalidArgumentException * Thrown if $state_id does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getState($state_id); @@ -75,6 +83,8 @@ public function getState($state_id); * * @return \Drupal\workflows\WorkflowInterface * The workflow entity. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function setStateLabel($state_id, $label); @@ -88,6 +98,8 @@ public function setStateLabel($state_id, $label); * * @return \Drupal\workflows\WorkflowInterface * The workflow entity. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function setStateWeight($state_id, $weight); @@ -102,10 +114,22 @@ public function setStateWeight($state_id, $weight); * * @throws \InvalidArgumentException * Thrown if $state_id does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function deleteState($state_id); /** + * Gets the initial state for the workflow. + * + * @return \Drupal\workflows\StateInterface + * The initial state. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. + */ + public function getInitialState(); + + /** * Adds a transition to the workflow. * * @param string $id @@ -122,6 +146,8 @@ public function deleteState($state_id); * * @throws \InvalidArgumentException * Thrown if either state does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function addTransition($id, $label, array $from_state_ids, $to_state_id); @@ -136,6 +162,8 @@ public function addTransition($id, $label, array $from_state_ids, $to_state_id); * * @throws \InvalidArgumentException * Thrown if $transition_id does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getTransition($transition_id); @@ -147,6 +175,8 @@ public function getTransition($transition_id); * * @return bool * TRUE if the transition exists, FALSE if not. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function hasTransition($transition_id); @@ -162,6 +192,8 @@ public function hasTransition($transition_id); * * @throws \InvalidArgumentException * Thrown if $transition_ids contains a transition ID that does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getTransitions(array $transition_ids = NULL); @@ -176,6 +208,8 @@ public function getTransitions(array $transition_ids = NULL); * * @return array * The transition IDs for a state for the provided direction. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getTransitionsForState($state_id, $direction = 'from'); @@ -192,6 +226,8 @@ public function getTransitionsForState($state_id, $direction = 'from'); * * @throws \InvalidArgumentException * Thrown if the transition does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function getTransitionFromStateToState($from_state_id, $to_state_id); @@ -205,6 +241,8 @@ public function getTransitionFromStateToState($from_state_id, $to_state_id); * * @return bool * TRUE if the transition exists, FALSE if not. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function hasTransitionFromStateToState($from_state_id, $to_state_id); @@ -221,6 +259,8 @@ public function hasTransitionFromStateToState($from_state_id, $to_state_id); * * @throws \InvalidArgumentException * Thrown if the transition does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function setTransitionLabel($transition_id, $label); @@ -237,6 +277,8 @@ public function setTransitionLabel($transition_id, $label); * * @throws \InvalidArgumentException * Thrown if the transition does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function setTransitionWeight($transition_id, $weight); @@ -253,6 +295,8 @@ public function setTransitionWeight($transition_id, $weight); * * @throws \InvalidArgumentException * Thrown if the transition does not exist or the states do not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function setTransitionFromStates($transition_id, array $from_state_ids); @@ -267,6 +311,8 @@ public function setTransitionFromStates($transition_id, array $from_state_ids) * * @throws \InvalidArgumentException * Thrown if the transition does not exist. + * + * @deprecated in Drupal 8.4.x and will be removed before 8.4.0. */ public function deleteTransition($transition_id); diff --git a/core/modules/workflows/src/WorkflowTypeInterface.php b/core/modules/workflows/src/WorkflowTypeInterface.php index 2412bf99f4..009f41c6da 100644 --- a/core/modules/workflows/src/WorkflowTypeInterface.php +++ b/core/modules/workflows/src/WorkflowTypeInterface.php @@ -68,14 +68,6 @@ public function checkWorkflowAccess(WorkflowInterface $entity, $operation, Accou public function decorateState(StateInterface $state); /** - * React to the removal of a state from a workflow. - * - * @param string $state_id - * The state ID of the state that is being removed. - */ - public function deleteState($state_id); - - /** * Decorates transitions so the WorkflowType can add additional information. * @param \Drupal\workflows\TransitionInterface $transition * The transition object to decorate. @@ -86,14 +78,6 @@ public function deleteState($state_id); public function decorateTransition(TransitionInterface $transition); /** - * React to the removal of a transition from a workflow. - * - * @param string $transition_id - * The transition ID of the transition that is being removed. - */ - public function deleteTransition($transition_id); - - /** * Gets the initial state for the workflow. * * @param \Drupal\workflows\WorkflowInterface $workflow @@ -170,4 +154,261 @@ public function getRequiredStates(); */ 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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + */ + 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. + * + * @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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + */ + 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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + */ + 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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + * + * @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 'from'. Possible + * values are: 'from' and 'to'. + * + * @return array + * The transition IDs for a state for the provided direction. + */ + public function getTransitionsForState($state_id, $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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + * + * @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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + * + * @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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + * + * @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 \Drupal\workflows\WorkflowTypeInterface + * The workflow type plugin. + * + * @throws \InvalidArgumentException + * Thrown if the transition does not exist. + */ + public function deleteTransition($transition_id); + } diff --git a/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml b/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml index 1502edc04e..1d9e74282b 100644 --- a/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml +++ b/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml @@ -6,6 +6,10 @@ workflow.type_settings.workflow_type_test: type: sequence sequence: type: ignore + transitions: + type: sequence + sequence: + type: ignore workflow.type_settings.workflow_type_required_state_test: type: mapping @@ -15,6 +19,26 @@ workflow.type_settings.workflow_type_required_state_test: 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 @@ -25,21 +49,24 @@ workflow.type_settings.workflow_type_complex_test: label: 'Example setting' states: type: sequence - label: 'Additional state configuration' + label: 'States' sequence: - type: mapping + type: workflows.state.complex_test_state label: 'States' - mapping: - extra: - type: string - label: 'Extra information' transitions: type: sequence - label: 'Additional transition configuration' + 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: - type: mapping label: 'Transitions' - mapping: - extra: - type: string - label: 'Extra information' + type: workflows.transition diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php index 2cc25afcd6..43d976c57e 100644 --- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php +++ b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php @@ -28,7 +28,8 @@ class ComplexTestType extends WorkflowTypeFormBase { */ public function decorateState(StateInterface $state) { if (isset($this->configuration['states'][$state->id()])) { - $state = new DecoratedState($state, $this->configuration['states'][$state->id()]['extra']); + $extra = isset($this->configuration['states'][$state->id()]['extra']) ? $this->configuration['states'][$state->id()]['extra'] : ''; + $state = new DecoratedState($state, $extra); } else { $state = new DecoratedState($state); @@ -41,7 +42,8 @@ public function decorateState(StateInterface $state) { */ public function decorateTransition(TransitionInterface $transition) { if (isset($this->configuration['transitions'][$transition->id()])) { - $transition = new DecoratedTransition($transition, $this->configuration['transitions'][$transition->id()]['extra']); + $extra = isset($this->configuration['transitions'][$transition->id()]['extra']) ? $this->configuration['transitions'][$transition->id()]['extra'] : ''; + $transition = new DecoratedTransition($transition, $extra); } else { $transition = new DecoratedTransition($transition); diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/PredefinedStatesWorkflowTestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/PredefinedStatesWorkflowTestType.php new file mode 100644 index 0000000000..3417477d8d --- /dev/null +++ b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/PredefinedStatesWorkflowTestType.php @@ -0,0 +1,90 @@ + 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 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' => [], + ]; + } + +} diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/RequiredStateTestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/RequiredStateTestType.php index 493355715a..a6d0695123 100644 --- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/RequiredStateTestType.php +++ b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/RequiredStateTestType.php @@ -34,12 +34,4 @@ public function initializeWorkflow(WorkflowInterface $workflow) { return $workflow; } - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - // No configuration is stored for the test type. - return []; - } - } diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php index 2ff68a84ad..ddd074a019 100644 --- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php +++ b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php @@ -17,14 +17,6 @@ class TestType extends WorkflowTypeBase { /** * {@inheritdoc} */ - public function defaultConfiguration() { - // No configuration is stored for the test type. - return []; - } - - /** - * {@inheritdoc} - */ public function getRequiredStates() { // Normally this is obtained from the annotation but we get from state to // allow dynamic testing. diff --git a/core/modules/workflows/tests/src/Kernel/PredefinedWorkflowTypeTest.php b/core/modules/workflows/tests/src/Kernel/PredefinedWorkflowTypeTest.php new file mode 100644 index 0000000000..f3f0b5768b --- /dev/null +++ b/core/modules/workflows/tests/src/Kernel/PredefinedWorkflowTypeTest.php @@ -0,0 +1,52 @@ + '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'])); + } + +} diff --git a/core/modules/workflows/tests/src/Kernel/RequiredStatesTest.php b/core/modules/workflows/tests/src/Kernel/RequiredStatesTest.php index c9ef7151d7..d99d308c49 100644 --- a/core/modules/workflows/tests/src/Kernel/RequiredStatesTest.php +++ b/core/modules/workflows/tests/src/Kernel/RequiredStatesTest.php @@ -106,11 +106,11 @@ public function testChangeRequiredStateAPI() { 'cooked', 'fresh', 'rotten', - ], array_keys($workflow->get('states'))); + ], array_keys($workflow->getTypePlugin()->getConfiguration()['states'])); $this->assertSame([ 'cook', 'rot', - ], array_keys($workflow->get('transitions'))); + ], array_keys($workflow->getTypePlugin()->getConfiguration()['transitions'])); // Ensure that transitions can be deleted. $workflow->deleteTransition('rot')->save(); diff --git a/core/modules/workflows/tests/src/Unit/StateTest.php b/core/modules/workflows/tests/src/Unit/StateTest.php index ae9fc46202..548225d9fe 100644 --- a/core/modules/workflows/tests/src/Unit/StateTest.php +++ b/core/modules/workflows/tests/src/Unit/StateTest.php @@ -4,6 +4,7 @@ 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\WorkflowInterface; @@ -19,28 +20,6 @@ class StateTest extends UnitTestCase { /** - * Sets up the Workflow Type manager so that workflow entities can be used. - */ - protected function setUp() { - parent::setUp(); - // Create a container so that the plugin manager and workflow type can be - // mocked. - $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); - $workflow_type->deleteState(Argument::any())->willReturn(NULL); - $workflow_type->deleteTransition(Argument::any())->willReturn(NULL); - $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal()); - $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); - \Drupal::setContainer($container); - } - - /** * @covers ::__construct * @covers ::id * @covers ::label @@ -48,7 +27,7 @@ protected function setUp() { */ public function testGetters() { $state = new State( - $this->prophesize(WorkflowInterface::class)->reveal(), + $this->prophesize(WorkflowTypeInterface::class)->reveal(), 'draft', 'Draft', 3 @@ -62,16 +41,16 @@ public function testGetters() { * @covers ::canTransitionTo */ public function testCanTransitionTo() { - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow + $workflow_type = new TestType([], '', []); + $workflow_type ->addState('draft', 'Draft') ->addState('published', 'Published') ->addTransition('publish', 'Publish', ['draft'], 'published'); - $state = $workflow->getState('draft'); + $state = $workflow_type->getState('draft'); $this->assertTrue($state->canTransitionTo('published')); $this->assertFalse($state->canTransitionTo('some_other_state')); - $workflow->deleteTransition('publish'); + $workflow_type->deleteTransition('publish'); $this->assertFalse($state->canTransitionTo('published')); } @@ -79,12 +58,12 @@ public function testCanTransitionTo() { * @covers ::getTransitionTo */ public function testGetTransitionTo() { - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow + $workflow_type = new TestType([], '', []); + $workflow_type ->addState('draft', 'Draft') ->addState('published', 'Published') ->addTransition('publish', 'Publish', ['draft'], 'published'); - $state = $workflow->getState('draft'); + $state = $workflow_type->getState('draft'); $transition = $state->getTransitionTo('published'); $this->assertEquals('Publish', $transition->label()); } @@ -94,9 +73,9 @@ public function testGetTransitionTo() { */ public function testGetTransitionToException() { $this->setExpectedException(\InvalidArgumentException::class, "Can not transition to 'published' state"); - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow->addState('draft', 'Draft'); - $state = $workflow->getState('draft'); + $workflow_type = new TestType([], '', []); + $workflow_type->addState('draft', 'Draft'); + $state = $workflow_type->getState('draft'); $state->getTransitionTo('published'); } @@ -104,15 +83,15 @@ public function testGetTransitionToException() { * @covers ::getTransitions */ public function testGetTransitions() { - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow + $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->getState('draft'); + $state = $workflow_type->getState('draft'); $transitions = $state->getTransitions(); $this->assertCount(2, $transitions); $this->assertEquals('Create new draft', $transitions['create_new_draft']->label()); @@ -123,10 +102,10 @@ public function testGetTransitions() { * @covers ::labelCallback */ public function testLabelCallback() { - $workflow = $this->prophesize(WorkflowInterface::class)->reveal(); + $workflow_type = $this->prophesize(WorkflowTypeInterface::class)->reveal(); $states = [ - new State($workflow, 'draft', 'Draft'), - new State($workflow, 'published', 'Published'), + new State($workflow_type, 'draft', 'Draft'), + new State($workflow_type, 'published', 'Published'), ]; $this->assertEquals(['Draft', 'Published'], array_map([State::class, 'labelCallback'], $states)); } diff --git a/core/modules/workflows/tests/src/Unit/TransitionTest.php b/core/modules/workflows/tests/src/Unit/TransitionTest.php index c343fed927..be59c941b7 100644 --- a/core/modules/workflows/tests/src/Unit/TransitionTest.php +++ b/core/modules/workflows/tests/src/Unit/TransitionTest.php @@ -4,6 +4,7 @@ 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\Transition; use Drupal\workflows\WorkflowInterface; @@ -19,33 +20,13 @@ class TransitionTest extends UnitTestCase { /** - * Sets up the Workflow Type manager so that workflow entities can be used. - */ - protected function setUp() { - parent::setUp(); - // Create a container so that the plugin manager and workflow type can be - // mocked. - $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); - $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal()); - $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); - \Drupal::setContainer($container); - } - - /** * @covers ::__construct * @covers ::id * @covers ::label */ public function testGetters() { $state = new Transition( - $this->prophesize(WorkflowInterface::class)->reveal(), + $this->prophesize(WorkflowTypeInterface::class)->reveal(), 'draft_published', 'Publish', ['draft'], @@ -60,7 +41,7 @@ public function testGetters() { * @covers ::to */ public function testFromAndTo() { - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); + $workflow = new TestType([], '', []); $workflow ->addState('draft', 'Draft') ->addState('published', 'Published') diff --git a/core/modules/workflows/tests/src/Unit/WorkflowTest.php b/core/modules/workflows/tests/src/Unit/WorkflowTest.php index 51cb912070..67c8205659 100644 --- a/core/modules/workflows/tests/src/Unit/WorkflowTest.php +++ b/core/modules/workflows/tests/src/Unit/WorkflowTest.php @@ -4,6 +4,7 @@ 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; @@ -26,14 +27,8 @@ protected function setUp() { // Create a container so that the plugin manager and workflow type can be // mocked. $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal()); + $workflow_manager->createInstance('test_type', Argument::any())->willReturn(new TestType([], '', [])); $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); \Drupal::setContainer($container); } @@ -64,7 +59,7 @@ public function testAddAndHasState() { * @covers ::addState */ public function testAddStateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('draft', 'Draft'); $workflow->addState('draft', 'Draft'); @@ -99,7 +94,7 @@ public function testGetStates() { 'archived', 'draft', 'published', - ], array_keys($workflow->get('states'))); + ], array_keys($workflow->getTypePlugin()->getConfiguration()['states'])); // Ensure we're returning state objects. $this->assertInstanceOf(State::class, $workflow->getStates()['draft']); @@ -126,7 +121,7 @@ public function testGetStates() { * @covers ::getStates */ public function testGetStatesException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'"); + $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->getStates(['state_that_does_not_exist']); } @@ -163,7 +158,7 @@ public function testGetState() { * @covers ::getState */ public function testGetStateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'"); + $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->getState('state_that_does_not_exist'); } @@ -183,7 +178,7 @@ public function testSetStateLabel() { * @covers ::setStateLabel */ public function testSetStateLabelException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->setStateLabel('draft', 'Draft'); } @@ -203,7 +198,7 @@ public function testSetStateWeight() { * @covers ::setStateWeight */ public function testSetStateWeightException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->setStateWeight('draft', 10); } @@ -212,43 +207,25 @@ public function testSetStateWeightException() { * @covers ::deleteState */ public function testDeleteState() { - // Create a container so that the plugin manager and workflow type can be - // mocked and test that - // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called - // correctly. - $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); - $workflow_type->deleteState('draft')->shouldBeCalled(); - $workflow_type->deleteTransition('create_new_draft')->shouldBeCalled(); - $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal()); - $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); - \Drupal::setContainer($container); - - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow + $workflow_type = new TestType([], '', []); + $workflow_type ->addState('draft', 'Draft') ->addState('published', 'Published') ->addTransition('publish', 'Publish', ['draft', 'published'], 'published') ->addTransition('create_new_draft', 'Create new draft', ['draft', 'published'], 'draft'); - $this->assertCount(2, $workflow->getStates()); - $this->assertCount(2, $workflow->getState('published')->getTransitions()); - $workflow->deleteState('draft'); - $this->assertFalse($workflow->hasState('draft')); - $this->assertCount(1, $workflow->getStates()); - $this->assertCount(1, $workflow->getState('published')->getTransitions()); + $this->assertCount(2, $workflow_type->getStates()); + $this->assertCount(2, $workflow_type->getState('published')->getTransitions()); + $workflow_type->deleteState('draft'); + $this->assertFalse($workflow_type->hasState('draft')); + $this->assertCount(1, $workflow_type->getStates()); + $this->assertCount(1, $workflow_type->getState('published')->getTransitions()); } /** * @covers ::deleteState */ public function testDeleteStateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->deleteState('draft'); } @@ -257,7 +234,7 @@ public function testDeleteStateException() { * @covers ::deleteState */ public function testDeleteOnlyStateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' can not be deleted from workflow 'test' as it is the only state"); + $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->addState('draft', 'Draft'); $workflow->deleteState('draft'); @@ -290,7 +267,7 @@ public function testAddTransition() { * @covers ::addTransition */ public function testAddTransitionDuplicateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('published', 'Published'); $workflow->addTransition('publish', 'Publish', ['published'], 'published'); @@ -311,7 +288,7 @@ public function testAddTransitionInvalidIdException() { * @covers ::addTransition */ public function testAddTransitionMissingFromException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('published', 'Published'); $workflow->addTransition('publish', 'Publish', ['draft'], 'published'); @@ -321,7 +298,7 @@ public function testAddTransitionMissingFromException() { * @covers ::addTransition */ public function testAddTransitionDuplicateTransitionStatesException() { - $this->setExpectedException(\InvalidArgumentException::class, "The 'publish' transition already allows 'draft' to 'published' transitions in workflow 'test'"); + $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 ->addState('draft', 'Draft') @@ -351,7 +328,7 @@ public function testAddTransitionConsistentAfterFromCatch() { * @covers ::addTransition */ public function testAddTransitionMissingToException() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('draft', 'Draft'); $workflow->addTransition('publish', 'Publish', ['draft'], 'published'); @@ -376,7 +353,7 @@ public function testGetTransitions() { ->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->get('transitions'))); + $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTypePlugin()->getConfiguration()['transitions'])); // Ensure we're returning transition objects. $this->assertInstanceOf(Transition::class, $workflow->getTransitions()['a_a']); @@ -429,7 +406,7 @@ public function testGetTransition() { * @covers ::getTransition */ public function testGetTransitionException() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition 'transition_that_does_not_exist' does not exist in workflow 'test'"); + $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->getTransition('transition_that_does_not_exist'); } @@ -482,7 +459,7 @@ public function testGetTransitionFromStateToState() { * @covers ::getTransitionFromStateToState */ public function testGetTransitionFromStateToStateException() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition from 'archived' to 'archived' does not exist in workflow 'test'"); + $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 @@ -514,7 +491,7 @@ public function testSetTransitionLabel() { * @covers ::setTransitionLabel */ public function testSetTransitionLabelException() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('published', 'Published'); $workflow->setTransitionLabel('draft-published', 'Publish'); @@ -538,7 +515,7 @@ public function testSetTransitionWeight() { * @covers ::setTransitionWeight */ public function testSetTransitionWeightException() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('published', 'Published'); $workflow->setTransitionWeight('draft-published', 10); @@ -572,7 +549,7 @@ public function testSetTransitionFromStates() { * @covers ::setTransitionFromStates */ public function testSetTransitionFromStatesMissingTransition() { - $this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow ->addState('draft', 'Draft') @@ -587,7 +564,7 @@ public function testSetTransitionFromStatesMissingTransition() { * @covers ::setTransitionFromStates */ public function testSetTransitionFromStatesMissingState() { - $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow ->addState('draft', 'Draft') @@ -601,7 +578,7 @@ public function testSetTransitionFromStatesMissingState() { * @covers ::setTransitionFromStates */ public function testSetTransitionFromStatesAlreadyExists() { - $this->setExpectedException(\InvalidArgumentException::class, "The 'create_new_draft' transition already allows 'draft' to 'draft' transitions in workflow 'test'"); + $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 ->addState('draft', 'Draft') @@ -617,40 +594,23 @@ public function testSetTransitionFromStatesAlreadyExists() { * @covers ::deleteTransition */ public function testDeleteTransition() { - // Create a container so that the plugin manager and workflow type can be - // mocked and test that - // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called - // correctly. - $container = new ContainerBuilder(); - $workflow_type = $this->prophesize(WorkflowTypeInterface::class); - $workflow_type->setConfiguration(Argument::any())->will(function ($arguments) { - $this->getConfiguration()->willReturn($arguments[0]); - }); - $workflow_type->decorateState(Argument::any())->willReturnArgument(0); - $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0); - $workflow_type->deleteTransition('publish')->shouldBeCalled(); - $workflow_manager = $this->prophesize(WorkflowTypeManager::class); - $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal()); - $container->set('plugin.manager.workflows.type', $workflow_manager->reveal()); - \Drupal::setContainer($container); - - $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); - $workflow + $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->getState('draft')->canTransitionTo('published')); - $workflow->deleteTransition('publish'); - $this->assertFalse($workflow->getState('draft')->canTransitionTo('published')); - $this->assertTrue($workflow->getState('draft')->canTransitionTo('draft')); + $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 'test'"); + $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow."); $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow'); $workflow->addState('published', 'Published'); $workflow->deleteTransition('draft-published');