...
 
Commits (341)
This diff is collapsed.
@CHARSET "UTF-8";
.current-revision td {
background-color: #faafbe;
}
.published-revision td {
background-color: #aaffaa;
}
.width-auto {
width: auto;
}
form.workbench-moderation-moderate-form,
form.workbench-moderation-moderate-form div,
form.workbench-moderation-moderate-form label {
display: inline;
}
#workbench-moderation-admin-transitions-form table {
min-width: 380px;
}
\ No newline at end of file
<?php
/**
* @file
* Provides a link to Moderation History for Views.
*/
class workbench_moderation_handler_field_history_link extends views_handler_field_node_link {
function render_link($data, $values) {
// ensure user has access to view moderation history for this node.
$node = node_load($this->get_value($values, 'nid'));
if (!_workbench_moderation_access('view history', $node)) {
return;
}
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = "node/$node->nid/moderation";
$text = !empty($this->options['text']) ? $this->options['text'] : t('View moderation history');
return $text;
}
}
<?php
/**
* @file
* Provides moderation links for Views.
*/
class workbench_moderation_handler_field_links extends views_handler_field {
function render($values) {
if ($values->{$this->aliases['is_current']}) {
$node = node_load($values->{$this->aliases['nid']}, $values->{$this->aliases['vid']});
return theme('links', array('links' => workbench_moderation_get_moderation_links($node, array('html' => TRUE, 'query' => array('destination' => $_GET['q'])))));
}
return '';
}
}
<?php
/**
* Field handler to translate a moderation state into its readable form.
*/
class workbench_moderation_handler_field_state extends views_handler_field_node {
function option_definition() {
$options = parent::option_definition();
$options['machine_name'] = array('default' => FALSE);
return $options;
}
/**
* Provide machine_name option for to node type display.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['machine_name'] = array(
'#title' => t('Output machine name'),
'#description' => t('Display field as the moderation state machine name.'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['machine_name']),
'#fieldset' => 'more',
);
}
/**
* Render node type as human readable name, unless using machine_name option.
*/
function render_name($data, $values) {
if ($this->options['machine_name'] != 1 && $data !== NULL && $data !== '') {
return t($this->sanitize_value(workbench_moderation_state_label($data)));
}
return $this->sanitize_value($data);
}
function render($values) {
$value = $this->get_value($values);
return $this->render_link($this->render_name($value, $values), $values);
}
}
<?php
/**
* @file
* Provides moderation filters for Views.
*/
/**
* Filter by whether a node type has moderation enabled or not.
*/
class workbench_moderation_handler_filter_moderated_type extends views_handler_filter_boolean_operator {
function query() {
if (!isset($this->value) || $this->value === NULL) {
return;
}
$node_types = workbench_moderation_moderate_node_types();
// If there are no node types using moderation set this variable to an array with a blank value.
// This will force the query to return no values.
if (empty($node_types)) {
$node_types = array('');
// Tell users to configure content types for moderation
// It's conceivable that a hook_menu_alter has changed the permission needed to get to admin/structure/types
// and as such, perhaps a better check should be used here.
if (user_access('administer content types')) {
$message = t('<a href="@settings" title="Content type administration">No content types have been configured to use Workbench Moderation.</a>', array('@settings' => url('admin/structure/types')));
$type = 'error';
}
// Non admins get a different message.
else {
$message = t('Moderation is not ready to for use at this time. Please contact your administrator.');
$type = 'warning';
}
drupal_set_message($message, $type, $repeat = FALSE);
}
$operator = ($this->value ? "IN" : "NOT IN");
$this->ensure_my_table();
$this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $node_types, $operator);
}
}
<?php
/**
* @file
* Filter based on moderation state.
*/
class workbench_moderation_handler_filter_state extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Moderation status');
$this->value_options = array_map('check_plain', workbench_moderation_state_labels());
}
}
}
<?php
/**
* @file
* Filter based on moderation privlieges.
*/
class workbench_moderation_handler_filter_user_can_moderate extends views_handler_filter {
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
drupal_set_message(t("This filter isn't even possible right now since workbench moderation permissions are incomplete--there's no way to figure out what transitions a user may make for a particular type of content."), 'error');
}
function query() {
// add table, add node table, add where statments like "(n.type = 'blog' AND moderation.state IN ('review', 'approve')) OR (n.type = 'article' AND moderation.state IN ('review'))"
// $this->ensure_my_table();
// $node_alias = $this->query->ensure_table('node');
// $this->query->add_where();
}
}
/**
* @file
* Sets the summary for Workbench moderation on vertical tabs.
*/
(function ($) {
Drupal.behaviors.workbenchModerationSettingsSummary = {
attach: function(context) {
$('fieldset.node-form-options', context).drupalSetSummary(function (context) {
var vals = [];
$('input:checked', context).parent().each(function () {
vals.push(Drupal.checkPlain($.trim($(this).text())));
});
if ($('select[name="workbench_moderation_state_new"]', context).val()) {
vals.push(Drupal.checkPlain($('select[name="workbench_moderation_state_new"] option:selected').text()));
}
return vals.join(', ');
});
}
};
})(jQuery);
<?php
/**
* Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
* more information.
*/
function workbench_moderation_nodedraft_page_manager_tasks() {
return array(
// This is a 'page' task and will fall under the page admin UI
'task type' => 'page',
'title' => t('Node draft'),
'admin title' => t('The draft page for moderated nodes.'),
'admin description' => t('When enabled, this overrides the default node view at node/%node/draft'),
'admin path' => 'node/%node/draft',
// Menu hooks so that we can alter the node/%node menu entry to point to us.
'hook menu alter' => 'workbench_moderation_nodedraft_menu_alter',
// This is task uses 'context' handlers and must implement these to give the
// handler data it needs.
'handler type' => 'context',
'get arguments' => 'workbench_moderation_nodedraft_get_arguments',
'get context placeholders' => 'workbench_moderation_nodedraft_get_contexts',
// Allow this to be enabled or disabled:
'disabled' => variable_get('workbench_moderation_nodedraft_disabled', TRUE),
'enable callback' => 'workbench_moderation_nodedraft_enable',
);
}
/**
* Callback defined by workbench_moderation_nodedraft_page_manager_tasks().
*
* Alter menu item so that admin/workbench comes here.
*/
function workbench_moderation_nodedraft_menu_alter(&$items, $task) {
if (variable_get('workbench_moderation_nodedraft_disabled', TRUE)) {
return;
}
$callback = $items['node/%node/draft']['page callback'];
// Override the node edit handler for our purpose.
if ($callback == 'workbench_moderation_node_view_draft' || variable_get('page_manager_override_anyway', FALSE)) {
$items['node/%node/draft']['page callback'] = 'workbench_moderation_nodedraft';
$items['node/%node/draft']['file path'] = $task['path'];
$items['node/%node/draft']['file'] = $task['file'];
}
else {
//variable_set('workbench_moderation_nodedraft_disabled', TRUE);
if (!empty($GLOBALS['page_manager_enabling_workbench'])) {
drupal_set_message(t('Page manager module is unable to enable Workbench Moderation Draft Node because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
}
return;
}
}
/**
* Entry point for our overridden My Workbench.
*
* This function asks its assigned handlers who, if anyone, would like
* to run with it. If no one does, it passes through to the main node draft page.
*/
function workbench_moderation_nodedraft($node) {
// Load my task plugin
$task = page_manager_get_task('nodedraft');
// Get the most recent revision to pass to the task handler.
$current_node = workbench_moderation_node_current_load($node);
// Load the node into a context.
ctools_include('context');
ctools_include('context-task-handler');
$contexts = ctools_context_handler_get_task_contexts($task, '', array($current_node));
$output = ctools_context_handler_render($task, '', $contexts, array($current_node->nid));
if ($output !== FALSE) {
return $output;
}
module_load_include('inc', 'workbench_moderation', 'workbench_moderation.node');
$function = 'workbench_moderation_node_view_draft';
foreach (module_implements('page_manager_override') as $module) {
$call = $module . '_page_manager_override';
if (($rc = $call('workbench')) && function_exists($rc)) {
$function = $rc;
break;
}
}
// Otherwise, fall back.
return $function($node);
}
/**
* Callback to enable/disable the page from the UI.
*/
function workbench_moderation_nodedraft_enable($cache, $status) {
variable_set('workbench_moderation_nodedraft_disabled', $status);
// Set a global flag so that the menu routine knows it needs
// to set a message if enabling cannot be done.
if (!$status) {
$GLOBALS['page_manager_enabling_workbench'] = TRUE;
}
}
/**
* Callback to get arguments provided by this task handler.
*
* Since this is the node view and there is no UI on the arguments, we
* create dummy arguments that contain the needed data.
*/
function workbench_moderation_nodedraft_get_arguments($task, $subtask_id) {
return array(
array(
'keyword' => 'node',
'identifier' => t('Node draft being viewed'),
'id' => 1,
'name' => 'entity_id:node',
'settings' => array(),
),
);
}
/**
* Callback to get context placeholders provided by this handler.
*/
function workbench_moderation_nodedraft_get_contexts($task, $subtask_id) {
return ctools_context_get_placeholders_from_argument(page_manager_node_view_get_arguments($task, $subtask_id));
}
<?php
/**
* Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for
* more information.
*/
function workbench_moderation_noderevision_page_manager_tasks() {
return array(
// This is a 'page' task and will fall under the page admin UI
'task type' => 'page',
'title' => t('Node revision'),
'admin title' => t('The revision page for moderated nodes.'),
'admin description' => t('When enabled, this overrides the default node view at node/%node/revisions/%/view'),
'admin path' => 'node/%node/revisions/%/view',
// Menu hooks so that we can alter the node/%node menu entry to point to us.
'hook menu alter' => 'workbench_moderation_noderevision_menu_alter',
// This is task uses 'context' handlers and must implement these to give the
// handler data it needs.
'handler type' => 'context',
'get arguments' => 'workbench_moderation_noderevision_get_arguments',
'get context placeholders' => 'workbench_moderation_noderevisoin_get_contexts',
// Allow this to be enabled or disabled:
'disabled' => variable_get('workbench_moderation_noderevision_disabled', TRUE),
'enable callback' => 'workbench_moderation_noderevision_enable',
);
}
/**
* Callback defined by workbench_moderation_noderevision_page_manager_tasks().
*
* Alter menu item so that admin/workbench comes here.
*/
function workbench_moderation_noderevision_menu_alter(&$items, $task) {
if (variable_get('workbench_moderation_noderevision_disabled', TRUE)) {
return;
}
$callback = $items['node/%node/revisions/%/view']['page callback'];
// Override the node edit handler for our purpose.
if ($callback == 'workbench_moderation_node_view_revision' || variable_get('page_manager_override_anyway', FALSE)) {
$items['node/%node/revisions/%/view']['page callback'] = 'workbench_moderation_noderevision';
$items['node/%node/revisions/%/view']['file path'] = $task['path'];
$items['node/%node/revisions/%/view']['file'] = $task['file'];
}
else {
variable_set('workbench_moderation_noderevision_disabled', TRUE);
if (!empty($GLOBALS['page_manager_enabling_workbench_noderevision'])) {
drupal_set_message(t('Page manager module is unable to enable Workbench Moderation Revision Node because some other module already has overridden with %callback.', array('%callback' => $callback)), 'warning');
}
return;
}
}
/**
* Entry point for our overridden My Workbench.
*
* This function asks its assigned handlers who, if anyone, would like
* to run with it. If no one does, it passes through to the main node draft page.
*/
function workbench_moderation_noderevision($node) {
// Load my task plugin
$task = page_manager_get_task('noderevision');
// Load the node into a context.
ctools_include('context');
ctools_include('context-task-handler');
$contexts = ctools_context_handler_get_task_contexts($task, '', array($node));
$output = ctools_context_handler_render($task, '', $contexts, array($node->nid));
if ($output !== FALSE) {
return $output;
}
module_load_include('inc', 'workbench_moderation', 'workbench_moderation.node');
$function = 'workbench_moderation_node_view_revision';
foreach (module_implements('page_manager_override') as $module) {
$call = $module . '_page_manager_override';
if (($rc = $call('workbench')) && function_exists($rc)) {
$function = $rc;
break;
}
}
// Otherwise, fall back.
return $function($node);
}
/**
* Callback to enable/disable the page from the UI.
*/
function workbench_moderation_noderevision_enable($cache, $status) {
variable_set('workbench_moderation_noderevision_disabled', $status);
// Set a global flag so that the menu routine knows it needs
// to set a message if enabling cannot be done.
if (!$status) {
$GLOBALS['page_manager_enabling_workbench_noderevision'] = TRUE;
}
}
/**
* Callback to get arguments provided by this task handler.
*
* Since this is the node view and there is no UI on the arguments, we
* create dummy arguments that contain the needed data.
*/
function workbench_moderation_noderevision_get_arguments($task, $subtask_id) {
return array(
array(
'keyword' => 'node',
'identifier' => t('Node revision being viewed'),
'id' => 1,
'name' => 'entity_id:node',
'settings' => array(),
),
);
}
/**
* Callback to get context placeholders provided by this handler.
*/
function workbench_moderation_noderevision_get_contexts($task, $subtask_id) {
return ctools_context_get_placeholders_from_argument(page_manager_node_view_get_arguments($task, $subtask_id));
}
<?php
/**
* @file
* Tests moderation states when nodes are (un)published by other modules.
*/
class WorkbenchModerationExternalNodeUpdateTestCase extends WorkbenchModerationTestCase {
/**
* {@inheritdoc}
*/
protected $profile = 'testing';
/**
* A test node.
*
* @var object
*/
protected $node;
/**
* Returns test case metadata.
*
* @return array
* The metadata.
*/
public static function getInfo() {
return array(
'name' => '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, 'is_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, 'is_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, 'is_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, 'is_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, 'is_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, 'is_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 = 'is_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 = 'is_current') {
$data = db_select('workbench_moderation_node_history', 'nh')
->fields('nh', array('from_state', 'state', 'published', 'is_current'))
->condition('nid', $this->node->nid, '=')
->condition($status, 1)
->execute()
->fetchAssoc();
return $data;
}
}
<?php
/**
* @file
* Tests for using file fields with workbench_moderation.module.
*/
class WorkbenchModerationFilesTestCase extends FileFieldTestCase {
protected $content_type;
protected $moderator_user;
protected $field_name;
function setUp() {
parent::setUp();
module_enable(array('drafty', 'workbench_moderation'));
// Create a new content type and enable moderation on it.
$type = $this->drupalCreateContentType();
$this->content_type = $type->name;
variable_set('node_options_' . $this->content_type, array('revision', 'moderation'));
// Add a file field to the new content type.
$this->field_name = strtolower($this->randomName());
$this->createFileField($this->field_name, $this->content_type);
$this->moderator_user = $this->drupalCreateUser(array(
'access content',
'access administration pages',
'administer site configuration',
'administer users',
'administer permissions',
'administer content types',
'administer nodes',
'bypass node access',
'view revisions',
'revert revisions',
"edit any {$this->content_type} content",
'view moderation history',
'view moderation messages',
'bypass workbench moderation',
));
$this->drupalLogin($this->moderator_user);
}
public static function getInfo() {
return array(
'name' => 'Workbench Moderation file attachments',
'description' => 'Test moderation on nodes with with file attachments.',
'group' => 'Workbench Moderation',
);
}
function testModeratedFileField() {
// Create a new node with an uploaded file.
$file = $this->getTestFile('text');
$edit = array(
'title' => $this->randomName(),
'files[' . $this->field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($file->uri),
);
$this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
// Get the new node.
$node = $this->drupalGetNodeByTitle($edit['title']);
$nid = $node->nid;
// Publish the node via the moderation form.
$moderate = array('state' => workbench_moderation_state_published());
$this->drupalPost("node/{$nid}/moderation", $moderate, t('Apply'));
// Update the node; remove the first file and add a second file.
$file = $this->getTestFile('text');
$edit = array(
'title' => $this->randomName(10) . '_second',
'files[' . $this->field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($file->uri),
);
$this->drupalPost("node/$nid/edit", array(), t('Remove'));
$this->drupalPost(NULL, $edit, t('Save'));
// Load the published node.
$published = node_load($nid, NULL, TRUE);
// Check for a published revision.
$this->assertTrue(isset($published->workbench_moderation['published']), t('Workbench moderation has published revision'));
// Load the draft revision.
$draft = clone $published;
$draft = workbench_moderation_node_current_load($draft);
// Check that the draft revision is different from the published revision.
$this->assertNotEqual($published->vid, $draft->vid, t('Workbench moderation loads second revision'));
// Check that the original file is present on the published revision.
$published_file = (object) $published->{$this->field_name}[LANGUAGE_NONE][0];
$this->assertFileExists($published_file, t('File is present on published revision'));
// Check that the second file is present on the draft revision.
$draft_file = (object) $draft->{$this->field_name}[LANGUAGE_NONE][0];
$this->assertFileExists($draft_file, t('File is present on draft revision'));
$this->assertNotEqual($published_file->uri, $draft_file->uri, t('File on published revision is different from file on draft revision'));
}
}
<?php
/**
* @file
* Node Access tests for workbench_moderation.module.
*/
class WorkbenchModerationNodeAccessTestCase extends WorkbenchModerationTestCase {
protected $test_user;
public static function getInfo() {
return array(
'name' => 'Node Access test',
'description' => 'Ensure that node access rules are enforced during moderation.',
'group' => 'Workbench Moderation',
);
}
function setUp($modules = array()) {
$modules = array_merge($modules, array('workbench_moderation_node_access_test'));
parent::setUp($modules);
}
/**
* Creates a node and tests the creation of node access rules.
*/
function testNodeAccessRecords() {
// Make sure this node type is moderated.
$is_moderated = workbench_moderation_node_type_moderated($this->content_type);
$this->assertTrue($is_moderated, t('The content type is moderated.'));
// Create an published node that has access records.
$node1 = $this->drupalCreateNode(array('type' => $this->content_type, 'title' => 'Published node', 'status' => 1));
$this->assertTrue(node_load($node1->nid), 'Test published node created.');
// Assert grants were added to node_access on creation.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->nid))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_wm_realm', 'Grant with test_wm_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an unpublished node that has no access records.
$node2 = $this->drupalCreateNode(array('type' => $this->content_type, 'title' => 'Unpublished node', 'status' => 0));
$this->assertTrue(node_load($node2->nid), 'Test unpublished node created.');
// Check that no access records are present for the node, since it is not published.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node2->nid))->fetchAll();
$this->assertEqual(count($records), 0, 'Returned no records for unpublished node.');
// Login for form testing.
$this->drupalLogin($this->moderator_user);
// Create a new draft of the published node.
$edit = array('title' => 'Draft node revision1');
$this->drupalPost("node/{$node1->nid}/edit", $edit, t('Save'));
// Assert grants still exist on node_access after draft created.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->nid))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_wm_realm', 'Grant with article_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an published node that has access records.
$node3 = $this->drupalCreateNode(array('type' => $this->content_type, 'title' => 'Published node', 'status' => 1));
$this->assertTrue(node_load($node3->nid), 'Test published node created.');
// Make sure the "Moderate" tab is accessible.
$this->drupalGet("node/{$node3->nid}/moderation");
// Attempt to change the moderation state without a token in the link.
$this->drupalGet("node/{$node3->nid}/moderation/{$node3->vid}/change-state/needs_review");
$this->assertResponse(403);
// Run the same state change with a valid token, which should succeed.
$this->drupalGet("node/{$node3->nid}/moderation/{$node3->vid}/change-state/needs_review", array(
'query' => array('token' => $this->drupalGetToken("{$node3->nid}:{$node3->vid}:needs_review"))
));
// Test that we have unpublished the node.
$this->assertResponse(200);
$node = node_load($node3->nid, NULL, TRUE);
$this->assertEqual($node->workbench_moderation['current']->state, 'needs_review', 'Node state changed to Needs Review via callback.');
$this->assertFalse($node->status, 'Node is no longer published after moderation.');
// Check that no access records are present for the node, since it is not published.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll();
$this->assertEqual(count($records), 0, 'Returned no records for unpublished node.');
// Publish the node via the moderation form.
$moderate = array('state' => workbench_moderation_state_published());
$this->drupalPost("node/{$node3->nid}/moderation", $moderate, t('Apply'));
// Ensure published status.
$node = node_load($node3->nid, NULL, TRUE);
$this->assertTrue(isset($node->workbench_moderation['published']), t('Workbench moderation has a published revision'));
// Check to see if grants are still present.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_wm_realm', 'Grant with article_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create a new draft.
$edit = array('title' => $this->randomName(10) . '_revision1');
$this->drupalPost("node/{$node3->nid}/edit", $edit, t('Save'));
// Check that grants are not changed.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_wm_realm', 'Grant with article_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Ensure that the published version is still present.
$node = node_load($node3->nid, NULL, TRUE);
debug($node->title);
$this->assertTrue($node->status, t('Content is published'));
$this->assertEqual($node->title, 'Published node', t('Published version is loaded.'));
// Load the published and draft revisions.
$draft = clone $node;
$draft = workbench_moderation_node_current_load($draft);
// Assert the two revisions are unique.
$this->assertEqual($node->vid, $node->workbench_moderation['published']->vid, t('Published revision is loaded by default'));
$this->assertTrue($node->status, t('Published revision has status = 1'));
$this->assertNotEqual($node->vid, $draft->vid, t('Draft revision is different from the published revision'));
// Test the node for an anon user.
$this->drupalLogout();
$this->drupalGet('node/' . $node->nid);
$this->assertRaw('Published node', t('Page title is "Published node"'));
}
}
<?php
/**
* @file
* Permissions-related tests for workbench_moderation.module.
*/
class WorkbenchModerationPermsTestCase extends DrupalWebTestCase {
protected $content_type;
protected $editor_user;
protected $author_user;
function setUp($modules = array()) {
$modules = array_merge($modules, array('drafty', 'workbench_moderation'));
parent::setUp($modules);
// Create a new content type and enable moderation on it.
$type = $this->drupalCreateContentType();
$this->content_type = $type->name;
variable_set('node_options_' . $this->content_type, array('revision', 'moderation'));
// The editor should be able to view all unpublished content, even without authoring perms.
$editor_permissions = array(
0 => 'view all unpublished content',
);
$this->editor_user = $this->drupalCreateUser($editor_permissions);
// The Author will create the content.
$author_permissions = array(
0 => 'create ' . $type->name . ' content',
);
$this->author_user = $this->drupalCreateUser($author_permissions);
}
}
class WorkbenchModerationViewUnpublishedTestCase extends WorkbenchModerationPermsTestCase {
public static function getInfo() {
return array(
'name' => 'View all unpublished content',
'description' => 'Create a user who can view unpublished content. Create a node and leave it unpublished. Try to view it.',
'group' => 'Workbench Moderation',
);
}
function setUp($modules = array()) {
parent::setUp($modules);
$this->drupalLogin($this->author_user);
}
function testViewUnpublished() {
$is_moderated = workbench_moderation_node_type_moderated($this->content_type);
$this->assertTrue($is_moderated, t('The content type is moderated.'));
// Create a new node and make sure it is unpublished.
$body_name = 'body[' . LANGUAGE_NONE . '][0]';
$edit = array(
'title' => $this->randomName(),
"{$body_name}[value]" => $this->randomString(128),
"{$body_name}[format]" => filter_default_format(),
);
$this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
// Get the new node.
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertFalse($node->status, t('New node is unpublished'));
$this->assertTrue(isset($node->workbench_moderation), t('Workbench moderation information is present on the node object'));
$this->assertFalse(isset($node->workbench_moderation['published']), t('Workbench moderation has no published revision'));
$this->assertEqual($node->uid, $this->author_user->uid, 'This node was authored by the author user.');
$this->verbose(print_r($this->loggedInUser, TRUE));
$this->drupalLogin($this->editor_user);
global $user;
$user = user_load($this->loggedInUser->uid);
$this->drupalGet($node->path['source']);
$this->assertFalse($node->status, t('This node is unpublished.'));
$this->assertResponse(200);
$this->assertFalse($node->uid == $this->loggedInUser->uid, t('The current user is not the author of this node.'));
$this->assertEqual($user->uid, $this->loggedInUser->uid, 'The current global user is the same as the logged in user.');
$this->assertEqual($user->uid, $this->editor_user->uid, 'The current user is the editor user.');
$this->assertTrue(user_access('view all unpublished content'), 'Current user has permission to view all unpublished content');
}
}
This diff is collapsed.
<?php
/**
* @file
* Tests for node transition hooks with workbench_moderation.module.
*/
class WorkbenchModerationTransitionTestCase extends WorkbenchModerationTestCase {
protected $properties;
public static function getInfo() {
return array(
'name' => 'Transition hook test',
'description' => 'Tests the usage of hook_workbench_moderation_transition().',
'group' => 'Workbench Moderation',
);
}
function setUp($modules = array()) {
// Enable a test module that will publish and unpublish nodes for us and
// provide hook implementations.
parent::setUp(array_merge($modules, array('workbench_moderation_test')));
$this->drupalLogin($this->moderator_user);
$this->properties = array('title', 'nid', 'vid', 'previous_state', 'new_state');
}
/**
* Asserts that transition values are as expected.
*
* @param array $expected
* An array of values to be set by the transition.
*/
private function assertTransition($expected = array()) {
foreach ($this->properties as $name) {
$value = workbench_moderation_test_get($name);
$this->assertEqual($value, $expected[$name], "Transition success: $name set to $value");
}
}
function testTransitionFromNodeForm() {
// Create a new node and publish it immediately.
$body_name = 'body[' . LANGUAGE_NONE . '][0]';
$edit = array(
'title' => $this->randomName(),
"{$body_name}[value]" => $this->randomString(128),
"{$body_name}[format]" => filter_default_format(),
'workbench_moderation_state_new' => workbench_moderation_state_published(),
);
$this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
// Test the published state transition.
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $edit['title'],
'previous_state' => 'draft',
'new_state' => 'published',
'status' => 1,
);
$this->assertTransition($expected);
// Create a new draft of the published node.
$node = $this->drupalGetNodeByTitle($edit['title']);
$edit = array('title' => 'Draft node revision1');
$this->drupalPost("node/{$node->nid}/edit", $edit, t('Save'));
$expected = array(
'nid' => 1,
'vid' => 2,
'title' => $edit['title'],
'previous_state' => 'published',
'new_state' => 'draft',
'status' => 0,
);
$this->assertTransition($expected);
// Moderate to needs review and check transition.
$this->drupalGet("node/1/moderation/2/change-state/needs_review", array(
'query' => array('token' => $this->drupalGetToken("1:2:needs_review"))
));
$expected = array(
'nid' => 1,
'vid' => 2,
'title' => $edit['title'],
'previous_state' => 'draft',
'new_state' => 'needs_review',
'status' => 0,
);
$this->assertTransition($expected);
// Publish the revision and check transition.
$this->drupalGet("node/1/moderation/2/change-state/published", array(
'query' => array('token' => $this->drupalGetToken("1:2:published"))
));
$expected = array(
'nid' => 1,
'vid' => 2,
'title' => $edit['title'],
'previous_state' => 'needs_review',
'new_state' => 'published',
'status' => 1,
);
$this->assertTransition($expected);
// Create a new node and make sure it is unpublished.
$body_name = 'body[' . LANGUAGE_NONE . '][0]';
$edit = array(
'title' => $this->randomName(),
"{$body_name}[value]" => $this->randomString(128),
"{$body_name}[format]" => filter_default_format(),
);
$this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
// Get the new node.
$node = $this->drupalGetNodeByTitle($edit['title']);
$expected = array(
'nid' => $node->nid,
'vid' => $node->vid,
'title' => $edit['title'],
'previous_state' => 'draft',
'new_state' => 'draft',
'status' => 0,
);
$this->assertTransition($expected);
}
function testUnpublishTransition() {
// Create a new node and publish it immediately. Assumes that
// WorkbenchModerationPublishFromNodeFormTestCase passes.
$body_name = 'body[' . LANGUAGE_NONE . '][0]';
$edit = array(
'title' => $this->randomName(),
"{$body_name}[value]" => $this->randomString(128),
"{$body_name}[format]" => filter_default_format(),
'workbench_moderation_state_new' => workbench_moderation_state_published(),
);
$this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title']);
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $edit['title'],
'previous_state' => 'draft',
'new_state' => 'published',
'status' => 1,
);
$this->assertTransition($expected);
// Unpublish the node via the unpublish confirmation form.
$this->drupalPost("node/{$node->nid}/moderation/{$node->vid}/unpublish", array(), t('Unpublish'));
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $edit['title'],
'previous_state' => 'published',
'new_state' => 'draft',
'status' => 0,
);
$this->assertTransition($expected);
}
public function testTransitionFromNodeSave() {
// Create a brand new unpublished node programmatically.
$edit = array(
'title' => $this->randomName(),
'type' => $this->content_type,
'status' => NODE_NOT_PUBLISHED,
);
$this->node = $this->drupalCreateNode($edit);
// Get the new node.
$node = $this->drupalGetNodeByTitle($edit['title']);
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $edit['title'],
'previous_state' => 'draft',
'new_state' => 'draft',
'status' => 0,
);
$this->assertTransition($expected);
$node = $this->drupalGetNodeByTitle($edit['title']);
$node = node_load($node->nid, NULL, TRUE);
// Update the status external to our processes.
$node->status = 1;
$node->title = 'New title';
node_save($node);
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $node->title,
'previous_state' => 'draft',
'new_state' => 'published',
'status' => 1,
);
$this->assertTransition($expected);
// Ensure that multiple saves that do not spawn a new PHP request are
// handled correctly to protect against stale static cache.
$node = $this->drupalGetNodeByTitle($node->title);
$node = node_load($node->nid, NULL, TRUE);
$node->status = 1;
$node->title = 'Newer title';
node_save($node);
$expected = array(
'nid' => 1,
'vid' => 1,
'title' => $node->title,
'previous_state' => 'published',
'new_state' => 'published',
'status' => 1,
);
$this->assertTransition($expected);
}
}
name = Workbench Moderation Node Access Test
description = Node Access test module for Workbench Moderation.
package = Workbench
core = 7.x
hidden = TRUE
<?php
/**
* @file
* Test module for node access control on moderated nodes.
*/
/**
* Implements hook_node_grants().
*/
function workbench_moderation_node_access_test_node_grants($account, $op) {
// Give everyone full grants so we don't break other node tests.
return array(
'test_wm_realm' => array(1),
);
}
/**
* Implements hook_node_access_records().
*/
function workbench_moderation_node_access_test_node_access_records($node) {
// Return nothing for unpublished nodes.
if (!$node->status) {
return array();
}
$grants[] = array(
'realm' => 'test_wm_realm',
'gid' => 1,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
return $grants;
}
name = Workbench Moderation Test
description = Test module for Workbench Moderation.
package = Workbench
core = 7.x
hidden = TRUE
<?php
/**
* @file
* Test module for Workbench Moderation.
*/
/**
* Implements hook_menu().
*/
function workbench_moderation_test_menu() {
return array(
'workbench_moderation_test/%node' => array(
'title' => 'Publish a node',
'page callback' => 'workbench_moderation_test_update_node',
'page arguments' => array(1),
'access arguments' => array('bypass workbench moderation'),
),
);
}
/**
* Implements hook_menu_alter().
*/
function workbench_moderation_test_menu_alter(&$items) {
// This menu altering replicates what restws_menu_alter() does.
// @see https://www.drupal.org/node/1838640
array_unshift($items['node/%node']['page arguments'], 'additional parameter', $items['node/%node']['page callback']);
$items['node/%node']['page callback'] = 'workbench_moderation_test_menu_node_callback';
}
function workbench_moderation_test_menu_node_callback($ignored_parameter, $page_callback) {
$args = func_get_args();
return call_user_func_array($page_callback, array_slice($args, 2));
}
/**
* 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'))));
}
/**
* Implements hook_workbench_moderation_transition().
*/
function workbench_moderation_test_workbench_moderation_transition($node, $previous_state, $new_state) {
foreach (array('title', 'nid', 'vid', 'status') as $key) {
workbench_moderation_test_set($key, $node->{$key});
}
workbench_moderation_test_set('previous_state', $previous_state);
workbench_moderation_test_set('new_state', $new_state);
}
/**
* Sets values for testing api hooks.
*
* @param $name
* The name of the value to store.
* @param $value
* The value to store.
*
* @return string | NULL
* The value of a requested named variable, if requested.
*/
function workbench_moderation_test_set($name, $value = NULL) {
$values = variable_get('workbench_moderation_test', array());
if (!is_null($value)) {
$values[$name] = $value;
variable_set('workbench_moderation_test', $values);
}
return isset($values[$name]) ? $values[$name] : NULL;
}
/**
* Gets values for testing api hooks.
*
* @param $name
* The name of the value to store.
*
* @return string | NULL
* The value of the variable, if set.
*/
function workbench_moderation_test_get($name) {
return workbench_moderation_test_set($name);
}
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @file
* API documentation file for Workbench Moderation.
*/
/**
* Allows modules to alter moderation access.
*
* @param &$access
* A boolean access declaration. Passed by reference.
* @param $op
* The operation being performed. May be 'view', 'update', 'delete',
* 'view revisions' or 'moderate'.
* @param $node
* The node being acted upon.
*/
function hook_workbench_moderation_access_alter(&$access, $op, $node) {
global $user;
// If the node is marked private, only let its owner moderate it.
if (empty($node->private) || $op != 'moderate') {
return;
}
if ($user->uid != $node->uid) {
$access = FALSE;
}
}
/**
* Allows modules to alter the list of possible next states for a node.
*
* @param &$states
* An array of possible state changes, or FALSE if none were found before