diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 5ce3ba9d9ca..54bfefaac94 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -47,16 +47,6 @@ const COMMENT_ANONYMOUS_MAY_CONTACT = 1; */ const COMMENT_ANONYMOUS_MUST_CONTACT = 2; -/** - * Comment form should be displayed on a separate page. - */ -const COMMENT_FORM_SEPARATE_PAGE = 0; - -/** - * Comment form should be shown below post or list of comments. - */ -const COMMENT_FORM_BELOW = 1; - /** * The time cutoff for comments marked as read for entity types other node. * @@ -257,151 +247,8 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array // @todo Make this configurable from the formatter see // http://drupal.org/node/1901110 - $view_mode = $context['view_mode']; - if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print') { - // Do not add any links if the node displayed for: - // - search indexing. - // - constructing a search result excerpt. - // - print. - return; - } - - $fields = \Drupal::service('comment.manager')->getFields('node'); - $current_user = \Drupal::currentUser(); - foreach ($fields as $field_name => $detail) { - // Skip fields that the node does not have. - if (!$node->hasField($field_name)) { - continue; - } - $links = array(); - $commenting_status = $node->get($field_name)->status; - if ($commenting_status) { - $field_definition = $node->getFieldDefinition($field_name); - // Node have commenting open or close. - if ($view_mode == 'rss') { - // Add a comments RSS element which is a URL to the comments of this node. - $options = array( - 'fragment' => 'comments', - 'absolute' => TRUE, - ); - $node->rss_elements[] = array( - 'key' => 'comments', - 'value' => $node->url('canonical', $options), - ); - } - elseif ($view_mode == 'teaser') { - // Teaser view: display the number of comments that have been posted, - // or a link to add new comments if the user has permission, the node - // is open to new comments, and there currently are none. - if ($current_user->hasPermission('access comments')) { - if (!empty($node->get($field_name)->comment_count)) { - $links['comment-comments'] = array( - 'title' => format_plural($node->get($field_name)->comment_count, '1 comment', '@count comments'), - 'attributes' => array('title' => t('Jump to the first comment of this posting.')), - 'fragment' => 'comments', - 'html' => TRUE, - ) + $node->urlInfo()->toArray(); - if (\Drupal::moduleHandler()->moduleExists('history')) { - $links['comment-new-comments'] = array( - 'title' => '', - 'href' => '', - 'attributes' => array( - 'class' => 'hidden', - 'title' => t('Jump to the first new comment of this posting.'), - 'data-history-node-last-comment-timestamp' => $node->get($field_name)->last_comment_timestamp, - 'data-history-node-field-name' => $field_name, - ), - 'html' => TRUE, - ); - } - } - } - // Provide a link to new comment form. - if ($commenting_status == CommentItemInterface::OPEN) { - $comment_form_location = $field_definition->getSetting('form_location'); - if ($current_user->hasPermission('post comments')) { - $links['comment-add'] = array( - 'title' => t('Add new comment'), - 'language' => $node->language(), - 'attributes' => array('title' => t('Add a new comment to this page.')), - 'fragment' => 'comment-form', - ); - if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['route_name'] = 'comment.reply'; - $links['comment-add']['route_parameters'] = array( - 'entity_type' => $node->getEntityTypeId(), - 'entity_id' => $node->id(), - 'field_name' => $field_name, - ); - } - else { - $links['comment-add'] += $node->urlInfo()->toArray(); - } - } - elseif (\Drupal::currentUser()->isAnonymous()) { - $links['comment-forbidden'] = array( - 'title' => \Drupal::service('comment.manager')->forbiddenMessage($node, $field_name), - 'html' => TRUE, - ); - } - } - } - else { - // Node in other view modes: add a "post comment" link if the user is - // allowed to post comments and if this node is allowing new comments. - if ($commenting_status == CommentItemInterface::OPEN) { - $comment_form_location = $field_definition->getSetting('form_location'); - if ($current_user->hasPermission('post comments')) { - // 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->get($field_name)->comment_count) && $current_user->hasPermission('access 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', - ); - if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['route_name'] = 'comment.reply'; - $links['comment-add']['route_parameters'] = array( - 'entity_type' => $node->getEntityTypeId(), - 'entity_id' => $node->id(), - 'field_name' => $field_name, - ); - } - else { - $links['comment-add'] += $node->urlInfo()->toArray(); - } - } - } - elseif (\Drupal::currentUser()->isAnonymous()) { - $links['comment-forbidden'] = array( - 'title' => \Drupal::service('comment.manager')->forbiddenMessage($node, $field_name), - 'html' => TRUE, - ); - } - } - } - } - - if (!empty($links)) { - $node_links['comment__' . $field_name] = array( - '#theme' => 'links__entity__comment__' . $field_name, - '#links' => $links, - '#attributes' => array('class' => array('links', 'inline')), - ); - if ($view_mode == 'teaser' && \Drupal::moduleHandler()->moduleExists('history') && \Drupal::currentUser()->isAuthenticated()) { - $node_links['comment__' . $field_name]['#attached']['library'][] = 'comment/drupal.node-new-comments-link'; - - // Embed the metadata for the "X new comments" link (if any) on this node. - $node_links['comment__' . $field_name]['#post_render_cache']['history_attach_timestamp'] = array( - array('node_id' => $node->id()), - ); - $node_links['comment__' . $field_name]['#post_render_cache']['Drupal\comment\CommentViewBuilder::attachNewCommentsLinkMetadata'] = array( - array('entity_type' => $node->getEntityTypeId(), 'entity_id' => $node->id(), 'field_name' => $field_name), - ); - } - } - } + $links = \Drupal::service('comment.link_builder')->buildCommentedEntityLinks($node, $context); + $node_links += $links; } /** diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml index 18405ac597c..b1208b15ee8 100644 --- a/core/modules/comment/comment.services.yml +++ b/core/modules/comment/comment.services.yml @@ -18,3 +18,7 @@ services: comment.post_render_cache: class: Drupal\comment\CommentPostRenderCache arguments: ['@entity.manager', '@entity.form_builder'] + + comment.link_builder: + class: Drupal\comment\CommentLinkBuilder + arguments: ['@current_user', '@comment.manager', '@module_handler', '@string_translation'] diff --git a/core/modules/comment/src/CommentLinkBuilder.php b/core/modules/comment/src/CommentLinkBuilder.php new file mode 100644 index 00000000000..6cdb6d66420 --- /dev/null +++ b/core/modules/comment/src/CommentLinkBuilder.php @@ -0,0 +1,223 @@ +currentUser = $current_user; + $this->commentManager = $comment_manager; + $this->moduleHandler = $module_handler; + $this->stringTranslation = $string_translation; + } + + /** + * {@inheritdoc} + */ + public function buildCommentedEntityLinks(ContentEntityInterface $entity, array &$context) { + $entity_links = array(); + $view_mode = $context['view_mode']; + if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print') { + // Do not add any links if the entity is displayed for: + // - search indexing. + // - constructing a search result excerpt. + // - print. + return array(); + } + + $fields = $this->commentManager->getFields($entity->getEntityTypeId()); + foreach ($fields as $field_name => $detail) { + // Skip fields that the entity does not have. + if (!$entity->hasField($field_name)) { + continue; + } + $links = array(); + $commenting_status = $entity->get($field_name)->status; + if ($commenting_status != CommentItemInterface::HIDDEN) { + // Entity has commenting status open or closed. + $field_definition = $entity->getFieldDefinition($field_name); + if ($view_mode == 'rss') { + // Add a comments RSS element which is a URL to the comments of this + // entity. + $options = array( + 'fragment' => 'comments', + 'absolute' => TRUE, + ); + $entity->rss_elements[] = array( + 'key' => 'comments', + 'value' => $entity->url('canonical', $options), + ); + } + elseif ($view_mode == 'teaser') { + // Teaser view: display the number of comments that have been posted, + // or a link to add new comments if the user has permission, the + // entity is open to new comments, and there currently are none. + if ($this->currentUser->hasPermission('access comments')) { + if (!empty($entity->get($field_name)->comment_count)) { + $links['comment-comments'] = array( + 'title' => $this->formatPlural($entity->get($field_name)->comment_count, '1 comment', '@count comments'), + 'attributes' => array('title' => $this->t('Jump to the first comment of this posting.')), + 'fragment' => 'comments', + ) + $entity->urlInfo()->toArray(); + if ($this->moduleHandler->moduleExists('history')) { + $links['comment-new-comments'] = array( + 'title' => '', + 'href' => '', + 'attributes' => array( + 'class' => 'hidden', + 'title' => $this->t('Jump to the first new comment of this posting.'), + 'data-history-node-last-comment-timestamp' => $entity->get($field_name)->last_comment_timestamp, + 'data-history-node-field-name' => $field_name, + ), + ); + } + } + } + // Provide a link to new comment form. + if ($commenting_status == CommentItemInterface::OPEN) { + $comment_form_location = $field_definition->getSetting('form_location'); + if ($this->currentUser->hasPermission('post comments')) { + $links['comment-add'] = array( + 'title' => $this->t('Add new comment'), + 'language' => $entity->language(), + 'attributes' => array('title' => $this->t('Add a new comment to this page.')), + 'fragment' => 'comment-form', + ); + if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { + $links['comment-add']['route_name'] = 'comment.reply'; + $links['comment-add']['route_parameters'] = array( + 'entity_type' => $entity->getEntityTypeId(), + 'entity_id' => $entity->id(), + 'field_name' => $field_name, + ); + } + else { + $links['comment-add'] += $entity->urlInfo()->toArray(); + } + } + elseif ($this->currentUser->isAnonymous()) { + $links['comment-forbidden'] = array( + 'title' => $this->commentManager->forbiddenMessage($entity, $field_name), + 'html' => TRUE, + ); + } + } + } + else { + // Entity in other view modes: add a "post comment" link if the user + // is allowed to post comments and if this entity is allowing new + // comments. + if ($commenting_status == CommentItemInterface::OPEN) { + $comment_form_location = $field_definition->getSetting('form_location'); + if ($this->currentUser->hasPermission('post comments')) { + // 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 == CommentItemInterface::FORM_SEPARATE_PAGE || (!empty($entity->get($field_name)->comment_count) && $this->currentUser->hasPermission('access comments'))) { + $links['comment-add'] = array( + 'title' => $this->t('Add new comment'), + 'attributes' => array('title' => $this->t('Share your thoughts and opinions related to this posting.')), + 'fragment' => 'comment-form', + ); + if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { + $links['comment-add']['route_name'] = 'comment.reply'; + $links['comment-add']['route_parameters'] = array( + 'entity_type' => $entity->getEntityTypeId(), + 'entity_id' => $entity->id(), + 'field_name' => $field_name, + ); + } + else { + $links['comment-add'] += $entity->urlInfo()->toArray(); + } + } + } + elseif ($this->currentUser->isAnonymous()) { + $links['comment-forbidden'] = array( + 'title' => $this->commentManager->forbiddenMessage($entity, $field_name), + 'html' => TRUE, + ); + } + } + } + } + + if (!empty($links)) { + $entity_links['comment__' . $field_name] = array( + '#theme' => 'links__entity__comment__' . $field_name, + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + if ($view_mode == 'teaser' && $this->moduleHandler->moduleExists('history') && $this->currentUser->isAuthenticated()) { + $entity_links['comment__' . $field_name]['#attached']['library'][] = 'comment/drupal.node-new-comments-link'; + + // Embed the metadata for the "X new comments" link (if any) on this + // entity. + $entity_links['comment__' . $field_name]['#post_render_cache']['history_attach_timestamp'] = array( + array('node_id' => $entity->id()), + ); + $entity_links['comment__' . $field_name]['#post_render_cache']['Drupal\comment\CommentViewBuilder::attachNewCommentsLinkMetadata'] = array( + array( + 'entity_type' => $entity->getEntityTypeId(), + 'entity_id' => $entity->id(), + 'field_name' => $field_name, + ), + ); + } + } + } + return $entity_links; + } + +} diff --git a/core/modules/comment/src/CommentLinkBuilderInterface.php b/core/modules/comment/src/CommentLinkBuilderInterface.php new file mode 100644 index 00000000000..995efcf903c --- /dev/null +++ b/core/modules/comment/src/CommentLinkBuilderInterface.php @@ -0,0 +1,32 @@ +authenticatedCanPostComments) { // We cannot use drupal_get_destination() because these links // sometimes appear on /node and taxonomy listing pages. - if ($entity->get($field_name)->getFieldDefinition()->getSetting('form_location') == COMMENT_FORM_SEPARATE_PAGE) { + if ($entity->get($field_name)->getFieldDefinition()->getSetting('form_location') == CommentItemInterface::FORM_SEPARATE_PAGE) { $destination = array('destination' => 'comment/reply/' . $entity->getEntityTypeId() . '/' . $entity->id() . '/' . $field_name . '#comment-form'); } else { diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php index e5ad723bd48..6234844bf44 100644 --- a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php @@ -182,7 +182,7 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP // Append comment form if the comments are open and the form is set to // display below the entity. Do not show the form for the print view mode. - if ($status == CommentItemInterface::OPEN && $comment_settings['form_location'] == COMMENT_FORM_BELOW && $this->viewMode != 'print') { + if ($status == CommentItemInterface::OPEN && $comment_settings['form_location'] == CommentItemInterface::FORM_BELOW && $this->viewMode != 'print') { // Only show the add comment form if the user has permission. if ($this->currentUser->hasPermission('post comments')) { // All users in the "anonymous" role can use the same form: it is fine diff --git a/core/modules/comment/src/Plugin/Field/FieldType/CommentItem.php b/core/modules/comment/src/Plugin/Field/FieldType/CommentItem.php index 57e41d8c464..b54564a9267 100644 --- a/core/modules/comment/src/Plugin/Field/FieldType/CommentItem.php +++ b/core/modules/comment/src/Plugin/Field/FieldType/CommentItem.php @@ -44,7 +44,7 @@ class CommentItem extends FieldItemBase implements CommentItemInterface { return array( 'default_mode' => CommentManagerInterface::COMMENT_MODE_THREADED, 'per_page' => 50, - 'form_location' => COMMENT_FORM_BELOW, + 'form_location' => CommentItemInterface::FORM_BELOW, 'anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT, 'preview' => DRUPAL_OPTIONAL, ) + parent::defaultInstanceSettings(); diff --git a/core/modules/comment/src/Plugin/Field/FieldType/CommentItemInterface.php b/core/modules/comment/src/Plugin/Field/FieldType/CommentItemInterface.php index f61d43d2812..4a67eb93645 100644 --- a/core/modules/comment/src/Plugin/Field/FieldType/CommentItemInterface.php +++ b/core/modules/comment/src/Plugin/Field/FieldType/CommentItemInterface.php @@ -27,4 +27,14 @@ interface CommentItemInterface { */ const OPEN = 2; + /** + * Comment form should be displayed on a separate page. + */ + const FORM_SEPARATE_PAGE = 0; + + /** + * Comment form should be shown below post or list of comments. + */ + const FORM_BELOW = 1; + } diff --git a/core/modules/comment/src/Tests/CommentLinksTest.php b/core/modules/comment/src/Tests/CommentLinksTest.php index fe4b5aac97d..bc4f60dff37 100644 --- a/core/modules/comment/src/Tests/CommentLinksTest.php +++ b/core/modules/comment/src/Tests/CommentLinksTest.php @@ -12,12 +12,26 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\comment\CommentInterface; /** - * Tests comment links based on environment configurations. + * Basic comment links tests to ensure markup present. * * @group comment */ class CommentLinksTest extends CommentTestBase { + /** + * Comment being tested. + * + * @var \Drupal\comment\CommentInterface + */ + protected $comment; + + /** + * Seen comments, array of comment IDs. + * + * @var array + */ + protected $seen = array(); + /** * Use the main node listing to test rendering on teasers. * @@ -39,9 +53,9 @@ class CommentLinksTest extends CommentTestBase { * possible conditions and tests the expected appearance of comment links in * each environment. */ - function testCommentLinks() { + public function testCommentLinks() { // Bartik theme alters comment links, so use a different theme. - theme_enable(array('stark')); + \Drupal::service('theme_handler')->enable(array('stark')); \Drupal::config('system.theme') ->set('default', 'stark') ->save(); @@ -51,265 +65,52 @@ class CommentLinksTest extends CommentTestBase { $roles = $this->web_user->getRoles(); entity_delete_multiple('user_role', array(reset($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(CommentItemInterface::OPEN, CommentItemInterface::CLOSED, CommentInterface::_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: CommentItemInterface::OPEN, CommentItemInterface::CLOSED or - * CommentItemInterface::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' => CommentItemInterface::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 = entity_create('comment', array( - 'cid' => NULL, - 'entity_id' => $this->node->id(), - 'entity_type' => 'node', - 'field_name' => 'comment', - 'pid' => 0, - 'uid' => 0, - 'status' => CommentInterface::PUBLISHED, - 'subject' => $this->randomMachineName(), - 'hostname' => '127.0.0.1', - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - 'comment_body' => array(LanguageInterface::LANGCODE_NOT_SPECIFIED => array($this->randomMachineName())), - )); - $comment->save(); - $this->comment = $comment; - } - else { - $cids = db_query("SELECT cid FROM {comment}")->fetchCol(); - entity_delete_multiple('comment', $cids); - unset($this->comment); - } - } + // Create a comment via CRUD API functionality, since + // $this->postComment() relies on actual user permissions. + $comment = entity_create('comment', array( + 'cid' => NULL, + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', + 'pid' => 0, + 'uid' => 0, + 'status' => CommentInterface::PUBLISHED, + 'subject' => $this->randomMachineName(), + 'hostname' => '127.0.0.1', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'comment_body' => array(LanguageInterface::LANGCODE_NOT_SPECIFIED => array($this->randomMachineName())), + )); + $comment->save(); + $this->comment = $comment; // Change comment settings. - $this->setCommentSettings('form_location', $info['form'], 'Set comment form location'); - $this->setCommentAnonymous($info['contact']); - if ($this->node->comment->status != $info['comments']) { - $this->node->comment = $info['comments']; - $this->node->save(); - } - - // Change user settings. - \Drupal::config('user.settings')->set('register', $info['user_register'])->save(); + $this->setCommentSettings('form_location', CommentItemInterface::FORM_BELOW, 'Set comment form location'); + $this->setCommentAnonymous(TRUE); + $this->node->comment = CommentItemInterface::OPEN; + $this->node->save(); // 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 \Drupal\simpletest\TestBase::error() - $t_form = array( - COMMENT_FORM_BELOW => 'below', - COMMENT_FORM_SEPARATE_PAGE => 'separate page', + $perms = array( + 'access comments' => 1, + 'post comments' => 1, + 'skip comment approval' => 1, + 'edit own comments' => 1, ); - $t_contact = array( - COMMENT_ANONYMOUS_MAY_CONTACT => 'optional', - COMMENT_ANONYMOUS_MAYNOT_CONTACT => 'disabled', - COMMENT_ANONYMOUS_MUST_CONTACT => 'required', - ); - $t_comments = array( - CommentItemInterface::OPEN => 'open', - CommentItemInterface::CLOSED => 'closed', - CommentItemInterface::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:
@verbose', 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. - * - * @param $info - * An associative array describing the environment to pass to - * setEnvironment(). - */ - function assertCommentLinks(array $info) { - $info = $this->setEnvironment($info); + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, $perms); $nid = $this->node->id(); + // Assert basic link is output, actual functionality is unit-tested in + // \Drupal\comment\Tests\CommentLinkBuilderTest. foreach (array('node', "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 - // \Drupal::service('comment.manager')->getCountNewComments() 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')); - } - // \Drupal::service('comment.manager')->getCountNewComments() 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 \Drupal\comment\CommentManagerInterface::forbiddenMessage() - if (!$this->loggedInUser) { - if ($this->web_user->hasPermission('post comments')) { - // The note depends on whether users are actually able to register. - if ($info['user_register'] != USER_REGISTER_ADMINISTRATORS_ONLY) { - $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'); - - // Verify that the "Add new comment" link points to the correct URL - // based on the comment form location configuration. - if ($info['form'] == COMMENT_FORM_SEPARATE_PAGE) { - $this->assertLinkByHref("comment/reply/node/$nid/comment#comment-form", 0, 'Comment form link destination is on a separate page.'); - $this->assertNoLinkByHref("node/$nid#comment-form"); - } - else { - $this->assertLinkByHref("node/$nid#comment-form", 0, 'Comment form link destination is on node.'); - $this->assertNoLinkByHref("comment/reply/node/$nid/comment#comment-form"); - } - } - - // 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), 'Comment form found below.'); - } - else { - $this->assertFalse(count($elements), 'Comment form not found below.'); - } - } + // In teaser view, a link containing the comment count is always + // expected. + if ($path == 'node') { + $this->assertLink(t('1 comment')); } + $this->assertLink('Add new comment'); } } diff --git a/core/modules/comment/src/Tests/CommentTestBase.php b/core/modules/comment/src/Tests/CommentTestBase.php index adc5196621e..05f139d6d3a 100644 --- a/core/modules/comment/src/Tests/CommentTestBase.php +++ b/core/modules/comment/src/Tests/CommentTestBase.php @@ -10,6 +10,7 @@ namespace Drupal\comment\Tests; use Drupal\comment\Entity\CommentType; use Drupal\comment\Entity\Comment; use Drupal\comment\CommentInterface; +use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\field\Entity\FieldInstanceConfig; use Drupal\simpletest\WebTestBase; @@ -265,7 +266,7 @@ abstract class CommentTestBase extends WebTestBase { * Defaults to 'comment'. */ public function setCommentForm($enabled, $field_name = 'comment') { - $this->setCommentSettings('form_location', ($enabled ? COMMENT_FORM_BELOW : COMMENT_FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.', $field_name); + $this->setCommentSettings('form_location', ($enabled ? CommentItemInterface::FORM_BELOW : CommentItemInterface::FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.', $field_name); } /** diff --git a/core/modules/comment/tests/src/CommentLinkBuilderTest.php b/core/modules/comment/tests/src/CommentLinkBuilderTest.php new file mode 100644 index 00000000000..66ff397a69e --- /dev/null +++ b/core/modules/comment/tests/src/CommentLinkBuilderTest.php @@ -0,0 +1,333 @@ +commentManager = $this->getMock('\Drupal\comment\CommentManagerInterface'); + $this->stringTranslation = $this->getStringTranslationStub(); + $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'); + $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountProxyInterface'); + $this->commentLinkBuilder = new CommentLinkBuilder($this->currentUser, $this->commentManager, $this->moduleHandler, $this->stringTranslation); + $this->commentManager->expects($this->any()) + ->method('getFields') + ->with('node') + ->willReturn(array( + 'comment' => array(), + )); + $this->commentManager->expects($this->any()) + ->method('forbiddenMessage') + ->willReturn("Can't let you do that Dave."); + $this->stringTranslation->expects($this->any()) + ->method('formatPlural') + ->willReturnArgument(1); + } + + /** + * Test the buildCommentedEntityLinks method. + * + * @param \Drupal\node\NodeInterface|\PHPUnit_Framework_MockObject_MockObject $node + * Mock node. + * @param array $context + * Context for the links. + * @param bool $has_access_comments + * TRUE if the user has 'access comments' permission. + * @param bool $history_exists + * TRUE if the history module exists. + * @param bool $has_post_comments + * TRUE if the use has 'post comments' permission. + * @param bool $is_anonymous + * TRUE if the user is anonymous. + * @param array $expected + * Array of expected links keyed by link ID. Can be either string (link + * title) or array of link properties. + * + * @dataProvider getLinkCombinations + * + * @covers ::buildCommentedEntityLinks() + */ + public function testCommentLinkBuilder(NodeInterface $node, $context, $has_access_comments, $history_exists, $has_post_comments, $is_anonymous, $expected) { + $this->moduleHandler->expects($this->any()) + ->method('moduleExists') + ->with('history') + ->willReturn($history_exists); + $this->currentUser->expects($this->any()) + ->method('hasPermission') + ->willReturnMap(array( + array('access comments', $has_access_comments), + array('post comments', $has_post_comments), + )); + $this->currentUser->expects($this->any()) + ->method('isAuthenticated') + ->willReturn(!$is_anonymous); + $this->currentUser->expects($this->any()) + ->method('isAnonymous') + ->willReturn($is_anonymous); + $links = $this->commentLinkBuilder->buildCommentedEntityLinks($node, $context); + if (!empty($expected)) { + if (!empty($links)) { + foreach ($expected as $link => $detail) { + if (is_array($detail)) { + // Array of link attributes. + foreach ($detail as $key => $value) { + $this->assertEquals($links['comment__comment']['#links'][$link][$key], $value); + } + } + else { + // Just the title. + $this->assertEquals($links['comment__comment']['#links'][$link]['title'], $detail); + } + } + } + else { + $this->fail('Expected links but found none.'); + } + } + else { + $this->assertSame($links, $expected); + } + if ($context['view_mode'] == 'rss' && $node->get('comment')->status) { + $found = FALSE; + if ($node->get('comment')->status) { + foreach ($node->rss_elements as $element) { + if ($element['key'] == 'comments') { + $found = TRUE; + break; + } + } + } + $this->assertTrue($found); + } + } + + /** + * Data provider for ::testCommentLinkBuilder. + */ + public function getLinkCombinations() { + $cases = array(); + // No links should be created if the entity doesn't have the field. + $cases[] = array( + $this->getMockNode(FALSE, CommentItemInterface::OPEN, CommentItemInterface::FORM_BELOW, 1), + array('view_mode' => 'teaser'), + TRUE, + TRUE, + TRUE, + TRUE, + array(), + ); + foreach (array('search_result', 'search_index', 'print') as $view_mode) { + // Nothing should be output in these view modes. + $cases[] = array( + $this->getMockNode(TRUE, CommentItemInterface::OPEN, CommentItemInterface::FORM_BELOW, 1), + array('view_mode' => $view_mode), + TRUE, + TRUE, + TRUE, + TRUE, + array(), + ); + } + // All other combinations. + $combinations = array( + 'is_anonymous' => array(FALSE, TRUE), + 'comment_count' => array(0, 1), + 'has_access_comments' => array(0, 1), + 'history_exists' => array(FALSE, TRUE), + 'has_post_comments' => array(0, 1), + 'form_location' => array(CommentItemInterface::FORM_BELOW, CommentItemInterface::FORM_SEPARATE_PAGE), + 'comments' => array( + CommentItemInterface::OPEN, + CommentItemInterface::CLOSED, + CommentItemInterface::HIDDEN, + ), + 'view_mode' => array( + 'teaser', 'rss', 'full', + ), + ); + $permutations = TestBase::generatePermutations($combinations); + foreach ($permutations as $combination) { + $case = array( + $this->getMockNode(TRUE, $combination['comments'], $combination['form_location'], $combination['comment_count']), + array('view_mode' => $combination['view_mode']), + $combination['has_access_comments'], + $combination['history_exists'], + $combination['has_post_comments'], + $combination['is_anonymous'], + ); + $expected = array(); + // When comments are enabled in teaser mode, and comments exist, and the + // user has access - we can output the comment count. + if ($combination['comments'] && $combination['view_mode'] == 'teaser' && $combination['comment_count'] && $combination['has_access_comments']) { + $expected['comment-comments'] = '1 comment'; + // And if history module exists, we can show a 'new comments' link. + if ($combination['history_exists']) { + $expected['comment-new-comments'] = ''; + } + } + // All view modes other than RSS. + if ($combination['view_mode'] != 'rss') { + // Where commenting is open. + if ($combination['comments'] == CommentItemInterface::OPEN) { + // And the user has post-comments permission. + if ($combination['has_post_comments']) { + // If the view mode is teaser, or the user can access comments and + // comments exist or the form is on a separate page. + if ($combination['view_mode'] == 'teaser' || ($combination['has_access_comments'] && $combination['comment_count']) || $combination['form_location'] == CommentItemInterface::FORM_SEPARATE_PAGE) { + // There should be a add comment link. + $expected['comment-add'] = array('title' => 'Add new comment'); + if ($combination['form_location'] == CommentItemInterface::FORM_BELOW) { + // On the same page. + $expected['comment-add']['route_name'] = 'node.view'; + } + else { + // On a separate page. + $expected['comment-add']['route_name'] = 'comment.reply'; + } + } + } + elseif ($combination['is_anonymous']) { + // Anonymous users get the forbidden message if the can't post + // comments. + $expected['comment-forbidden'] = "Can't let you do that Dave."; + } + } + } + + $case[] = $expected; + $cases[] = $case; + } + return $cases; + } + + /** + * Builds a mock node based on given scenario. + * + * @param bool $has_field + * TRUE if the node has the 'comment' field. + * @param int $comment_status + * One of CommentItemInterface::OPEN|HIDDEN|CLOSED + * @param int $form_location + * One of CommentItemInterface::FORM_BELOW|FORM_SEPARATE_PAGE + * @param int $comment_count + * Number of comments against the field. + * + * @return \Drupal\node\NodeInterface|\PHPUnit_Framework_MockObject_MockObject + * Mock node for testing. + */ + protected function getMockNode($has_field, $comment_status, $form_location, $comment_count) { + $node = $this->getMock('\Drupal\node\NodeInterface'); + $node->expects($this->once()) + ->method('hasField') + ->willReturn($has_field); + + if (empty($this->timestamp)) { + $this->timestamp = time(); + } + $field_item = (object) array( + 'status' => $comment_status, + 'comment_count' => $comment_count, + 'last_comment_timestamp' => $this->timestamp, + ); + $node->expects($this->any()) + ->method('get') + ->with('comment') + ->willReturn($field_item); + + $field_definition = $this->getMock('\Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->any()) + ->method('getSetting') + ->with('form_location') + ->willReturn($form_location); + $node->expects($this->any()) + ->method('getFieldDefinition') + ->with('comment') + ->willReturn($field_definition); + + $node->expects($this->any()) + ->method('language') + ->willReturn('und'); + + $node->expects($this->any()) + ->method('getEntityTypeId') + ->willReturn('node'); + + $node->expects($this->any()) + ->method('id') + ->willReturn(1); + + $url = $this->getMockBuilder('\Drupal\Core\Url') + ->disableOriginalConstructor() + ->getMock(); + $url->expects($this->any()) + ->method('toArray') + ->willReturn(array('route_name' => 'node.view')); + $node->expects($this->any()) + ->method('urlInfo') + ->willReturn($url); + $node->expects($this->any()) + ->method('url') + ->willReturn(array('route_name' => 'node.view')); + + return $node; + } + +}