You are here

public function WorkflowTransition::execute in Workflow 7.2

Same name and namespace in other branches
  1. 7 includes/Entity/WorkflowTransition.php \WorkflowTransition::execute()

Execute a transition (change state of a node).

Parameters

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

Return value

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

deprecated workflow_execute_transition() --> WorkflowTransition::execute().

File

includes/Entity/WorkflowTransition.php, line 263
Contains workflow\includes\Entity\WorkflowTransition. Contains workflow\includes\Entity\WorkflowTransitionController.

Class

WorkflowTransition
Implements an actual Transition.

Code

public function execute($force = FALSE) {
  $user = $this
    ->getUser();
  $old_sid = $this->old_sid;
  $new_sid = $this->new_sid;

  // Load the entity, if not already loaded.
  // This also sets the (empty) $revision_id in Scheduled Transitions.
  $entity = $this
    ->getEntity();

  // Only after getEntity(), the following are surely set.
  $entity_type = $this->entity_type;
  $entity_id = $this->entity_id;
  $field_name = $this->field_name;

  // Make sure $force is set in the transition, too.
  if ($force) {
    $this
      ->force($force);
  }
  $force = $this
    ->isForced();

  // Prepare an array of arguments for error messages.
  $args = array(
    '%user' => isset($user->name) ? $user->name : '',
    '%old' => $old_sid,
    '%new' => $new_sid,
  );
  if (!$this
    ->getOldState()) {
    drupal_set_message($message = t('You tried to set a Workflow State, but
        the entity is not relevant. Please contact your system administrator.'), 'error');
    $message = 'Setting a non-relevant Entity from state %old to %new';
    $uri = entity_uri($entity_type, $entity);
    watchdog('workflow', $message, $args, WATCHDOG_ERROR, l('view', $uri['path']));
    return $old_sid;
  }

  // Check if the state has changed.
  $state_changed = $old_sid != $new_sid;

  // If so, check the permissions.
  if ($state_changed) {

    // State has changed. Do some checks upfront.
    if (!$force) {

      // Make sure this transition is allowed by workflow module Admin UI.
      $roles = array_keys($user->roles);
      $roles = array_merge(array(
        WORKFLOW_ROLE_AUTHOR_RID,
      ), $roles);
      if (!$this
        ->isAllowed($roles, $user, $force)) {
        watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE);

        // If incorrect, quit.
        return $old_sid;
      }
    }
    if (!$force) {

      // Make sure this transition is allowed by custom module.
      // @todo D8: remove, or replace by 'transition pre'. See WorkflowState::getOptions().
      // @todo D8: replace all parameters that are included in $transition.
      $permitted = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this, $user);

      // Stop if a module says so.
      if (in_array(FALSE, $permitted, TRUE)) {
        watchdog('workflow', 'Transition vetoed by module.');
        return $old_sid;
      }
    }

    // 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.)
    $permitted = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);

    // Stop if a module says so.
    if (in_array(FALSE, $permitted, TRUE)) {
      watchdog('workflow', 'Transition vetoed by module.');
      return $old_sid;
    }
  }
  elseif ($this->comment) {

    // No need to ask permission for adding comments.
    // Since you should not add actions to a 'transition pre' event, there is
    // no need to invoke the event.
  }
  else {

    // There is no state change, and no comment.
    // We may need to clean up something.
  }
  if ($state_changed || $this->comment) {

    // Store the transition, so it can be easily fetched later on.
    // Store in an array, to prepare for multiple workflow_fields per entity.
    // This is a.o. used in hook_entity_update to trigger 'transition post'.
    // Only add the Transition once! or you will encounter endless loops in
    // hook_entity_update() in workflow_actions_entity_update et all.
    if (!isset($entity->workflow_transitions[$field_name])) {
      $entity->workflow_transitions[$field_name] =& $this;
    }

    // The transition is allowed. Let other modules modify the comment.
    // @todo D8: remove all but last items from $context.
    $context = array(
      'node' => $entity,
      'sid' => $new_sid,
      'old_sid' => $old_sid,
      'uid' => $user->uid,
      'transition' => $this,
    );
    drupal_alter('workflow_comment', $this->comment, $context);
  }

  // Now, change the database.
  // Log the new state in {workflow_node}.
  if (!$field_name) {
    if ($state_changed || $this->comment) {

      // If the node does not have an existing 'workflow' property,
      // save the $old_sid there, so it can be logged.
      if (!isset($entity->workflow)) {

        // This is a workflow_node sid.
        $entity->workflow = $old_sid;

        // This is a workflow_node sid.
      }

      // Change the state for {workflow_node}.
      // The equivalent for Field API is in WorkflowDefaultWidget::submit.
      $data = array(
        'nid' => $entity_id,
        'sid' => $new_sid,
        'uid' => isset($entity->workflow_uid) ? $entity->workflow_uid : $user->uid,
        'stamp' => REQUEST_TIME,
      );
      workflow_update_workflow_node($data);
      $entity->workflow = $new_sid;

      // This is a workflow_node sid.
    }
  }
  else {

    // This is a Workflow Field.
    // Until now, adding code here (instead of in workflow_execute_transition() )
    // doesn't work, creating an endless loop.
    // Update 10-dec-2016: the following line, added above, may have resolved that.
    //     if (!isset($entity->workflow_transitions[$field_name]))

    /*
         if ($state_changed || $this->comment) {
           // Do a separate update to update the field (Workflow Field API)
           // This will call hook_field_update() and WorkflowFieldDefaultWidget::submit().
           // $entity->{$field_name}[$this->language] = array();
           // $entity->{$field_name}[$this->language][0]['workflow']['workflow_sid'] = $new_sid;
           // $entity->{$field_name}[$this->language][0]['workflow']['workflow_comment'] = $this->comment;
           $entity->{$field_name}[$this->language][0]['transition'] = $this;

           // Save the entity, but not through entity_save(),
           // since this will check permissions again and trigger rules.
           // @TODO: replace below by a workflow_field setter callback.
           // The transition was successfully executed, or else a message was raised.
    //        entity_save($entity_type, $entity);
           // or
    //        field_attach_update($entity_type, $entity);

           // Reset the entity cache after update.
           entity_get_controller($entity_type)->resetCache(array($entity_id));

           $new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
         }
    */
  }
  $this->is_executed = TRUE;
  if ($state_changed || $this->comment) {

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

    // Register state change with watchdog.
    if ($state_changed) {
      $workflow = $this
        ->getWorkflow();

      // Get the workflow_settings, unified for workflow_node and workflow_field.
      // @todo D8: move settings back to Workflow (like workflownode currently is).
      // @todo D8: to move settings back, grep for "workflow->options" and "field['settings']".
      $field = _workflow_info_field($field_name, $workflow);
      if (($new_state = $this
        ->getNewState()) && !empty($field['settings']['watchdog_log'])) {
        $entity_type_info = entity_get_info($entity_type);
        $message = $this
          ->isScheduled() ? 'Scheduled state change of @type %label to %state_name executed' : 'State of @type %label set to %state_name';
        $args = array(
          '@type' => $entity_type_info['label'],
          '%label' => entity_label($entity_type, $entity),
          '%state_name' => $new_state
            ->label(),
        );
        $uri = entity_uri($entity_type, $entity);
        watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path']));
      }
    }

    // Remove any scheduled state transitions.
    foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) {

      /* @var $scheduled_transition WorkflowScheduledTransition */
      $scheduled_transition
        ->delete();
    }

    // Notify modules that transition has occurred.
    // Action triggers should take place in response to this callback, not the 'transaction pre'.
    if (!$field_name) {

      // Now that workflow data is saved, reset stuff to avoid problems
      // when Rules etc want to resave the data.
      // Remember, this is only for nodes, and node_save() is not necessarily performed.
      unset($entity->workflow_comment);
      module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
      entity_get_controller('node')
        ->resetCache(array(
        $entity->nid,
      ));

      // from entity_load(), node_save();
    }
    else {

      // module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
      // 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:
      // - First, $entity->workflow_transitions[] is set for easy re-fetching.
      // - Then, post_execute() is invoked via workflowfield_entity_insert(), _update().
    }
  }
  return $new_sid;
}