diff --git a/core/modules/workflows/src/Entity/Workflow.php b/core/modules/workflows/src/Entity/Workflow.php index d652814..5caf038 100644 --- a/core/modules/workflows/src/Entity/Workflow.php +++ b/core/modules/workflows/src/Entity/Workflow.php @@ -112,14 +112,14 @@ public function preSave(EntityStorageInterface $storage) { } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getTypePlugin() { return $this->getPluginCollection()->get($this->type); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getPluginCollections() { return ['type_settings' => $this->getPluginCollection()]; diff --git a/core/modules/workflows/src/Form/WorkflowStateAddForm.php b/core/modules/workflows/src/Form/WorkflowStateAddForm.php index 8001d44..faacba6 100644 --- a/core/modules/workflows/src/Form/WorkflowStateAddForm.php +++ b/core/modules/workflows/src/Form/WorkflowStateAddForm.php @@ -88,6 +88,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, $entity->addState($values['id'], $values['label']); if (isset($values['type_settings'])) { $configuration = $entity->getTypePlugin()->getConfiguration(); + // @todo move to submitStateConfigurationForm. #2849827. $configuration['states'][$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 7e8115f..b326e96 100644 --- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php +++ b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php @@ -25,6 +25,11 @@ abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterface { /** + * A regex for matching a valid state/transition machine name. + */ + const VALID_ID_REGEX = '/[^a-z0-9_]+/'; + + /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { @@ -141,10 +146,10 @@ public function getInitialState(WorkflowInterface $workflow) { * {@inheritdoc} */ public function addState($state_id, $label) { - if (isset($this->configuration['states'][$state_id])) { + if ($this->hasState($state_id)) { throw new \InvalidArgumentException("The state '$state_id' already exists in workflow."); } - if (preg_match('/[^a-z0-9_]+/', $state_id)) { + if (preg_match(static::VALID_ID_REGEX, $state_id)) { throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores"); } $this->configuration['states'][$state_id] = [ @@ -172,20 +177,7 @@ public function getStates($state_ids = NULL) { } /** @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; + return static::labelWeightMultisort($states); } /** @@ -208,7 +200,7 @@ public function getState($state_id) { * {@inheritdoc} */ public function setStateLabel($state_id, $label) { - if (!isset($this->configuration['states'][$state_id])) { + if (!$this->hasState($state_id)) { throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.'"); } $this->configuration['states'][$state_id]['label'] = $label; @@ -219,7 +211,7 @@ public function setStateLabel($state_id, $label) { * {@inheritdoc} */ public function setStateWeight($state_id, $weight) { - if (!isset($this->configuration['states'][$state_id])) { + if (!$this->hasState($state_id)) { throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.'"); } $this->configuration['states'][$state_id]['weight'] = $weight; @@ -230,7 +222,7 @@ public function setStateWeight($state_id, $weight) { * {@inheritdoc} */ public function deleteState($state_id) { - if (!isset($this->configuration['states'][$state_id])) { + if (!$this->hasState($state_id)) { throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.'"); } if (count($this->configuration['states']) === 1) { @@ -258,10 +250,10 @@ public function deleteState($state_id) { * {@inheritdoc} */ public function addTransition($transition_id, $label, array $from_state_ids, $to_state_id) { - if (isset($this->configuration['transitions'][$transition_id])) { + if ($this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow.'"); } - if (preg_match('/[^a-z0-9_]+/', $transition_id)) { + if (preg_match(static::VALID_ID_REGEX, $transition_id)) { throw new \InvalidArgumentException("The transition ID '$transition_id' must contain only lowercase letters, numbers, and underscores."); } @@ -284,6 +276,7 @@ public function addTransition($transition_id, $label, array $from_state_ids, $to throw $e; } + ksort($this->configuration['transitions']); return $this; } @@ -296,10 +289,22 @@ public function getTransitions(array $transition_ids = NULL) { } /** @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. + return static::labelWeightMultisort($transitions); + } + + /** + * Sort states or transitions by weight and label. + * + * @param \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[] $objects + * Objects to multi-sort. + * + * @return \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[] + * An array of sorted transitions or states. + */ + protected static function labelWeightMultisort($objects) { + if (count($objects) > 1) { $weights = $labels = []; - foreach ($transitions as $id => $transition) { + foreach ($objects as $id => $transition) { $weights[$id] = $transition->weight(); $labels[$id] = $transition->label(); } @@ -307,16 +312,16 @@ public function getTransitions(array $transition_ids = NULL) { $weights, SORT_NUMERIC, SORT_ASC, $labels, SORT_NATURAL, SORT_ASC ); - $transitions = array_replace($weights, $transitions); + return array_replace($weights, $objects); } - return $transitions; + return $objects; } /** * {@inheritdoc} */ public function getTransition($transition_id) { - if (!isset($this->configuration['transitions'][$transition_id])) { + if (!$this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.'"); } $transition = new Transition( @@ -362,7 +367,7 @@ public function getTransitionFromStateToState($from_state_id, $to_state_id) { * {@inheritdoc} */ public function hasTransitionFromStateToState($from_state_id, $to_state_id) { - return !empty($this->getTransitionIdFromStateToState($from_state_id, $to_state_id)); + return $this->getTransitionIdFromStateToState($from_state_id, $to_state_id) !== NULL; } /** @@ -389,12 +394,10 @@ protected function getTransitionIdFromStateToState($from_state_id, $to_state_id) * {@inheritdoc} */ public function setTransitionLabel($transition_id, $label) { - if (isset($this->configuration['transitions'][$transition_id])) { - $this->configuration['transitions'][$transition_id]['label'] = $label; - } - else { + if (!$this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow."); } + $this->configuration['transitions'][$transition_id]['label'] = $label; return $this; } @@ -402,12 +405,10 @@ public function setTransitionLabel($transition_id, $label) { * {@inheritdoc} */ public function setTransitionWeight($transition_id, $weight) { - if (isset($this->configuration['transitions'][$transition_id])) { - $this->configuration['transitions'][$transition_id]['weight'] = $weight; - } - else { + if (!$this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.'"); } + $this->configuration['transitions'][$transition_id]['weight'] = $weight; return $this; } @@ -415,7 +416,7 @@ public function setTransitionWeight($transition_id, $weight) { * {@inheritdoc} */ public function setTransitionFromStates($transition_id, array $from_state_ids) { - if (!isset($this->configuration['transitions'][$transition_id])) { + if (!$this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow."); } @@ -425,9 +426,9 @@ public function setTransitionFromStates($transition_id, array $from_state_ids) { 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."); + $existing_transition_id = $this->getTransitionIdFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to']); + if ($transition_id !== $existing_transition_id) { + throw new \InvalidArgumentException("The '$existing_transition_id' transition already allows '$from_state_id' to '{$this->configuration['transitions'][$transition_id]['to']}' transitions in workflow."); } } } @@ -437,7 +438,6 @@ public function setTransitionFromStates($transition_id, array $from_state_ids) { $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; } @@ -446,12 +446,10 @@ public function setTransitionFromStates($transition_id, array $from_state_ids) { * {@inheritdoc} */ public function deleteTransition($transition_id) { - if (isset($this->configuration['transitions'][$transition_id])) { - unset($this->configuration['transitions'][$transition_id]); - } - else { + if (!$this->hasTransition($transition_id)) { throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow."); } + unset($this->configuration['transitions'][$transition_id]); return $this; } diff --git a/core/modules/workflows/src/WorkflowTypeInterface.php b/core/modules/workflows/src/WorkflowTypeInterface.php index 009f41c..d84d1a7 100644 --- a/core/modules/workflows/src/WorkflowTypeInterface.php +++ b/core/modules/workflows/src/WorkflowTypeInterface.php @@ -164,6 +164,9 @@ public function onDependencyRemoval(array $dependencies); * * @return \Drupal\workflows\WorkflowTypeInterface * The workflow type plugin. + * + * @throws \InvalidArgumentException + * Thrown if a state already exists or state ID is invalid. */ public function addState($state_id, $label);