diff --git a/core/modules/workflow_ui/src/Form/WorkflowAddForm.php b/core/modules/workflow_ui/src/Form/WorkflowAddForm.php new file mode 100644 index 0000000..07ce876 --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowAddForm.php @@ -0,0 +1,105 @@ +workflowTypePluginManager = $workflow_type_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('core.workflow_type_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + /* @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $workflow->label(), + '#description' => $this->t('Label for the Workflow.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $workflow->id(), + '#machine_name' => [ + 'exists' => [Workflow::class, 'load'], + ], + ]; + + $workflow_types = array_map(function ($plugin_definition) { + return $plugin_definition['label']; + }, $this->workflowTypePluginManager->getDefinitions()); + $form['workflow_type'] = [ + '#type' => 'select', + '#title' => $this->t('Workflow type'), + '#required' => TRUE, + '#options' => $workflow_types, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /* @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Created the %label Workflow.', [ + '%label' => $workflow->label(), + ])); + $form_state->setRedirectUrl($workflow->toUrl()); + } + + /** + * {@inheritdoc} + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + $entity->set('label', $values['label']); + $entity->set('id', $values['id']); + $entity->set('type', $values['workflow_type']); + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowDeleteForm.php b/core/modules/workflow_ui/src/Form/WorkflowDeleteForm.php new file mode 100644 index 0000000..446aaaa --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowDeleteForm.php @@ -0,0 +1,49 @@ +t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.workflow.collection'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->entity->delete(); + + drupal_set_message($this->t( + 'Workflow %label deleted.', + ['%label' => $this->entity->label()] + )); + + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowEditForm.php b/core/modules/workflow_ui/src/Form/WorkflowEditForm.php new file mode 100644 index 0000000..5cd4a3f --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowEditForm.php @@ -0,0 +1,192 @@ +entity; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $workflow->label(), + '#description' => $this->t('Label for the Workflow.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $workflow->id(), + '#machine_name' => [ + 'exists' => [Workflow::class, 'load'], + ], + '#disabled' => TRUE, + ]; + + $header = [ + 'state' => $this->t('State'), + 'weight' => $this->t('Weight'), + 'operations' => $this->t('Operations') + ]; + $form['states_container'] = [ + '#type' => 'details', + '#title' => $this->t('States'), + '#open' => TRUE, + '#collapsible' => 'FALSE', + ]; + $form['states_container']['states'] = [ + '#type' => 'table', + '#header' => $header, + '#title' => $this->t('States'), + '#empty' => $this->t('There are no states yet.'), + '#tabledrag' => [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'state-weight', + ], + ], + ]; + foreach ($workflow->getStates() as $state) { + $links['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('entity.workflow.edit_state_form', ['workflow' => $workflow->id(), 'workflow_state' => $state->id()]), + 'attributes' => ['aria-label' => $this->t('Edit @state state', ['@state' => $state->label()])], + ]; + $links['delete'] = [ + 'title' => t('Delete'), + 'url' => Url::fromRoute('entity.workflow.delete_state_form', ['workflow' => $workflow->id(), 'workflow_state' => $state->id()]), + 'attributes' => ['aria-label' => $this->t('Delete @state state', ['@state' => $state->label()])], + ]; + $form['states_container']['states'][$state->id()] = [ + '#attributes' => ['class' => ['draggable']], + 'state' => ['#markup' => $state->label()], + '#weight' => $state->weight(), + 'weight' => [ + '#type' => 'weight', + '#title' => t('Weight for @title', ['@title' => $state->label()]), + '#title_display' => 'invisible', + '#default_value' => $state->weight(), + '#attributes' => ['class' => ['state-weight']], + ], + 'operations' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; + } + $form['states_container']['state_add'] = [ + '#markup' => $workflow->toLink($this->t('Add a new state'), 'add-state-form')->toString(), + ]; + + $header = [ + 'label' => $this->t('Label'), + 'weight' => $this->t('Weight'), + 'from' => $this->t('From'), + 'to' => $this->t('To'), + 'operations' => $this->t('Operations') + ]; + $form['transitions_container'] = [ + '#type' => 'details', + '#title' => $this->t('Transitions'), + '#open' => TRUE, + ]; + $form['transitions_container']['transitions'] = [ + '#type' => 'table', + '#header' => $header, + '#title' => $this->t('Transitions'), + '#empty' => $this->t('There are no transitions yet.'), + '#tabledrag' => [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'transition-weight', + ], + ], + ]; + foreach ($workflow->getTransitions() as $transition) { + $links['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('entity.workflow.edit_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]), + 'attributes' => ['aria-label' => $this->t('Edit @transition transition', ['@transition' => $transition->label()])], + ]; + $links['delete'] = [ + 'title' => t('Delete'), + 'url' => Url::fromRoute('entity.workflow.delete_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]), + 'attributes' => ['aria-label' => $this->t('Delete @transition transition', ['@transition' => $transition->label()])], + ]; + $form['transitions_container']['transitions'][$transition->id()] = [ + '#attributes' => ['class' => ['draggable']], + 'label' => ['#markup' => $transition->label()], + '#weight' => $transition->weight(), + 'weight' => [ + '#type' => 'weight', + '#title' => t('Weight for @title', ['@title' => $transition->label()]), + '#title_display' => 'invisible', + '#default_value' => $transition->weight(), + '#attributes' => ['class' => ['transition-weight']], + ], + 'from' => [ + '#theme' => 'item_list', + '#items' => array_map([State::class, 'labelCallback'], $transition->from()), + '#context' => ['list_style' => 'comma-list'], + ], + 'to' => ['#markup' => $transition->to()->label()], + 'operations' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; + } + $form['transitions_container']['transition_add'] = [ + '#markup' => $workflow->toLink($this->t('Add a new transition'), 'add-transition-form')->toString(), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /* @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Saved the %label Workflow.', ['%label' => $workflow->label()])); + $form_state->setRedirectUrl($workflow->toUrl('collection')); + } + + /** + * {@inheritdoc} + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + $entity->set('label', $values['label']); + $entity->set('id', $values['id']); + foreach ($values['states'] as $state_id => $state_values) { + $entity->setStateWeight($state_id, $state_values['weight']); + } + foreach ($values['transitions'] as $transition_id => $transition_values) { + $entity->setTransitionWeight($transition_id, $transition_values['weight']); + } + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowStateAddForm.php b/core/modules/workflow_ui/src/Form/WorkflowStateAddForm.php new file mode 100644 index 0000000..f5955e7 --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowStateAddForm.php @@ -0,0 +1,115 @@ +getEntity(); + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => '', + '#description' => $this->t('Label for the state.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#machine_name' => [ + 'exists' => [$this, 'exists'], + ], + ]; + + // Add additional form fields from the workflow type plugin. + $form['type_settings'] = [ + $workflow->get('type') => $workflow->getTypePlugin()->buildStateConfigurationForm($form_state, $workflow), + '#tree' => TRUE, + ]; + + return $form; + } + + /** + * Determines if the workflow state already exists. + * + * @param string $state_id + * The workflow state ID. + * + * @return bool + * TRUE if the workflow state exists, FALSE otherwise. + */ + public function exists($state_id) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $original_workflow */ + $original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id()); + return $original_workflow->hasState($state_id); + } + + /** + * Copies top-level form values to entity properties + * + * This form can only change values for a state, which is part of workflow. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the current form should operate upon. + * @param array $form + * A nested array of form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + + // This is fired twice so we have to check that the entity does not already + // have the state. + if (!$entity->hasState($values['id'])) { + $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()]; + $entity->set('type_settings', $configuration); + } + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Created %label state.', [ + '%label' => $workflow->getState($form_state->getValue('id'))->label(), + ])); + $form_state->setRedirectUrl($workflow->toUrl('edit-form')); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#submit' => ['::submitForm', '::save'], + ]; + return $actions; + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowStateDeleteForm.php b/core/modules/workflow_ui/src/Form/WorkflowStateDeleteForm.php new file mode 100644 index 0000000..51a1ef7 --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowStateDeleteForm.php @@ -0,0 +1,99 @@ +t('Are you sure you want to delete %state from %workflow?', ['%state' => $this->workflow->getState($this->stateId)->label(), '%workflow' => $this->workflow->label()]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return $this->workflow->toUrl(); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Workflow\WorkflowInterface $workflow + * The workflow entity being edited. + * @param string|null $workflow_state + * The workflow state being deleted. + * + * @return array + * The form structure. + */ + public function buildForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL, $workflow_state = NULL) { + if (!$workflow->hasState($workflow_state)) { + throw new NotFoundHttpException(); + } + $this->workflow = $workflow; + $this->stateId = $workflow_state; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + $workflow_label = $this->workflow->getState($this->stateId)->label(); + $this->workflow + ->deleteState($this->stateId) + ->save(); + + drupal_set_message($this->t( + 'State %label deleted.', + ['%label' => $workflow_label] + )); + + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowStateEditForm.php b/core/modules/workflow_ui/src/Form/WorkflowStateEditForm.php new file mode 100644 index 0000000..606cdd7 --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowStateEditForm.php @@ -0,0 +1,168 @@ +stateId = $workflow_state; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + /* @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->getEntity(); + $state = $workflow->getState($this->stateId); + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $state->label(), + '#description' => $this->t('Label for the state.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $this->stateId, + '#machine_name' => [ + 'exists' => [$this, 'exists'], + ], + '#disabled' => TRUE, + ]; + + // Add additional form fields from the workflow type plugin. + $form['type_settings'] = [ + $workflow->get('type') => $workflow->getTypePlugin()->buildStateConfigurationForm($form_state, $workflow, $state), + '#tree' => TRUE, + ]; + + $header = [ + 'label' => $this->t('Transition'), + 'state' => $this->t('To'), + 'operations' => $this->t('Operations'), + ]; + $form['transitions'] = [ + '#type' => 'table', + '#header' => $header, + '#empty' => $this->t('There are no states yet.'), + ]; + foreach ($state->getTransitions() as $transition) { + $links['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('entity.workflow.edit_transition_form', [ + 'workflow' => $workflow->id(), + 'workflow_transition' => $transition->id() + ]), + ]; + $links['delete'] = [ + 'title' => t('Delete'), + 'url' => Url::fromRoute('entity.workflow.delete_transition_form', [ + 'workflow' => $workflow->id(), + 'workflow_transition' => $transition->id() + ]), + ]; + $form['transitions'][$transition->id()] = [ + 'label' => [ + '#markup' => $transition->label(), + ], + 'state' => [ + '#markup' => $transition->to()->label(), + ], + 'operations' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; + } + + return $form; + } + + /** + * Copies top-level form values to entity properties + * + * This form can only change values for a state, which is part of workflow. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the current form should operate upon. + * @param array $form + * A nested array of form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + $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()]; + $entity->set('type_settings', $configuration); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Saved %label state.', [ + '%label' => $workflow->getState($this->stateId)->label(), + ])); + $form_state->setRedirectUrl($workflow->toUrl('edit-form')); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#submit' => ['::submitForm', '::save'], + ]; + + $actions['delete'] = [ + '#type' => 'link', + '#title' => $this->t('Delete'), + // Deleting a state is editing a workflow. + '#access' => $this->entity->access('edit'), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + ], + '#url' => Url::fromRoute('entity.workflow.delete_state_form', [ + 'workflow' => $this->entity->id(), + 'workflow_state' => $this->stateId + ]) + ]; + + return $actions; + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowTransitionAddForm.php b/core/modules/workflow_ui/src/Form/WorkflowTransitionAddForm.php new file mode 100644 index 0000000..2b39a76 --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowTransitionAddForm.php @@ -0,0 +1,151 @@ +getEntity(); + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => '', + '#description' => $this->t('Label for the transition.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#machine_name' => [ + 'exists' => [$this, 'exists'], + ], + ]; + + // @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that + // only valid transitions are selectable. + $states = array_map([State::class, 'labelCallback'], $workflow->getStates()); + $form['from'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('From'), + '#required' => TRUE, + '#default_value' => [], + '#options' => $states, + ]; + $form['to'] = [ + '#type' => 'radios', + '#title' => $this->t('To'), + '#required' => TRUE, + '#default_value' => [], + '#options' => $states, + ]; + + // Add additional form fields from the workflow type plugin. + $form['type_settings'] = [ + $workflow->get('type') => $workflow->getTypePlugin()->buildTransitionConfigurationForm($form_state, $workflow), + '#tree' => TRUE, + ]; + + return $form; + } + + /** + * Determines if the workflow transition already exists. + * + * @param string $transition_id + * The workflow transition ID. + * + * @return bool + * TRUE if the workflow transition exists, FALSE otherwise. + */ + public function exists($transition_id) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $original_workflow */ + $original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id()); + return $original_workflow->hasTransition($transition_id); + } + + /** + * Copies top-level form values to entity properties + * + * This form can only change values for a state, which is part of workflow. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the current form should operate upon. + * @param array $form + * A nested array of form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + if (!$form_state->isValidationComplete()) { + // Only do something once form validation is complete. + return; + } + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + $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()]; + $entity->set('type_settings', $configuration); + } + } + + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->getEntity(); + $values = $form_state->getValues(); + foreach (array_filter($values['from']) as $from_state_id) { + if ($workflow->hasTransitionFromStateToState($from_state_id, $values['to'])) { + $form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [ + '%from' => $workflow->getState($from_state_id)->label(), + '%to' => $workflow->getState($values['to'])->label(), + ])); + } + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Created %label transition.', [ + '%label' => $form_state->getValue('label'), + ])); + $form_state->setRedirectUrl($workflow->toUrl('edit-form')); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#submit' => ['::submitForm', '::save'], + ]; + return $actions; + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowTransitionDeleteForm.php b/core/modules/workflow_ui/src/Form/WorkflowTransitionDeleteForm.php new file mode 100644 index 0000000..f49f41f --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowTransitionDeleteForm.php @@ -0,0 +1,102 @@ +t('Are you sure you want to delete %transition from %workflow?', ['%transition' => $this->transition->label(), '%workflow' => $this->workflow->label()]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return $this->workflow->toUrl(); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Workflow\WorkflowInterface $workflow + * The workflow entity being edited. + * @param string|null $workflow_transition + * The workflow transition being deleted. + * + * @return array + * The form structure. + */ + public function buildForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL, $workflow_transition = NULL) { + try { + $this->transition = $workflow->getTransition($workflow_transition); + } + catch (\InvalidArgumentException $e) { + throw new NotFoundHttpException(); + } + $this->workflow = $workflow; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->workflow + ->deleteTransition($this->transition->id()) + ->save(); + + drupal_set_message($this->t('%transition transition deleted.', ['%transition' => $this->transition->label()])); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/core/modules/workflow_ui/src/Form/WorkflowTransitionEditForm.php b/core/modules/workflow_ui/src/Form/WorkflowTransitionEditForm.php new file mode 100644 index 0000000..653b1bd --- /dev/null +++ b/core/modules/workflow_ui/src/Form/WorkflowTransitionEditForm.php @@ -0,0 +1,171 @@ +transitionId = $workflow_transition; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + /* @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->getEntity(); + $transition = $workflow->getTransition($this->transitionId); + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $transition->label(), + '#description' => $this->t('Label for the transition.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'value', + '#value' => $this->transitionId, + ]; + + // @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that + // only valid transitions are selectable. + $states = array_map([State::class, 'labelCallback'], $workflow->getStates()); + $form['from'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('From'), + '#required' => TRUE, + '#default_value' => array_keys($transition->from()), + '#options' => $states, + ]; + $form['to'] = [ + '#type' => 'radios', + '#title' => $this->t('To'), + '#required' => TRUE, + '#default_value' => $transition->to()->id(), + '#options' => $states, + '#disabled' => TRUE, + ]; + + // Add additional form fields from the workflow type plugin. + $form['type_settings'] = [ + $workflow->get('type') => $workflow->getTypePlugin()->buildTransitionConfigurationForm($form_state, $workflow, $transition), + '#tree' => TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->getEntity(); + $values = $form_state->getValues(); + foreach (array_filter($values['from']) as $from_state_id) { + if ($workflow->hasTransitionFromStateToState($from_state_id, $values['to'])) { + $transition = $workflow->getTransitionFromStateToState($from_state_id, $values['to']); + if ($transition->id() !== $values['id']) { + $form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [ + '%from' => $workflow->getState($from_state_id)->label(), + '%to' => $workflow->getState($values['to'])->label(), + ])); + } + } + } + } + + /** + * Copies top-level form values to entity properties + * + * This form can only change values for a state, which is part of workflow. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the current form should operate upon. + * @param array $form + * A nested array of form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + if (!$form_state->isValidationComplete()) { + // Only do something once form validation is complete. + return; + } + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $values = $form_state->getValues(); + $form_state->set('created_transition', FALSE); + $entity->setTransitionLabel($values['id'], $values['label']); + $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()]; + $entity->set('type_settings', $configuration); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $this->entity; + $workflow->save(); + drupal_set_message($this->t('Saved %label transition.', [ + '%label' => $workflow->getTransition($this->transitionId)->label(), + ])); + $form_state->setRedirectUrl($workflow->toUrl('edit-form')); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#submit' => ['::submitForm', '::save'], + ]; + + $actions['delete'] = [ + '#type' => 'link', + '#title' => $this->t('Delete'), + // Deleting a transition is editing a workflow. + '#access' => $this->entity->access('edit'), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + ], + '#url' => Url::fromRoute('entity.workflow.delete_transition_form', [ + 'workflow' => $this->entity->id(), + 'workflow_transition' => $this->transitionId + ]) + ]; + + return $actions; + } + +} diff --git a/core/modules/workflow_ui/src/WorkflowListBuilder.php b/core/modules/workflow_ui/src/WorkflowListBuilder.php new file mode 100644 index 0000000..0711128 --- /dev/null +++ b/core/modules/workflow_ui/src/WorkflowListBuilder.php @@ -0,0 +1,102 @@ +get('entity_type.manager')->getStorage($entity_type->id()), + $container->get('core.workflow_type_manager') + ); + } + + /** + * Constructs a new WorkflowListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_manager + * The workflow type plugin manager. + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, PluginManagerInterface $workflow_type_manager) { + parent::__construct($entity_type, $storage); + $this->workflowTypeManager = $workflow_type_manager; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'workflow_admin_overview_form'; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = $this->t('Workflow'); + $header['type'] = $this->t('Type'); + $header['states'] = $this->t('States'); + + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\Core\Workflow\WorkflowInterface $entity */ + $row['label'] = $entity->label(); + + $row['type']['data'] = [ + '#markup' => $entity->getTypePlugin()->label() + ]; + + $items = array_map([State::class, 'labelCallback'], $entity->getStates()); + $row['states']['data'] = [ + '#theme' => 'item_list', + '#context' => ['list_style' => 'comma-list'], + '#items' => $items, + ]; + + return $row + parent::buildRow($entity); + } + + /** + * {@inheritdoc} + */ + public function render() { + $build = parent::render(); + $workflow_types_count = count($this->workflowTypeManager->getDefinitions()); + if ($workflow_types_count === 0) { + $build['table']['#empty'] = $this->t('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.'); + } + return $build; + } + +} diff --git a/core/modules/workflow_ui/tests/src/Functional/WorkflowUiNoTypeTest.php b/core/modules/workflow_ui/tests/src/Functional/WorkflowUiNoTypeTest.php new file mode 100644 index 0000000..8ac0f06 --- /dev/null +++ b/core/modules/workflow_ui/tests/src/Functional/WorkflowUiNoTypeTest.php @@ -0,0 +1,54 @@ +drupalPlaceBlock('local_actions_block'); + } + + /** + * Tests the creation of a workflow through the UI. + */ + public function testWorkflowUiWithNoType() { + $this->drupalLogin($this->createUser(['access administration pages', 'administer workflows'])); + $this->drupalGet('admin/config/workflow/workflows/add'); + // There are no workflow types so this should be a 403. + $this->assertSession()->statusCodeEquals(403); + + $this->drupalGet('admin/config/workflow/workflows'); + $this->assertSession()->pageTextContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.'); + $this->assertSession()->pageTextNotContains('Add workflow'); + + $this->container->get('module_installer')->install(['workflow_type_test']); + // The render cache needs to be cleared because although the cache tags are + // correctly set the render cache does not pick it up. + \Drupal::cache('render')->deleteAll(); + + $this->drupalGet('admin/config/workflow/workflows'); + $this->assertSession()->pageTextNotContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.'); + $this->assertSession()->linkExists('Add workflow'); + $this->assertSession()->pageTextContains('There is no Workflow yet.'); + } + +} diff --git a/core/modules/workflow_ui/tests/src/Functional/WorkflowUiTest.php b/core/modules/workflow_ui/tests/src/Functional/WorkflowUiTest.php new file mode 100644 index 0000000..29dbc38 --- /dev/null +++ b/core/modules/workflow_ui/tests/src/Functional/WorkflowUiTest.php @@ -0,0 +1,250 @@ +drupalPlaceBlock('local_actions_block'); + } + + /** + * Tests route access/permissions. + */ + public function testAccess() { + // Create a minimal workflow for testing. + $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_test']); + $workflow + ->addState('published', 'Published') + ->addTransition('publish', 'Publish', ['published'], 'published') + ->save(); + + $paths = [ + 'admin/config/workflow/workflows', + 'admin/config/workflow/workflows/add', + 'admin/config/workflow/workflows/manage/test', + 'admin/config/workflow/workflows/manage/test/delete', + 'admin/config/workflow/workflows/manage/test/add_state', + 'admin/config/workflow/workflows/manage/test/state/published', + 'admin/config/workflow/workflows/manage/test/state/published/delete', + 'admin/config/workflow/workflows/manage/test/add_transition', + 'admin/config/workflow/workflows/manage/test/transition/publish', + 'admin/config/workflow/workflows/manage/test/transition/publish/delete', + ]; + + foreach ($paths as $path) { + $this->drupalGet($path); + // No access. + $this->assertSession()->statusCodeEquals(403); + } + $this->drupalLogin($this->createUser(['administer workflows'])); + foreach ($paths as $path) { + $this->drupalGet($path); + // User has access. + $this->assertSession()->statusCodeEquals(200); + } + } + + /** + * Tests the creation of a workflow through the UI. + */ + public function testWorkflowCreation() { + $workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow'); + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $this->drupalLogin($this->createUser(['access administration pages', 'administer workflows'])); + $this->drupalGet('admin/config/workflow'); + $this->assertSession()->linkByHrefExists('admin/config/workflow/workflows'); + $this->clickLink('Workflows'); + $this->assertSession()->pageTextContains('There is no Workflow yet.'); + $this->clickLink('Add workflow'); + $this->submitForm(['label' => 'Test', 'id' => 'test', 'workflow_type' => 'workflow_type_test'], 'Save'); + $this->assertSession()->pageTextContains('Created the Test Workflow.'); + $this->assertSession()->pageTextContains('There are no states yet.'); + $this->clickLink('Add a new state'); + $this->submitForm(['label' => 'Published', 'id' => 'published'], 'Save'); + $this->assertSession()->pageTextContains('Created Published state.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertFalse($workflow->getState('published')->canTransitionTo('published'), 'No default transition from published to published exists.'); + + $this->clickLink('Add a new state'); + // Don't create a draft to draft transition by default. + $this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('Created Draft state.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertFalse($workflow->getState('draft')->canTransitionTo('draft'), 'Can not transition from draft to draft'); + + $this->clickLink('Add a new transition'); + $this->submitForm(['id' => 'publish', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published'], 'Save'); + $this->assertSession()->pageTextContains('Created Publish transition.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'), 'Can transition from draft to published'); + + $this->clickLink('Add a new transition'); + $this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('Created Create new draft transition.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'), 'Can transition from draft to draft'); + + // The fist state to edit on the page should be published. + $this->clickLink('Edit'); + $this->assertSession()->fieldValueEquals('label', 'Published'); + // Change the label. + $this->submitForm(['label' => 'Live'], 'Save'); + $this->assertSession()->pageTextContains('Saved Live state.'); + + // Allow published to draft. + $this->clickLink('Edit', 3); + $this->submitForm(['from[published]' => 'published'], 'Save'); + $this->assertSession()->pageTextContains('Saved Create new draft transition.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertTrue($workflow->getState('published')->canTransitionTo('draft'), 'Can transition from published to draft'); + + // Try creating a duplicate transition. + $this->clickLink('Add a new transition'); + $this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[published]' => 'published', 'to' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.'); + // Try creating a transition which duplicates the states of another. + $this->submitForm(['id' => 'create_new_draft2', 'label' => 'Create new draft again', 'from[published]' => 'published', 'to' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('The transition from Live to Draft already exists.'); + + // Create a new transition. + $this->submitForm(['id' => 'save_and_publish', 'label' => 'Save and publish', 'from[published]' => 'published', 'to' => 'published'], 'Save'); + $this->assertSession()->pageTextContains('Created Save and publish transition.'); + // Edit the new transition and try to add an existing transition. + $this->clickLink('Edit', 4); + $this->submitForm(['from[draft]' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('The transition from Draft to Live already exists.'); + + // Delete the transition. + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'published'), 'Can transition from published to published'); + $this->clickLink('Delete'); + $this->assertSession()->pageTextContains('Are you sure you want to delete Save and publish from Test?'); + $this->submitForm([], 'Delete'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertFalse($workflow->hasTransitionFromStateToState('published', 'published'), 'Cannot transition from published to published'); + + // Try creating a duplicate state. + $this->drupalGet('admin/config/workflow/workflows/manage/test'); + $this->clickLink('Add a new state'); + $this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save'); + $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.'); + + // Ensure that weight changes the state ordering. + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals('published', $workflow->getInitialState()->id()); + $this->drupalGet('admin/config/workflow/workflows/manage/test'); + $this->submitForm(['states[draft][weight]' => '-1'], 'Save'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals('draft', $workflow->getInitialState()->id()); + + // This will take us to the list of workflows, so we need to edit the + // workflow again. + $this->clickLink('Edit'); + + // Ensure that weight changes the transition ordering. + $this->assertEquals(['publish', 'create_new_draft'], array_keys($workflow->getTransitions())); + $this->drupalGet('admin/config/workflow/workflows/manage/test'); + $this->submitForm(['transitions[create_new_draft][weight]' => '-1'], 'Save'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitions())); + + // This will take us to the list of workflows, so we need to edit the + // workflow again. + $this->clickLink('Edit'); + + // Delete the Draft state. + $this->clickLink('Delete'); + $this->assertSession()->pageTextContains('Are you sure you want to delete Draft from Test?'); + $this->submitForm([], 'Delete'); + $this->assertSession()->pageTextContains('State Draft deleted.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertFalse($workflow->hasState('draft'), 'Draft state deleted'); + $this->assertTrue($workflow->hasState('published'), 'Workflow still has published state'); + + // Delete the published state. + $this->clickLink('Delete'); + $this->assertSession()->pageTextContains('Are you sure you want to delete Live from Test?'); + $this->submitForm([], 'Delete'); + $this->assertSession()->pageTextContains('State Live deleted.'); + $this->assertSession()->pageTextContains('There are no states yet.'); + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertFalse($workflow->hasState('published'), 'Published state deleted'); + + // Delete the entire workflow. + $this->clickLink('Delete'); + $this->assertSession()->pageTextContains('Are you sure you want to delete Test?'); + $this->submitForm([], 'Delete'); + $this->assertSession()->pageTextContains('Workflow Test deleted.'); + $this->assertSession()->pageTextContains('There is no Workflow yet.'); + $this->assertNull($workflow_storage->loadUnchanged('test'), 'The test workflow has been deleted'); + } + + /** + * Tests that workflow types can add form fields to states and transitions. + */ + public function testWorkflowDecoration() { + // Create a minimal workflow for testing. + $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test']); + $workflow + ->addState('published', 'Published') + ->addTransition('publish', 'Publish', ['published'], 'published') + ->save(); + + $this->assertEquals('', $workflow->getState('published')->getExtra()); + $this->assertEquals('', $workflow->getTransition('publish')->getExtra()); + + $this->drupalLogin($this->createUser(['administer workflows'])); + + // Add additional state information when editing. + $this->drupalGet('admin/config/workflow/workflows/manage/test/state/published'); + $this->assertSession()->pageTextContains('Extra information added to state'); + $this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra state information'], 'Save'); + + // Add additional transition information when editing. + $this->drupalGet('admin/config/workflow/workflows/manage/test/transition/publish'); + $this->assertSession()->pageTextContains('Extra information added to transition'); + $this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra transition information'], 'Save'); + + $workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow'); + /** @var \Drupal\Core\Workflow\WorkflowInterface $workflow */ + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals('Extra state information', $workflow->getState('published')->getExtra()); + $this->assertEquals('Extra transition information', $workflow->getTransition('publish')->getExtra()); + + // Add additional state information when adding. + $this->drupalGet('admin/config/workflow/workflows/manage/test/add_state'); + $this->assertSession()->pageTextContains('Extra information added to state'); + $this->submitForm(['label' => 'Draft', 'id' => 'draft', 'type_settings[workflow_type_complex_test][extra]' => 'Extra state information on add'], 'Save'); + + // Add additional transition information when adding. + $this->drupalGet('admin/config/workflow/workflows/manage/test/add_transition'); + $this->assertSession()->pageTextContains('Extra information added to transition'); + $this->submitForm(['id' => 'draft_published', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published', 'type_settings[workflow_type_complex_test][extra]' => 'Extra transition information on add'], 'Save'); + + $workflow = $workflow_storage->loadUnchanged('test'); + $this->assertEquals('Extra state information on add', $workflow->getState('draft')->getExtra()); + $this->assertEquals('Extra transition information on add', $workflow->getTransition('draft_published')->getExtra()); + } + +} diff --git a/core/modules/workflow_ui/workflow_ui.info.yml b/core/modules/workflow_ui/workflow_ui.info.yml new file mode 100644 index 0000000..8f49f13 --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.info.yml @@ -0,0 +1,7 @@ +name: 'Workflow UI' +type: module +description: 'Provides UI for managing workflows. This module can be used with the Content moderation module to add highly cusomisable workflows to content.' +version: VERSION +core: 8.x +package: Core (Experimental) +configure: workflow_ui.overview diff --git a/core/modules/workflow_ui/workflow_ui.links.action.yml b/core/modules/workflow_ui/workflow_ui.links.action.yml new file mode 100644 index 0000000..e3a80f7 --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.links.action.yml @@ -0,0 +1,5 @@ +entity.workflow.add_form: + route_name: 'entity.workflow.add_form' + title: 'Add workflow' + appears_on: + - entity.workflow.collection diff --git a/core/modules/workflow_ui/workflow_ui.links.menu.yml b/core/modules/workflow_ui/workflow_ui.links.menu.yml new file mode 100644 index 0000000..a6ac512 --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.links.menu.yml @@ -0,0 +1,7 @@ +# Workflow menu items definition +entity.workflow.collection: + title: 'Workflows' + route_name: entity.workflow.collection + description: 'Configure workflows.' + parent: system.admin_config_workflow + diff --git a/core/modules/workflow_ui/workflow_ui.module b/core/modules/workflow_ui/workflow_ui.module new file mode 100644 index 0000000..5035ef1 --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.module @@ -0,0 +1,58 @@ +' . t('About') . ''; + $output .= '

' . t('The Workflow UI module provides a UI for creating workflows content. This lets site admins define workflows and their states, and then define transitions between those states. For more information, see the online documentation for the Workflow UI module.', [':workflow' => 'https://www.drupal.org/documentation/modules/workflow_ui']) . '

'; + return $output; + } +} + +/** + * Implements hook_entity_type_build(). + */ +function workflow_ui_entity_type_build(array &$entity_types) { + /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ + $entity_types['workflow'] + ->setFormClass('add', WorkflowAddForm::class) + ->setFormClass('edit', WorkflowEditForm::class) + ->setFormClass('delete', WorkflowDeleteForm::class) + ->setFormClass('add-state', WorkflowStateAddForm::class) + ->setFormClass('edit-state', WorkflowStateEditForm::class) + ->setFormClass('delete-state', WorkflowStateDeleteForm::class) + ->setFormClass('add-transition', WorkflowTransitionAddForm::class) + ->setFormClass('edit-transition', WorkflowTransitionEditForm::class) + ->setFormClass('delete-transition', WorkflowTransitionDeleteForm::class) + ->setListBuilderClass(WorkflowListBuilder::class) + ->set('admin_permission', 'administer workflows') + ->setLinkTemplate('add-form', '/admin/config/workflow/workflows/add') + ->setLinkTemplate('edit-form', '/admin/config/workflow/workflows/manage/{workflow}') + ->setLinkTemplate('delete-form', '/admin/config/workflow/workflows/manage/{workflow}/delete') + ->setLinkTemplate('add-state-form', '/admin/config/workflow/workflows/manage/{workflow}/add_state') + ->setLinkTemplate('add-transition-form', '/admin/config/workflow/workflows/manage/{workflow}/add_transition') + ->setLinkTemplate('collection', '/admin/config/workflow/workflows'); +} diff --git a/core/modules/workflow_ui/workflow_ui.permissions.yml b/core/modules/workflow_ui/workflow_ui.permissions.yml new file mode 100644 index 0000000..88573b6 --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.permissions.yml @@ -0,0 +1,4 @@ +'administer workflows': + title: 'Administer workflows' + description: 'Create and edit workflows.' + 'restrict access': TRUE diff --git a/core/modules/workflow_ui/workflow_ui.routing.yml b/core/modules/workflow_ui/workflow_ui.routing.yml new file mode 100644 index 0000000..b5d853b --- /dev/null +++ b/core/modules/workflow_ui/workflow_ui.routing.yml @@ -0,0 +1,47 @@ +entity.workflow.add_state_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/add_state' + defaults: + _entity_form: 'workflow.add-state' + _title: 'Add state' + requirements: + _entity_access: 'workflow.edit' + +entity.workflow.edit_state_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}' + defaults: + _entity_form: 'workflow.edit-state' + _title: 'Edit state' + requirements: + _entity_access: 'workflow.edit' + +entity.workflow.delete_state_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}/delete' + defaults: + _form: '\Drupal\workflow_ui\Form\WorkflowStateDeleteForm' + _title: 'Delete state' + requirements: + _entity_access: 'workflow.edit' + +entity.workflow.add_transition_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/add_transition' + defaults: + _entity_form: 'workflow.add-transition' + _title: 'Add state' + requirements: + _entity_access: 'workflow.edit' + +entity.workflow.edit_transition_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}' + defaults: + _entity_form: 'workflow.edit-transition' + _title: 'Edit state' + requirements: + _entity_access: 'workflow.edit' + +entity.workflow.delete_transition_form: + path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}/delete' + defaults: + _form: '\Drupal\workflow_ui\Form\WorkflowTransitionDeleteForm' + _title: 'Delete state' + requirements: + _entity_access: 'workflow.edit'