I think what is going on is that during normal node saving via the node edit form is that in workbench_moderation_moderate() the latest revision is unpublished here:

  // If this revision is to be published, the new moderation record should be
  // the only one flagged 'published' in both
  // {workbench_moderation_node_history} AND {node_revision}
  if ($new_revision->published) {
    $query = db_update('workbench_moderation_node_history')
      ->condition('nid', $node->nid)
      ->fields(array('published' => 0))
      ->execute();
    $query = db_update('node_revision')
      ->condition('nid', $node->nid)
      ->fields(array('status' => 0))
      ->execute();
  }

Then the 'real' node revision is saved via a shutdown function workbench_moderation_store()

function workbench_moderation_store($node) {
  if (!isset($node->nid)) {
    watchdog('Workbench moderation', 'Failed to save node revision: node not passed to shutdown function.', array(), WATCHDOG_NOTICE);
    return;
  }
  watchdog('Workbench moderation', 'Saved node revision: %node as live version for node %live.', array('%node' => $node->vid, '%live' => $node->nid), WATCHDOG_NOTICE, l($node->title, 'node/' . $node->nid));
  $live_revision = workbench_moderation_node_live_load($node);
  // Make sure we're published.
  $live_revision->status = 1;
  // Don't create a new revision.
  $live_revision->revision = 0;
  // Prevent another moderation record from being written.
  $live_revision->workbench_moderation['updating_live_revision'] = TRUE;

  // Reset flag from taxonomy_field_update() so that {taxonomy_index} values aren't written twice.
  $taxonomy_index_flag = &drupal_static('taxonomy_field_update', array());
  unset($taxonomy_index_flag[$node->nid]);

  // Save the node.
  node_save($live_revision);
}

Of course there's a high chance that this won't work during a migration (or any other programatic node save).

Comments

dalin’s picture

My workaround is to do this in complete($entity, $row):

    // Work around workbench's strangeness.
    if ($row->status) {
      db_update('node_revision')
        ->condition('nid', $entity->nid)
        ->fields(array('status' => 0))
        ->execute();
    }
dalin’s picture

And to elaborate I'm doing the following in prepare($entity, $row)

  public function prepare($entity, stdClass $row) {
    // Workbench status.
    // @see workbench_moderation_node_update()
    if (!$row->status) {
      if ($row->copy_complete) {
        $entity->workbench_moderation_state_new = 'ready_to_be_published';
      }
      elseif ($row->copy_ready) {
        $entity->workbench_moderation_state_new = 'ready_for_copy_edit';
      }
      else {
        $entity->workbench_moderation_state_new = 'draft';
      }
    }
    else {
      $entity->workbench_moderation_state_new = 'published';
    }
stevector’s picture

Project: Workbench » Workbench Moderation
Category: bug » feature
Status: Active » Postponed (maintainer needs more info)

Changing issue queues to Workbench Moderation.

I recently used a combination of Feeds and Rules for node imports. I also found it easiest to directly change workbench_moderation_state_new.

Having never used Migrate module myself I'm not sure how to best aid its imports.

Dalin, what could Workbench Moderation add to make Migrate imports easier?

dalin’s picture

Status: Postponed (maintainer needs more info) » Active

Just the whole shutdown function seems a bit crufty. In scenarios where a lot of nodes are being acted on it results in a lot of memory overhead. And if the process is terminated prematurely then things are in an inconsistent state. Partly I'm not understanding why the shutdown function needs to exist. If the standard node hooks aren't sufficient, then perhaps there needs to be another hook added somewhere?

stevector’s picture

The shutdown function is crufty. It's necessary to re-save the published version of a node after saving an unpublished "forward revision." All of the modules in this space, ERS, State Machine, etc use something like this (hook_exit also works) to get around the fact that core always puts the most recently saved thing in the node table.

I'll be bringing this up at my core conversation in Denver: http://denver2012.drupal.org/content/i-just-want-edit-node

dixon_’s picture

Cross linking since this is semi-related: #1445824: Support for Migrate module

stevector’s picture

I've run into this myself now working on a migration. I think a solution is to make smarter that problem db_update() dalin points out.

So

    $query = db_update('node_revision')
      ->condition('nid', $node->nid)
      ->condition('vid', $node->vid, '!=')
      ->fields(array('status' => 0))
      ->execute();

Instead of

    $query = db_update('node_revision')
      ->condition('nid', $node->nid)
       ->fields(array('status' => 0))
      ->execute();

The module works in normal usage without that condition because the crufty shutdown function sets the status of the published revision back to 1. As dalin points out, that can fail in a migration.

And I think this patch brings the code closer to the comments which say that other revisions should be switched to status 0, not this one.

stevector’s picture

Issue summary: View changes

Replaced code tags with php tags

jp.stacey’s picture

For reference, I've had some success within the migration by setting the following in the prepare() method:

public function prepare($entity, $row) {
  $entity->revision = FALSE;
  $entity->is_new = !(isset($entity->nid) && ($entity->nid));
  $entity->workbench_moderation_state_current = 'published';
  $entity->workbench_moderation_state_new = 'published';
}

This ensures that node_save() still saves a revision (it should spot the lack of nid and set ->is_new itself anyway) but prevents workbench_moderation_moderate() from firing during hook_node_update()/hook_node_insert(), which might otherwise register a node-moderate action with the shutdown functions.

I'm not sure why Migration (or possibly Migrate D2D in this instance) sets $entity->revision to TRUE. Does it really need to?

Edit: ->is_new has to be set based on presence or absence of the node ID. Otherwise:

  1. simply setting $entity->is_new to TRUE to try to trick node_save(),
  2. because $entity->revision has been set to FALSE to trick workbench_moderation_moderate(),
  3. causes an existing node to be INSERTed, leading to a PDO exception.

This was incorrectly reported on #2204917: High-water mark and integrity constraint violation: duplicating changed nodes? as a bug in the Migrate or D2D projects, but actually came from this code!

damonbla’s picture

Just wanted to say thank you jp.stacey, #8 just fixed the problem for us. Could not figure out for the life of us why nodes looked like they were published (and said Published on the nodes themselves) but still came up as not published when looking harder (like the css classes and user visibility). Adding that prepare() function to the migrate code worked like a charm.

dgtlmoon’s picture

Thumbs up for the fix in #8 (Is this a bug request? kind of in the middle right?)

one_orange_cat’s picture

It took a number of things to get my migrate code working in the end but #8 was the charm, thanks!! More details here: https://www.drupal.org/node/1445824#comment-9176399

mikeryan’s picture

Just going through my first migration involving workbench_moderation and hitting lots of pain with that crufty shutdown function hack. Two pain points:

  1. Migrating 50,000 published nodes means after the migration is done, we wait a good long time (assuming we don't run out of memory - hint, an occasional drupal_static_reset() would be really helpful) for the extra node_save calls to complete. Basically, migration time is doubled.
  2. node_save() unconditionally sets the changed timestamp to REQUEST_TIME. To work around this when trying to migrate these timestamps, Migrate writes the necessary value directly after calling node_save(). Then workbench_moderation comes back around and sets it back to REQUEST_TIME...

We're going to try to @jp.stacey's suggestion above, thanks. But there really needs to be a less hacky way to get the proper moderation statuses set - a way to get it done within the original hook.

mikeryan’s picture

So far so good (although we have some testing to go to make sure everything's ending up in the right state). I found it simpler to do it through field mappings rather than by setting things in prepare():

    $this->addFieldMapping('workbench_moderation_state_new')
      ->defaultValue('published');
    $this->addFieldMapping('workbench_moderation_state_current')
      ->defaultValue('published');
    $this->addFieldMapping('revision')
      ->defaultValue(0);
mikeryan’s picture

See my updates to the migration support in https://www.drupal.org/node/1445824#comment-9255387 - with that patch, you should only need to map workbench_moderation_state_new in the normal way and everything should work (so that should address this issue as well).

danepowell’s picture

Be careful with the patch in #7, under heavy load it will result in content becoming published even if you save it as a draft.

earthmanweb’s picture

$entity->is_new = !(isset($entity->nid) && ($entity->nid));

jp.stacey, #8 - DUDE! Thank you for this, I was tearing my hair out with this one.

dalin’s picture

FWIW, you can simplify that as

$entity->is_new = empty($entity->nid);