Sorry if this has been explained else where, I might have missed it. And I have been reading a lot.
I'm using workflow 7.2.x-dev with workflow_field module to add a workflow to an entity.

I am a bit confused at the moment on the best way to programmatically change a state with the new (for me anyway) OOP way of dealing with workflow.

1. Can somebody please give me an easy example to programmatically change a workflow state. I am also especially interested setting up a new scheduled transition. Basically:
if user change state from A->B, I want to automatically set up a scheduled change to change to C at time x

I know there is Rules module, but I would prefer to use code, as there are some other things I want to do.

2. I take it, to detect a state change, the best is still to use hook_workflow with $op transition_pre? Or is there a better way?

Comments

Max_Headroom’s picture

Did some more digging around in the code and some things became clearer.
There is a serious lack of examples and documentation regarding entity transitions . But in the spirit of open source, I will make some notes over the next few days as I work on this and publish it.

Max_Headroom’s picture

Title: Programmatically change state » Workflow 7.x.2.x for developers documentation
Component: Code » Documentation
Category: Support request » Task

Please contribute to this documentation with your input and code snippets.

VenDG’s picture

I just so happen to be trying to figure this out also.

I created a content type with a workflow field with radio buttons that toggle between submitted and approved. (the workflow has three states: creation, submitted, approved). Based on looking through the code, I created a module that programmatically changes the value of the workflow field. I can edit a node, leave the radio button on the workflow field unchanged and after saving when I view the node again, the approved radio button is selected, but the workflow history still shows the state as submitted. I am missing something but I am not sure what, to get the field change value reflected in the history.

Here is my code so far. I have devel turned on.

/**
* Implements hook_node_presave().
* 
*  hook_entity_presave()
*/

function workflowdemo_entity_presave($entity, $type) {
global $user;

$nstate1 = 2; //submitted workflow state value
$nstate2 = 3; //approved workflow state value

//try to get current workflow state

//workflow_node_current_state($entity, $entity_type = 'node', $field_name = '');
//$sid = workflow_node_current_state($entity, $entity_type, $field_name);
$sid = workflow_node_current_state($entity, 'workflow_content', 'field_content_state');

drupal_set_message('DEBUG: PRESAVE Called');
drupal_set_message('DEBUG: Current Workflow State - '. $sid);

if ($sid == $nstate1) { //Submitted

$new_sid = $nstate2;         //Approved

drupal_set_message('DEBUG: New Workflow State - '. $new_sid);

  // Fire the transition.
  $comment = '';
  $transition = new WorkflowTransition();
  $transition->setValues('workflow_content', $entity, 'field_content_state', $sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
  workflow_execute_transition('workflow_content', $entity, 'field_content_state', $transition, $force = TRUE);

}

dpm('_entity_presave');
dpm($entity);

}
VenDG’s picture

Hook_entity_presave doesn't work, switched to hook_entity_update and the transition appears in the history.

Here is the updated code:

/**
* Implements hook_node_update().
*
*  hook_entity_update()
*/
function workflowdemo_entity_update($entity, $type) {
global $user;

$nstate1 = 2; //submitted workflow state value
$nstate2 = 3; //approved workflow state value

//try to get current workflow state
$sid = workflow_node_current_state($entity, $type, 'field_content_state');

drupal_set_message('DEBUG: Current Workflow State - '. $sid);

if ($sid == $nstate1) { //Submitted
$new_sid = $nstate2;         //Approved

drupal_set_message('DEBUG: New Workflow State - '. $new_sid);
  // Fire the transition.
  $comment = '';
  $transition = new WorkflowTransition();
  $transition->setValues($type, $entity, 'field_content_state', $sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
  workflow_execute_transition($type, $entity, 'field_content_state', $transition, $force = TRUE);
}
dpm($entity);
}

ETA: I am using workflow field (the new way of working with workflow) and not workflow node (the old way of working with workflow).

Max_Headroom’s picture

Thanks for this.
I just have a question for the developer (johnv):
Is the function: workflow_node_current_state() going to be depreciated at some stage, or even going to have a name change to i.e.: workflow_entity_current_state()?

johnv’s picture

@Max_Headroom, the function workflow_node_current_state() will stay in the D7-version. Probably in D8 the name will change.
It is already widely used in other contrib modules, so I cannot change the interface. It also works for both workflowNode and workflowField.

IMO the current 2.x version is functional complete. So only bug fixes will enter. As feature requests, there are 2 grouped requests (Multi review, planned transitions).

I realize that the last weeks several issues are raised that I could answer or fix, but these weeks are full of holidays and projects.

Max_Headroom’s picture

Going back to my original question in #1, I have made this example.
When workflow has a state change, create a scheduled transition to next state.

In my example, say I have content (entity) that have the following states:
(creation)
Preview
Approved
Expired

At creation of the content, the state will automatically change to Preview as per normal workflow operations.
When a person previewed the content, he or she will change the state to Approved by means such as the workflow form of Workflow Field.

How ever, the content must expire after 30 days, so a scheduled transition must be created.

The code:
Initial thought would be to use hook_workflow with 'transition pre' or 'transition post' op. It does not work with 'transition pre' as it seems it cause a loop back which deletes the scheduled transition from the database as soon as it is created. Workflow Field does not call 'transition post' and it is recommended in the API to rather use an entity event. I then decided to use hook_entity_update.


/**
 * Implements hook_entity_update().
 *
 */
function MY_MODULE_entity_update($entity, $entity_type) {
  if ($entity_type == 'MY ENTITY') { //this is the entity like a node that has the workflow field
//@todo: check for more checks if hook update is called during other entity update operations.
    foreach ($entity->workflow_transitions as $key => $value) {
      $field_name = $key;
      $workflow = $value;
      break; // There should only be one workflow at this point
    }

    //Example states. This can be fetched from else where.
    //$creation = 1 // not used
    $preview = 2;
    $approved = 3;
    $expired = 4;

    //For clarity that the state change that triggers hook_entity_update is going to be the current sid after state change
    $this_sid = $workflow->new_sid;

    $timestamp = time();

    // Check if the entity has been put into approved state manually
    if ($this_sid == $approved) {

      //Entity will expire after 30 days
      $scheduled = strtotime('+30 day', $timestamp);
      $comment = '';

      $uid = 1; //Or use global $user, $user->uid

      $scheduled_transition = new WorkflowScheduledTransition();
      $scheduled_transition->setValues($entity_type, $entity, $field_name, $this_sid, $expired, $uid, $scheduled, $comment);
      $scheduled_transition->save();
    }
  }
}

Hope this is of help to somebody.

johnv’s picture

Title: Workflow 7.x.2.x for developers documentation » Developer documentation for Workflow module
Version: 7.x-2.x-dev » 8.x-1.x-dev
estoyausente’s picture

These examples are so old and now doesn't work.

I'm trying to change the status programmatically for a entity and I don't know exactly the correct way. I have a little snippet like this:

 $transition = WorkflowTransition::create(['product_status']);
 $transition->setValues('New status id', 1, 'Changed automatically on import process.');
 $transition->save();
 WorkflowManager::executeTransitionsOfEntity($bb_product);
$bb_product->save();

My code doesn't work but I don't find any other example or a correct way to change the Workflow state for a entity. Somebody can help me? I think that it's a useful example for all people. I can create a documentation page to explain some concepts like this.

bogdog400’s picture

Sorry. I don't have an answer, but I'm commenting in the hope that someone will notice. I'm about to try to use the workflow module and it would be much easier if there was a bit more documentation. Like any documentation would be great.

jlscott’s picture

Here is a code snippet that creates and executes a workflow transition for a D8 site:

                // Generate a workflow transition to submitted
                /** @var \Drupal\Core\Session\AccountInterface $user */
                $user = \Drupal::currentUser();
                $transition = WorkflowTransition::create([$entity_workflow, 'field_name' => 'field_fbad_workflow']);
                $transition->setTargetEntity($entity);
                $workflow_comment = t('Submitted for building by @user on @now',
                  ['@user' => $user->getAccountName(), '@now' => date('d-m-Y H:i')]);
                $transition->setValues('fbad_submitted', $user->id(), REQUEST_TIME, $workflow_comment);
                $transition->execute(TRUE);
                if ($tid = $transition->id()) {
                  // Set the new value of the workflow field
                  $entity->field_fbad_workflow->value = 'fbad_submitted';
                }

Note the need to manually update the workflow value in the field once the transition has executed successfully.

$entity_workflow is the initial value of the workflow state of the $entity (=node), and "fbad_submitted" is the destination state.

estoyausente’s picture

Note the need to manually update the workflow value in the field once the transition has executed successfully.

Thank for the snippet! It works as expected but I have a doubt/appreciation.

I have this snippet in a drush command. The transaction is created correctly and as you say the new status is changed .... sometimes! drush command (by default) is executed as anonymous. Anonymous doesn't have enough permissions to execute the transaction and it throw an error. The transaction is created but the status changed throw an error.

I execute the drush command as a user 1 and it works, but It's a little bit hacky because when the transactions is created I set the 1 as a owner of the transaction. It not be clear for me, but it works; I only want to leave this message to help others (or me in the future :P)

jlscott’s picture

Hi @estoyausente. The transition will execute as the user specified, so instead of loading the account for the "current user" in the first line of the snippet, you can load another user account that doe shave the required permissions.

estoyausente’s picture

mm... yes, it has sense! Thanks for answering! Really the user 1 is the best user in my case, but is a good idea set it at begin of the snippet in place of executing the command as a user 1.

johnv’s picture

Title: Developer documentation for Workflow module » How to programmatically use Workflow module

Please see also some example code in workfow_devel module.

johnv’s picture