"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.9/core/modules/workflows/src/Plugin/WorkflowTypeBase.php" (18 Nov 2020, 14672 Bytes) of package /linux/www/drupal-8.9.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "WorkflowTypeBase.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\workflows\Plugin;
    4 
    5 use Drupal\Component\Plugin\PluginBase;
    6 use Drupal\Core\Plugin\PluginWithFormsTrait;
    7 use Drupal\workflows\State;
    8 use Drupal\workflows\StateInterface;
    9 use Drupal\workflows\Transition;
   10 use Drupal\workflows\TransitionInterface;
   11 use Drupal\workflows\WorkflowInterface;
   12 use Drupal\workflows\WorkflowTypeInterface;
   13 
   14 /**
   15  * A base class for Workflow type plugins.
   16  *
   17  * @see \Drupal\workflows\Annotation\WorkflowType
   18  */
   19 abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterface {
   20 
   21   use PluginWithFormsTrait;
   22 
   23   /**
   24    * A regex for matching a valid state/transition machine name.
   25    */
   26   const VALID_ID_REGEX = '/[^a-z0-9_]+/';
   27 
   28   /**
   29    * {@inheritdoc}
   30    */
   31   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
   32     parent::__construct($configuration, $plugin_id, $plugin_definition);
   33     $this->setConfiguration($configuration);
   34   }
   35 
   36   /**
   37    * {@inheritdoc}
   38    */
   39   public function label() {
   40     $definition = $this->getPluginDefinition();
   41     // The label can be an object.
   42     // @see \Drupal\Core\StringTranslation\TranslatableMarkup
   43     return $definition['label'];
   44   }
   45 
   46   /**
   47    * {@inheritdoc}
   48    */
   49   public function workflowHasData(WorkflowInterface $workflow) {
   50     return FALSE;
   51   }
   52 
   53   /**
   54    * {@inheritdoc}
   55    */
   56   public function workflowStateHasData(WorkflowInterface $workflow, StateInterface $state) {
   57     return FALSE;
   58   }
   59 
   60   /**
   61    * {@inheritdoc}
   62    */
   63   public function getConfiguration() {
   64     return $this->configuration;
   65   }
   66 
   67   /**
   68    * {@inheritdoc}
   69    */
   70   public function setConfiguration(array $configuration) {
   71     $this->configuration = $configuration + $this->defaultConfiguration();
   72   }
   73 
   74   /**
   75    * {@inheritdoc}
   76    */
   77   public function getRequiredStates() {
   78     return $this->getPluginDefinition()['required_states'];
   79   }
   80 
   81   /**
   82    * {@inheritdoc}
   83    */
   84   public function defaultConfiguration() {
   85     return [
   86       'states' => [],
   87       'transitions' => [],
   88     ];
   89   }
   90 
   91   /**
   92    * {@inheritdoc}
   93    */
   94   public function calculateDependencies() {
   95     return [];
   96   }
   97 
   98   /**
   99    * {@inheritdoc}
  100    */
  101   public function onDependencyRemoval(array $dependencies) {
  102     return FALSE;
  103   }
  104 
  105   /**
  106    * {@inheritdoc}
  107    */
  108   public function getInitialState() {
  109     $ordered_states = $this->getStates();
  110     return reset($ordered_states);
  111   }
  112 
  113   /**
  114    * {@inheritdoc}
  115    */
  116   public function addState($state_id, $label) {
  117     if ($this->hasState($state_id)) {
  118       throw new \InvalidArgumentException("The state '$state_id' already exists in workflow.");
  119     }
  120     if (preg_match(static::VALID_ID_REGEX, $state_id)) {
  121       throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores");
  122     }
  123     $this->configuration['states'][$state_id] = [
  124       'label' => $label,
  125       'weight' => $this->getNextWeight($this->configuration['states']),
  126     ];
  127     ksort($this->configuration['states']);
  128     return $this;
  129   }
  130 
  131   /**
  132    * {@inheritdoc}
  133    */
  134   public function hasState($state_id) {
  135     return isset($this->configuration['states'][$state_id]);
  136   }
  137 
  138   /**
  139    * {@inheritdoc}
  140    */
  141   public function getStates($state_ids = NULL) {
  142     if ($state_ids === NULL) {
  143       $state_ids = array_keys($this->configuration['states']);
  144     }
  145     /** @var \Drupal\workflows\StateInterface[] $states */
  146     $states = array_combine($state_ids, array_map([$this, 'getState'], $state_ids));
  147     return static::labelWeightMultisort($states);
  148   }
  149 
  150   /**
  151    * {@inheritdoc}
  152    */
  153   public function getState($state_id) {
  154     if (!isset($this->configuration['states'][$state_id])) {
  155       throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
  156     }
  157     return new State(
  158       $this,
  159       $state_id,
  160       $this->configuration['states'][$state_id]['label'],
  161       $this->configuration['states'][$state_id]['weight']
  162     );
  163   }
  164 
  165   /**
  166    * {@inheritdoc}
  167    */
  168   public function setStateLabel($state_id, $label) {
  169     if (!$this->hasState($state_id)) {
  170       throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
  171     }
  172     $this->configuration['states'][$state_id]['label'] = $label;
  173     return $this;
  174   }
  175 
  176   /**
  177    * {@inheritdoc}
  178    */
  179   public function setStateWeight($state_id, $weight) {
  180     if (!$this->hasState($state_id)) {
  181       throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
  182     }
  183     if (!is_numeric($weight)) {
  184       $label = $this->getState($state_id)->label();
  185       throw new \InvalidArgumentException("The weight '$weight' must be numeric for state '$label'.");
  186     }
  187     $this->configuration['states'][$state_id]['weight'] = $weight;
  188     return $this;
  189   }
  190 
  191   /**
  192    * {@inheritdoc}
  193    */
  194   public function deleteState($state_id) {
  195     if (!$this->hasState($state_id)) {
  196       throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow.");
  197     }
  198     if (count($this->configuration['states']) === 1) {
  199       throw new \InvalidArgumentException("The state '$state_id' can not be deleted from workflow as it is the only state.");
  200     }
  201 
  202     foreach ($this->configuration['transitions'] as $transition_id => $transition) {
  203       if ($transition['to'] === $state_id) {
  204         $this->deleteTransition($transition_id);
  205         continue;
  206       }
  207       $from_key = array_search($state_id, $transition['from'], TRUE);
  208       if ($from_key !== FALSE) {
  209         // Remove state from the from array.
  210         unset($transition['from'][$from_key]);
  211         if (empty($transition['from'])) {
  212           // There are no more 'from' entries, remove the transition.
  213           $this->deleteTransition($transition_id);
  214           continue;
  215         }
  216         // We changed the from state, update the transition.
  217         $this->setTransitionFromStates($transition_id, $transition['from']);
  218       }
  219     }
  220     unset($this->configuration['states'][$state_id]);
  221     return $this;
  222   }
  223 
  224   /**
  225    * {@inheritdoc}
  226    */
  227   public function addTransition($transition_id, $label, array $from_state_ids, $to_state_id) {
  228     if ($this->hasTransition($transition_id)) {
  229       throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow.");
  230     }
  231     if (preg_match(static::VALID_ID_REGEX, $transition_id)) {
  232       throw new \InvalidArgumentException("The transition ID '$transition_id' must contain only lowercase letters, numbers, and underscores.");
  233     }
  234 
  235     if (!$this->hasState($to_state_id)) {
  236       throw new \InvalidArgumentException("The state '$to_state_id' does not exist in workflow.");
  237     }
  238     $this->configuration['transitions'][$transition_id] = [
  239       'label' => $label,
  240       'from' => [],
  241       'to' => $to_state_id,
  242       // Always add to the end.
  243       'weight' => $this->getNextWeight($this->configuration['transitions']),
  244     ];
  245 
  246     try {
  247       $this->setTransitionFromStates($transition_id, $from_state_ids);
  248     }
  249     catch (\InvalidArgumentException $e) {
  250       unset($this->configuration['transitions'][$transition_id]);
  251       throw $e;
  252     }
  253 
  254     ksort($this->configuration['transitions']);
  255     return $this;
  256   }
  257 
  258   /**
  259    * {@inheritdoc}
  260    */
  261   public function getTransitions(array $transition_ids = NULL) {
  262     if ($transition_ids === NULL) {
  263       $transition_ids = array_keys($this->configuration['transitions']);
  264     }
  265     /** @var \Drupal\workflows\TransitionInterface[] $transitions */
  266     $transitions = array_combine($transition_ids, array_map([$this, 'getTransition'], $transition_ids));
  267     return static::labelWeightMultisort($transitions);
  268   }
  269 
  270   /**
  271    * Sort states or transitions by weight, label, and key.
  272    *
  273    * @param \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[] $objects
  274    *   An array of state or transition objects to multi-sort, keyed by the
  275    *   state or transition ID.
  276    *
  277    * @return \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[]
  278    *   An array of sorted transitions or states, keyed by the state or
  279    *   transition ID.
  280    */
  281   protected static function labelWeightMultisort($objects) {
  282     if (count($objects) > 1) {
  283       // Separate weights, labels, and keys into arrays.
  284       $weights = $labels = [];
  285       $keys = array_keys($objects);
  286       foreach ($objects as $id => $object) {
  287         $weights[$id] = $object->weight();
  288         $labels[$id] = $object->label();
  289       }
  290       // Sort weights, labels, and keys in the same order as each other.
  291       array_multisort(
  292       // Use the numerical weight as the primary sort.
  293         $weights, SORT_NUMERIC, SORT_ASC,
  294         // When objects have the same weight, sort them alphabetically by label.
  295         $labels, SORT_NATURAL, SORT_ASC,
  296         // Ensure that the keys (the object IDs) are sorted in the same order as
  297         // the weights.
  298         $keys
  299       );
  300       // Combine keys and weights to make sure the weights are keyed with the
  301       // correct keys.
  302       $weights = array_combine($keys, $weights);
  303       // Return the objects sorted by weight.
  304       return array_replace($weights, $objects);
  305     }
  306     return $objects;
  307   }
  308 
  309   /**
  310    * {@inheritdoc}
  311    */
  312   public function getTransition($transition_id) {
  313     if (!$this->hasTransition($transition_id)) {
  314       throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
  315     }
  316     return new Transition(
  317       $this,
  318       $transition_id,
  319       $this->configuration['transitions'][$transition_id]['label'],
  320       $this->configuration['transitions'][$transition_id]['from'],
  321       $this->configuration['transitions'][$transition_id]['to'],
  322       $this->configuration['transitions'][$transition_id]['weight']
  323     );
  324   }
  325 
  326   /**
  327    * {@inheritdoc}
  328    */
  329   public function hasTransition($transition_id) {
  330     return isset($this->configuration['transitions'][$transition_id]);
  331   }
  332 
  333   /**
  334    * {@inheritdoc}
  335    */
  336   public function getTransitionsForState($state_id, $direction = TransitionInterface::DIRECTION_FROM) {
  337     $transition_ids = array_keys(array_filter($this->configuration['transitions'], function ($transition) use ($state_id, $direction) {
  338       return in_array($state_id, (array) $transition[$direction], TRUE);
  339     }));
  340     return $this->getTransitions($transition_ids);
  341   }
  342 
  343   /**
  344    * {@inheritdoc}
  345    */
  346   public function getTransitionFromStateToState($from_state_id, $to_state_id) {
  347     $transition_id = $this->getTransitionIdFromStateToState($from_state_id, $to_state_id);
  348     if (empty($transition_id)) {
  349       throw new \InvalidArgumentException("The transition from '$from_state_id' to '$to_state_id' does not exist in workflow.");
  350     }
  351     return $this->getTransition($transition_id);
  352   }
  353 
  354   /**
  355    * {@inheritdoc}
  356    */
  357   public function hasTransitionFromStateToState($from_state_id, $to_state_id) {
  358     return $this->getTransitionIdFromStateToState($from_state_id, $to_state_id) !== NULL;
  359   }
  360 
  361   /**
  362    * Gets the transition ID from state to state.
  363    *
  364    * @param string $from_state_id
  365    *   The state ID to transition from.
  366    * @param string $to_state_id
  367    *   The state ID to transition to.
  368    *
  369    * @return string|null
  370    *   The transition ID, or NULL if no transition exists.
  371    */
  372   protected function getTransitionIdFromStateToState($from_state_id, $to_state_id) {
  373     foreach ($this->configuration['transitions'] as $transition_id => $transition) {
  374       if (in_array($from_state_id, $transition['from'], TRUE) && $transition['to'] === $to_state_id) {
  375         return $transition_id;
  376       }
  377     }
  378     return NULL;
  379   }
  380 
  381   /**
  382    * {@inheritdoc}
  383    */
  384   public function setTransitionLabel($transition_id, $label) {
  385     if (!$this->hasTransition($transition_id)) {
  386       throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
  387     }
  388     $this->configuration['transitions'][$transition_id]['label'] = $label;
  389     return $this;
  390   }
  391 
  392   /**
  393    * {@inheritdoc}
  394    */
  395   public function setTransitionWeight($transition_id, $weight) {
  396     if (!$this->hasTransition($transition_id)) {
  397       throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
  398     }
  399     if (!is_numeric($weight)) {
  400       $label = $this->getTransition($transition_id)->label();
  401       throw new \InvalidArgumentException("The weight '$weight' must be numeric for transition '$label'.");
  402     }
  403     $this->configuration['transitions'][$transition_id]['weight'] = $weight;
  404     return $this;
  405   }
  406 
  407   /**
  408    * {@inheritdoc}
  409    */
  410   public function setTransitionFromStates($transition_id, array $from_state_ids) {
  411     if (!$this->hasTransition($transition_id)) {
  412       throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
  413     }
  414 
  415     // Ensure that the states exist.
  416     foreach ($from_state_ids as $from_state_id) {
  417       if (!$this->hasState($from_state_id)) {
  418         throw new \InvalidArgumentException("The state '$from_state_id' does not exist in workflow.");
  419       }
  420       if ($this->hasTransitionFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to'])) {
  421         $existing_transition_id = $this->getTransitionIdFromStateToState($from_state_id, $this->configuration['transitions'][$transition_id]['to']);
  422         if ($transition_id !== $existing_transition_id) {
  423           throw new \InvalidArgumentException("The '$existing_transition_id' transition already allows '$from_state_id' to '{$this->configuration['transitions'][$transition_id]['to']}' transitions in workflow.");
  424         }
  425       }
  426     }
  427 
  428     // Preserve the order of the state IDs in the from value and don't save any
  429     // keys.
  430     $from_state_ids = array_values($from_state_ids);
  431     sort($from_state_ids);
  432     $this->configuration['transitions'][$transition_id]['from'] = $from_state_ids;
  433 
  434     return $this;
  435   }
  436 
  437   /**
  438    * {@inheritdoc}
  439    */
  440   public function deleteTransition($transition_id) {
  441     if (!$this->hasTransition($transition_id)) {
  442       throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow.");
  443     }
  444     unset($this->configuration['transitions'][$transition_id]);
  445     return $this;
  446   }
  447 
  448   /**
  449    * Gets the weight for a new state or transition.
  450    *
  451    * @param array $items
  452    *   An array of states or transitions information where each item has a
  453    *   'weight' key with a numeric value.
  454    *
  455    * @return int
  456    *   The weight for a new item in the array so that it has the highest weight.
  457    */
  458   protected function getNextWeight(array $items) {
  459     return array_reduce($items, function ($carry, $item) {
  460       return max($carry, $item['weight'] + 1);
  461     }, 0);
  462   }
  463 
  464 }