/** * Execute a transition (change state of a node). * * @param bool $force * If set to TRUE, workflow permissions will be ignored. * * @return int * New state ID. If execution failed, old state ID is returned, * * deprecated workflow_execute_transition() --> WorkflowTransition::execute(). */ 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); } // 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'. $entity->workflow_transitions[$field_name] = $this; // 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. } // 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. /* 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' => check_plain(t($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; }
/** * Execute a transition (change state of a node). * @deprecated: workflow_execute_transition() --> WorkflowTransition::execute(). * * @param bool $force * If set to TRUE, workflow permissions will be ignored. * * @return int * new state ID. If execution failed, old state ID is returned, */ public function execute($force = FALSE) { global $user; $old_sid = $this->old_sid; $new_sid = $this->new_sid; $entity_type = $this->entity_type; $entity_id = $this->entity_id; $entity = $this->getEntity(); // Entity may not be loaded, yet. $field_name = $this->field_name; if ($old_sid == $new_sid) { // Stop if not going to a different state. // Write comment into history though. if ($this->comment) { $this->stamp = REQUEST_TIME; if (!$field_name) { // @todo D8: remove; this is only for Node API. $entity->workflow_stamp = REQUEST_TIME; workflow_update_workflow_node_stamp($entity_id, $this->stamp); } $result = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); $this->save(); if (!$field_name) { // @todo D8: remove; this is only for Node API. unset($entity->workflow_comment); // @todo D8: remove; this line is only for Node API. } $result = module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); } // Clear any references in the scheduled listing. foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) { $scheduled_transition->delete(); } return $new_sid; } if (!$force) { // Make sure this transition is allowed. $result = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Did anybody veto this choice? if (in_array(FALSE, $result)) { // If vetoed, quit. return $old_sid; } } // 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' => $this->uid, 'transition' => $this); drupal_alter('workflow_comment', $this->comment, $context); $args = array('%user' => $user->name, '%old' => $old_sid, '%new' => $new_sid); $transition = workflow_get_workflow_transitions_by_sid_target_sid($old_sid, $new_sid); if (!$transition && !$force) { watchdog('workflow', 'Attempt to go to nonexistent transition (from %old to %new)', $args, WATCHDOG_ERROR); return $old_sid; } // Make sure this transition is valid and allowed for the current user. // Check allow-ability of state change if user is not superuser (might be cron). if ($user->uid != 1 && !$force) { if (!workflow_transition_allowed($transition->tid, array_merge(array_keys($user->roles), array('author')))) { watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE); return $old_sid; } } // Invoke a callback indicating a transition is about to occur. // Modules may veto the transition by returning FALSE. $result = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Stop if a module says so. if (in_array(FALSE, $result)) { watchdog('workflow', 'Transition vetoed by module.'); return $old_sid; } // Log the new state in {workflow_node_history}. // This is only valid for Node API. if (!$field_name) { // If the node does not have an existing 'workflow' property, save the $old_sid there, so it can be logged. if (!isset($entity->workflow)) { $entity->workflow = $old_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; } // Log the transition in {workflow_node_history}. $this->is_executed = TRUE; $this->save(); // Register state change with watchdog. if ($state = WorkflowState::load($new_sid)) { $workflow = $state->getWorkflow(); if (!empty($workflow->options['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' => $state->label()); $uri = entity_uri($entity_type, $entity); watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path'])); } } // Notify modules that transition has occurred. // Action triggers should take place in response to this callback, not the previous one. module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Clear any references in the scheduled listing. foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) { $scheduled_transition->delete(); } return $new_sid; }