I'm having an issue implementing a hook_workbench_moderation_transition() in my custom module.

The scenario is the following:

I have two custom states: "Unpublished" and "Rejected". I've setup the transitions in a way that I can essentially transition to either of these, while editing the node. Since the edit form always creates a new draft from the published version, the first hurdle was figuring out how to essentially re-build the logic in workbench_moderation_node_unpublish_form() to specifically allow me to transition from a revision that is *not* the live revision *and* simultaneously update the live revision to unpublished.

I was always ending up with a situation where the node is transitioned, but never got unpublished. I even wrote code to specifically change the 'status' field of the 'node' table to unpublished, but it mysteriously was always getting set back to published.

I finally figured out that what is happening is the shutdown function workbench_moderation_store() is being called *after* I run my transition to unpublish the live revision, but that function doesn't check well enough to see if there is a live revision anymore, and just saves the node as published anyway.

The fix was trivial, once I figured out what was going on. Patch in first comment.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jwilson3’s picture

Status: Active » Needs review
FileSize
1.14 KB
jwilson3’s picture

Title: Let state transitions on recent revisions unpublished an older published revision » Allow unpublishing of older live revisions on state change
jwilson3’s picture

Here is the custom code you could model to test this out:

/**
 * Implements hook_workbench_moderation_transition().
 */
function iin_content_moderation_workbench_moderation_transition($node, $previous_state, $new_state) {
   watchdog('Content moderation', 'Node transitioned from %previous_state to %new_state', array('%previous_state' => $previous_state, '%new_state' => $new_state), WATCHDOG_NOTICE, l($node->title, 'node/' . $node->nid));

  // Much of the following code is inspired from studying the following:
  //
  // workbench_moderation_node_unpublish_form
  // workbench_moderation_node_unpublish_form_submit
  // workbench_moderation_moderate
  // workbench_moderation_store
  //
  // In an ideal world, workbench moderation module could abstract the
  // unpublish logic outside of the form logic so that it could be called
  // programatically from any workflow state or transition, and always know how
  // to properly unpublish the live revision. For now, we have to handle this
  // on our own.


  // We must ensure a node is unpublished when transitioned to "Unpublished"
  // or "Rejected" states (these are custom workbench_moderation states created
  // for this specific website).
  if (in_array($new_state, array('unpublished', 'rejected'))) {

    // We're only concerned with unpublishing a live revision.
    if (!isset($node->workbench_moderation['published'])) {
      return;
    }

    watchdog('Content moderation', "%transition node's live revision: %vid", array('@transition' => $new_state, '@vid' => $node->workbench_moderation['published']->vid), WATCHDOG_NOTICE, l($node->title, 'node/' . $node->nid));

    // If the unpublish action was taken on a non-current verison (eg a draft)
    // we must also transition the live version, so moderate the node's
    // 'live' published version as well.
    if (!workbench_moderation_node_is_current($node)) {
      $node->workbench_moderation['my_revision'] = $node->workbench_moderation['published'];
      $node->vid = $node->workbench_moderation['published']->vid;

      // Calling the parent function (seemingly recursively) allows us to
      // add an entry in the workflow_moderation_node_history table to log
      // the transition from the published vid to an unpublished state;
      // it also handles the actual act of unpublishing the node. However,
      // since we fake the 'my_revision' and $node->vid, the
      // $node->workbench_moderation['published'] is unset by the parent
      // function, preventing infinite recursion.
      //
      // Because this action unpublishes the 'live' version, the shutdown
      // function (workbench_moderation_store()) that was registered during
      // the original transition will fail to load the live node and cause a
      // PHP critical error. Therefore I've patched the
      // workbench_moderation module to better handle this case when a
      // transition to unpublished or rejected (even from a draft) causes
      // the live version to be unpublished.
      workbench_moderation_moderate($node, $new_state);
    }

    // Finally we need to use our own shutdown function to ensure the node
    // is really unpublished.
    drupal_register_shutdown_function('iin_content_moderation_unpublish', $node);

    drupal_set_message(t('The live revision of this content has been unpublished.'));
  }
}

/**
 * This function is used by drupal_register_shutdown_function to delay the
 * unpublish action.
 *
 * @todo use node_save() instead of straight update queries, to allow rules and others to react?
 */
function iin_content_moderation_unpublish($node) {
  if (!isset($node->nid)) {
    return;
  }
  watchdog('Content moderation', "Unpublish node %nid's live revision: %vid", array('%nid' => $node->nid, '%vid' => $node->vid), WATCHDOG_NOTICE, l($node->title, 'node/' . $node->nid));

  // Ensure the node is unpublished in workbench history.
  $query = db_update('workbench_moderation_node_history')
    ->condition('nid', $node->nid)
    ->fields(array('published' => 0))
    ->execute();

  // Ensure the node is unpublished in node revisions table.
  $query = db_update('node_revision')
    ->condition('nid', $node->nid)
    ->fields(array('status' => 0))
    ->execute();

  // Ensure node is unpublished in node table.
  $query = db_update('node')
    ->condition('nid', $node->nid)
    ->fields(array('status' => 0))
    ->execute();
}

The actual error messages that are showing up in the logs after trying to run through transitions are the following:

Strict warning: Creating default object from empty value in workbench_moderation_store() (line 1567 of /sites/all/modules/contrib/workbench_moderation/workbench_moderation.module).

And

EntityMalformedException: Missing bundle property on entity of type node. in entity_extract_ids() (line 7633 of /includes/common.inc).

Both errors were the result of not having a properly loaded node in the $live_revision variable of the shutdown function.

jwilson3’s picture

Version: 7.x-2.x-dev » 7.x-1.x-dev
FileSize
976 bytes

Should be 7.x-1.x-dev... and also a patch that will apply.

ShaneOnABike’s picture

Actually I discovered the same issue when enabling features it was pulling up this error message...

Any way that you could roll this through to 2.x also?

Thanks

jwilson3’s picture

I personally dont have the need to re-roll this patch for 2.x nor do I have a project to test it on with 2.x, but did you look at the actual patch? -- it is 4 lines of code, you might try giving it a test, apply it by hand to your 2.x and if it works for you provide a reroll here. ;)

sinn’s picture

jwilson3, try patch https://drupal.org/node/1436260#comment-7506957. I think it is more common fix

lolandese’s picture

Status: Needs review » Needs work