From 7dc13c540c5593f50f8cf44214ddaada5a242d15 Mon Sep 17 00:00:00 2001 From: John Voskuilen <john.voskuilen@sapito.nl> Date: Thu, 13 Jun 2024 11:23:45 +0200 Subject: [PATCH] Issue #3452716: Warning : Undefined array key to_sid in WorkflowTransitionElement::copyFormValuesToTransition() when using Ajax button from other field --- src/Element/WorkflowTransitionElement.php | 22 +++----- src/Entity/WorkflowManager.php | 7 +-- src/Entity/WorkflowTransition.php | 16 ++---- src/Entity/WorkflowTransitionInterface.php | 2 +- src/Form/WorkflowTransitionForm.php | 2 +- src/Plugin/Action/WorkflowStateActionBase.php | 2 +- src/Plugin/Field/FieldType/WorkflowItem.php | 50 +++++++++++++++---- .../FieldWidget/WorkflowDefaultWidget.php | 12 +---- workflow.form.inc | 14 +++++- 9 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/Element/WorkflowTransitionElement.php b/src/Element/WorkflowTransitionElement.php index b3fc2207..ba4a7312 100644 --- a/src/Element/WorkflowTransitionElement.php +++ b/src/Element/WorkflowTransitionElement.php @@ -6,7 +6,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; use Drupal\workflow\Entity\Workflow; -use Drupal\workflow\Entity\WorkflowTransitionInterface; /** * Provides a form element for the WorkflowTransitionForm and ~Widget. @@ -346,28 +345,23 @@ class WorkflowTransitionElement extends FormElement { // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed. $user = workflow_current_user(); + // Read value from form input, else widget values. + // May return NULL's upon hitting a 3rd party button (e.g., file upload) + $action_values = _workflow_transition_form_get_triggering_button($transition, $form_state, $values); + $field_name = $action_values['field_name']; + $to_sid = $action_values['to_sid']; + // Get user input from element. - $field_name = $transition->getFieldName(); $uid = $user->id(); $force = FALSE; - // Read value from form input, else widget values. - $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 = $values['comment'][0]['value'] ?? ''; // @todo Why is 'timestamp' empty at create Node - when is it unset? - $timestamp_values = $values['timestamp'][0]['value'] ?? ['scheduled' => FALSE]; + $timestamp_values = $values['timestamp'][0]['value'] ?? ['scheduled' => false]; $is_scheduled = (bool) $timestamp_values['scheduled']; $timestamp = WorkflowTransitionTimestamp::valueCallback($timestamp_values, $timestamp_values, $form_state); - 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 below exception. // Extract the data from $values, depending on the type of widget. // @todo D8: use massageFormValues($values, $form, $form_state). @@ -400,7 +394,7 @@ class WorkflowTransitionElement extends FormElement { $transition = self::copyAttachedFields($transition, $form, $form_state, $values); // Update targetEntity's itemList with the workflow field in two formats. - $transition->updateEntity(); // jvo + $transition->updateEntityWorkflowField(); // Update form_state, so core can update entity as well. $to_sid = $transition->getToSid(); diff --git a/src/Entity/WorkflowManager.php b/src/Entity/WorkflowManager.php index 06e597a9..f487a44d 100644 --- a/src/Entity/WorkflowManager.php +++ b/src/Entity/WorkflowManager.php @@ -155,7 +155,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; + $transition = $entity->{$field_name}[0]->getTransition(); 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. @@ -203,6 +203,7 @@ class WorkflowManager implements WorkflowManagerInterface { } else { // Execute and check the result. + $transition->updateEntityChangedTime(); $new_sid = $transition->execute(); $executed = ($new_sid == $transition->getToSid()) ? TRUE : FALSE; } @@ -302,9 +303,9 @@ class WorkflowManager implements WorkflowManagerInterface { return $sid; } - // Entity is new or in preview or there is no current state. Use previous state. + // Use previous state if Entity is new/in preview/without current state. // (E.g., content was created before adding workflow.) - if (!$sid || !empty($entity->isNew()) || !empty($entity->in_preview)) { + if ((!$sid) || $entity->isNew() || (!empty($entity->in_preview))) { $sid = self::getPreviousStateId($entity, $field_name); } diff --git a/src/Entity/WorkflowTransition.php b/src/Entity/WorkflowTransition.php index 2f7ff705..642d5307 100644 --- a/src/Entity/WorkflowTransition.php +++ b/src/Entity/WorkflowTransition.php @@ -647,7 +647,8 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition $do_update_entity = (!$this->isScheduled() && !$this->isExecuted()); if ($do_update_entity) { // Update targetEntity's itemList with the workflow field in two formats. - $this->updateEntity(); + $this->updateEntityWorkflowField(); + $this->updateEntityChangedTime(); $entity->save(); } elseif ($this->isScheduled()) { @@ -667,18 +668,11 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition /** * Update the workflow field of the entity in two formats. */ - public function updateEntity() { + public function updateEntityWorkflowField() { $entity = $this->getTargetEntity(); $field_name = $this->getFieldName(); - $to_sid = $this->getToSid(); - // N.B. Align the following functions: - // - WorkflowDefaultWidget::massageFormValues(); - // - WorkflowManager::executeTransition(). - $items = $entity->{$field_name}; - $items->setValue($to_sid); - $items->__set('_workflow_transition', $this); - - $this->updateEntityChangedTime(); + // Set the Transition to the field. This also sets the value to State ID. + $entity->{$field_name}->setValue($this); } /** diff --git a/src/Entity/WorkflowTransitionInterface.php b/src/Entity/WorkflowTransitionInterface.php index 7a2c0823..70d3dfc9 100644 --- a/src/Entity/WorkflowTransitionInterface.php +++ b/src/Entity/WorkflowTransitionInterface.php @@ -285,7 +285,7 @@ interface WorkflowTransitionInterface extends WorkflowConfigTransitionInterface, /** * Updates the entity's workflow field with value and transition. */ - public function updateEntity(); + public function updateEntityWorkflowField(); /** * Returns if this is an Executed Transition. diff --git a/src/Form/WorkflowTransitionForm.php b/src/Form/WorkflowTransitionForm.php index 7b76b2cf..6d417da3 100644 --- a/src/Form/WorkflowTransitionForm.php +++ b/src/Form/WorkflowTransitionForm.php @@ -90,7 +90,7 @@ class WorkflowTransitionForm extends ContentEntityForm { $entity_id = $entity->id(); // Only 1 scheduled transition can be found, but multiple executed ones. - $transition ??= ($entity->{$field_name}->__get('_workflow_transition') ); + $transition ??= $entity->{$field_name}[0]->getTransition(); $transition ??= WorkflowScheduledTransition::loadByProperties($entity_type_id, $entity_id, [], $field_name); if (!$transition) { // Create a transition, to pass to the form. No need to use setValues(). diff --git a/src/Plugin/Action/WorkflowStateActionBase.php b/src/Plugin/Action/WorkflowStateActionBase.php index 6dd5c64b..aea7cf23 100644 --- a/src/Plugin/Action/WorkflowStateActionBase.php +++ b/src/Plugin/Action/WorkflowStateActionBase.php @@ -172,7 +172,7 @@ abstract class WorkflowStateActionBase extends ConfigurableActionBase implements $transition->force($config['force']); $transition->setTargetEntity($entity); // Update targetEntity's itemList with the workflow field in two formats. - $transition->updateEntity(); + $transition->updateEntityWorkflowField(); $entity->setOwnerId($transition->getOwnerId()); // Add the WorkflowTransitionForm element to the page. diff --git a/src/Plugin/Field/FieldType/WorkflowItem.php b/src/Plugin/Field/FieldType/WorkflowItem.php index c3b53042..f4cd03f4 100644 --- a/src/Plugin/Field/FieldType/WorkflowItem.php +++ b/src/Plugin/Field/FieldType/WorkflowItem.php @@ -12,6 +12,7 @@ use Drupal\Core\Url; use Drupal\options\Plugin\Field\FieldType\ListItemBase; use Drupal\workflow\Entity\Workflow; use Drupal\workflow\Entity\WorkflowState; +use Drupal\workflow\Entity\WorkflowTransitionInterface; /** * Plugin implementation of the 'workflow' field type. @@ -56,15 +57,15 @@ class WorkflowItem extends ListItemBase { ->setLabel(t('Workflow state')) ->addConstraint('Length', ['max' => 128]) ->setRequired(TRUE); - /* - $propertyDefinitions[$key]['workflow_transition'] = DataDefinition::create('any') - // $properties['workflow_transition'] = DataDefinition::create('WorkflowTransition') - ->setLabel(t('Transition')) - ->setDescription(t('The computed WorkflowItem object.')) - ->setComputed(TRUE) - ->setClass('\Drupal\workflow\Entity\WorkflowTransition'); - // ->setSetting('date source', 'value'); - */ + + $propertyDefinitions[$key]['_workflow_transition'] = DataDefinition::create('any') + // = DataDefinition::create('WorkflowTransition') + ->setLabel(t('Transition')) + ->setDescription(t('The WorkflowTransition setting the Workflow state.')) + ->setComputed(TRUE) + // ->setClass('\Drupal\workflow\Entity\WorkflowTransition') + // ->setSetting('date source', 'value') + ; } return $propertyDefinitions[$key]; } @@ -223,6 +224,37 @@ class WorkflowItem extends ListItemBase { return $is_empty; } + /** + * {@inheritdoc} + * + * Set both the Transition property AND the to_sid value. + */ + public function setValue($values, $notify = TRUE) { + if ($values instanceof WorkflowTransitionInterface) { + /** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $values */ + $to_sid = $values->getToSid(); + $keys = array_keys($this->definition->getPropertyDefinitions()); + $values = [ + $keys[0] => $to_sid, // 'value'. + $keys[1] => $values, // '_workflow_transition'. + ]; + } + parent::setValue($values, $notify); + } + + /** + * Gets the '_workflow_transition' property. + * + * @return \Drupal\workflow\Entity\WorkflowTransitionInterface + * The Transition object, or NULL of not set. + */ + public function getTransition() { + // Implements $transition = $entity->{$field_name}->__get('_workflow_transition') ?? NULL; + /** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */ + $property = $this->get('_workflow_transition'); + return $property->getValue(); + } + /** * {@inheritdoc} */ diff --git a/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php b/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php index 022cc50e..780f3012 100644 --- a/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php +++ b/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php @@ -130,17 +130,9 @@ class WorkflowDefaultWidget extends WidgetBase { // Let the widget massage the submitted values. $values = $this->massageFormValues($values, $form, $form_state); - // Make sure the targetEntity is set correctly. - $is_new = $transition->getTargetEntity()->isNew(); - if ($is_new){ - // For some reason this is not OK for inserting Nodes, so update $items. - $to_sid = $transition->getToSid(); - $items->setValue($to_sid); - $items->__set('_workflow_transition', $transition); - } - // Update the entity in a 'normal' situation. // Update targetEntity's itemList with the workflow field in two formats. - $transition->updateEntity(); + // In other locations, $transition->updateEntityWorkflowField() is used. + $items->setValue($transition); } /** diff --git a/workflow.form.inc b/workflow.form.inc index 58a52ff5..5e60f4fd 100644 --- a/workflow.form.inc +++ b/workflow.form.inc @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\Html; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\workflow\Entity\WorkflowTransitionInterface; @@ -219,19 +220,30 @@ function _workflow_transition_form_get_action_buttons(array &$form, FormStateInt * @return array * A $field_name => $to_sid array. */ -function _workflow_transition_form_get_triggering_button(FormStateInterface $form_state) { +function _workflow_transition_form_get_triggering_button(WorkflowTransitionInterface $transition, FormStateInterface $form_state, array $values) { $result = ['field_name' => NULL, 'to_sid' => NULL]; $triggering_element = $form_state->getTriggeringElement(); if (isset($triggering_element['#workflow'])) { + // This is a Workflow action button/dropbutton. $result['field_name'] = $triggering_element['#workflow']['field_name']; $result['to_sid'] = $triggering_element['#workflow']['to_sid']; } else { + // This is a normal Save button, + // or another button like 'File upload'. $input = $form_state->getUserInput(); + // Field_name may not exist due to '#access' = false. $result['field_name'] = $input['field_name'] ?? NULL; + // To_sid is taken from the Workflow widget, not from the button. $result['to_sid'] = $input['to_sid'][0]['target_id'] ?? NULL; } + // Try to get new Sid from a value; + // $result['to_sid'] ??= $values['to_sid'][0]['target_id'] ?? NULL; + + // A 3rd party button is hit (e.g., File upload field), get default value. + $result['field_name'] ??= $transition->getFieldName(); + $result['to_sid'] ??= $transition->getFromSid(); return $result; } -- GitLab