diff options
-rw-r--r-- | modules/comment/comment.module | 86 | ||||
-rw-r--r-- | modules/comment/comment.pages.inc | 64 | ||||
-rw-r--r-- | modules/comment/comment.test | 299 | ||||
-rw-r--r-- | modules/simpletest/drupal_web_test_case.php | 49 |
4 files changed, 417 insertions, 81 deletions
diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 01f7f9443..78d35c039 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -611,15 +611,13 @@ function theme_comment_block() { function comment_node_view($node, $view_mode) { $links = array(); - if ($node->comment) { + if ($node->comment != COMMENT_NODE_HIDDEN) { if ($view_mode == 'rss') { - if ($node->comment != COMMENT_NODE_HIDDEN) { - // Add a comments RSS element which is a URL to the comments of this node. - $node->rss_elements[] = array( - 'key' => 'comments', - 'value' => url('node/' . $node->nid, array('fragment' => 'comments', 'absolute' => TRUE)) - ); - } + // Add a comments RSS element which is a URL to the comments of this node. + $node->rss_elements[] = array( + 'key' => 'comments', + 'value' => url('node/' . $node->nid, array('fragment' => 'comments', 'absolute' => TRUE)) + ); } elseif ($view_mode == 'teaser') { // Teaser view: display the number of comments that have been posted, @@ -634,9 +632,8 @@ function comment_node_view($node, $view_mode) { 'fragment' => 'comments', 'html' => TRUE, ); - - $new = comment_num_new($node->nid); - if ($new) { + // Show a link to the first new comment. + if ($new = comment_num_new($node->nid)) { $links['comment-new-comments'] = array( 'title' => format_plural($new, '1 new comment', '@count new comments'), 'href' => "node/$node->nid", @@ -647,21 +644,21 @@ function comment_node_view($node, $view_mode) { ); } } + } + if ($node->comment == COMMENT_NODE_OPEN) { + if (user_access('post comments')) { + $links['comment-add'] = array( + 'title' => t('Add new comment'), + 'href' => "comment/reply/$node->nid", + 'attributes' => array('title' => t('Add a new comment to this page.')), + 'fragment' => 'comment-form', + ); + } else { - if ($node->comment == COMMENT_NODE_OPEN) { - if (user_access('post comments')) { - $links['comment-add'] = array( - 'title' => t('Add new comment'), - 'href' => "comment/reply/$node->nid", - 'attributes' => array('title' => t('Add a new comment to this page.')), - 'fragment' => 'comment-form', - 'html' => TRUE, - ); - } - else { - $links['comment_forbidden']['title'] = theme('comment_post_forbidden', array('node' => $node)); - } - } + $links['comment_forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('node' => $node)), + 'html' => TRUE, + ); } } } @@ -671,30 +668,31 @@ function comment_node_view($node, $view_mode) { // But we don't want this link if we're building the node for search // indexing or constructing a search result excerpt. if ($node->comment == COMMENT_NODE_OPEN) { + $comment_form_location = variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW); if (user_access('post comments')) { - $links['comment-add'] = array( - 'title' => t('Add new comment'), - 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), - 'fragment' => 'comment-form', - 'html' => TRUE, - ); - if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['href'] = "comment/reply/$node->nid"; - } - else { - $links['comment-add']['href'] = "node/$node->nid"; + // Show the "post comment" link if the form is on another page, or + // if there are existing comments that the link will skip past. + if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($node->comment_count) && user_access('access comments'))) { + $links['comment-add'] = array( + 'title' => t('Add new comment'), + 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), + 'href' => "node/$node->nid", + 'fragment' => 'comment-form', + ); + if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { + $links['comment-add']['href'] = "comment/reply/$node->nid"; + } } } else { - $links['comment_forbidden']['title'] = theme('comment_post_forbidden', array('node' => $node)); + $links['comment_forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('node' => $node)), + 'html' => TRUE, + ); } } } - if (isset($links['comment_forbidden'])) { - $links['comment_forbidden']['html'] = TRUE; - } - $node->content['links']['comment'] = array( '#theme' => 'links__node__comment', '#links' => $links, @@ -705,7 +703,7 @@ function comment_node_view($node, $view_mode) { // page. We compare $node and $page_node to ensure that comments are not // appended to other nodes shown on the page, for example a node_reference // displayed in 'full' view mode within another node. - if ($node->comment && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview) && user_access('access comments')) { + if ($node->comment && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) { $node->content['comments'] = comment_node_page_additions($node); } } @@ -723,7 +721,7 @@ function comment_node_page_additions($node) { // Only attempt to render comments if the node has visible comments. // Unpublished comments are not included in $node->comment_count, so show // comments unconditionally if the user is an administrator. - if ($node->comment_count || user_access('administer comments')) { + if (($node->comment_count && user_access('access comments')) || user_access('administer comments')) { $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); if ($cids = comment_get_thread($node, $mode, $comments_per_page)) { @@ -2340,7 +2338,7 @@ function theme_comment_post_forbidden($variables) { if (!isset($authenticated_post_comments)) { // We only output a link if we are certain that users will get permission // to post comments by logging in. - $comment_roles = user_roles(TRUE, 'post comments') + user_roles(TRUE, 'skip comment approval'); + $comment_roles = user_roles(TRUE, 'post comments'); $authenticated_post_comments = isset($comment_roles[DRUPAL_AUTHENTICATED_RID]); } diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc index 03722e6dc..726795a41 100644 --- a/modules/comment/comment.pages.inc +++ b/modules/comment/comment.pages.inc @@ -33,26 +33,26 @@ function comment_reply($node, $pid = NULL) { $op = isset($_POST['op']) ? $_POST['op'] : ''; $build = array(); - if (user_access('access comments')) { - // The user is previewing a comment prior to submitting it. - if ($op == t('Preview')) { - if (user_access('post comments')) { - $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) array('pid' => $pid, 'nid' => $node->nid)); - } - else { - drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); - } + // The user is previewing a comment prior to submitting it. + if ($op == t('Preview')) { + if (user_access('post comments')) { + $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) array('pid' => $pid, 'nid' => $node->nid)); } else { - // $pid indicates that this is a reply to a comment. - if ($pid) { + drupal_set_message(t('You are not authorized to post comments.'), 'error'); + drupal_goto("node/$node->nid"); + } + } + else { + // $pid indicates that this is a reply to a comment. + if ($pid) { + if (user_access('access comments')) { // Load the comment whose cid = $pid $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.signature_format, u.picture, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid AND c.status = :status', array( ':cid' => $pid, ':status' => COMMENT_PUBLISHED, ))->fetchObject(); - if ( $comment ) { + if ($comment) { // If that comment exists, make sure that the current comment and the // parent comment both belong to the same parent node. if ($comment->nid != $node->nid) { @@ -71,29 +71,29 @@ function comment_reply($node, $pid = NULL) { drupal_goto("node/$node->nid"); } } - // This is the case where the comment is in response to a node. Display the node. - elseif (user_access('access content')) { - $build['comment_node'] = node_view($node); - } - - // Should we show the reply box? - if ($node->comment != COMMENT_NODE_OPEN) { - drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); - drupal_goto("node/$node->nid"); - } - elseif (user_access('post comments')) { - $edit = array('nid' => $node->nid, 'pid' => $pid); - $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) $edit); - } else { - drupal_set_message(t('You are not authorized to post comments.'), 'error'); + drupal_set_message(t('You are not authorized to view comments.'), 'error'); drupal_goto("node/$node->nid"); } } - } - else { - drupal_set_message(t('You are not authorized to view comments.'), 'error'); - drupal_goto("node/$node->nid"); + // This is the case where the comment is in response to a node. Display the node. + elseif (user_access('access content')) { + $build['comment_node'] = node_view($node); + } + + // Should we show the reply box? + if ($node->comment != COMMENT_NODE_OPEN) { + drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); + drupal_goto("node/$node->nid"); + } + elseif (user_access('post comments')) { + $edit = array('nid' => $node->nid, 'pid' => $pid); + $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) $edit); + } + else { + drupal_set_message(t('You are not authorized to post comments.'), 'error'); + drupal_goto("node/$node->nid"); + } } return $build; diff --git a/modules/comment/comment.test b/modules/comment/comment.test index dad144c77..a76d89c96 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -473,6 +473,280 @@ class CommentInterfaceTest extends CommentHelperCase { $this->assertEqual($node->last_comment_uid, 0, t('The value of node last_comment_uid is zero.')); $this->assertEqual($node->comment_count, 2, t('The value of node comment_count is 2.')); } + + /** + * Tests comment links. + * + * The output of comment links depends on various environment conditions: + * - Various Comment module configuration settings, user registration + * settings, and user access permissions. + * - Whether the user is authenticated or not, and whether any comments exist. + * + * To account for all possible cases, this test creates permutations of all + * possible conditions and tests the expected appearance of comment links in + * each environment. + */ + function testCommentLinks() { + // Bartik theme alters comment links, so use a different theme. + theme_enable(array('garland')); + variable_set('theme_default', 'garland'); + + // Remove additional user permissions from $this->web_user added by setUp(), + // since this test is limited to anonymous and authenticated roles only. + user_role_delete(key($this->web_user->roles)); + + // Matrix of possible environmental conditions and configuration settings. + // See setEnvironment() for details. + $conditions = array( + 'authenticated' => array(FALSE, TRUE), + 'comment count' => array(FALSE, TRUE), + 'access comments' => array(0, 1), + 'post comments' => array(0, 1), + 'form' => array(COMMENT_FORM_BELOW, COMMENT_FORM_SEPARATE_PAGE), + // USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL is irrelevant for this + // test; there is only a difference between open and closed registration. + 'user_register' => array(USER_REGISTER_VISITORS, USER_REGISTER_ADMINISTRATORS_ONLY), + // @todo Complete test coverage for: + //'comments' => array(COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, COMMENT_NODE_HIDDEN), + //// COMMENT_ANONYMOUS_MUST_CONTACT is irrelevant for this test. + //'contact ' => array(COMMENT_ANONYMOUS_MAY_CONTACT, COMMENT_ANONYMOUS_MAYNOT_CONTACT), + ); + + $environments = $this->generatePermutations($conditions); + foreach ($environments as $info) { + $this->assertCommentLinks($info); + } + } + + /** + * Re-configures the environment, module settings, and user permissions. + * + * @param $info + * An associative array describing the environment to setup: + * - Environment conditions: + * - authenticated: Boolean whether to test with $this->web_user or + * anonymous. + * - comment count: Boolean whether to test with a new/unread comment on + * $this->node or no comments. + * - Configuration settings: + * - form: COMMENT_FORM_BELOW or COMMENT_FORM_SEPARATE_PAGE. + * - user_register: USER_REGISTER_ADMINISTRATORS_ONLY or + * USER_REGISTER_VISITORS. + * - contact: COMMENT_ANONYMOUS_MAY_CONTACT or + * COMMENT_ANONYMOUS_MAYNOT_CONTACT. + * - comments: COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, or + * COMMENT_NODE_HIDDEN. + * - User permissions: + * These are granted or revoked for the user, according to the + * 'authenticated' flag above. Pass 0 or 1 as parameter values. See + * user_role_change_permissions(). + * - access comments + * - post comments + * - skip comment approval + * - edit own comments + */ + function setEnvironment(array $info) { + static $current; + + // Apply defaults to initial environment. + if (!isset($current)) { + $current = array( + 'authenticated' => FALSE, + 'comment count' => FALSE, + 'form' => COMMENT_FORM_BELOW, + 'user_register' => USER_REGISTER_VISITORS, + 'contact' => COMMENT_ANONYMOUS_MAY_CONTACT, + 'comments' => COMMENT_NODE_OPEN, + 'access comments' => 0, + 'post comments' => 0, + // Enabled by default, because it's irrelevant for this test. + 'skip comment approval' => 1, + 'edit own comments' => 0, + ); + } + // Complete new environment with current environment. + $info = array_merge($current, $info); + + // Change environment conditions. + if ($current['authenticated'] != $info['authenticated']) { + if ($this->loggedInUser) { + $this->drupalLogout(); + } + else { + $this->drupalLogin($this->web_user); + } + } + if ($current['comment count'] != $info['comment count']) { + if ($info['comment count']) { + // Create a comment via CRUD API functionality, since + // $this->postComment() relies on actual user permissions. + $comment = (object) array( + 'cid' => NULL, + 'nid' => $this->node->nid, + 'node_type' => $this->node->type, + 'pid' => 0, + 'uid' => 0, + 'status' => COMMENT_PUBLISHED, + 'subject' => $this->randomName(), + 'hostname' => ip_address(), + 'language' => LANGUAGE_NONE, + 'comment_body' => array(LANGUAGE_NONE => array($this->randomName())), + ); + comment_save($comment); + $this->comment = $comment; + + // comment_num_new() relies on node_last_viewed(), so ensure that no one + // has seen the node of this comment. + db_delete('history')->condition('nid', $this->node->nid)->execute(); + } + else { + $cids = db_query("SELECT cid FROM {comment}")->fetchCol(); + comment_delete_multiple($cids); + unset($this->comment); + } + } + + // Change comment settings. + variable_set('comment_form_location_' . $this->node->type, $info['form']); + variable_set('comment_anonymous_' . $this->node->type, $info['contact']); + if ($this->node->comment != $info['comments']) { + $this->node->comment = $info['comments']; + node_save($this->node); + } + + // Change user settings. + variable_set('user_register', $info['user_register']); + + // Change user permissions. + $rid = ($this->loggedInUser ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID); + $perms = array_intersect_key($info, array('access comments' => 1, 'post comments' => 1, 'skip comment approval' => 1, 'edit own comments' => 1)); + user_role_change_permissions($rid, $perms); + + // Output verbose debugging information. + // @see DrupalTestCase::error() + $t_form = array( + COMMENT_FORM_BELOW => 'below', + COMMENT_FORM_SEPARATE_PAGE => 'separate page', + ); + $t_contact = array( + COMMENT_ANONYMOUS_MAY_CONTACT => 'optional', + COMMENT_ANONYMOUS_MAYNOT_CONTACT => 'disabled', + COMMENT_ANONYMOUS_MUST_CONTACT => 'required', + ); + $t_comments = array( + COMMENT_NODE_OPEN => 'open', + COMMENT_NODE_CLOSED => 'closed', + COMMENT_NODE_HIDDEN => 'hidden', + ); + $verbose = $info; + $verbose['form'] = $t_form[$info['form']]; + $verbose['contact'] = $t_contact[$info['contact']]; + $verbose['comments'] = $t_comments[$info['comments']]; + $message = t('Changed environment:<pre>@verbose</pre>', array( + '@verbose' => var_export($verbose, TRUE), + )); + $this->assert('debug', $message, 'Debug'); + + // Update current environment. + $current = $info; + + return $info; + } + + /** + * Asserts that comment links appear according to the passed environment setup. + * + * @param $info + * An associative array describing the environment to pass to + * setEnvironment(). + */ + function assertCommentLinks(array $info) { + $info = $this->setEnvironment($info); + + $nid = $this->node->nid; + + foreach (array('', "node/$nid") as $path) { + $this->drupalGet($path); + + // User is allowed to view comments. + if ($info['access comments']) { + if ($path == '') { + // In teaser view, a link containing the comment count is always + // expected. + if ($info['comment count']) { + $this->assertLink(t('1 comment')); + + // For logged in users, a link containing the amount of new/unread + // comments is expected. + // See important note about comment_num_new() below. + if ($this->loggedInUser && isset($this->comment) && !isset($this->comment->seen)) { + $this->assertLink(t('1 new comment')); + $this->comment->seen = TRUE; + } + } + } + } + else { + $this->assertNoLink(t('1 comment')); + $this->assertNoLink(t('1 new comment')); + } + // comment_num_new() is based on node views, so comments are marked as + // read when a node is viewed, regardless of whether we have access to + // comments. + if ($path == "node/$nid" && $this->loggedInUser && isset($this->comment)) { + $this->comment->seen = TRUE; + } + + // User is not allowed to post comments. + if (!$info['post comments']) { + $this->assertNoLink('Add new comment'); + + // Anonymous users should see a note to log in or register in case + // authenticated users are allowed to post comments. + // @see theme_comment_post_forbidden() + if (!$this->loggedInUser) { + if (user_access('post comments', $this->web_user)) { + // The note depends on whether users are actually able to register. + if ($info['user_register']) { + $this->assertText('Log in or register to post comments'); + } + else { + $this->assertText('Log in to post comments'); + } + } + else { + $this->assertNoText('Log in or register to post comments'); + $this->assertNoText('Log in to post comments'); + } + } + } + // User is allowed to post comments. + else { + $this->assertNoText('Log in or register to post comments'); + + // "Add new comment" is always expected, except when there are no + // comments or if the user cannot see them. + if ($path == "node/$nid" && $info['form'] == COMMENT_FORM_BELOW && (!$info['comment count'] || !$info['access comments'])) { + $this->assertNoLink('Add new comment'); + } + else { + $this->assertLink('Add new comment'); + } + + // Also verify that the comment form appears according to the configured + // location. + if ($path == "node/$nid") { + $elements = $this->xpath('//form[@id=:id]', array(':id' => 'comment-form')); + if ($info['form'] == COMMENT_FORM_BELOW) { + $this->assertTrue(count($elements), t('Comment form found below.')); + } + else { + $this->assertFalse(count($elements), t('Comment form not found below.')); + } + } + } + } + } } /** @@ -634,11 +908,12 @@ class CommentAnonymous extends CommentHelperCase { $this->assertTrue($this->commentExists($anonymous_comment2), t('Anonymous comment with contact info (optional) found.')); // Ensure anonymous users cannot post in the name of registered users. + $langcode = LANGUAGE_NONE; $edit = array( 'name' => $this->admin_user->name, 'mail' => $this->randomName() . '@example.com', 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(), + "comment_body[$langcode][0][value]" => $this->randomName(), ); $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); $this->assertText(t('The name you used belongs to a registered user.')); @@ -698,14 +973,14 @@ class CommentAnonymous extends CommentHelperCase { // NOTE: if authenticated user has permission to post comments, then a // "Login or register to post comments" type link may be shown. $this->drupalGet('node/' . $this->node->nid); - $this->assertNoPattern('/<div ([^>]*?)id="comments"([^>]*?)>/', t('Comments were not displayed.')); + $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); $this->assertNoLink('Add new comment', t('Link to add comment was found.')); // Attempt to view node-comment form while disallowed. $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertText('You are not authorized to view comments', t('Error attempting to post comment.')); + $this->assertText('You are not authorized to post comments', t('Error attempting to post comment.')); $this->assertNoFieldByName('subject', '', t('Subject field not found.')); - $this->assertNoFieldByName('comment[value]', '', t('Comment field not found.')); + $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', t('Comment field not found.')); user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( 'access comments' => TRUE, @@ -713,9 +988,23 @@ class CommentAnonymous extends CommentHelperCase { 'skip comment approval' => FALSE, )); $this->drupalGet('node/' . $this->node->nid); - $this->assertPattern('/<div ([^>]*?)id="comments"([^>]*?)>/', t('Comments were displayed.')); + $this->assertPattern('@<h2[^>]*>Comments</h2>@', t('Comments were displayed.')); $this->assertLink('Log in', 1, t('Link to log in was found.')); $this->assertLink('register', 1, t('Link to register was found.')); + + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + )); + $this->drupalGet('node/' . $this->node->nid); + $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); + $this->assertFieldByName('subject', '', t('Subject field found.')); + $this->assertFieldByName("comment_body[$langcode][0][value]", '', t('Comment field found.')); + + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $anonymous_comment3->id); + $this->assertText('You are not authorized to view comments', t('Error attempting to post reply.')); + $this->assertNoText($author_name, t('Comment not displayed.')); } } diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 841451746..fa9917d77 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -578,6 +578,55 @@ abstract class DrupalTestCase { } return $str; } + + /** + * Converts a list of possible parameters into a stack of permutations. + * + * Takes a list of parameters containing possible values, and converts all of + * them into a list of items containing every possible permutation. + * + * Example: + * @code + * $parameters = array( + * 'one' => array(0, 1), + * 'two' => array(2, 3), + * ); + * $permutations = $this->permute($parameters); + * // Result: + * $permutations == array( + * array('one' => 0, 'two' => 2), + * array('one' => 1, 'two' => 2), + * array('one' => 0, 'two' => 3), + * array('one' => 1, 'two' => 3), + * ) + * @endcode + * + * @param $parameters + * An associative array of parameters, keyed by parameter name, and whose + * values are arrays of parameter values. + * + * @return + * A list of permutations, which is an array of arrays. Each inner array + * contains the full list of parameters that have been passed, but with a + * single value only. + */ + public static function generatePermutations($parameters) { + $all_permutations = array(array()); + foreach ($parameters as $parameter => $values) { + $new_permutations = array(); + // Iterate over all values of the parameter. + foreach ($values as $value) { + // Iterate over all existing permutations. + foreach ($all_permutations as $permutation) { + // Add the new parameter value to existing permutations. + $new_permutations[] = $permutation + array($parameter => $value); + } + } + // Replace the old permutations with the new permutations. + $all_permutations = $new_permutations; + } + return $all_permutations; + } } /** |