Better Workflows with State Machine 2.x

When we last discussed State Machine, we highlighted how easy it was for developers to create custom workflows via the State Machine API.

The goal of State Machine is to provide an API first approach to workflows within Drupal. A simple user interface is included, but the developer ultimately has power and flexibility to extend and customize their workflows across various environments as they see fit.

When we last discussed State Machine, we highlighted how easy it was for developers to create custom workflows via the State Machine API.

The goal of State Machine is to provide an API first approach to workflows within Drupal. A simple user interface is included, but the developer ultimately has power and flexibility to extend and customize their workflows across various environments as they see fit.

We also reviewed State Flow, a sub-module packaged with State Machine which provides you with a base workflow, and showed how Energy.gov extends State Flow for its own custom needs.

We're happy to share that State Machine has released a 2.x branch sporting some great new features to make your workflows even better.

Easier to Alter

The method for implementing a custom workflow changed from using variable_set() for defining the node types that should implement your custom workflow to using hook_state_flow_machine_type_alter(). Using Energy.gov as an example, we simply assign the machine type to match the key we declared in hook_state_flow_plugins().

/**
* Implements hook_state_flow_plugins().
*/

function energy_workflow_state_flow_plugins() {
$info = array();
$path = drupal_get_path('module', 'energy_workflow') . '/plugins';
$info['energy_workflow'] = array(
'handler' => array(
'parent' => 'state_flow',
'class' => 'EnergyWorkflow',
'file' => 'energy_workflow.inc',
'path' => $path,
),
);
return $info;
}
/**
* Implements hook_state_flow_machine_type_alter()
*
* @param string $machine_type
* @param object $node
*/
function

energy_workflow_state_flow_machine_type_alter(&$machine_type, $node) {
$machine_type = 'energy_workflow';
}

You can also easily have multiple workflows by adding logic to change the machine type based on node type, instead of having to declare them as variables.

Bulk Revision Editing

A new administration page puts more power in users hands by allowing publishers to use your defined states and events on many items at once.

Similar to the philosophy of Views Bulk Operations, this is a neat feature for users who may want to fire events across multiple revisions.

For example, if you have micro-sites or a group of pages that all need to be launched simultaneously, State Flow includes a set of batch operations to publish those node revisions at once.

Revisions are administered by filters and operations. The filters dictate which revisions are selectable by administrators when they are applied. The operations serve as actions to perform on the revisions once they are selected.

For developers, these filters and operations are completely alterable and are packaged with their own hooks! This provides an opportunity to create unique bulk administration pages for your users.

Want to create a custom filter? Simply invoke hook_node_revision_filters() and add your filter to the form array. You can then invoke hook_query_node_revision_alter() to enact whatever logic you want for that custom filter.

Use cases include filters for revisions by taxonomy, the existence of certain field value, or even user access.

Here is an example of a tags filter and its query callback:

/**
* Implements hook_node_revision_filters()
*/

function mymodule_node_revision_filters() {
$filters = array();
//function to get tags$options=

mymodule_tags_callback();

$filters['mymodule_tags'] = array(
'form' => array(
'#type' => 'select',
'#title' => t('Content Tag'),
'#options' => $options,
),
);

return $filters;
}

/**
* Implements hook_query_node_revision_alter()
*/

function mymodule_query_node_revision_alter(QueryAlterableInterface $query) {
// Get the filter form the session
if ($filters = $query->getMetaData('filters')) {
if ($filter = isset($filters['elc_workflow_tags']) ? $filters['mymodule_tags'] : NULL) {
/**
* Custom code to join other tables
*/

}
}
}

This also true for custom operations. Utilizing hook_node_revision_operations(), we can easily create custom actions, in bulk, with the revisions exposed in this administration page.

Scheduling

Last, but not least, State Machine 2.x introduces workflow scheduling to allow users to set a day and time via the Date Popup module of when a node should transition to a defined state. This is done via a new submodule called State Flow Schedule.

State Flow Schedule extends State Flow to provide you with a default Scheduled state and Schedule event. Once a date and time is defined, the value is passed as a log message so users can easily view the workflow log of a node and check when it will transition.

Scheduling is heavily tied to cron. This means content won't actually transition to the desired state until cron runs, so you should setup your environment accordingly.

Scheduling can also be extended. In Energy.gov, we had an additional event called Immediately Schedule to allow those with the appropriate permissions to skip the workflow for content starting in the draft state.

To utilize this, simply define the new event, then add it to array of definable scheduling events via hook_state_flow_schedule_events_alter().

/**
* @file
* Energy.gov implementation of State Flow, an extension of the State Machine class
*/

class

EnergyWorkflow extends StateFlow {

public function init() {
// Initialize states
$this->create_state('draft', array(
'label' => t('Draft'),
));
$this->create_state('needs review', array(
'label' => t('Needs Review'),
));
$this->create_state('approved', array(
'label' => t('Approved'),
));
$this->create_state('unpublished', array(
'label' => t('Unpublished'),
));
$this->create_state('published', array(
'label' => t('Published'),
'on_enter' => array($this, 'on_enter_published'),
'on_exit' => array($this, 'on_exit_published'),
));
$this->create_state('scheduled', array(
'label' => t('Scheduled'),
'on_exit' => array($this, 'on_exit_scheduled'),
));

// Initialize events
$this->create_event('for review', array(
'label' => t('For Review'),
'origin' => 'draft',
'target' => 'needs review',
));
$this->create_event('immediate publish', array(
'label' => t('Immediate Publish'),
'origin' => 'draft',
'target' => 'published',
'guard' => 'energy_workflow_guard_publisher',
));
$this->create_event('approve', array(
'label' => t('Approve'),
'origin' => 'needs review',
'target' => 'approved',
'guard' => 'energy_workflow_guard_editor',
));
$this->create_event('reject', array(
'label' => t('Reject'),
'origin' => 'needs review',
'target' => 'draft',
'guard' => 'energy_workflow_guard_editor',
));
$this->create_event('publish', array(
'label' => t('Publish'),
'origin' => array('approved', 'scheduled'),
'target' => 'published',
'guard' => 'energy_workflow_guard_publisher',
));
$this->create_event('unpublish', array(
'label' => t('Unpublish'),
'origin' => 'published',
'target' => 'unpublished',
'guard' => 'energy_workflow_guard_publisher',
));
$this->create_event('to draft', array(
'label' => t('To Draft'),
'origin' => array('needs review', 'approved', 'unpublished', 'scheduled'),
'target' => 'draft',
));
$this->create_event('schedule', array(
'label' => t('Schedule'),
'origin' => 'approved',
'target' => 'scheduled',
'guard' => 'state_flow_guard_schedule',
));
$this->create_event('immediate schedule', array(
'label' => t('Immediate Schedule'),
'origin' => 'draft',
'target' => 'scheduled',
'guard' => array('energy_workflow_guard_publisher', 'state_flow_guard_schedule'),
));
}

/**
* Other class info
*/

}

/**
* Implements hook_state_flow_schedule_events_alter()
*
* @param array $scheduled_events
*/

function energy_workflow_state_flow_schedule_events_alter(&$scheduled_events) {
$scheduled_events[] = 'immediate schedule';
}

State Machine 3.x

These are great new features, and State Machine 3.x is promising to be even better!

The plan is to refactor State Flow to be entity agnostic and ultimately connect with Workbench Moderation 2.x.

Come out to the 2012 Drupalcon Denver workflow-based module collaboration sprint with Steve Persch and myself to get on the ground floor of this very unique and important collaboration.

Fredric Mitchell