You are here

public function WorkflowTransition::execute in Workflow 8

Execute a transition (change state of an entity).

A Scheduled Transition shall only be saved, unless the 'schedule' property is set.

@usage $transition->schedule(FALSE); $to_sid = $transition->execute(TRUE);

Parameters

bool $force: If set to TRUE, workflow permissions will be ignored.

Return value

string New state ID. If execution failed, old state ID is returned,

Overrides WorkflowTransitionInterface::execute

1 call to WorkflowTransition::execute()
WorkflowTransition::executeAndUpdateEntity in src/Entity/WorkflowTransition.php
Executes a transition (change state of an entity), from OUTSIDE the entity.

File

src/Entity/WorkflowTransition.php, line 436

Class

WorkflowTransition
Implements an actual, executed, Transition.

Namespace

Drupal\workflow\Entity

Code

public function execute($force = FALSE) {

  // Load the entity, if not already loaded.
  // This also sets the (empty) $revision_id in Scheduled Transitions.

  /** @var \Drupal\Core\Entity\EntityInterface $entity */
  $entity = $this
    ->getTargetEntity();

  // Load explicit User object (not via $transition) for adding Role later.

  /** @var \Drupal\user\UserInterface $user */
  $user = $this
    ->getOwner();
  $from_sid = $this
    ->getFromSid();
  $to_sid = $this
    ->getToSid();
  $field_name = $this
    ->getFieldName();
  $comment = $this
    ->getComment();

  // Create a label to identify this transition,
  // even upon insert, when id() is not set, yet.
  $label = $from_sid . '-' . $to_sid;
  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 && !$entity
    ->isDefaultRevision()) {
    $entity_id = $entity_id . $entity
      ->getRevisionId();
  }
  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) {
    $this
      ->force($force);
  }
  $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 !!!
  }

  // @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()) {

    // Make sure this transition is allowed by workflow module Admin UI.
    if (!$force) {
      $user
        ->addRole(WORKFLOW_ROLE_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 !!!
    }
  }

  /*
   * Output: process the transition.
   */
  if ($this
    ->isScheduled()) {

    /*
     * Log the transition in {workflow_transition_scheduled}.
     */
    $this
      ->save();
  }
  else {

    // The transition is allowed, but not scheduled.
    // 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;
    if (!$this
      ->isEmpty()) {

      /*
       * Log the transition in {workflow_transition_history}.
       */
      $this
        ->save();

      // Register state change with watchdog.
      if ($this
        ->hasStateChange() && !empty($this
        ->getWorkflow()
        ->getSetting('watchdog_log'))) {
        if ($this
          ->getEntityTypeId() == 'workflow_scheduled_transition') {
          $message = 'Scheduled state change of @entity_type_label %entity_label to %sid2 executed';
        }
        else {
          $message = 'State of @entity_type_label %entity_label set to %sid2';
        }
        $this
          ->logError($message, 'notice');
      }

      // Notify modules that transition has occurred.
      // Action triggers should take place in response to this callback, not the 'transaction pre'.
      //
      // \Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]);
      // We have a problem here with Rules, Trigger, etc. when invoking
      // 'transition post': the entity has not been saved, yet. we are still
      // IN the transition, not AFTER. Alternatives:
      // 1. Save the field here explicitly, using field_attach_save;
      // 2. Move the invoke to another place: hook_entity_insert(), hook_entity_update();
      // 3. Rely on the entity hooks. This works for Rules, not for Trigger.
      // --> We choose option 2:
      // @todo D8: figure out usage of $entity->workflow_transitions[$field_name].
      // - First, $entity->workflow_transitions[] is set for easy re-fetching.
      // - Then, post_execute() is invoked via workflow_entity_insert(), _update().
    }
  }

  // Save value in static from top of this function.
  $static_info[$entity_id][$field_name][$label] = $to_sid;
  return $to_sid;
}