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

Issue #3473989: Prepare fixing pre hook - factor out code

parent 077f3635
Branches
Tags
No related merge requests found
Pipeline #520142 passed with warnings
......@@ -266,54 +266,12 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
* CRUD functions.
*/
/**
/**
* {@inheritdoc}
*/
public function execute($force = FALSE) {
// Load the entity, if not already loaded.
// This also sets the (empty) $revision_id in Scheduled Transitions.
$entity = $this->getTargetEntity();
$user = $this->getOwner();
$from_sid = $this->getFromSid();
$to_sid = $this->getToSid();
$field_name = $this->getFieldName();
$comment = $this->getComment();
static $static_info = NULL;
$entity_id = $entity->id();
// For non-default revisions, there is no way of executing the same transition twice in one call.
// Set a random identifier since we won't be needing to access this variable later.
if ($entity instanceof RevisionableInterface) {
/** @var \Drupal\Core\Entity\RevisionableInterface $entity */
if (!$entity->isDefaultRevision()) {
$entity_id .= $entity->getRevisionId();
}
}
// Create a label to identify this transition,
// even upon insert, when id() is not set, yet.
$label = "{$from_sid}-{$to_sid}";
if (isset($static_info[$entity_id][$field_name][$label]) && !$this->isEmpty()) {
// Error: this Transition is already executed.
// On the development machine, execute() is called twice, when
// on an Edit Page, the entity has a scheduled transition, and
// user changes it to 'immediately'.
// Why does this happen?? ( BTW. This happens with every submit.)
// Remedies:
// - search root cause of second call.
// - try adapting code of transition->save() to avoid second record.
// - avoid executing twice.
$message = 'Transition is executed twice in a call. The second call for
@entity_type %entity_id is not executed.';
$this->logError($message);
// Return the result of the last call.
return $static_info[$entity_id][$field_name][$label]; // <-- exit !!!
}
// OK. Prepare for next round. Do not set last_sid!!
$static_info[$entity_id][$field_name][$label] = $from_sid;
// Make sure $force is set in the transition, too.
if ($force) {
......@@ -321,67 +279,23 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
}
$force = $this->isForced();
// Store the transition(s), so it can be easily fetched later on.
// This is a.o. used in:
// - hook_entity_update to trigger 'transition post',
// - hook workflow_access_node_access_records.
$entity->workflow_transitions[$field_name] = $this;
if (!$this->isValid()) {
return $from_sid; // <-- exit !!!
return $from_sid;
}
// @todo Move below code to $this->isAllowed().
// If the state has changed, check the permissions.
// No need to check if Comments or attached fields are filled.
if ($this->hasStateChange()) {
// Determine if user is owner of the entity. If so, add role.
$is_owner = WorkflowRole::isOwner($user, $entity);
if ($is_owner) {
$user->addRole(WorkflowRole::AUTHOR_RID);
}
if (!$this->isAllowed($user, $force)) {
$message = 'User %user not allowed to go from state %sid1 to %sid2';
$this->logError($message);
return FALSE; // <-- exit !!!
}
// Make sure this transition is valid and allowed for the current user.
// Invoke a callback indicating a transition is about to occur.
// Modules may veto the transition by returning FALSE.
// (Even if $force is TRUE, but they shouldn't do that.)
// P.S. The D7 hook_workflow 'transition permitted' is removed,
// in favour of below hook_workflow 'transition pre'.
$permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition pre', $this, $user]);
// Stop if a module says so.
if (in_array(FALSE, $permitted, TRUE)) {
// @todo There is a watchdog error, but no UI-error. Is this OK?
$message = 'Transition vetoed by module.';
$this->logError($message, 'notice');
return FALSE; // <-- exit !!!
}
$isExecutedAlready = $this->isExecutedAlready();
if ($isExecutedAlready) {
return $to_sid;
}
/*
* Output: process the transition.
*/
if (!$this->isScheduled()) {
// The transition is allowed and must be executed now.
// Let other modules modify the comment.
// The transition (in $context) contains all relevant data.
$context = ['transition' => $this];
\Drupal::moduleHandler()->alter('workflow_comment', $comment, $context);
$this->setComment($comment);
$this->isExecuted = TRUE;
}
$this->alterComment();
// Save the transition in {workflow_transition_history} or
// Save the transition in {workflow_transition_scheduled}.
$this->save();
// Save value in static from top of this function.
$static_info[$entity_id][$field_name][$label] = $to_sid;
return $to_sid;
}
......@@ -415,19 +329,70 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
// We create a new transition, or update an existing one.
// Do not update the entity itself.
// Validate transition, save in history table and delete from schedule table.
=> $this->execute($force),
=> $this
->force($force)
->execute(),
default
// Update targetEntity's itemList with the workflow field in two formats.
=> $this
->setEntityWorkflowField()
->setEntityChangedTime()
// Save the TargetEntity.
->getTargetEntity()
->save() ? $to_sid : $from_sid,
};
return $sid;
}
/**
* {@inheritdoc}
*/
public function isExecutedAlready() {
if ($this->isEmpty()) {
return FALSE;
}
$entity = $this->getTargetEntity();
$field_name = $this->getFieldName();
$from_sid = $this->getFromSid();
$to_sid = $this->getToSid();
static $static_info = NULL;
$id = $entity->id() ?? 0;
// For non-default revisions, there is no way of executing the same transition twice in one call.
// Set a random identifier since we won't be needing to access this variable later.
$vid = 0;
if ($entity instanceof RevisionableInterface) {
/** @var \Drupal\Core\Entity\RevisionableInterface $entity */
if (!$entity->isDefaultRevision()) {
$vid = $entity->getRevisionId();
}
}
if (isset($static_info[$id][$vid][$field_name][$from_sid][$to_sid])) {
// Error: this Transition is already executed.
// On the development machine, execute() is called twice, when
// on an Edit Page, the entity has a scheduled transition, and
// user changes it to 'immediately'.
// Why does this happen?? ( BTW. This happens with every submit.)
// Remedies:
// - search root cause of second call.
// - try adapting code of transition->save() to avoid second record.
// - avoid executing twice.
$message = 'Transition is executed twice in a call. The second call for
@entity_type %entity_id is not executed.';
$this->logError($message);
// Return the result of the last call.
return $static_info[$id][$vid][$field_name][$from_sid][$to_sid]; // <-- exit !!!
}
// OK. Prepare for next round.
$static_info[$id][$vid][$field_name][$from_sid][$to_sid] = TRUE;
return FALSE;
}
/**
* {@inheritdoc}
*
......@@ -435,6 +400,9 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
* @todo Also update entity with additional fields.
*/
public function setEntityWorkflowField(): WorkflowTransitionInterface {
if ($this->isScheduled()) {
return $this;
}
$entity = $this->getTargetEntity();
$field_name = $this->getFieldName();
......@@ -537,10 +505,10 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
break;
case $this->id():
// Update the transition. It already exists.
WorkflowEntityHooks::deleteTransitionsOfEntity($entity, 'workflow_scheduled_transition', $field_name);
$result = parent::save();
break;
// Update the transition. It already exists.
WorkflowEntityHooks::deleteTransitionsOfEntity($entity, 'workflow_scheduled_transition', $field_name);
$result = parent::save();
break;
default:
// Insert the executed transition, unless it has already been inserted.
......@@ -652,35 +620,82 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
*/
/**
* Determines if the Transition is valid and can be executed.
* Invokes 'hook_workflow_comment'.
*/
public function alterComment() {
if ($this->isScheduled()) {
return $this;
}
// The transition is allowed and must be executed now.
// Let other modules modify the comment.
$comment = $this->getComment();
// The transition (in $context) contains all relevant data.
$context = ['transition' => $this];
\Drupal::moduleHandler()->alter('workflow_comment', $comment, $context);
$this->setComment($comment);
return $this;
}
/**
* {@inheritdoc}
*
* @todo Add to isAllowed() ?
* @todo Add checks to WorkflowTransitionElement ?
*
* @return bool
* TRUE is the Transition is OK, else FALSE.
*/
public function isValid() {
$valid = TRUE;
// Load the entity, if not already loaded.
// This also sets the (empty) $revision_id in Scheduled Transitions.
$entity = $this->getTargetEntity();
$user = $this->getOwner();
$force = $this->isForced();
if (!$entity) {
// @todo There is a watchdog error, but no UI-error. Is this OK?
$message = 'User tried to execute a Transition without an entity.';
$this->logError($message);
$valid = FALSE;
return FALSE;
}
elseif (!$this->getFieldName()) {
// elseif (!$this->getFromSid()) {
// @todo The page is not correctly refreshed after this error.
$message = $this->t('You tried to set a Workflow State, but
the entity is not relevant. Please contact your system administrator.');
if (!$this->getFieldName()) {
// @todo The page is not correctly refreshed after this error.
$message = $this->t('The entity is not relevant for setting
a Workflow State. Please contact your system administrator.');
$this->messenger()->addError($message);
$message = 'Setting a non-relevant Entity from state %sid1 to %sid2';
$this->logError($message);
$valid = FALSE;
return FALSE;
}
// @todo Move below code to $this->isAllowed().
// If the state has changed, check the permissions.
// No need to check if Comments or attached fields are filled.
if ($this->hasStateChange()) {
if (!$this->isAllowed($user, $force)) {
$message = 'User %user not allowed to go from state %sid1 to %sid2';
$this->logError($message);
return FALSE; // <-- exit !!!
}
}
if ($this->hasStateChange()) {
// Make sure this transition is valid and allowed for the current user.
// Invoke a callback indicating a transition is about to occur.
// Modules may veto the transition by returning FALSE.
// (Even if $force is TRUE, but they shouldn't do that.)
// P.S. The D7 hook_workflow 'transition permitted' is removed,
// in favour of below hook_workflow 'transition pre'.
$permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition pre', $this, $user]);
// Stop if a module says so.
if (in_array(FALSE, $permitted, TRUE)) {
// @todo There is a watchdog error, but no UI-error. Is this OK?
$message = 'Transition vetoed by module.';
$this->logError($message, 'notice');
return FALSE; // <-- exit !!!
}
}
return $valid;
......@@ -1336,8 +1351,8 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
$output[] = "From/To = {$transition->getFromState()} > {$transition->getToState()}";
$output[] = "Comment = {$user_name} says: {$transition->getComment()}";
$output[] = "Forced = " . ($transition->isForced() ? 'yes' : 'no')
. "; Scheduled = " . ($transition->isScheduled() ? 'yes' : 'no')
. "; Executed = " . ($transition->isExecuted() ? 'yes' : 'no');
. "; Scheduled = " . ($transition->isScheduled() ? 'yes' : 'no')
. "; Executed = " . ($transition->isExecuted() ? 'yes' : 'no');
foreach ($this->getAttachedFieldDefinitions() as $field_name => $field) {
$value = (string) ($this->{$field_name}?->value ?? 'value not found (for scheduled transition?)');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment