From 3c7447f86536980f12824b981a0e8d9debf3d1ca Mon Sep 17 00:00:00 2001 From: webchick Date: Wed, 28 Nov 2012 23:28:47 -0800 Subject: [PATCH] Issue #880940 by realityloop, xjm, Jody Lynn, tim.plunkett, smortimore, dagmar: Added view/revert/delete revisions per content type. --- .../Tests/NodeRevisionPermissionsTest.php | 122 ++++++++++---- .../Drupal/node/Tests/NodeRevisionsTest.php | 159 +++++++++++++++++- core/modules/node/node.install | 15 ++ core/modules/node/node.module | 40 +++-- core/modules/node/node.pages.inc | 7 +- .../lib/Drupal/rdf/Tests/RdfaMarkupTest.php | 2 +- .../Tests/Entity/EntityRevisionsTest.php | 6 +- .../lib/Drupal/views/Tests/UI/UITestBase.php | 2 +- .../views/Tests/Wizard/WizardTestBase.php | 2 +- 9 files changed, 299 insertions(+), 56 deletions(-) diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php index 7af3429283..53b9d94016 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php @@ -16,9 +16,16 @@ class NodeRevisionPermissionsTest extends NodeTestBase { // Map revision permission names to node revision access ops. protected $map = array( - 'view' => 'view revisions', - 'update' => 'revert revisions', - 'delete' => 'delete revisions', + 'view' => 'view all revisions', + 'update' => 'revert all revisions', + 'delete' => 'delete all revisions', + ); + + // Map revision permission names to node type revision access ops. + protected $type_map = array( + 'view' => 'view page revisions', + 'update' => 'revert page revisions', + 'delete' => 'delete page revisions', ); public static function getInfo() { @@ -32,19 +39,28 @@ public static function getInfo() { function setUp() { parent::setUp(); - // Create a node with several revisions. - $node = $this->drupalCreateNode(); - $this->node_revisions[] = $node; - - for ($i = 0; $i < 3; $i++) { - // Create a revision for the same nid and settings with a random log. - $revision = clone $node; - $revision->setNewRevision(); - $revision->log = $this->randomName(32); - node_save($revision); - $this->node_revisions[] = $revision; + $types = array('page', 'article'); + + foreach ($types as $type) { + // Create a node with several revisions. + $nodes[$type] = $this->drupalCreateNode(array('type' => $type)); + $this->node_revisions[$type][] = $nodes[$type]; + + for ($i = 0; $i < 3; $i++) { + // Create a revision for the same nid and settings with a random log. + $revision = clone $nodes[$type]; + $revision->revision = 1; + $revision->log = $this->randomName(32); + node_save($revision); + $this->node_revisions[$type][] = $revision; + } } + } + /** + * Tests general revision access permissions. + */ + function testNodeRevisionAccessAnyType() { // Create three users, one with each revision permission. foreach ($this->map as $op => $permission) { // Create the user. @@ -64,18 +80,14 @@ function setUp() { $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes')); $admin_account->is_admin = TRUE; $this->accounts['admin'] = $admin_account; + $accounts['admin'] = $admin_account; // Create a normal account (returns FALSE for all revision permissions). $normal_account = $this->drupalCreateUser(); $normal_account->op = FALSE; $this->accounts[] = $normal_account; - } - - /** - * Tests the _node_revision_access() function. - */ - function testNodeRevisionAccess() { - $revision = node_revision_load($this->node_revisions[1]->vid); + $accounts[] = $normal_account; + $revision = $this->node_revisions['page'][1]; $parameters = array( 'op' => array_keys($this->map), @@ -83,24 +95,70 @@ function testNodeRevisionAccess() { ); $permutations = $this->generatePermutations($parameters); + foreach ($permutations as $case) { - if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) { - $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted."); - } - else { - $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted."); + // Skip this test if there are no revisions for the node. + if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $revision->nid))->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { + if (!empty($case['account']->is_admin) || user_access($this->map[$case['op']], $case['account'])) { + $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted."); + } + else { + $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted."); + } } } // Test that access is FALSE for a node administrator with an invalid $node // or $op parameters. - $admin_account = $this->accounts['admin']; + $admin_account = $accounts['admin']; $this->assertFalse(_node_revision_access($revision, 'invalid-op', $admin_account), '_node_revision_access() returns FALSE with an invalid op.'); + } + + /** + * Tests revision access permissions for a specific content type. + */ + function testNodeRevisionAccessPerType() { + // Create three users, one with each revision permission. + foreach ($this->type_map as $op => $permission) { + // Create the user. + $account = $this->drupalCreateUser( + array( + 'access content', + 'edit any page content', + 'delete any page content', + $permission, + ) + ); + $account->op = $op; + $accounts[] = $account; + } - // Test that the $account parameter defaults to the "logged in" user. - $original_user = $GLOBALS['user']; - $GLOBALS['user'] = $admin_account; - $this->assertTrue(_node_revision_access($revision, 'view'), '_node_revision_access() returns TRUE when used with global user.'); - $GLOBALS['user'] = $original_user; + $parameters = array( + 'op' => array_keys($this->type_map), + 'account' => $accounts, + ); + + // Test that the accounts have access to the correspoding page revision permissions. + $revision = $this->node_revisions['page'][1]; + + $permutations = $this->generatePermutations($parameters); + foreach ($permutations as $case) { + // Skip this test if there are no revisions for the node. + if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $revision->nid))->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { + if (!empty($case['account']->is_admin) || user_access($this->type_map[$case['op']], $case['account'])) { + $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} granted."); + } + else { + $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} not granted."); + } + } + } + + // Test that the accounts have no access to the article revisions. + $revision = $this->node_revisions['article'][1]; + + foreach ($permutations as $case) { + $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->type_map[$case['op']]} did not grant revision permission for articles."); + } } } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php index 877c003f50..2ce2abbed4 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -16,8 +16,8 @@ class NodeRevisionsTest extends NodeTestBase { public static function getInfo() { return array( - 'name' => 'Node revisions', - 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions.', + 'name' => 'Node revisions by type', + 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions for users with access for this content type.', 'group' => 'Node', ); } @@ -25,9 +25,17 @@ public static function getInfo() { function setUp() { parent::setUp(); - // Create and login user. - $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content', - 'delete revisions', 'delete any page content', 'administer nodes')); + // Create and log in user. + $web_user = $this->drupalCreateUser( + array( + 'view page revisions', + 'revert page revisions', + 'delete page revisions', + 'edit any page content', + 'delete any page content' + ) + ); + $this->drupalLogin($web_user); // Create initial node. @@ -190,3 +198,144 @@ function testNodeRevisionWithoutLogMessage() { $this->assertTrue(empty($node_revision->log), 'After a new node revision is saved with an empty log message, the log message for the node is empty.'); } } + +/** + * Tests actions against revisions for user with access to all revisions. + */ +class NodeRevisionsAllTestCase extends NodeTestBase { + protected $nodes; + protected $logs; + protected $profile = "standard"; + + public static function getInfo() { + return array( + 'name' => 'Node revisions all', + 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions for user with access to all.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + // Create and log in user. + $web_user = $this->drupalCreateUser( + array( + 'view page revisions', + 'revert page revisions', + 'delete page revisions', + 'edit any page content', + 'delete any page content' + ) + ); + $this->drupalLogin($web_user); + + // Create an initial node. + $node = $this->drupalCreateNode(); + + $settings = get_object_vars($node); + $settings['revision'] = 1; + + $nodes = array(); + $logs = array(); + + // Get the original node. + $nodes[] = $node; + + // Create three revisions. + $revision_count = 3; + for ($i = 0; $i < $revision_count; $i++) { + $logs[] = $settings['log'] = $this->randomName(32); + + // Create revision with a random title and body and update variables. + $this->drupalCreateNode($settings); + $node = node_load($node->nid); // Make sure we get revision information. + $settings = get_object_vars($node); + $nodes[] = $node; + } + + $this->nodes = $nodes; + $this->logs = $logs; + } + + /** + * Checks node revision operations. + */ + function testRevisions() { + $nodes = $this->nodes; + $logs = $this->logs; + + // Get last node for simple checks. + $node = $nodes[3]; + + // Create and login user. + $content_admin = $this->drupalCreateUser( + array( + 'view all revisions', + 'revert all revisions', + 'delete all revisions', + 'edit any page content', + 'delete any page content' + ) + ); + $this->drupalLogin($content_admin); + + // Confirm the correct revision text appears on "view revisions" page. + $this->drupalGet("node/$node->nid/revisions/$node->vid/view"); + $this->assertText($node->body[LANGUAGE_NOT_SPECIFIED][0]['value'], t('Correct text displays for version.')); + + // Confirm the correct log message appears on "revisions overview" page. + $this->drupalGet("node/$node->nid/revisions"); + foreach ($logs as $log) { + $this->assertText($log, t('Log message found.')); + } + + // Confirm that this is the current revision. + $this->assertTrue($node->isCurrentRevision(), 'Third node revision is the current one.'); + + // Confirm that revisions revert properly. + $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert')); + $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', + array( + '@type' => 'Basic page', + '%title' => $nodes[1]->title, + '%revision-date' => format_date($nodes[1]->revision_timestamp) + )), + 'Revision reverted.'); + $reverted_node = node_load($node->nid); + $this->assertTrue(($nodes[1]->body[LANGUAGE_NOT_SPECIFIED][0]['value'] == $reverted_node->body[LANGUAGE_NOT_SPECIFIED][0]['value']), t('Node reverted correctly.')); + + // Confirm that this is not the current version. + $node = node_load($node->nid, $node->vid); + $this->assertFalse($node->isCurrentRevision(), 'Third node revision is not the current one.'); + + // Confirm revisions delete properly. + $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); + $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', + array( + '%revision-date' => format_date($nodes[1]->revision_timestamp), + '@type' => 'Basic page', + '%title' => $nodes[1]->title, + )), + 'Revision deleted.'); + $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', + array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, + 'Revision not found.'); + + // Set the revision timestamp to an older date to make sure that the + // confirmation message correctly displays the stored revision date. + $old_revision_date = REQUEST_TIME - 86400; + db_update('node_revision') + ->condition('vid', $nodes[2]->vid) + ->fields(array( + 'timestamp' => $old_revision_date, + )) + ->execute(); + $this->drupalPost("node/$node->nid/revisions/{$nodes[2]->vid}/revert", array(), t('Revert')); + $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', array( + '@type' => 'Basic page', + '%title' => $nodes[2]->title, + '%revision-date' => format_date($old_revision_date), + ))); + } +} diff --git a/core/modules/node/node.install b/core/modules/node/node.install index c6fd7e6819..697d3a7c43 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -718,6 +718,21 @@ function node_update_8012() { } +/** + * Renames global revision permissions to use the word 'all'. + */ +function node_update_8013() { + $actions = array('view', 'delete', 'revert'); + + foreach ($actions as $action) { + db_update('role_permission') + ->fields(array('permission' => $action . ' all revisions')) + ->condition('permission', $action . ' revisions') + ->condition('module', 'node') + ->execute(); + } +} + /** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 3b5f21a3fe..ea2b4ce077 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1219,14 +1219,16 @@ function node_permission() { 'view own unpublished content' => array( 'title' => t('View own unpublished content'), ), - 'view revisions' => array( - 'title' => t('View content revisions'), + 'view all revisions' => array( + 'title' => t('View all revisions'), ), - 'revert revisions' => array( - 'title' => t('Revert content revisions'), + 'revert all revisions' => array( + 'title' => t('Revert all revisions'), + 'description' => t('Role requires permission view revisions and edit rights for nodes in question, or administer nodes.'), ), - 'delete revisions' => array( - 'title' => t('Delete content revisions'), + 'delete all revisions' => array( + 'title' => t('Delete all revisions'), + 'description' => t('Role requires permission to view revisions and delete rights for nodes in question, or administer nodes.'), ), ); @@ -1531,12 +1533,17 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL, $langc $access = &drupal_static(__FUNCTION__, array()); $map = array( - 'view' => 'view revisions', - 'update' => 'revert revisions', - 'delete' => 'delete revisions', + 'view' => 'view all revisions', + 'update' => 'revert all revisions', + 'delete' => 'delete all revisions', + ); + $type_map = array( + 'view' => "view $node->type revisions", + 'update' => "revert $node->type revisions", + 'delete' => "delete $node->type revisions", ); - if (!$node || !isset($map[$op])) { + if (!$node || !isset($map[$op]) || !isset($type_map[$op])) { // If there was no node to check against, or the $op was not one of the // supported ones, we return access denied. return FALSE; @@ -1557,7 +1564,7 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL, $langc if (!isset($access[$cid])) { // Perform basic permission checks first. - if (!user_access($map[$op], $account) && !user_access('administer nodes', $account)) { + if (!user_access($map[$op], $account) && !user_access($type_map[$op], $account) && !user_access('administer nodes', $account)) { return $access[$cid] = FALSE; } @@ -2861,6 +2868,17 @@ function node_list_permissions($type) { "delete any $type->type content" => array( 'title' => t('%type_name: Delete any content', array('%type_name' => $type->name)), ), + "view $type->type revisions" => array( + 'title' => t('%type_name: View revisions', array('%type_name' => $type->name)), + ), + "revert $type->type revisions" => array( + 'title' => t('%type_name: Revert revisions', array('%type_name' => $type->name)), + 'description' => t('Role requires permission view revisions and edit rights for nodes in question, or administer nodes.'), + ), + "delete $type->type revisions" => array( + 'title' => t('%type_name: Delete revisions', array('%type_name' => $type->name)), + 'description' => t('Role requires permission to view revisions and delete rights for nodes in question, or administer nodes.'), + ), ); return $perms; } diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 71c72b8ddf..0a18ec10d2 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -255,16 +255,19 @@ function node_revision_overview($node) { $revisions = node_revision_list($node); $rows = array(); + $type = $node->type; + $revert_permission = FALSE; - if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { + if ((user_access("revert $type revisions") || user_access('revert all revisions') || user_access('administer nodes')) && node_access('update', $node)) { $revert_permission = TRUE; } $delete_permission = FALSE; - if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) { + if ((user_access("delete $type revisions") || user_access('delete all revisions') || user_access('administer nodes')) && node_access('delete', $node)) { $delete_permission = TRUE; } foreach ($revisions as $revision) { $row = array(); + $type = $node->type; if ($revision->current_vid > 0) { $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'short'), "node/$node->nid"), '!username' => theme('username', array('account' => $revision)))) . (($revision->log != '') ? '

' . filter_xss($revision->log) . '

' : ''), diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php index 00e4259337..db24097ed2 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php @@ -99,7 +99,7 @@ function testDrupalRdfaAttributes() { */ function testAttributesInMarkupFile() { // Create a user to post the image. - $admin_user = $this->drupalCreateUser(array('edit own article content', 'revert revisions', 'administer content types')); + $admin_user = $this->drupalCreateUser(array('edit own article content', 'revert article revisions', 'administer content types')); $this->drupalLogin($admin_user); $langcode = LANGUAGE_NOT_SPECIFIED; diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityRevisionsTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityRevisionsTest.php index c94972dbce..6783494418 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityRevisionsTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityRevisionsTest.php @@ -34,9 +34,9 @@ public function setUp() { // Create and login user. $this->web_user = $this->drupalCreateUser(array( - 'view revisions', - 'revert revisions', - 'delete revisions', + 'view all revisions', + 'revert all revisions', + 'delete all revisions', 'administer entity_test content', )); $this->drupalLogin($this->web_user); diff --git a/core/modules/views/lib/Drupal/views/Tests/UI/UITestBase.php b/core/modules/views/lib/Drupal/views/Tests/UI/UITestBase.php index eaf1d7d28f..334dfadfd9 100644 --- a/core/modules/views/lib/Drupal/views/Tests/UI/UITestBase.php +++ b/core/modules/views/lib/Drupal/views/Tests/UI/UITestBase.php @@ -28,7 +28,7 @@ protected function setUp() { $this->adminUser = $this->drupalCreateUser(array('administer views')); - $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions')); + $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions')); $this->drupalLogin($views_admin); } diff --git a/core/modules/views/lib/Drupal/views/Tests/Wizard/WizardTestBase.php b/core/modules/views/lib/Drupal/views/Tests/Wizard/WizardTestBase.php index acf02eb0d3..0abb524be6 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Wizard/WizardTestBase.php +++ b/core/modules/views/lib/Drupal/views/Tests/Wizard/WizardTestBase.php @@ -25,7 +25,7 @@ function setUp() { parent::setUp(); // Create and log in a user with administer views permission. - $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions')); + $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions')); $this->drupalLogin($views_admin); } -- GitLab