workflow-8.x-1.x-dev/src/Plugin/Action/WorkflowStateActionBase.php
src/Plugin/Action/WorkflowStateActionBase.php
<?php
namespace Drupal\workflow\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\workflow\Entity\Workflow;
use Drupal\workflow\Entity\WorkflowState;
use Drupal\workflow\Entity\WorkflowTransition;
use Drupal\workflow\Form\WorkflowTransitionForm;
use Drupal\workflow\Plugin\Field\FieldWidget\WorkflowDefaultWidget;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Sets an entity to a new or given state.
*/
abstract class WorkflowStateActionBase extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
/**
* The logger service.
*
* @var \Drupal\Core\Logger\LoggerChannel
*/
protected $logger;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
$this->logger = \Drupal::logger('workflow_action');
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return parent::calculateDependencies() + [
'module' => ['workflow'],
];
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$configuration = parent::defaultConfiguration();
$configuration += [
'field_name' => '',
'to_sid' => '',
'comment' => "New state is set by a triggered Action.",
'force' => FALSE,
];
return $configuration;
}
/**
* Gets the entity's transition that must be executed.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which a transition must be fetched.
* @param string $field_name
* The field_name.
*
* @return \Drupal\workflow\Entity\WorkflowTransitionInterface|null
* The Transition object, or NULL if not found.
*/
protected function getTransitionForExecution(EntityInterface $entity, $field_name) {
$user = workflow_current_user();
$config = $this->configuration;
$field_name = $config['field_name'];
$to_sid = $config['to_sid'];
$comment = $config['comment'];
$force = $config['force'];
if (!$entity) {
$this->logger->notice('Unable to get current entity - entity is not defined.',
[]);
return NULL;
}
$entity_id = $entity->id();
if (!$entity_id) {
$this->logger->notice('Unable to get current entity ID - entity is not yet saved.',
[]);
return NULL;
}
$field_name = workflow_get_field_name($entity, $field_name);
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
if (!$entity->hasField($field_name)) {
$this->logger->notice("Unable to process entity %id - entity does not have field %field_name.",
[
'%id' => $entity_id,
'%field_name' => $field_name,
]);
return NULL;
}
$current_sid = workflow_node_current_state($entity, $field_name);
if (!$current_sid) {
$this->logger->notice('Unable to get current workflow state of entity %id.',
[
'%id' => $entity_id,
'%field_name' => $field_name,
]);
return NULL;
}
// In 'after saving new content', node is already saved. Avoid 2nd insert.
// @todo Outdated code?
$entity->enforceIsNew(FALSE);
$timestamp = WorkflowTransition::getDefaultRequestTime();
// Translate the Comment. Parse the $comment variables.
$comment = $this->t($comment, [
'%title' => $entity->label(),
// "@" and "%" will automatically run check_plain().
'%state' => workflow_get_sid_name($to_sid),
'%user' => $user->getDisplayName(),
]);
$transition = WorkflowTransition::create([
'from_sid' => $current_sid,
'entity' => $entity,
'field_name' => $field_name,
]);
$transition->setValues($to_sid, $user->id(), $timestamp, $comment);
$transition->force($force);
return $transition;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$element = [];
$config = $this->configuration;
$config_field_name = $config['field_name'];
$to_sid = $config['to_sid'];
$comment = $config['comment'];
$force = $config['force'];
// @todo Support also other entity types then 'node'.
$entity_type_id = 'node';
$entity = NULL;
$workflow = NULL;
// Restore some key from OptionsWidgetBase::getEmptyLabel().
$config_field_name = ($config_field_name == '_none')
? ''
: $config_field_name;
// Find a field_name for the widget.
$field_name = $config_field_name;
if (!$field_name) {
$field_map = workflow_get_workflow_fields_by_entity_type($entity_type_id);
// Get the field name of the (arbitrary) first node type.
$field_name = key($field_map);
if (!$field_name) {
// We are in problem.
}
}
if ($field_name) {
// Get the first field/bundle, matching or not.
$fields = _workflow_info_fields(NULL, $entity_type_id, '', $field_name);
$field_config = reset($fields);
$bundles = $field_config->getBundles();
$entity_bundle = reset($bundles);
$wid = $field_config?->getSetting('workflow_type') ?? '';
$state = $to_sid ? WorkflowState::load($to_sid) : NULL;
// If user has changed field name, then reset the state.
if ($wid !== ($state?->getWorkflowId() ?? NULL)) {
$workflow = Workflow::load($wid);
$to_sid = $workflow->getCreationSid();
}
}
// Create the helper entity.
$entity_type_manager = \Drupal::service('entity_type.manager');
// $entity = new Node([], $entity_type_id, $entity_bundle);
$entity = $entity_type_manager->getStorage($entity_type_id)->create([
'type' => $entity_bundle,
]);
/** @var \Drupal\workflow\Plugin\Field\WorkflowItemListInterface $items */
$items = $entity->{$field_name};
// Create the Transition with config data.
$transition = $items->getDefaultTransition();
// Update Transition with config settings.
// Update Transition without using $transition->setValues().
// @todo Why this strange from_sid? Perhaps not relevant/used at all?
$transition->{'from_sid'}->set(0, $to_sid);
$transition->{'to_sid'}->set(0, $to_sid);
$transition->setComment((string) $comment);
// @todo Value FALSE is not reflected in widget.
$transition->force($force);
$transition->setTargetEntity($entity);
// Update targetEntity's itemList with the workflow field in two formats.
$transition->setEntityWorkflowField();
$entity->setOwnerId($transition->getOwnerId());
// Prepare adaptations for Actions UI / VBO-form.
// Overwrite / Prepare a UI wrapper. It might be a (collapsible) fieldset.
if ($workflow) {
$workflow_settings = $workflow->getSettings();
// Set/reset 'options' to Avoid Action Buttons, because that
// removes the options box&more. No Buttons in config screens!
// Note: This is handled by the widget itself.
// $workflow->setSetting('options', 'select');
// .
$workflow->setSetting('fieldset', 1);
// Do not set title, as Workflow type may not be defined, yet.
$workflow->setSetting('name_as_title', FALSE);
}
// Add the WorkflowTransitionForm element to the page.
// Set/reset 'options' to Avoid Action Buttons, because that
// removes the options box&more. No Buttons in config screens!
// $workflow = $transition->getWorkflow();
// $workflow->setSetting('options', 'select');
// Option 1: call transitionElement() directly.
// $element['#default_value'] = $transition;
// $element = WorkflowTransitionElement::alter($element, $form_state, $form);
// .
$dummy_form = [];
// Note: Align ['#parents'] in Widget::form...(), Form::copy..(), ...
$dummy_form['#parents'] = ['workflow_transition_action_config'];
$workflow_form_state = NULL;
// The following line creates a $form_state with WT object.
$form_state_additions = [];
$form_object = WorkflowTransitionForm::createInstance(
$transition,
$workflow_form_state,
$form_state_additions
);
// Option 2: call WorkflowDefaultWidget via NodeFormDisplay.
$widget = WorkflowDefaultWidget::createInstance($transition);
if ($widget) {
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$items = $entity->get($field_name);
$workflow_form = $widget->form($items, $dummy_form, $workflow_form_state);
// Fetch the element from the widget.
// Note: Align ['#parents'] in Widget::form...(), Form::copy..(), ...
$element = $workflow_form['widget'][0];
}
// Reset workflow settings, since workflow is a global object.
if ($workflow) {
$workflow->setSettings($workflow_settings);
}
$element['field_name']['#access'] = TRUE;
$element['field_name']['widget']['#access'] = TRUE;
$element['field_name']['widget']['#required'] = FALSE;
$element['field_name']['widget']['#description']
.= '</br>'
. $this->t('May be left empty.');
// @todo Sometimes not all field names are listed.
// Change field_name option 'None' to 'Any'.
$element['field_name']['widget']['#options'] =
['' => $this->t('- Any -')]
+ $element['field_name']['widget']['#options'];
unset($element['field_name']['widget']['#options']['_none']);
$element['field_name']['widget']['#default_value'] = $config_field_name;
if ($config_field_name) {
$element['to_sid']['#access'] = TRUE;
$element['to_sid']['widget']['#access'] = TRUE;
$element['to_sid']['#description'] = $this->t('Please select the state that should be assigned when this action runs.');
}
else {
$element['to_sid']['#access'] = FALSE;
$element['to_sid']['widget']['#options'] = [];
}
$description = $this->t('This message will be written
into the workflow history log when the action runs.
You may include the following variables: %state, %title, %user.');
$element['comment']['widget']['#description'] = $description;
$element['comment']['widget'][0]['#description'] = $description;
$element['comment']['widget'][0]['value']['#description'] = $description;
$form['workflow_transition_action_config'] = $element;
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$transition = $form['workflow_transition_action_config']['#workflow_transition'];
$field_name = $transition->getFieldName();
// When using Widget/Form, read $input.
$values = $form_state->getUserInput()['workflow_transition_action_config'][$field_name];
// When using Element, read $values.
// $values = $form_state->getValues()['workflow_transition_action_config'][$field_name];
$configuration = [
'field_name' => $values['field_name'] ?? '',
'to_sid' => $values['to_sid'] ?? '',
'comment' => $values['comment'][0]['value'],
'force' => $values['force'] ?? FALSE,
];
$this->configuration = $configuration;
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$config = $this->configuration;
$field_name = $config['field_name'];
$to_sid = $config['to_sid'];
$comment = $config['comment'];
$force = $config['force'];
$field_name = workflow_get_field_name($object, $field_name);
$transition = $this->getTransitionForExecution($object, $field_name);
if (!$transition) {
$this->messenger()->addWarning(
$this->t('The entity %label is not valid for this action.',
['%label' => $object ? $object->label() : ''])
);
return;
}
if ($field_name !== $transition->getFieldName()) {
$this->messenger()->addWarning(
$this->t('The entity %label is not valid for this action. Wrong field name.',
['%label' => $object ? $object->label() : ''])
);
return;
}
/*
* Set the new/next state.
*/
$entity = $transition->getTargetEntity();
$user = $transition->getOwner();
if ($to_sid == '') {
$to_sid = $transition->getWorkflow()->getNextSid($entity, $field_name, $user, $force);
}
$transition->to_sid = $to_sid;
// The following is already set above.
// $transition->setComment($comment);
// $transition->force($force); .
// Fire the transition.
$transition->executeAndUpdateEntity($force);
}
/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
$access = AccessResult::allowed();
return $return_as_object ? $access : $access->isAllowed();
}
}
