diff --git a/tests/external_node_update.test b/tests/external_node_update.test new file mode 100644 index 0000000..c78ddd9 --- /dev/null +++ b/tests/external_node_update.test @@ -0,0 +1,189 @@ + 'External node update', + 'description' => 'Test if nodes are correctly moderated when updated by third party modules.', + 'group' => 'Workbench Moderation', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp($modules = array()) { + // Enable a test module that will publish and unpublish nodes for us. + parent::setUp(array_merge($modules, array('workbench_moderation_test'))); + + $this->drupalLogin($this->moderator_user); + } + + /** + * Tests if nodes can be moderated by third party modules. + */ + public function testNodeSave() { + // Create a brand new unpublished node programmatically. + $settings = array( + 'title' => $this->randomName(), + 'type' => $this->content_type, + 'status' => NODE_NOT_PUBLISHED, + ); + $this->node = $this->drupalCreateNode($settings); + + // Assert that the node is initially in state draft and not published. + $expected = array('state' => 'draft'); + $this->assertModerationStatus($expected, 'current', 'The moderation status is correct for a newly created node.'); + $this->assertNoPublishedRecord('A newly created node does not have a published entry in the node history table.'); + $this->assertPublicationState(FALSE, 'A newly created node is not published.'); + + // Resave the node and check that the status doesn't change. + $this->resaveNode(); + $this->assertModerationStatus($expected, 'current', 'The moderation status is correct for a newly created node.'); + $this->assertNoPublishedRecord('A newly created node does not have a published entry in the node history table.'); + $this->assertPublicationState(FALSE, 'A newly created node is not published.'); + + // Publish the node in an external module and check that the moderation + // state changes accordingly. + $this->drupalGet('workbench_moderation_test/' . $this->node->nid . '/publish'); + $this->refreshNode(); + $expected = array('state' => 'published'); + $this->assertModerationStatus($expected, 'current', 'The moderation state changed to "published" if the node is published externally.'); + $this->assertModerationStatus($expected, 'published', 'A published moderation state record is created when the node is published externally.'); + $this->assertPublicationState(TRUE, 'A node which is published externally is actually published.'); + + // Resave the node and check that the status doesn't change. + $this->resaveNode(); + $this->assertModerationStatus($expected, 'current', 'The moderation state changed to "published" if the node is published externally.'); + $this->assertModerationStatus($expected, 'published', 'A published moderation state record is created when the node is published externally.'); + $this->assertPublicationState(TRUE, 'A node which is published externally is actually published.'); + + // Unpublish the node in an external module and check that the moderation + // state changes accordingly. + $this->drupalGet('workbench_moderation_test/' . $this->node->nid . '/unpublish'); + $this->refreshNode(); + $expected = array('state' => 'draft'); + $this->assertModerationStatus($expected, 'current', 'The moderation state changed to "draft" if the node is unpublished externally.'); + $this->assertNoPublishedRecord('The published moderation state record is removed when the node is unpublished externally.'); + $this->assertPublicationState(FALSE, 'A node which is unpublished externally is actually unpublished.'); + + // Resave the node and check that the status doesn't change. + $this->resaveNode(); + $this->assertModerationStatus($expected, 'current', 'The moderation state changed to "draft" if the node is unpublished externally.'); + $this->assertNoPublishedRecord('The published moderation state record is removed when the node is unpublished externally.'); + $this->assertPublicationState(FALSE, 'A node which is unpublished externally is actually unpublished.'); + } + + /** + * Resave the node in an external module. + */ + public function resaveNode() { + $this->drupalGet('workbench_moderation_test/' . $this->node->nid); + $this->refreshNode(); + } + + /** + * Checks if the node history table matches the expected values. + * + * @param array $expected + * An associative array containing expected moderation status values. + * @param string $status + * Which status to assert. Can be either 'current' or 'published'. + * @param string $message + * The message to display along with the assertion. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + public function assertModerationStatus(array $expected, $status = 'current', $message = '') { + $record = $this->getModerationRecord($status); + $success = TRUE; + foreach ($expected as $key => $value) { + $success |= $this->assertEqual($value, $record[$key], format_string('Found value %value for %key, expected %expected.', array( + '%key' => $key, + '%value' => $record[$key], + '%expected' => $value, + ))); + } + + return $this->assertTrue($success, $message); + } + + /** + * Checks if the node is not marked as 'published' in the node history table. + * + * @param string $message + * The message to display along with the assertion. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + public function assertNoPublishedRecord($message = '') { + $record = $this->getModerationRecord('published'); + return $this->assertFalse($record, $message); + } + + /** + * Checks that the test node has the expected publication state. + * + * @param bool $expected + * TRUE if the the node should be published, FALSE otherwise. + * @param string $message + * The message to display along with the assertion. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + public function assertPublicationState($expected, $message = '') { + return $this->assertEqual($expected, $this->node->status, $message); + } + + /** + * Refreshes the test node so it matches the actual state in the database. + */ + public function refreshNode() { + $this->node = node_load($this->node->nid, NULL, TRUE); + } + + /** + * Returns a moderation status record of the tested node. + * + * @param string $status + * Which status to return. Can be either 'current' or 'published'. + * + * @return array + * The node's record(s) from the {workbench_moderation_node_history} table. + */ + protected function getModerationRecord($status = 'current') { + return db_select('workbench_moderation_node_history', 'nh') + ->fields('nh', array('from_state', 'state', 'published', 'current')) + ->condition('nid', $this->node->nid, '=') + ->condition($status, 1) + ->execute() + ->fetchAssoc(); + } + +} diff --git a/tests/workbench_moderation_test.info b/tests/workbench_moderation_test.info new file mode 100644 index 0000000..0f9e013 --- /dev/null +++ b/tests/workbench_moderation_test.info @@ -0,0 +1,5 @@ +name = Workbench Moderation Test +description = Test module for Workbench Moderation. +package = Workbench +core = 7.x +hidden = TRUE diff --git a/tests/workbench_moderation_test.module b/tests/workbench_moderation_test.module new file mode 100644 index 0000000..f1dc668 --- /dev/null +++ b/tests/workbench_moderation_test.module @@ -0,0 +1,37 @@ + array( + 'title' => 'Publish a node', + 'page callback' => 'workbench_moderation_test_update_node', + 'page arguments' => array(1), + 'access arguments' => array('bypass workbench moderation'), + ), + ); +} + +/** + * Page callback. Publishes, unpublishes or resaves the given node. + * + * @param object $node + * The node to publish, unpublish or resave. + * @param string $action + * Optionally the action to take, either 'publish' or 'unpublish'. If omitted + * the node will be resaved. + */ +function workbench_moderation_test_update_node($node, $action = NULL) { + if (!empty($action)) { + $node->status = $action == 'publish' ? NODE_PUBLISHED : NODE_NOT_PUBLISHED; + } + node_save($node); + return array('#markup' => t('Node status: @status', array('@status' => $node->status ? t('published') : t('unpublished')))); +} diff --git a/workbench_moderation.info b/workbench_moderation.info index f065940..3f9939f 100644 --- a/workbench_moderation.info +++ b/workbench_moderation.info @@ -13,5 +13,6 @@ files[] = includes/workbench_moderation_handler_filter_state.inc files[] = includes/workbench_moderation_handler_filter_moderated_type.inc files[] = includes/workbench_moderation_handler_filter_user_can_moderate.inc files[] = workbench_moderation.migrate.inc +files[] = tests/external_node_update.test files[] = tests/workbench_moderation.test files[] = tests/workbench_moderation.files.test diff --git a/workbench_moderation.module b/workbench_moderation.module index 6c7d5ed..f4a6ef5 100644 --- a/workbench_moderation.module +++ b/workbench_moderation.module @@ -675,13 +675,31 @@ function workbench_moderation_node_update($node) { return; } - // Set default moderation state values. + // Set moderation state values. if (!isset($node->workbench_moderation_state_current)) { - $node->workbench_moderation_state_current = ($node->status ? workbench_moderation_state_published() : workbench_moderation_state_none()); - }; + $node->workbench_moderation_state_current = !empty($node->original->workbench_moderation['current']->state) ? $node->original->workbench_moderation['current']->state : workbench_moderation_state_none(); + } if (!isset($node->workbench_moderation_state_new)) { - $node->workbench_moderation_state_new = variable_get('workbench_moderation_default_state_' . $node->type, workbench_moderation_state_none()); - }; + // Moving from published to unpublished. + if ($node->status == NODE_NOT_PUBLISHED && isset($node->original->status) && $node->original->status == NODE_PUBLISHED) { + // @todo Currently we cannot set the state correctly if the default state + // is "Published". + // @see https://www.drupal.org/node/1436260 + $node->workbench_moderation_state_new = variable_get('workbench_moderation_default_state_' . $node->type, workbench_moderation_state_none()); + } + // Moving from unpublished to published. + elseif ($node->status == NODE_PUBLISHED && isset($node->original->status) && $node->original->status == NODE_NOT_PUBLISHED) { + $node->workbench_moderation_state_new = workbench_moderation_state_published(); + } + else { + if (!empty($node->original->workbench_moderation['current']->state)) { + $node->workbench_moderation_state_new = $node->original->workbench_moderation['current']->state; + } + else { + $node->workbench_moderation_state_new = variable_get('workbench_moderation_default_state_' . $node->type, workbench_moderation_state_none()); + } + } + } // If this is a new node, give it some information about 'my revision'. if (!isset($node->workbench_moderation)) { @@ -1599,18 +1617,27 @@ function workbench_moderation_moderate($node, $state) { elseif (isset($node->workbench_moderation['published']) && $new_revision->vid == $node->workbench_moderation['published']->vid && $new_revision->from_state == workbench_moderation_state_published()) { // If we're moderating the published revision to a non-published state, // remove the workbench moderation 'published' property. + $query = db_update('workbench_moderation_node_history') + ->condition('hid', $node->workbench_moderation['published']->hid) + ->fields(array('published' => 0)) + ->execute(); unset($node->workbench_moderation['published']); + $node->workbench_moderation['current']->unpublishing = TRUE; } - // If we're moderating an unpublished revision and there is an existing - // published revision, make sure that the published revision is live. - // We do this in a shutdown function to avoid race conditions when - // running node_save() from within a node submission. - if (!empty($node->workbench_moderation['published'])) { - drupal_register_shutdown_function('workbench_moderation_store', $node); + // If we need to make changes to the currently published node we do this in a + // shutdown function to avoid race conditions when running node_save() from + // within a node submission. We need to change the published node: + // - If we're moderating an unpublished revision and there is an existing + // published revision, make sure that the published revision is live. + // - If we are moving to unpublished state we should make sure the published + // revision is the 'current' revision. + if (!empty($node->workbench_moderation['published']) || !empty($node->workbench_moderation['current']->unpublishing)) { + // Clone the node to make sure our data arrives intact in the shutdown + // function. It might still be altered before the shutdown is reached. + drupal_register_shutdown_function('workbench_moderation_store', clone $node); } - // Notify other modules that the state was changed. module_invoke_all('workbench_moderation_transition', $node, $old_revision->state, $state); } @@ -1637,9 +1664,18 @@ function workbench_moderation_store($node) { 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; + + // If we are saving a published node, work from the live revision, otherwise + // make sure that the entry in the {node} table points to the current + // revision. + if (empty($node->workbench_moderation['current']->unpublishing)) { + $live_revision = workbench_moderation_node_live_load($node); + $live_revision->status = 1; + } + else { + $live_revision = workbench_moderation_node_current_load($node); + $live_revision->status = 0; + } // Don't create a new revision. $live_revision->revision = 0; // Prevent another moderation record from being written. diff --git a/workbench_moderation.node.inc b/workbench_moderation.node.inc index 5baaf59..b8c7b91 100644 --- a/workbench_moderation.node.inc +++ b/workbench_moderation.node.inc @@ -284,24 +284,9 @@ function workbench_moderation_node_unpublish_form_submit($form, &$form_state) { global $user; $node = $form['node']['#value']; - // Remove the moderation record's "published" flag. - $query = db_update('workbench_moderation_node_history') - ->condition('hid', $node->workbench_moderation['published']->hid) - ->fields(array('published' => 0)) - ->execute(); - - // Moderate the revision. + // Moderate the revision. This will do the heavy lifting. workbench_moderation_moderate($node, $form_state['values']['state']); - // Make sure the 'current' revision is the 'live' revision -- ie, the revision - // in {node}. - $live_revision = workbench_moderation_node_current_load($node); - $live_revision->status = 0; - $live_revision->revision = 0; - $live_revision->workbench_moderation['updating_live_revision'] = TRUE; - // @TODO: do we trust node_save() here? - node_save($live_revision); - drupal_set_message(t('The live revision of this content has been unpublished.')); $form_state['redirect'] ="node/{$node->nid}/moderation"; }