fromAugust 2015

Back to the Future IV

Extended Workflows with CPS

Fabulous colorful future illustration

In previous episodes, Marty McFly time-traveled to 1955 … Oh wait, that’s the wrong article.

So: In the previous issue of Drupal Watchdog, we introduced the Content Preview System (CPS). With CPS you can preview a site as what it would look like in the future, and allow tracking changes to the site in so-called ‘site versions’. With this powerful concept, it is possible to view a site as it will look after the changes have actually been made. Once all interested parties are satisfied with the outcome, the site version is published and all the changes go “live.”

There are two main advantages to this approach: Being able to publish several things together in context (e.g., articles with supporting sidebar or news content, an article series, a feature gallery, etc.), and being able to tightly control the workflow of content that is shown publicly to your visitors.

While this concept for a single editor is already powerful, it really starts to shine once there is a content team.

And that’s what this article is about: Extending the workflow of CPS to work for teams with different roles, permissions, and responsibilities.

You Will Learn To:

  • Set up CPS Workflow Simple for three different roles;
  • Use CPS reviews to enhance your workflow;
  • Set up an entity-based workflow that is part of the larger workflow;
  • Extend workflows.

A Simple Workflow with CPS

In the following example, it is assumed that the entities you use are revisionable (e.g., use file_entity_revisions, taxonomy_revision, etc. modules) to make full use of CPS. Only revisionable entities will work with the CPS workflow.

To get started, download the CPS, Diff, and Drafty modules and have drush download any other dependencies:

$ drush dl cps drafty diff
$ drush -y en cps_workflow_simple cps_node diff

This will download the following modules to your code base: cps, ctools, diff, drafty, entity, entity_status, iib, mailsystem, mimemail, views

After this step you will see a nice “Site version” widget below the admin and shortcuts menus.

Image A. The CPS simple workflow consists of a Draft, Review, and Publish phase

As described in the previous issue, you can then create a new “Site version”, change items on the site, preview the changes, and, finally, publish them.

For the setup of the CPS simple workflow, you will need to set up the roles and permissions per the Workflow Roles table (see Workflow Roles And Permissions sidebar).

CPS Workflow adds a new review state to the workflow, Draft » Review » Published.

Previously, any added site version was directly published; now it is first submitted for review. For this to work correctly, it is important that the content editor does not have permission to “Publish site versions” and that the cps_workflow_reviewers_email is properly set up.

$ drush vset cps_workflow_reviewers_email

In this case, the reviewer’s e-mail is assumed to go to a group of people who are equally responsible for the content.

Once this is set up, the link to publish a “site version” on the site version edit page will be replaced with a link to “Submit for review”.

The Simple workflow records a message for each workflow state change, allowing editors and publishers to communicate changes. This is comparable to GitHub comments on pull requests. (For a more entity-centric communication, see the “CPS Reviews” section later in this article.)

After the “site version” has been submitted for review, the editor has the possibility to withdraw it again in case the editor wants to make more changes.

While not included in CPS Workflow Simple itself, it is usually a good idea to deny edit access to any entity that is part of a changeset that is in “review” stage. That way, last minute edits by an editor cannot accidentally be published. (See "the “Extending the Workflow” section below for an example of how to achieve that.)

The team of content publishers will receive an e-mail notification once the “site version” has been submitted for review; clicking on the link in the e-mail will automatically bring them to the “site version” edit page. There, the content publisher will see a list of all changed or newly created nodes and can then, after their review, either decline to publish the changeset with a message or publish it.

Depending on organizational workflow, the publishing might also happen at a later stage; however, no state for this exists in the Simple CPS workflow.

After publishing, the original editor of the changeset gets an e-mail notification that their “site version” was published.

Useful hint: As a last measure, a changeset that was published too soon, or should not have been published at all, can be unpublished again. However, only the last published changeset can be unpublished at a time; after that, the next-to-last, and so on.

CPS Reviews

One useful submodule when dealing with content changes is the CPS Review module. While the Simple CPS workflow is mainly tailored for small changesets, once there are numerous changes to different nodes it can be quite problematic to communicate all the needed adjustments via one “review” comment field. (Similar to how on GitHub pull requests, it makes more sense to provide a contextual review).

By enabling the cps_review submodule –

$ drush en cps_review

– every entity gets a new review form that is unique per changeset. Therefore, changes can be discussed in context.

After setting up appropriate permissions, CPS reviews can be added, replied to, and, ultimately, unpublished. (Compare that to “resolved” for comments in word processing programs.)

To find reviews that have been accidentally unpublished, go to Admin » Structure » CPS Reviews, find the review in question, and re-publish it – assuming you have the appropriate permissions.

CPS per-Entity Workflow

For many cases the simple workflow is sufficient, but for more advanced cases the granularity of being able to approve single entities within a changeset is needed.

With the help of the cps_workflow module this can be achieved. However, by itself, cps_workflow doesn’t do anything; custom code is needed to provide the needed workflow states and transitions. You can find a full sample of CPS workflow modules at

Download and enable the module.

$ drush dl cps_workflow
$ cd sites/all/modules
$ git clone git://
$ drush -y en cps_workflow cps_workflow_sample

This will enable a new tab called “Workflow” on every node, which allows you to control the per-entity workflow.

In our sample workflow, the life of an entity workflow goes from a draft stage to a review stage to approval.

While using cps_workflow_simple together with cps_workflow is supported, it is not mandatory. A possible workflow scenario here would be to provide the full changeset again for review to a supervisor after all content has been approved individually, before a content release manager publishes the changeset.

Again, a more extensive implementation might choose to lock down content that has already been approved so that no more changes are possible.

Sample CPS per-Entity Workflow

The following is a sample entity workflow to get started with:

 * Implements hook_cps_workflow_info().
function cps_workflow_sample_cps_workflow_info() {
$workflows = [];
$workflows['node'] = [
  'entity path' => 'node/%node',
  'default state' => 'draft',
  'default transition' => 'new draft',
  'states' => [
    'draft' => [ 'label' => 'Draft', 'weight' => -10, ],
    'review' => [ 'label' => 'In review', 'weight' =>  0, ],
    'approved' => [ 'label' => 'Approved', 'weight' => 10, ],
  'transitions' => [
    'new draft' => [ 'label' => 'New Draft', 'valid states' => ['system only'], 'state' => 'draft' ],
    'ready for review' => [ 'label' => 'Ready for review', 'valid states' => ['draft'], 'state' => 'review' ],
    'approve' => [ 'label' => 'Approve', 'valid states' => ['review'], 'state' => 'approved' ],
    'back to draft' => [ 'label' => 'Back to Draft', 'valid states' => ['review'], 'state' => 'draft' ],
return $workflows;

First of all, the entity path per entity type needs to be specified. Since every entity can provide that itself, entity_translation, for example, has a quite complex helper method to find that path; cps_workflow expects the user to provide the path.

Next, the default state and default transition are provided, and are assigned whenever an entity is created or modified within a changeset. This ensures that the entity is in a known state, before further transitions are applied.

Then, a list of states is provided, in this case: draft, review and approved.

Finally, a list of transitions is given, and each transition defines what valid states this transition can take place on.

Note: You can specify custom transition keys and then use hook_cps_workflows_alter(), for example, to change properties of the defined workflows.

The full sample workflow is pictured in Image (B). More custom work is needed to ensure that only a changeset, for example, can be published that has all entities in approved state.

Image B. The CPS per entity workflow allows each entity to go through various workflow states, provided by the cps_workflow_sample module.

Extending the Workflow

Workflows usually have one thing in common: They are very different for every organization.

Therefore, CPS can be easily extended using a variety of hooks for almost every use case you can think of. Especially if you add the Rules module to it (using the normal entity update hooks by checking for updates to the changeset entities), you already have a highly flexible system to extend your workflows.

A summary of CPS-specific hooks can be found in the Useful Hooks and More sidebar.

As an additional example to help get you started, let's consider the following code. It denies access to entities that are currently in a changeset with the status “in review” and makes an exception for users who are allowed to publish content (e.g., the content publisher).They can still make small edits before publishing the changeset.

 * Implements hook_node_access().
function mymodule_node_access($node, $op, $account) {
  // Only deal with create, update and delete.
  if (!in_array($op, array('create', 'update', 'delete'))) {
  // Check only non-live changesets.
  $changeset_id = cps_get_current_changeset(TRUE);
  if ($changeset_id == CPS_PUBLISHED_CHANGESET) {
  $changeset = cps_changeset_load($changeset_id);
  // Allow users with the publish changesets permission to do last-minute edits.
  if ($changeset->status == 'review' && !user_access('publish changesets', $account)) {
    return NODE_ACCESS_DENY;

With CPS, a powerful and flexible system is provided that can be tailored to suit the most simple to the most advanced editorial workflows.

Workflow Roles And Permissions

1. In most organizations the following roles are usually present:

  • The Content Editor is responsible for adding and editing articles.
  • The Content Publisher is responsible for verifying the changes and publishing them “live.”
  • The Content Administrator manages all content on the site.
  • A Drupal Administrator has all permissions.

2. The following permissions matrix is used to set this up:

  • Administer site versions permission – for the editor, publisher, and administrator – is necessary for the other permissions to work.
  • Edit all site versions allows the administrator to edit all site versions, even those owned by other people.
  • View site versions allows viewing and changing – by the editor, publisher, and administrator – between different site versions, via the widget.
  • Publish site versions grants permission to the publisher and administrator.

“Bypass content access control” should not be given to even the “Administrator” role, because it can allow him to circumvent CPS under some circumstances.
3. In addition to those permissions, there is also:

  • Preview site versions, which allows viewing a site version when the ‘changeset_id’ parameter is provided manually on the URL. This is useful to allow less-privileged roles to see what the page would look like in the future.

Useful Hooks and More

The following CPS-specific hooks are useful to provide your own workflow.
Note: A “site version” in the UI is called a “changeset” in the code:

  • hook_cps_changeset_states_alter() allows adding or removing workflow states that are recorded in the changeset history.
  • hook_cps_changeset_operations_alter() allows adding operations, like ‘submit’ and ‘decline’ to the CPS changeset operation links as displayed in the overview page and in listings.
  • hook_cps_changeset_access_alter() allows changing the CPS changeset access based on your custom workflow rules.
  • Since each changeset has a history, hook_cps_changeset_history_status_alter() can be used to provide useful texts like “Declined” in the history tab.

For sending e-mails on entity updates, either hook_entity_update() or the Rules module can be used, making use of the $entity->status and $entity->oldStatus properties.

Image: ©Prisca Haase