Skip to content
Snippets Groups Projects
Commit ad73361b authored by John Voskuilen's avatar John Voskuilen
Browse files

Issue #2926094: Move to_sid and comment fields to widget

parent 079a876d
Branches
Tags
No related merge requests found
Pipeline #128755 passed with warnings
......@@ -177,7 +177,7 @@ class WorkflowTransitionElement extends FormElement {
// Save the current value of the entity in the form, for later Workflow-module specific references.
// We add prefix, since #tree == FALSE.
$element['workflow_transition'] = [
$element['_workflow_transition'] = [
'#type' => 'value',
'#value' => $transition,
];
......@@ -239,7 +239,9 @@ class WorkflowTransitionElement extends FormElement {
// It may be replaced later if 'Action buttons' are chosen.
// This overrides BaseFieldDefinition. @todo Apply for form and widget.
// @todo Repair $workflow->'name_as_title': no container if no details (schedule/comment).
$element['to_sid'] = [
$attribute_name = 'to_sid';
$attribute_key = 'target_id';
$element[$attribute_name]['widget'][0][$attribute_key] = [
// Avoid error with grouped options when workflow not set.
'#type' => ($wid) ? $options_type : 'select',
'#title' => (!$workflow_settings['name_as_title'] && !$transition->isExecuted())
......@@ -249,8 +251,12 @@ class WorkflowTransitionElement extends FormElement {
'#access' => TRUE,
'#options' => $options,
'#default_value' => $default_value,
'#weight' => $field_weight,
];
// '#weight' => $field_weight,
// Remove autocomplete settings from BaseFieldDefinitions().
'#maxlength' => 999,
'#size' => 0,
] + $element[$attribute_name]['widget'][0][$attribute_key];
if (_workflow_use_action_buttons($options_type)) {
// In WorkflowTransitionForm, a default 'Submit' button is added there.
// In Entity Form, workflow_form_alter() adds button per permitted state.
......@@ -260,29 +266,32 @@ class WorkflowTransitionElement extends FormElement {
// It will be replaced by action buttons, but sometimes, the select box
// is still shown.
// @see workflow_form_alter().
$element['to_sid']['#type'] = 'select';
$element['to_sid']['#access'] = FALSE;
$element[$attribute_name]['widget'][0][$attribute_key] = [
'#type' => 'select',
'#access' => FALSE,
] + $element[$attribute_name]['widget'][0][$attribute_key];
}
// Display scheduling form under certain conditions.
$element['timestamp']['widget'][0]['value'] = [
'#type' => 'workflow_transition_timestamp',
'#default_value' => $transition,
] + ($element['timestamp']['widget'][0]['value'] ?? []);
] + $element['timestamp']['widget'][0]['value'];
$element['timestamp']['#weight'] = $field_weight;
// Show comment, when both Field and Instance allow this.
// This overrides BaseFieldDefinition. @todo Apply for form and widget.
$element['comment'] = [
'#type' => 'textarea',
// This overrides BaseFieldDefinition.
$attribute_name = 'comment';
$attribute_key = 'value';
$element[$attribute_name]['widget'][0][$attribute_key] = [
// '#type' => 'textarea',
'#title' => t('Comment'),
'#description' => t('Briefly describe the changes you have made.'),
'#access' => $workflow_settings['comment_log_node'] != '0', // Align with action buttons.
'#default_value' => $transition->getComment(),
'#required' => $workflow_settings['comment_log_node'] == '2',
'#rows' => 2,
'#weight' => $field_weight,
];
'#rows' => 2, //@todo Use correct field setting UI.
] + $element[$attribute_name]['widget'][0][$attribute_key];
$element['force'] = [
'#type' => 'checkbox',
......@@ -332,34 +341,33 @@ class WorkflowTransitionElement extends FormElement {
*/
public static function copyFormValuesToTransition(EntityInterface $transition, array $form, FormStateInterface $form_state, array $item) {
/** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
// $values = $form_state->getValues();
$values = $item;
$input = $form_state->getUserInput();
if (!isset($values['to_sid'])) {
$entity_id = $transition->getTargetEntityId();
\Drupal::messenger()->addError(t('Error: content @id has no workflow attached. The data is not saved.', ['@id' => $entity_id]));
// The new state is still the previous state.
return $transition;
}
// @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed.
$user = workflow_current_user();
// Get user input from element.
$field_name = $transition->getFieldName();
$uid = $user->id();
$timestamp = \Drupal::time()->getRequestTime();
$force = FALSE;
// Read value from form input, else widget values.
$action_info = _workflow_transition_form_get_triggering_button($form_state);
$to_sid = $action_info['to_sid'] ?? $values['to_sid'];
$action_values = _workflow_transition_form_get_triggering_button($form_state);
$to_sid = $action_values['to_sid'] ?? $values['to_sid'][0]['target_id'];
// Note: when editing existing Transition, user may still change comments.
$comment = $input['comment'] ?? $input[$field_name][0]['comment'] ?? '';
$timestamp_widget = $values['timestamp'][0]['value'] ?? $values['timestamp']['widget'][0]['value'];
$is_scheduled = (bool) $timestamp_widget['scheduled'];
$timestamp = WorkflowTransitionTimestamp::valueCallback($timestamp_widget, $timestamp_widget, $form_state);
$comment = $input['comment'][0]['value'] ?? '';
$timestamp_values = $values['timestamp'][0]['value'];
$is_scheduled = (bool) $timestamp_values['scheduled'];
$timestamp = WorkflowTransitionTimestamp::valueCallback($timestamp_values, $timestamp_values, $form_state)
?? \Drupal::time()->getRequestTime();
if (!isset($to_sid)) {
$entity_id = $transition->getTargetEntityId();
\Drupal::messenger()->addError(t('Error: content @id has no workflow attached. The data is not saved.', ['@id' => $entity_id]));
// The new state is still the previous state.
return $transition;
}
// @todo D8: add the VBO use case.
/*
......@@ -402,6 +410,10 @@ class WorkflowTransitionElement extends FormElement {
$transition->force($force);
}
// Also add the transition to the ItemList.
$entity = $transition->getTargetEntity();
$entity->{$field_name}->__set('_workflow_transition', $transition);
// Add the attached fields.
// Caveat: This works automatically on a Workflow Form,
// but only with a hack on a widget.
......@@ -411,15 +423,9 @@ class WorkflowTransitionElement extends FormElement {
foreach ($fields as $field_name => $field) {
$user_input = $input[$field_name] ?? [];
if (isset($values[$field_name])) {
// On Workflow Form (e.g., history tab, block).
// @todo In latest tests, this line seems not necessary.
// @todo This line seems necessary for node edit, not for node view.
$transition->{$field_name} = $values[$field_name];
}
elseif ($user_input) {
// On Workflow Widget (e.g., on node, comment).
// @todo Some field types are not supported here.
$transition->{$field_name} = $user_input;
}
// #2899025 For each field, let other modules modify the copied values,
// as a workaround for not-supported field types.
......
......@@ -154,7 +154,7 @@ class WorkflowManager implements WorkflowManagerInterface {
foreach ($field_names as $field_name) {
/** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
// @todo Transition is created in widget or WorkflowTransitionForm.
$transition = $entity->{$field_name}->__get('_workflow_transition') ?? NULL; // @todo #default_value.
$transition = $entity->{$field_name}->__get('_workflow_transition') ?? NULL;
if (!$transition) {
// We come from creating/editing an entity via entity_form, with core widget or hidden Workflow widget.
// @todo D8: From an Edit form with hidden widget.
......
......@@ -148,9 +148,8 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
*
* @see entity_create()
*/
public function __construct(array $values = [], $entityType = 'workflow_transition', $bundle = FALSE, array $translations = []) {
// Please be aware that $entity_type and $entityType are different things!
parent::__construct($values, $entityType, $bundle, $translations);
public function __construct(array $values = [], $entity_type_id = 'workflow_transition', $bundle = FALSE, array $translations = []) {
parent::__construct($values, $entity_type_id, $bundle, $translations);
$this->eventDispatcher = \Drupal::service('event_dispatcher');
// This transition is not scheduled.
$this->isScheduled = FALSE;
......@@ -329,9 +328,9 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
/**
* {@inheritdoc}
*/
public static function loadByProperties($entity_type, $entity_id, array $revision_ids = [], $field_name = '', $langcode = '', $sort = 'ASC', $transition_type = 'workflow_transition') {
public static function loadByProperties($entity_type_id, $entity_id, array $revision_ids = [], $field_name = '', $langcode = '', $sort = 'ASC', $transition_type = 'workflow_transition') {
$limit = 1;
$transitions = self::loadMultipleByProperties($entity_type, [$entity_id], $revision_ids, $field_name, $langcode, $limit, $sort, $transition_type);
$transitions = self::loadMultipleByProperties($entity_type_id, [$entity_id], $revision_ids, $field_name, $langcode, $limit, $sort, $transition_type);
if ($transitions) {
$transition = reset($transitions);
return $transition;
......@@ -342,11 +341,11 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
/**
* {@inheritdoc}
*/
public static function loadMultipleByProperties($entity_type, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = 'workflow_transition') {
public static function loadMultipleByProperties($entity_type_id, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = 'workflow_transition') {
/** @var \Drupal\Core\Entity\Query\QueryInterface $query */
$query = \Drupal::entityQuery($transition_type)
->condition('entity_type', $entity_type)
->condition('entity_type', $entity_type_id)
->accessCheck(FALSE)
->sort('timestamp', $sort)
->addTag($transition_type);
......@@ -718,9 +717,9 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
// @todo D8: the following line only returns Node, not Term.
/* return $this->entity = $this->get('entity_id')->entity; */
$entity_type = $this->getTargetEntityTypeId();
$entity_type_id = $this->getTargetEntityTypeId();
if ($id = $this->getTargetEntityId()) {
$this->entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id);
$this->entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($id);
}
return $this->entity;
}
......@@ -853,9 +852,6 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
* {@inheritdoc}
*/
public function schedule($schedule = TRUE) {
// We do a tricky thing here. The id of the entity is altered, so
// all functions of another subclass are called.
// $this->entityTypeId = ($schedule) ? 'workflow_scheduled_transition' : 'workflow_transition';
$this->isScheduled = $schedule;
return $this;
}
......@@ -1034,12 +1030,11 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
->setLabel(t('To state'))
->setDescription(t('The {workflow_states}.sid the entity transitioned to.'))
->setSetting('target_type', 'workflow_state')
// @todo D8: Activate this. Test with both Form and Widget.
// ->setDisplayOptions('form', [
// 'type' => 'select',
->setDisplayOptions('form', [
'type' => 'select',
// 'weight' => -5,
// ])
// ->setDisplayConfigurable('form', TRUE)
])
->setDisplayConfigurable('form', TRUE)
->setReadOnly(TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
......@@ -1084,7 +1079,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
'type' => 'workflow_transition_timestamp',
// 'type' => 'datetime_timestamp',
// 'label' => 'hidden',
'weight' => -100,
// 'weight' => -100,
])
->setDisplayConfigurable('form', TRUE)
->setRevisionable(TRUE);
......@@ -1093,16 +1088,16 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
->setLabel(t('Log message'))
->setDescription(t('The comment explaining this transition.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
// @todo D8: activate this. Test with both Form and Widget.
// ->setDisplayOptions('form', [
// 'type' => 'string_textarea',
->setTranslatable(TRUE)
->setDisplayOptions('form', [
'type' => 'textarea',
// 'weight' => 25,
// 'settings' => [
// 'rows' => 4,
// ],
// ])
// ->setDisplayConfigurable('form', FALSE);
'settings' => [
//@todo: Why shows 'Manage fields' 5 rows in the beginning, not 2?
'rows' => 2,
],
])
->setDisplayConfigurable('form', TRUE);
return $fields;
}
......
......@@ -64,8 +64,9 @@ class WorkflowItem extends ListItemBase {
* @var array
*/
static $propertyDefinitions;
$definition['settings']['target_type'] = 'workflow_transition';
// Use underscore to avoid confusion with EntityTypeId.
// @todo Better use 'target_transition'.
$definition['settings']['target_type'] = '_workflow_transition';
// Definitions vary by entity type and bundle, so key them accordingly.
$key = $definition['settings']['target_type'] . ':';
$key .= $definition['settings']['target_bundle'] ?? '';
......
......@@ -5,9 +5,11 @@ namespace Drupal\workflow\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\workflow\Element\WorkflowTransitionElement;
use Drupal\workflow\Entity\Workflow;
use Drupal\workflow\Entity\WorkflowManager;
use Drupal\workflow\Entity\WorkflowTransition;
/**
* Plugin implementation of the 'workflow_default' widget.
......@@ -56,41 +58,74 @@ class WorkflowDefaultWidget extends WidgetBase {
$field_name = $field_storage->getName();
$transition = WorkflowManager::getDefaultTransition($entity, $field_name);
// Here, on entity form, not the $element is added, but the entity form.
// Problem 1: adding the element, does not add added fields.
// Problem 2: adding the form, generates wrong UI.
// Problem 3: does not work on ScheduledTransition.
//
// Step 1: use the Element.
$element['#default_value'] = $transition;
$element = WorkflowTransitionElement::transitionElement($element, $form_state, $form);
// Step 2: use the Form, in order to get extra fields.
// To prepare the widget, use the Form, in order to get extra fields.
$form_state_additions = [
'input' => $form_state->getUserInput(),
'values' => $form_state->getValues(),
'triggering_element' => $form_state->getTriggeringElement(),
];
$workflow_form ??= WorkflowManager::getWorkflowTransitionForm($entity, $field_name, $form_state_additions);
$workflow_form = WorkflowManager::getWorkflowTransitionForm($entity, $field_name, $form_state_additions);
// Determine and add the attached fields.
// @todo Option 2: remove above Step 1 and use this form?
$attached_fields = WorkflowManager::getAttachedFields('workflow_transition', $wid);
foreach ($attached_fields as $key => $attached_field) {
$workflow_form ??= WorkflowManager::getWorkflowTransitionForm($entity, $field_name, $form_state_additions);
$element[$key] = $workflow_form[$key] ?? NULL;
// Then, remove all form elements, keep widget elements.
$base_fields = WorkflowTransition::baseFieldDefinitions($transition->getEntityType());
$fields = $attached_fields + $base_fields + [
'_workflow_transition' => '_workflow_transition',
'force' => 'force',
];
foreach (Element::children($workflow_form) as $attribute_name) {
if (array_key_exists($attribute_name, $fields)) {
$element[$attribute_name] ??= $workflow_form[$attribute_name];
}
// Option 3: use the true Element.
// $form = $this->element($form, $form_state, $transition);
// $element['workflow_transition'] = [
// '#type' => 'workflow_transition',
// '#title' => $this->t('Workflow transition'),
// '#default_value' => $transition,
// ];
}
// The following are not in Element::children.
$element['#default_value'] = $workflow_form['#default_value'];
// Overwrite value set by Form.
$element['#type'] = 'details';
$element['#collapsible'] = $workflow_form['#collapsible'];
$element['#open'] = $workflow_form['#open'];
return $element;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
// parent::extractFormValues($items, $form, $form_state);
// Override WidgetBase::extractFormValues() since
// it extracts field values without respecting #tree = TRUE.
// So, the following function massageFormValues has nothing to do.
$field_name = $this->fieldDefinition->getName();
$transition = $form_state->getValue('_workflow_transition');
$values = ['transition' =>
// $form_state->getValues()
$form_state->getUserInput()
+ ['#default_value' => $transition],
];
$key_exists = TRUE;
if ($key_exists) {
// Let the widget massage the submitted values.
$values = $this->massageFormValues($values, $form, $form_state);
// Get the new value from an action button if set in the workflow settings.
$action_info = _workflow_transition_form_get_triggering_button($form_state);
if ($field_name == $action_info['field_name']) {
// $values = [0 => $action_info['to_sid']];
}
// Assign the values and remove the empty ones.
$items->setValue($values['transition']['value']);
$items->filterEmptyItems();
// Also add the transition to the ItemList.
$items->__set('_workflow_transition', $transition);
}
}
/**
* {@inheritdoc}
*
......@@ -106,7 +141,6 @@ class WorkflowDefaultWidget extends WidgetBase {
* @todo Remove update of {node_form} table. (separate task, because it has features, too.)
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// @todo #2287057: verify if submit() really is only used for UI.
// If not, $user must be passed.
$user = workflow_current_user();
......@@ -120,7 +154,7 @@ class WorkflowDefaultWidget extends WidgetBase {
if (!empty($item)) {
// Use a proprietary version of copyFormValuesToEntity().
/** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
$transition = $item['workflow_transition'];
$transition = $item['#default_value'];
$transition = WorkflowTransitionElement::copyFormValuesToTransition($transition, $form, $form_state, $item);
// Try to execute the transition. Return $from_sid when error.
......@@ -177,7 +211,6 @@ class WorkflowDefaultWidget extends WidgetBase {
// - WorkflowDefaultWidget::massageFormValues();
// - WorkflowManager::executeTransition().
// Set the transition back, to be used in hook_entity_update().
$item['workflow_transition'] = $transition;
// Set the value at the proper location.
if ($transition && $transition->isScheduled()) {
$item['value'] = $from_sid;
......
......@@ -247,18 +247,16 @@ function hook_workflow_permitted_state_transitions_alter(array &$transitions, ar
* Better use hook_form_workflow_transition_form_alter.
*/
function hook_field_widget_single_element_workflow_default_form_alter(&$element, FormStateInterface $form_state, $context) {
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $context['widget']->getPluginId(), '');
// A hook specific for the 'workflow_default' widget.
// The name is specified in the annotation of WorkflowDefaultWidget.
workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
// A widget on an entity form.
if ('workflow_default' != $context['widget']->getPluginId()) {
// This can never happen.
return;
}
// The Transition object contains all you need.
// You may find it in one of two locations.
// The $transition object contains all you need.
/** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
$transition = $element['#default_value'] ?? NULL;
if (!$transition) {
......@@ -266,13 +264,13 @@ function hook_field_widget_single_element_workflow_default_form_alter(&$element,
}
// An example of customizing/overriding the workflow widget.
// Beware, until now, you must do this twice: on the widget and on the form.
// Beware: until now, you must do this twice: on the widget and on the form.
$uid = $transition->getOwnerId();
if ($uid == 10) {
\Drupal::messenger()->addWarning('(Test/Devel message) For you, user 1,
the scheduling is disabled, and commenting is required.');
// Let us prohibit scheduling for user 1.
$element['workflow_scheduling']['#access'] = FALSE;
$element['timestamp']['#access'] = FALSE;
// Let us require commenting for user 1.
if ($element['comment']['#access'] ?? FALSE) {
$element['comment']['#required'] = TRUE;
......@@ -289,19 +287,15 @@ function hook_field_widget_single_element_workflow_default_form_alter(&$element,
* hook_form_alter(). See below for more info.
*/
function hook_form_workflow_transition_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $form_id, '');
// The WorkflowTransitionForm (E.g., Workflow History tab, Block).
// It has its own handling.
// @todo Fill WorkflowTransitionForm with Widget, to have 1 way-of-working.
// This object contains all you need. You may find it in one of two locations.
// The $transition object contains all you need.
/** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
$transition = $form['workflow_transition']['#value'];
$transition = $form['#default_value'];
// An example of customizing/overriding the workflow widget.
// Beware, until now, you must do this twice: on the widget and on the form.
// Beware: until now, you must do this twice: on the widget and on the form.
$uid = $transition->getOwnerId();
if ($uid == 1) {
if ($uid == 10) {
\Drupal::messenger()->addWarning('(Test/Devel message) For you, user 1,
the scheduling is disabled, and commenting is required.');
// Let us prohibit scheduling for user 1.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment