Most websites have their own set of strict requirements around how they want their content to reach the end user. Some require nodes to just be published or unpublished. Others require a rudimentary workflow that adds one extra step between a node being unpublished and a node being published. On the far end of the spectrum, some organizations have very granular, company-specific approval processes. In any of these cases, a developer can easily create a custom State Flow/State Machine plugin that can meet a variety of business needs. State Machine is a contributed module created by Frederic Mitchell that adds workflow to content publishing in Drupal.

In this post, I’m going to go over the steps to create your own custom module workflow plugin. For this example let’s consider the following to be our requirements:

  1. The Workflow has 5 states – Draft, Needs Review, Approved, Published, Unpublished
  2. There are 5 events or "transitions" – Submit for review, Approve, Request updates, Publish, Unpublish
  3. Only certain roles can approve/publish/unpublish content
  4. Some custom code needs to be executed when the node is moved into the "Approved" state

Getting Started

First, download and install State Machine and State Flow in your contrib modules directory. Then in your custom modules folder create a folder for your custom module. Let’s call it "phase2_workflow." My contrib module is at sites/all/modules/contrib/state_machine and my custom module is at sites/all/modules/custom/phase2_workflow. Inside my custom module I’ll make the info file, module file, install file and a plugins directory. Inside the plugins directory I’ll make a file called "phase2_workflow.inc." Your module should look something like this so far.

Be sure to make a dependency to state_flow in the info file

Defining the plugin

In my plugin file I’m going to define a class called "Phase2Workflow" that extends the StateFlow class that comes with the contrib module. Inside I’m going to start with making an empty init function.

Defining States

Start defining the states outlined in the requirements with the create_state() function provided by the StateFlow class.

Defining Events

You can define transitions using the create_event() function. create_event() accepts the machine name of the event as it’s first parameter and a configuration array as the second. In the configuration array you must define what states have access to this event through origin and target values.

Notice in the "Request Updates" event the origin is an array instead of a string. The origin and target values of the configuration array can either be a single string or a mult-valued array to illustrate that this transition is available to many states. In this case, I want to be able to transition something that is "published", "needs review", or "approved" with the "Request Updates" transition.

Setting the permissions

We’ve roughly satisfied our first two requirements with the states and events we’ve defined so far so let’s take a look at the third: Only certain roles can approve/publish/unpublish content. Let’s start by implementing hook_permission() in the module file so we have those permissions to refer to when making transitions. For now we’re just going to make the two needed for the requirements:

Once these permissions are defined you can refer to them in guard functions. Guard functions are callbacks you define when creating events that check if the user has the permission to execute that transition. In our example, we want to make sure only users with our defined permissions can use our events. So, in the create_event() function for "approve", "publish" and "unpublish" we can put a guard callback:

Define these guard callbacks in the module file. I’ve created a helper function to help making the check for the user permission a little easier.

Now, only people that have roles that have these permissions can make these transitions.

Executing custom code on state change

To satisfy our last requirement let’s go back to the states we created in the init of our custom StateFlow class. create_state(), like create_event(), can also accept a configuration array as a second argument. You can specify custom callbacks defined in your custom class to run when an item enters or exits a certain state. In our example, I’m using the on_enter event to execute a function I’ve defined.

Bringing it all together

I wrap things up by implementing hook_state_flow_plugins() and hook_state_flow_machine_type_alter(). The first is to make my module aware of the plugin in the plugins directory and the second is to tell the CMS which content types should adhere to this workflow. If you only want this workflow to be for a specific content type, do a check first on the $node argument’s type.

That’s it! This is just a rudimentary example but I hope it provides you with a jumping off point! I’ve posted the code on github here: https://github.com/phase2/state-flow-plugin-example

  • Drupal Man

    Thanks for sharing John. Does this technic let us save the state of a menu structure, blocks enabled/disabled and weight, nodequeue order of items, and so on? It might be that the answer is obviously “yes” but I tend to ask it as I have not dealt with state machine technic in the past. Thanks!

    • Fredric Mitchell

      No. This only deals with nodes.