Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,138 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentAccessControlHandler.
*/
namespace Drupal\comment;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the comment entity type.
*
* @see \Drupal\comment\Entity\Comment
*/
class CommentAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
/** @var \Drupal\comment\CommentInterface|\Drupal\user\EntityOwnerInterface $entity */
$comment_admin = $account->hasPermission('administer comments');
if ($operation == 'approve') {
return AccessResult::allowedIf($comment_admin && !$entity->isPublished())
->cachePerPermissions()
->cacheUntilEntityChanges($entity);
}
if ($comment_admin) {
$access = AccessResult::allowed()->cachePerPermissions();
return ($operation != 'view') ? $access : $access->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
}
switch ($operation) {
case 'view':
return AccessResult::allowedIf($account->hasPermission('access comments') && $entity->isPublished())->cachePerPermissions()->cacheUntilEntityChanges($entity)
->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
case 'update':
return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($entity);
default:
// No opinion.
return AccessResult::neutral()->cachePerPermissions();
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermission($account, 'post comments');
}
/**
* {@inheritdoc}
*/
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
if ($operation == 'edit') {
// Only users with the "administer comments" permission can edit
// administrative fields.
$administrative_fields = array(
'uid',
'status',
'created',
'date',
);
if (in_array($field_definition->getName(), $administrative_fields, TRUE)) {
return AccessResult::allowedIfHasPermission($account, 'administer comments');
}
// No user can change read-only fields.
$read_only_fields = array(
'hostname',
'uuid',
'cid',
'thread',
'comment_type',
'pid',
'entity_id',
'entity_type',
'field_name',
);
if (in_array($field_definition->getName(), $read_only_fields, TRUE)) {
return AccessResult::forbidden();
}
// If the field is configured to accept anonymous contact details - admins
// can edit name, homepage and mail. Anonymous users can also fill in the
// fields on comment creation.
if (in_array($field_definition->getName(), ['name', 'mail', 'homepage'], TRUE)) {
if (!$items) {
// We cannot make a decision about access to edit these fields if we
// don't have any items and therefore cannot determine the Comment
// entity. In this case we err on the side of caution and prevent edit
// access.
return AccessResult::forbidden();
}
/** @var \Drupal\comment\CommentInterface $entity */
$entity = $items->getEntity();
$commented_entity = $entity->getCommentedEntity();
$anonymous_contact = $commented_entity->get($entity->getFieldName())->getFieldDefinition()->getSetting('anonymous');
$admin_access = AccessResult::allowedIfHasPermission($account, 'administer comments');
$anonymous_access = AccessResult::allowedIf($entity->isNew() && $account->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT && $account->hasPermission('post comments'))
->cachePerPermissions()
->cacheUntilEntityChanges($entity)
->cacheUntilEntityChanges($field_definition->getConfig($commented_entity->bundle()))
->cacheUntilEntityChanges($commented_entity);
return $admin_access->orIf($anonymous_access);
}
}
if ($operation == 'view') {
$entity = $items ? $items->getEntity() : NULL;
// Admins can view any fields except hostname, other users need both the
// "access comments" permission and for the comment to be published. The
// mail field is hidden from non-admins.
$admin_access = AccessResult::allowedIf($account->hasPermission('administer comments') && $field_definition->getName() != 'hostname')
->cachePerPermissions();
$anonymous_access = AccessResult::allowedIf($account->hasPermission('access comments') && (!$entity || $entity->isPublished()) && !in_array($field_definition->getName(), array('mail', 'hostname'), TRUE))
->cachePerPermissions();
if ($entity) {
$anonymous_access->cacheUntilEntityChanges($entity);
}
return $admin_access->orIf($anonymous_access);
}
return parent::checkFieldAccess($operation, $field_definition, $account, $items);
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentBreadcrumbBuilder.
*/
namespace Drupal\comment;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Class to define the comment breadcrumb builder.
*/
class CommentBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait;
/**
* The comment storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* Constructs the CommentBreadcrumbBuilder.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->storage = $entity_manager->getStorage('comment');
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
return $route_match->getRouteName() == 'comment.reply' && $route_match->getParameter('entity');
}
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
$breadcrumb = [Link::createFromRoute($this->t('Home'), '<front>')];
$entity = $route_match->getParameter('entity');
$breadcrumb[] = new Link($entity->label(), $entity->urlInfo());
if (($pid = $route_match->getParameter('pid')) && ($comment = $this->storage->load($pid))) {
/** @var \Drupal\comment\CommentInterface $comment */
// Display link to parent comment.
// @todo Clean-up permalink in https://www.drupal.org/node/2198041
$breadcrumb[] = new Link($comment->getSubject(), $comment->urlInfo());
}
return $breadcrumb;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentFieldItemList.
*/
namespace Drupal\comment;
use Drupal\Core\Field\FieldItemList;
/**
* Defines a item list class for comment fields.
*/
class CommentFieldItemList extends FieldItemList {
/**
* {@inheritdoc}
*/
public function get($index) {
// The Field API only applies the "field default value" to newly created
// entities. In the specific case of the "comment status", though, we need
// this default value to be also applied for existing entities created
// before the comment field was added, which have no value stored for the
// field.
if ($index == 0 && empty($this->list)) {
$field_default_value = $this->getFieldDefinition()->getDefaultValue($this->getEntity());
return $this->appendItem($field_default_value[0]);
}
return parent::get($index);
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset) {
// For consistency with what happens in get(), we force offsetExists() to
// be TRUE for delta 0.
if ($offset === 0) {
return TRUE;
}
return parent::offsetExists($offset);
}
}

View file

@ -0,0 +1,400 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentForm.
*/
namespace Drupal\comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base for controller for comment forms.
*/
class CommentForm extends ContentEntityForm {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('current_user'),
$container->get('renderer')
);
}
/**
* Constructs a new CommentForm.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user, RendererInterface $renderer) {
parent::__construct($entity_manager);
$this->currentUser = $current_user;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
protected function init(FormStateInterface $form_state) {
$comment = $this->entity;
// Make the comment inherit the current content language unless specifically
// set.
if ($comment->isNew()) {
$language_content = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
$comment->langcode->value = $language_content->getId();
}
parent::init($form_state);
}
/**
* Overrides Drupal\Core\Entity\EntityForm::form().
*/
public function form(array $form, FormStateInterface $form_state) {
/** @var \Drupal\comment\CommentInterface $comment */
$comment = $this->entity;
$entity = $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->load($comment->getCommentedEntityId());
$field_name = $comment->getFieldName();
$field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
$config = $this->config('user.settings');
// Use #comment-form as unique jump target, regardless of entity type.
$form['#id'] = Html::getUniqueId('comment_form');
$form['#theme'] = array('comment_form__' . $entity->getEntityTypeId() . '__' . $entity->bundle() . '__' . $field_name, 'comment_form');
$anonymous_contact = $field_definition->getSetting('anonymous');
$is_admin = $comment->id() && $this->currentUser->hasPermission('administer comments');
if (!$this->currentUser->isAuthenticated() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
$form['#attached']['library'][] = 'core/drupal.form';
$form['#attributes']['data-user-info-from-browser'] = TRUE;
}
// Vary per role, because we check a permission above and attach an asset
// library only for authenticated users.
$form['#cache']['contexts'][] = 'user.roles';
// If not replying to a comment, use our dedicated page callback for new
// Comments on entities.
if (!$comment->id() && !$comment->hasParentComment()) {
$form['#action'] = $this->url('comment.reply', array('entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name));
}
$comment_preview = $form_state->get('comment_preview');
if (isset($comment_preview)) {
$form += $comment_preview;
}
$form['author'] = array();
// Display author information in a details element for comment moderators.
if ($is_admin) {
$form['author'] += array(
'#type' => 'details',
'#title' => $this->t('Administration'),
);
}
// Prepare default values for form elements.
if ($is_admin) {
$author = $comment->getAuthorName();
$status = $comment->getStatus();
if (empty($comment_preview)) {
$form['#title'] = $this->t('Edit comment %title', array(
'%title' => $comment->getSubject(),
));
}
}
else {
if ($this->currentUser->isAuthenticated()) {
$author = $this->currentUser->getUsername();
}
else {
$author = ($comment->getAuthorName() ? $comment->getAuthorName() : '');
}
$status = ($this->currentUser->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED);
}
$date = '';
if ($comment->id()) {
$date = !empty($comment->date) ? $comment->date : DrupalDateTime::createFromTimestamp($comment->getCreatedTime());
}
// Add the author name field depending on the current user.
$form['author']['name'] = array(
'#type' => 'textfield',
'#title' => $this->t('Your name'),
'#default_value' => $author,
'#required' => ($this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
'#maxlength' => 60,
'#size' => 30,
);
if ($is_admin) {
$form['author']['name']['#type'] = 'entity_autocomplete';
$form['author']['name']['#target_type'] = 'user';
$form['author']['name']['#selection_settings'] = ['include_anonymous' => FALSE];
$form['author']['name']['#process_default_value'] = FALSE;
// The user name is validated and processed in static::buildEntity() and
// static::validate().
$form['author']['name']['#element_validate'] = array();
$form['author']['name']['#title'] = $this->t('Authored by');
$form['author']['name']['#description'] = $this->t('Leave blank for %anonymous.', array('%anonymous' => $config->get('anonymous')));
}
elseif ($this->currentUser->isAuthenticated()) {
$form['author']['name']['#type'] = 'item';
$form['author']['name']['#value'] = $form['author']['name']['#default_value'];
$form['author']['name']['#theme'] = 'username';
$form['author']['name']['#account'] = $this->currentUser;
}
elseif($this->currentUser->isAnonymous()) {
$form['author']['name']['#attributes']['data-drupal-default-value'] = $config->get('anonymous');
}
// Add author email and homepage fields depending on the current user.
$form['author']['mail'] = array(
'#type' => 'email',
'#title' => $this->t('Email'),
'#default_value' => $comment->getAuthorEmail(),
'#required' => ($this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
'#maxlength' => 64,
'#size' => 30,
'#description' => $this->t('The content of this field is kept private and will not be shown publicly.'),
'#access' => ($comment->getOwner()->isAnonymous() && $is_admin) || ($this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
);
$form['author']['homepage'] = array(
'#type' => 'url',
'#title' => $this->t('Homepage'),
'#default_value' => $comment->getHomepage(),
'#maxlength' => 255,
'#size' => 30,
'#access' => $is_admin || ($this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
);
// Add administrative comment publishing options.
$form['author']['date'] = array(
'#type' => 'datetime',
'#title' => $this->t('Authored on'),
'#default_value' => $date,
'#size' => 20,
'#access' => $is_admin,
);
$form['author']['status'] = array(
'#type' => 'radios',
'#title' => $this->t('Status'),
'#default_value' => $status,
'#options' => array(
CommentInterface::PUBLISHED => $this->t('Published'),
CommentInterface::NOT_PUBLISHED => $this->t('Not published'),
),
'#access' => $is_admin,
);
$this->renderer->addCacheableDependency($form, $config);
// The form depends on the field definition.
$this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle()));
return parent::form($form, $form_state, $comment);
}
/**
* Overrides Drupal\Core\Entity\EntityForm::actions().
*/
protected function actions(array $form, FormStateInterface $form_state) {
$element = parent::actions($form, $form_state);
/* @var \Drupal\comment\CommentInterface $comment */
$comment = $this->entity;
$entity = $comment->getCommentedEntity();
$field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
$preview_mode = $field_definition->getSetting('preview');
// No delete action on the comment form.
unset($element['delete']);
// Mark the submit action as the primary action, when it appears.
$element['submit']['#button_type'] = 'primary';
// Only show the save button if comment previews are optional or if we are
// already previewing the submission.
$element['submit']['#access'] = ($comment->id() && $this->currentUser->hasPermission('administer comments')) || $preview_mode != DRUPAL_REQUIRED || $form_state->get('comment_preview');
$element['preview'] = array(
'#type' => 'submit',
'#value' => $this->t('Preview'),
'#access' => $preview_mode != DRUPAL_DISABLED,
'#validate' => array('::validate'),
'#submit' => array('::submitForm', '::preview'),
);
return $element;
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\comment\CommentInterface $comment */
$comment = parent::buildEntity($form, $form_state);
if (!$form_state->isValueEmpty('date') && $form_state->getValue('date') instanceOf DrupalDateTime) {
$comment->setCreatedTime($form_state->getValue('date')->getTimestamp());
}
else {
$comment->setCreatedTime(REQUEST_TIME);
}
$author_name = $form_state->getValue('name');
if (!$this->currentUser->isAnonymous()) {
// Assign the owner based on the given user name - none means anonymous.
$accounts = $this->entityManager->getStorage('user')
->loadByProperties(array('name' => $author_name));
$account = reset($accounts);
$uid = $account ? $account->id() : 0;
$comment->setOwnerId($uid);
}
// If the comment was posted by an anonymous user and no author name was
// required, use "Anonymous" by default.
if ($comment->getOwnerId() === 0 && (!isset($author_name) || $author_name === '')) {
$comment->setAuthorName($this->config('user.settings')->get('anonymous'));
}
// Validate the comment's subject. If not specified, extract from comment
// body.
if (trim($comment->getSubject()) == '') {
if ($comment->hasField('comment_body')) {
// The body may be in any format, so:
// 1) Filter it into HTML
// 2) Strip out all HTML tags
// 3) Convert entities back to plain-text.
$comment_text = $comment->comment_body->processed;
$comment->setSubject(Unicode::truncate(trim(Html::decodeEntities(strip_tags($comment_text))), 29, TRUE, TRUE));
}
// Edge cases where the comment body is populated only by HTML tags will
// require a default subject.
if ($comment->getSubject() == '') {
$comment->setSubject($this->t('(No subject)'));
}
}
return $comment;
}
/**
* {@inheritdoc}
*/
protected function getEditedFieldNames(FormStateInterface $form_state) {
return array_merge(['created', 'name'], parent::getEditedFieldNames($form_state));
}
/**
* {@inheritdoc}
*/
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
// Manually flag violations of fields not handled by the form display.
foreach ($violations->getByField('created') as $violation) {
$form_state->setErrorByName('date', $violation->getMessage());
}
foreach ($violations->getByField('name') as $violation) {
$form_state->setErrorByName('name', $violation->getMessage());
}
parent::flagViolations($violations, $form, $form_state);
}
/**
* Form submission handler for the 'preview' action.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* The current state of the form.
*/
public function preview(array &$form, FormStateInterface $form_state) {
$comment_preview = comment_preview($this->entity, $form_state);
$comment_preview['#title'] = $this->t('Preview comment');
$form_state->set('comment_preview', $comment_preview);
$form_state->setRebuild();
}
/**
* Overrides Drupal\Core\Entity\EntityForm::save().
*/
public function save(array $form, FormStateInterface $form_state) {
$comment = $this->entity;
$entity = $comment->getCommentedEntity();
$field_name = $comment->getFieldName();
$uri = $entity->urlInfo();
$logger = $this->logger('content');
if ($this->currentUser->hasPermission('post comments') && ($this->currentUser->hasPermission('administer comments') || $entity->{$field_name}->status == CommentItemInterface::OPEN)) {
$comment->save();
$form_state->setValue('cid', $comment->id());
// Add a log entry.
$logger->notice('Comment posted: %subject.', array(
'%subject' => $comment->getSubject(),
'link' => $this->l(t('View'), $comment->urlInfo()->setOption('fragment', 'comment-' . $comment->id()))
));
// Explain the approval queue if necessary.
if (!$comment->isPublished()) {
if (!$this->currentUser->hasPermission('administer comments')) {
drupal_set_message($this->t('Your comment has been queued for review by site administrators and will be published after approval.'));
}
}
else {
drupal_set_message($this->t('Your comment has been posted.'));
}
$query = array();
// Find the current display page for this comment.
$field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$field_name];
$page = $this->entityManager->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
if ($page > 0) {
$query['page'] = $page;
}
// Redirect to the newly posted comment.
$uri->setOption('query', $query);
$uri->setOption('fragment', 'comment-' . $comment->id());
}
else {
$logger->warning('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->getSubject()));
drupal_set_message($this->t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->getSubject())), 'error');
// Redirect the user to the entity they are commenting on.
}
$form_state->setRedirectUrl($uri);
}
}

View file

@ -0,0 +1,260 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\user\EntityOwnerInterface;
use Drupal\Core\Entity\EntityChangedInterface;
/**
* Provides an interface defining a comment entity.
*/
interface CommentInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
/**
* Comment is awaiting approval.
*/
const NOT_PUBLISHED = 0;
/**
* Comment is published.
*/
const PUBLISHED = 1;
/**
* Determines if this comment is a reply to another comment.
*
* @return bool
* TRUE if the comment has a parent comment otherwise FALSE.
*/
public function hasParentComment();
/**
* Returns the parent comment entity if this is a reply to a comment.
*
* @return \Drupal\comment\CommentInterface|NULL
* A comment entity of the parent comment or NULL if there is no parent.
*/
public function getParentComment();
/**
* Returns the entity to which the comment is attached.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity on which the comment is attached.
*/
public function getCommentedEntity();
/**
* Returns the ID of the entity to which the comment is attached.
*
* @return int
* The ID of the entity to which the comment is attached.
*/
public function getCommentedEntityId();
/**
* Returns the type of the entity to which the comment is attached.
*
* @return string
* An entity type.
*/
public function getCommentedEntityTypeId();
/**
* Sets the field ID for which this comment is attached.
*
* @param string $field_name
* The field name through which the comment was added.
*
* @return $this
* The class instance that this method is called on.
*/
public function setFieldName($field_name);
/**
* Returns the name of the field the comment is attached to.
*
* @return string
* The name of the field the comment is attached to.
*/
public function getFieldName();
/**
* Returns the subject of the comment.
*
* @return string
* The subject of the comment.
*/
public function getSubject();
/**
* Sets the subject of the comment.
*
* @param string $subject
* The subject of the comment.
*
* @return $this
* The class instance that this method is called on.
*/
public function setSubject($subject);
/**
* Returns the comment author's name.
*
* For anonymous authors, this is the value as typed in the comment form.
*
* @return string
* The name of the comment author.
*/
public function getAuthorName();
/**
* Sets the name of the author of the comment.
*
* @param string $name
* A string containing the name of the author.
*
* @return $this
* The class instance that this method is called on.
*/
public function setAuthorName($name);
/**
* Returns the comment author's email address.
*
* For anonymous authors, this is the value as typed in the comment form.
*
* @return string
* The email address of the author of the comment.
*/
public function getAuthorEmail();
/**
* Returns the comment author's home page address.
*
* For anonymous authors, this is the value as typed in the comment form.
*
* @return string
* The homepage address of the author of the comment.
*/
public function getHomepage();
/**
* Sets the comment author's home page address.
*
* For anonymous authors, this is the value as typed in the comment form.
*
* @param string $homepage
* The homepage address of the author of the comment.
*
* @return $this
* The class instance that this method is called on.
*/
public function setHomepage($homepage);
/**
* Returns the comment author's hostname.
*
* @return string
* The hostname of the author of the comment.
*/
public function getHostname();
/**
* Sets the hostname of the author of the comment.
*
* @param string $hostname
* The hostname of the author of the comment.
*
* @return $this
* The class instance that this method is called on.
*/
public function setHostname($hostname);
/**
* Returns the time that the comment was created.
*
* @return int
* The timestamp of when the comment was created.
*/
public function getCreatedTime();
/**
* Sets the creation date of the comment.
*
* @param int $created
* The timestamp of when the comment was created.
*
* @return $this
* The class instance that this method is called on.
*/
public function setCreatedTime($created);
/**
* Checks if the comment is published.
*
* @return bool
* TRUE if the comment is published.
*/
public function isPublished();
/**
* Returns the comment's status.
*
* @return int
* One of CommentInterface::PUBLISHED or CommentInterface::NOT_PUBLISHED
*/
public function getStatus();
/**
* Sets the published status of the comment entity.
*
* @param bool $status
* Set to TRUE to publish the comment, FALSE to unpublish.
*
* @return \Drupal\comment\CommentInterface
* The class instance that this method is called on.
*/
public function setPublished($status);
/**
* Returns the alphadecimal representation of the comment's place in a thread.
*
* @return string
* The alphadecimal representation of the comment's place in a thread.
*/
public function getThread();
/**
* Sets the alphadecimal representation of the comment's place in a thread.
*
* @param string $thread
* The alphadecimal representation of the comment's place in a thread.
*
* @return $this
* The class instance that this method is called on.
*/
public function setThread($thread);
/**
* Returns the permalink URL for this comment.
*
* @return \Drupal\Core\Url
*/
public function permalink();
/**
* Get the comment type id for this comment.
*
* @return string
* The id of the comment type.
*/
public function getTypeId();
}

View file

@ -0,0 +1,232 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentLazyBuilders.
*/
namespace Drupal\comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Render\Renderer;
/**
* Defines a service for comment #lazy_builder callbacks.
*/
class CommentLazyBuilders {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity form builder service.
*
* @var \Drupal\Core\Entity\EntityFormBuilderInterface
*/
protected $entityFormBuilder;
/**
* Comment manager service.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* Current logged in user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\Renderer
*/
protected $renderer;
/**
* Constructs a new CommentLazyBuilders object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
* The entity form builder service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current logged in user.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* The comment manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Render\Renderer $renderer
* The renderer service.
*/
public function __construct(EntityManagerInterface $entity_manager, EntityFormBuilderInterface $entity_form_builder, AccountInterface $current_user, CommentManagerInterface $comment_manager, ModuleHandlerInterface $module_handler, Renderer $renderer) {
$this->entityManager = $entity_manager;
$this->entityFormBuilder = $entity_form_builder;
$this->currentUser = $current_user;
$this->commentManager = $comment_manager;
$this->moduleHandler = $module_handler;
$this->renderer = $renderer;
}
/**
* #lazy_builder callback; builds the comment form.
*
* @param string $commented_entity_type_id
* The commented entity type ID.
* @param string $commented_entity_id
* The commented entity ID.
* @param string $field_name
* The comment field name.
* @param string $comment_type_id
* The comment type ID.
*
* @return array
* A renderable array containing the comment form.
*/
public function renderForm($commented_entity_type_id, $commented_entity_id, $field_name, $comment_type_id) {
$values = array(
'entity_type' => $commented_entity_type_id,
'entity_id' => $commented_entity_id,
'field_name' => $field_name,
'comment_type' => $comment_type_id,
'pid' => NULL,
);
$comment = $this->entityManager->getStorage('comment')->create($values);
return $this->entityFormBuilder->getForm($comment);
}
/**
* #lazy_builder callback; builds a comment's links.
*
* @param string $comment_entity_id
* The comment entity ID.
* @param string $view_mode
* The view mode in which the comment entity is being viewed.
* @param string $langcode
* The language in which the comment entity is being viewed.
* @param bool $is_in_preview
* Whether the comment is currently being previewed.
*
* @return array
* A renderable array representing the comment links.
*/
public function renderLinks($comment_entity_id, $view_mode, $langcode, $is_in_preview) {
$links = array(
'#theme' => 'links__comment',
'#pre_render' => array('drupal_pre_render_links'),
'#attributes' => array('class' => array('links', 'inline')),
);
if (!$is_in_preview) {
/** @var \Drupal\comment\CommentInterface $entity */
$entity = $this->entityManager->getStorage('comment')->load($comment_entity_id);
$commented_entity = $entity->getCommentedEntity();
$links['comment'] = $this->buildLinks($entity, $commented_entity);
// Allow other modules to alter the comment links.
$hook_context = array(
'view_mode' => $view_mode,
'langcode' => $langcode,
'commented_entity' => $commented_entity,
);
$this->moduleHandler->alter('comment_links', $links, $entity, $hook_context);
}
return $links;
}
/**
* Build the default links (reply, edit, delete ) for a comment.
*
* @param \Drupal\comment\CommentInterface $entity
* The comment object.
* @param \Drupal\Core\Entity\EntityInterface $commented_entity
* The entity to which the comment is attached.
*
* @return array
* An array that can be processed by drupal_pre_render_links().
*/
protected function buildLinks(CommentInterface $entity, EntityInterface $commented_entity) {
$links = array();
$status = $commented_entity->get($entity->getFieldName())->status;
if ($status == CommentItemInterface::OPEN) {
if ($entity->access('delete')) {
$links['comment-delete'] = array(
'title' => t('Delete'),
'url' => $entity->urlInfo('delete-form'),
);
}
if ($entity->access('update')) {
$links['comment-edit'] = array(
'title' => t('Edit'),
'url' => $entity->urlInfo('edit-form'),
);
}
if ($entity->access('create')) {
$links['comment-reply'] = array(
'title' => t('Reply'),
'url' => Url::fromRoute('comment.reply', [
'entity_type' => $entity->getCommentedEntityTypeId(),
'entity' => $entity->getCommentedEntityId(),
'field_name' => $entity->getFieldName(),
'pid' => $entity->id(),
]),
);
}
if (!$entity->isPublished() && $entity->access('approve')) {
$links['comment-approve'] = array(
'title' => t('Approve'),
'url' => Url::fromRoute('comment.approve', ['comment' => $entity->id()]),
);
}
if (empty($links) && $this->currentUser->isAnonymous()) {
$links['comment-forbidden']['title'] = $this->commentManager->forbiddenMessage($commented_entity, $entity->getFieldName());
}
}
// Add translations link for translation-enabled comment bundles.
if ($this->moduleHandler->moduleExists('content_translation') && $this->access($entity)->isAllowed()) {
$links['comment-translations'] = array(
'title' => t('Translate'),
'url' => $entity->urlInfo('drupal:content-translation-overview'),
);
}
return array(
'#theme' => 'links__comment__comment',
// The "entity" property is specified to be present, so no need to check.
'#links' => $links,
'#attributes' => array('class' => array('links', 'inline')),
);
}
/**
* Wraps content_translation_translate_access.
*/
protected function access(EntityInterface $entity) {
return content_translation_translate_access($entity);
}
}

View file

@ -0,0 +1,229 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentLinkBuilder.
*/
namespace Drupal\comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
/**
* Defines a class for building markup for comment links on a commented entity.
*
* Comment links include 'login to post new comment', 'add new comment' etc.
*/
class CommentLinkBuilder implements CommentLinkBuilderInterface {
use StringTranslationTrait;
/**
* Current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Comment manager service.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* Module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new CommentLinkBuilder object.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* Comment manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* Module handler service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* String translation service.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
*/
public function __construct(AccountInterface $current_user, CommentManagerInterface $comment_manager, ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, EntityManagerInterface $entity_manager) {
$this->currentUser = $current_user;
$this->commentManager = $comment_manager;
$this->moduleHandler = $module_handler;
$this->stringTranslation = $string_translation;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function buildCommentedEntityLinks(FieldableEntityInterface $entity, array &$context) {
$entity_links = array();
$view_mode = $context['view_mode'];
if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print' || $view_mode == 'rss') {
// Do not add any links if the entity is displayed for:
// - search indexing.
// - constructing a search result excerpt.
// - print.
// - rss.
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 == '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.')),
'fragment' => 'comments',
'url' => $entity->urlInfo(),
);
if ($this->moduleHandler->moduleExists('history')) {
$links['comment-new-comments'] = array(
'title' => '',
'url' => Url::fromRoute('<current>'),
'attributes' => array(
'class' => 'hidden',
'title' => $this->t('Jump to the first new comment.'),
'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('Share your thoughts and opinions.')),
'fragment' => 'comment-form',
);
if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) {
$links['comment-add']['url'] = Url::fromRoute('comment.reply', [
'entity_type' => $entity->getEntityTypeId(),
'entity' => $entity->id(),
'field_name' => $field_name,
]);
}
else {
$links['comment-add'] += ['url' => $entity->urlInfo()];
}
}
elseif ($this->currentUser->isAnonymous()) {
$links['comment-forbidden'] = array(
'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
);
}
}
}
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.')),
'fragment' => 'comment-form',
);
if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) {
$links['comment-add']['url'] = Url::fromRoute('comment.reply', [
'entity_type' => $entity->getEntityTypeId(),
'entity' => $entity->id(),
'field_name' => $field_name,
]);
}
else {
$links['comment-add']['url'] = $entity->urlInfo();
}
}
}
elseif ($this->currentUser->isAnonymous()) {
$links['comment-forbidden'] = array(
'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
);
}
}
}
}
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]['#cache']['contexts'][] = 'user';
$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]['#attached']['drupalSettings']['history']['lastReadTimestamps'][$entity->id()] = (int) history_read($entity->id());
$new_comments = $this->commentManager->getCountNewComments($entity);
if ($new_comments > 0) {
$page_number = $this->entityManager
->getStorage('comment')
->getNewCommentPageNumber($entity->{$field_name}->comment_count, $new_comments, $entity);
$query = $page_number ? ['page' => $page_number] : NULL;
$value = [
'new_comment_count' => (int) $new_comments,
'first_new_comment_link' => $entity->url('canonical', [
'query' => $query,
'fragment' => 'new',
]),
];
$parents = ['comment', 'newCommentsLinks', $entity->getEntityTypeId(), $field_name, $entity->id()];
NestedArray::setValue($entity_links['comment__' . $field_name]['#attached']['drupalSettings'], $parents, $value);
}
}
}
}
return $entity_links;
}
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentLinkBuilderInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\FieldableEntityInterface;
/**
* Defines an interface for building comment links on a commented entity.
*
* Comment links include 'login to post new comment', 'add new comment' etc.
*/
interface CommentLinkBuilderInterface {
/**
* Builds links for the given entity.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* Entity for which the links are being built.
* @param array $context
* Array of context passed from the entity view builder.
*
* @return array
* Array of entity links.
*/
public function buildCommentedEntityLinks(FieldableEntityInterface $entity, array &$context);
}

View file

@ -0,0 +1,235 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentManager.
*/
namespace Drupal\comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\user\RoleInterface;
/**
* Comment manager contains common functions to manage comment fields.
*/
class CommentManager implements CommentManagerInterface {
use StringTranslationTrait;
use UrlGeneratorTrait;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity query factory.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $queryFactory;
/**
* Whether the \Drupal\user\RoleInterface::AUTHENTICATED_ID can post comments.
*
* @var bool
*/
protected $authenticatedCanPostComments;
/**
* The user settings config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $userConfig;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Construct the CommentManager object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
* The entity query factory.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(EntityManagerInterface $entity_manager, QueryFactory $query_factory, ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
$this->entityManager = $entity_manager;
$this->queryFactory = $query_factory;
$this->userConfig = $config_factory->get('user.settings');
$this->stringTranslation = $string_translation;
$this->urlGenerator = $url_generator;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public function getFields($entity_type_id) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
return array();
}
$map = $this->entityManager->getFieldMapByFieldType('comment');
return isset($map[$entity_type_id]) ? $map[$entity_type_id] : array();
}
/**
* {@inheritdoc}
*/
public function addBodyField($comment_type_id) {
if (!FieldConfig::loadByName('comment', $comment_type_id, 'comment_body')) {
// Attaches the body field by default.
$field = $this->entityManager->getStorage('field_config')->create(array(
'label' => 'Comment',
'bundle' => $comment_type_id,
'required' => TRUE,
'field_storage' => FieldStorageConfig::loadByName('comment', 'comment_body'),
));
$field->save();
// Assign widget settings for the 'default' form mode.
entity_get_form_display('comment', $comment_type_id, 'default')
->setComponent('comment_body', array(
'type' => 'text_textarea',
))
->save();
// Assign display settings for the 'default' view mode.
entity_get_display('comment', $comment_type_id, 'default')
->setComponent('comment_body', array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 0,
))
->save();
}
}
/**
* {@inheritdoc}
*/
public function forbiddenMessage(EntityInterface $entity, $field_name) {
if (!isset($this->authenticatedCanPostComments)) {
// We only output a link if we are certain that users will get the
// permission to post comments by logging in.
$this->authenticatedCanPostComments = $this->entityManager
->getStorage('user_role')
->load(RoleInterface::AUTHENTICATED_ID)
->hasPermission('post comments');
}
if ($this->authenticatedCanPostComments) {
// We cannot use the redirect.destination service here because these links
// sometimes appear on /node and taxonomy listing pages.
if ($entity->get($field_name)->getFieldDefinition()->getSetting('form_location') == CommentItemInterface::FORM_SEPARATE_PAGE) {
$comment_reply_parameters = [
'entity_type' => $entity->getEntityTypeId(),
'entity' => $entity->id(),
'field_name' => $field_name,
];
$destination = array('destination' => $this->url('comment.reply', $comment_reply_parameters, array('fragment' => 'comment-form')));
}
else {
$destination = array('destination' => $entity->url('canonical', array('fragment' => 'comment-form')));
}
if ($this->userConfig->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
// Users can register themselves.
return $this->t('<a href="@login">Log in</a> or <a href="@register">register</a> to post comments', array(
'@login' => $this->urlGenerator->generateFromRoute('user.login', array(), array('query' => $destination)),
'@register' => $this->urlGenerator->generateFromRoute('user.register', array(), array('query' => $destination)),
));
}
else {
// Only admins can add new users, no public registration.
return $this->t('<a href="@login">Log in</a> to post comments', array(
'@login' => $this->urlGenerator->generateFromRoute('user.login', array(), array('query' => $destination)),
));
}
}
return '';
}
/**
* {@inheritdoc}
*/
public function getCountNewComments(EntityInterface $entity, $field_name = NULL, $timestamp = 0) {
// @todo Replace module handler with optional history service injection
// after https://www.drupal.org/node/2081585.
if ($this->currentUser->isAuthenticated() && $this->moduleHandler->moduleExists('history')) {
// Retrieve the timestamp at which the current user last viewed this entity.
if (!$timestamp) {
if ($entity->getEntityTypeId() == 'node') {
$timestamp = history_read($entity->id());
}
else {
$function = $entity->getEntityTypeId() . '_last_viewed';
if (function_exists($function)) {
$timestamp = $function($entity->id());
}
else {
// Default to 30 days ago.
// @todo Remove once https://www.drupal.org/node/1029708 lands.
$timestamp = COMMENT_NEW_LIMIT;
}
}
}
$timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT);
// Use the timestamp to retrieve the number of new comments.
$query = $this->queryFactory->get('comment')
->condition('entity_type', $entity->getEntityTypeId())
->condition('entity_id', $entity->id())
->condition('created', $timestamp, '>')
->condition('status', CommentInterface::PUBLISHED);
if ($field_name) {
// Limit to a particular field.
$query->condition('field_name', $field_name);
}
return $query->count()->execute();
}
return FALSE;
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentManagerInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
/**
* Comment manager contains common functions to manage comment fields.
*/
interface CommentManagerInterface {
/**
* Comments are displayed in a flat list - expanded.
*/
const COMMENT_MODE_FLAT = 0;
/**
* Comments are displayed as a threaded list - expanded.
*/
const COMMENT_MODE_THREADED = 1;
/**
* Utility function to return an array of comment fields.
*
* @param string $entity_type_id
* The content entity type to which the comment fields are attached.
*
* @return array
* An array of comment field map definitions, keyed by field name. Each
* value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears, as an array with entity
* types as keys and the array of bundle names as values.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldMap()
*/
public function getFields($entity_type_id);
/**
* Creates a comment_body field.
*
* @param string $comment_type
* The comment bundle.
*/
public function addBodyField($comment_type);
/**
* Provides a message if posting comments is forbidden.
*
* If authenticated users can post comments, a message is returned that
* prompts the anonymous user to log in (or register, if applicable) that
* redirects to entity comment form. Otherwise, no message is returned.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to which comments are attached to.
* @param string $field_name
* The field name on the entity to which comments are attached to.
*
* @return string
* HTML for a "you can't post comments" notice.
*/
public function forbiddenMessage(EntityInterface $entity, $field_name);
/**
* Returns the number of new comments available on a given entity for a user.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to which the comments are attached to.
* @param string $field_name
* (optional) The field_name to count comments for. Defaults to any field.
* @param int $timestamp
* (optional) Time to count from. Defaults to time of last user access the
* entity.
*
* @return int|false
* The number of new comments or FALSE if the user is not authenticated.
*/
public function getCountNewComments(EntityInterface $entity, $field_name = NULL, $timestamp = 0);
}

View file

@ -0,0 +1,265 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentStatistics.
*/
namespace Drupal\comment;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\EntityOwnerInterface;
class CommentStatistics implements CommentStatisticsInterface {
/**
* The current database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The current logged in user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs the CommentStatistics service.
*
* @param \Drupal\Core\Database\Connection $database
* The active database connection.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current logged in user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/
public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state) {
$this->database = $database;
$this->currentUser = $current_user;
$this->entityManager = $entity_manager;
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public function read($entities, $entity_type, $accurate = TRUE) {
$options = $accurate ? array() : array('target' => 'replica');
$stats = $this->database->select('comment_entity_statistics', 'ces', $options)
->fields('ces')
->condition('ces.entity_id', array_keys($entities), 'IN')
->condition('ces.entity_type', $entity_type)
->execute();
$statistics_records = array();
while ($entry = $stats->fetchObject()) {
$statistics_records[] = $entry;
}
return $statistics_records;
}
/**
* {@inheritdoc}
*/
public function delete(EntityInterface $entity) {
$this->database->delete('comment_entity_statistics')
->condition('entity_id', $entity->id())
->condition('entity_type', $entity->getEntityTypeId())
->execute();
}
/**
* {@inheritdoc}
*/
public function create(FieldableEntityInterface $entity, $fields) {
$query = $this->database->insert('comment_entity_statistics')
->fields(array(
'entity_id',
'entity_type',
'field_name',
'cid',
'last_comment_timestamp',
'last_comment_name',
'last_comment_uid',
'comment_count',
));
foreach ($fields as $field_name => $detail) {
// Skip fields that entity does not have.
if (!$entity->hasField($field_name)) {
continue;
}
// Get the user ID from the entity if it's set, or default to the
// currently logged in user.
$last_comment_uid = 0;
if ($entity instanceof EntityOwnerInterface) {
$last_comment_uid = $entity->getOwnerId();
}
if (!isset($last_comment_uid)) {
// Default to current user when entity does not implement
// EntityOwnerInterface or author is not set.
$last_comment_uid = $this->currentUser->id();
}
// Default to REQUEST_TIME when entity does not have a changed property.
$last_comment_timestamp = REQUEST_TIME;
// @todo Make comment statistics language aware and add some tests. See
// https://www.drupal.org/node/2318875
if ($entity instanceof EntityChangedInterface) {
$last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
}
$query->values(array(
'entity_id' => $entity->id(),
'entity_type' => $entity->getEntityTypeId(),
'field_name' => $field_name,
'cid' => 0,
'last_comment_timestamp' => $last_comment_timestamp,
'last_comment_name' => NULL,
'last_comment_uid' => $last_comment_uid,
'comment_count' => 0,
));
}
$query->execute();
}
/**
* {@inheritdoc}
*/
public function getMaximumCount($entity_type) {
return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', array(':entity_type' => $entity_type))->fetchField();
}
/**
* {@inheritdoc}
*/
public function getRankingInfo() {
return array(
'comments' => array(
'title' => t('Number of comments'),
'join' => array(
'type' => 'LEFT',
'table' => 'comment_entity_statistics',
'alias' => 'ces',
// Default to comment field as this is the most common use case for
// nodes.
'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_name = 'comment'",
),
// Inverse law that maps the highest view count on the site to 1 and 0
// to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
// in order to ensure that the :comment_scale argument is treated as
// a numeric type, because the PostgreSQL PDO driver sometimes puts
// values in as strings instead of numbers in complex expressions like
// this.
'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (ROUND(:comment_scale, 4)))',
'arguments' => array(':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0),
),
);
}
/**
* {@inheritdoc}
*/
public function update(CommentInterface $comment) {
// Allow bulk updates and inserts to temporarily disable the maintenance of
// the {comment_entity_statistics} table.
if (!$this->state->get('comment.maintain_entity_statistics')) {
return;
}
$query = $this->database->select('comment_field_data', 'c');
$query->addExpression('COUNT(cid)');
$count = $query->condition('c.entity_id', $comment->getCommentedEntityId())
->condition('c.entity_type', $comment->getCommentedEntityTypeId())
->condition('c.field_name', $comment->getFieldName())
->condition('c.status', CommentInterface::PUBLISHED)
->condition('default_langcode', 1)
->execute()
->fetchField();
if ($count > 0) {
// Comments exist.
$last_reply = $this->database->select('comment_field_data', 'c')
->fields('c', array('cid', 'name', 'changed', 'uid'))
->condition('c.entity_id', $comment->getCommentedEntityId())
->condition('c.entity_type', $comment->getCommentedEntityTypeId())
->condition('c.field_name', $comment->getFieldName())
->condition('c.status', CommentInterface::PUBLISHED)
->condition('default_langcode', 1)
->orderBy('c.created', 'DESC')
->range(0, 1)
->execute()
->fetchObject();
// Use merge here because entity could be created before comment field.
$this->database->merge('comment_entity_statistics')
->fields(array(
'cid' => $last_reply->cid,
'comment_count' => $count,
'last_comment_timestamp' => $last_reply->changed,
'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
'last_comment_uid' => $last_reply->uid,
))
->keys(array(
'entity_id' => $comment->getCommentedEntityId(),
'entity_type' => $comment->getCommentedEntityTypeId(),
'field_name' => $comment->getFieldName(),
))
->execute();
}
else {
// Comments do not exist.
$entity = $comment->getCommentedEntity();
// Get the user ID from the entity if it's set, or default to the
// currently logged in user.
if ($entity instanceof EntityOwnerInterface) {
$last_comment_uid = $entity->getOwnerId();
}
if (!isset($last_comment_uid)) {
// Default to current user when entity does not implement
// EntityOwnerInterface or author is not set.
$last_comment_uid = $this->currentUser->id();
}
$this->database->update('comment_entity_statistics')
->fields(array(
'cid' => 0,
'comment_count' => 0,
// Use the changed date of the entity if it's set, or default to
// REQUEST_TIME.
'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
'last_comment_name' => '',
'last_comment_uid' => $last_comment_uid,
))
->condition('entity_id', $comment->getCommentedEntityId())
->condition('entity_type', $comment->getCommentedEntityTypeId())
->condition('field_name', $comment->getFieldName())
->execute();
}
// Reset the cache of the commented entity so that when the entity is loaded
// the next time, the statistics will be loaded again.
$this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache(array($comment->getCommentedEntityId()));
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentStatisticsInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityInterface;
/**
* Provides an interface for storing and retrieving comment statistics.
*/
interface CommentStatisticsInterface {
/**
* Returns an array of ranking information for hook_ranking().
*
* @return array
* Array of ranking information as expected by hook_ranking().
*
* @see hook_ranking()
* @see comment_ranking()
*/
public function getRankingInfo();
/**
* Read comment statistics records for an array of entities.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* Array of entities on which commenting is enabled, keyed by id
* @param string $entity_type
* The entity type of the passed entities.
* @param bool $accurate
* (optional) Indicates if results must be completely up to date. If set to
* FALSE, a replica database will used if available. Defaults to TRUE.
*
* @return object[]
* Array of statistics records.
*/
public function read($entities, $entity_type, $accurate = TRUE);
/**
* Delete comment statistics records for an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which comment statistics should be deleted.
*/
public function delete(EntityInterface $entity);
/**
* Update or insert comment statistics records after a comment is added.
*
* @param \Drupal\comment\CommentInterface $comment
* The comment added or updated.
*/
public function update(CommentInterface $comment);
/**
* Find the maximum number of comments for the given entity type.
*
* Used to influence search rankings.
*
* @param string $entity_type
* The entity type to consider when fetching the maximum comment count for.
*
* @return int
* The maximum number of comments for and entity of the given type.
*
* @see comment_update_index()
*/
public function getMaximumCount($entity_type);
/**
* Insert an empty record for the given entity.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The created entity for which a statistics record is to be initialized.
* @param array $fields
* Array of comment field definitions for the given entity.
*/
public function create(FieldableEntityInterface $entity, $fields);
}

View file

@ -0,0 +1,340 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentStorage.
*/
namespace Drupal\comment;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the controller class for comments.
*
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class,
* adding required special handling for comment entities.
*/
class CommentStorage extends SqlContentEntityStorage implements CommentStorageInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a CommentStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
* An array of entity info for the entity type.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
parent::__construct($entity_info, $database, $entity_manager, $cache, $language_manager);
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
return new static(
$entity_info,
$container->get('database'),
$container->get('entity.manager'),
$container->get('current_user'),
$container->get('cache.entity'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getMaxThread(CommentInterface $comment) {
$query = $this->database->select('comment_field_data', 'c')
->condition('entity_id', $comment->getCommentedEntityId())
->condition('field_name', $comment->getFieldName())
->condition('entity_type', $comment->getCommentedEntityTypeId())
->condition('default_langcode', 1);
$query->addExpression('MAX(thread)', 'thread');
return $query->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function getMaxThreadPerThread(CommentInterface $comment) {
$query = $this->database->select('comment_field_data', 'c')
->condition('entity_id', $comment->getCommentedEntityId())
->condition('field_name', $comment->getFieldName())
->condition('entity_type', $comment->getCommentedEntityTypeId())
->condition('thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
->condition('default_langcode', 1);
$query->addExpression('MAX(thread)', 'thread');
return $query->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $divisor = 1) {
// Count how many comments (c1) are before $comment (c2) in display order.
// This is the 0-based display ordinal.
$query = $this->database->select('comment_field_data', 'c1');
$query->innerJoin('comment_field_data', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_name = c1.field_name');
$query->addExpression('COUNT(*)', 'count');
$query->condition('c2.cid', $comment->id());
if (!$this->currentUser->hasPermission('administer comments')) {
$query->condition('c1.status', CommentInterface::PUBLISHED);
}
if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
// For rendering flat comments, cid is used for ordering comments due to
// unpredictable behavior with timestamp, so we make the same assumption
// here.
$query->condition('c1.cid', $comment->id(), '<');
}
else {
// For threaded comments, the c.thread column is used for ordering. We can
// use the sorting code for comparison, but must remove the trailing
// slash.
$query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) - 1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) - 1))');
}
$query->condition('c1.default_langcode', 1);
$query->condition('c2.default_langcode', 1);
$ordinal = $query->execute()->fetchField();
return ($divisor > 1) ? floor($ordinal / $divisor) : $ordinal;
}
/**
* {@inheritdoc}
*/
public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name = 'comment') {
$field = $entity->getFieldDefinition($field_name);
$comments_per_page = $field->getSetting('per_page');
if ($total_comments <= $comments_per_page) {
// Only one page of comments.
$count = 0;
}
elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
// Flat comments.
$count = $total_comments - $new_comments;
}
else {
// Threaded comments.
// 1. Find all the threads with a new comment.
$unread_threads_query = $this->database->select('comment_field_data', 'comment')
->fields('comment', array('thread'))
->condition('entity_id', $entity->id())
->condition('entity_type', $entity->getEntityTypeId())
->condition('field_name', $field_name)
->condition('status', CommentInterface::PUBLISHED)
->condition('default_langcode', 1)
->orderBy('created', 'DESC')
->orderBy('cid', 'DESC')
->range(0, $new_comments);
// 2. Find the first thread.
$first_thread_query = $this->database->select($unread_threads_query, 'thread');
$first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder');
$first_thread = $first_thread_query
->fields('thread', array('thread'))
->orderBy('torder')
->range(0, 1)
->execute()
->fetchField();
// Remove the final '/'.
$first_thread = substr($first_thread, 0, -1);
// Find the number of the first comment of the first unread thread.
$count = $this->database->query('SELECT COUNT(*) FROM {comment_field_data} WHERE entity_id = :entity_id
AND entity_type = :entity_type
AND field_name = :field_name
AND status = :status
AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread
AND default_langcode = 1', array(
':status' => CommentInterface::PUBLISHED,
':entity_id' => $entity->id(),
':field_name' => $field_name,
':entity_type' => $entity->getEntityTypeId(),
':thread' => $first_thread,
))->fetchField();
}
return $comments_per_page > 0 ? (int) ($count / $comments_per_page) : 0;
}
/**
* {@inheritdoc}
*/
public function getChildCids(array $comments) {
return $this->database->select('comment_field_data', 'c')
->fields('c', array('cid'))
->condition('pid', array_keys($comments), 'IN')
->condition('default_langcode', 1)
->execute()
->fetchCol();
}
/**
* {@inheritdoc}
*
* To display threaded comments in the correct order we keep a 'thread' field
* and order by that value. This field keeps this data in
* a way which is easy to update and convenient to use.
*
* A "thread" value starts at "1". If we add a child (A) to this comment,
* we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
* brother of (A) will get "1.2". Next brother of the parent of (A) will get
* "2" and so on.
*
* First of all note that the thread field stores the depth of the comment:
* depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
*
* Now to get the ordering right, consider this example:
*
* 1
* 1.1
* 1.1.1
* 1.2
* 2
*
* If we "ORDER BY thread ASC" we get the above result, and this is the
* natural order sorted by time. However, if we "ORDER BY thread DESC"
* we get:
*
* 2
* 1.2
* 1.1.1
* 1.1
* 1
*
* Clearly, this is not a natural way to see a thread, and users will get
* confused. The natural order to show a thread by time desc would be:
*
* 2
* 1
* 1.2
* 1.1
* 1.1.1
*
* which is what we already did before the standard pager patch. To achieve
* this we simply add a "/" at the end of each "thread" value. This way, the
* thread fields will look like this:
*
* 1/
* 1.1/
* 1.1.1/
* 1.2/
* 2/
*
* we add "/" since this char is, in ASCII, higher than every number, so if
* now we "ORDER BY thread DESC" we get the correct order. However this would
* spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
* to consider the trailing "/" so we use a substring only.
*/
public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page = 0, $pager_id = 0) {
$query = $this->database->select('comment_field_data', 'c');
$query->addField('c', 'cid');
$query
->condition('c.entity_id', $entity->id())
->condition('c.entity_type', $entity->getEntityTypeId())
->condition('c.field_name', $field_name)
->condition('c.default_langcode', 1)
->addTag('entity_access')
->addTag('comment_filter')
->addMetaData('base_table', 'comment')
->addMetaData('entity', $entity)
->addMetaData('field_name', $field_name);
if ($comments_per_page) {
$query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->limit($comments_per_page);
if ($pager_id) {
$query->element($pager_id);
}
$count_query = $this->database->select('comment_field_data', 'c');
$count_query->addExpression('COUNT(*)');
$count_query
->condition('c.entity_id', $entity->id())
->condition('c.entity_type', $entity->getEntityTypeId())
->condition('c.field_name', $field_name)
->condition('c.default_langcode', 1)
->addTag('entity_access')
->addTag('comment_filter')
->addMetaData('base_table', 'comment')
->addMetaData('entity', $entity)
->addMetaData('field_name', $field_name);
$query->setCountQuery($count_query);
}
if (!$this->currentUser->hasPermission('administer comments')) {
$query->condition('c.status', CommentInterface::PUBLISHED);
if ($comments_per_page) {
$count_query->condition('c.status', CommentInterface::PUBLISHED);
}
}
if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
$query->orderBy('c.cid', 'ASC');
}
else {
// See comment above. Analysis reveals that this doesn't cost too
// much. It scales much much better than having the whole comment
// structure.
$query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
$query->orderBy('torder', 'ASC');
}
$cids = $query->execute()->fetchCol();
$comments = array();
if ($cids) {
$comments = $this->loadMultiple($cids);
}
return $comments;
}
/**
* {@inheritdoc}
*/
public function getUnapprovedCount() {
return $this->database->select('comment_field_data', 'c')
->condition('status', CommentInterface::NOT_PUBLISHED, '=')
->condition('default_langcode', 1)
->countQuery()
->execute()
->fetchField();
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentStorageInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
/**
* Defines an interface for comment entity storage classes.
*/
interface CommentStorageInterface extends EntityStorageInterface {
/**
* Gets the maximum encoded thread value for the top level comments.
*
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @return string
* The maximum encoded thread value among the top level comments of the
* node $comment belongs to.
*/
public function getMaxThread(CommentInterface $comment);
/**
* Gets the maximum encoded thread value for the children of this comment.
*
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @return string
* The maximum encoded thread value among all replies of $comment.
*/
public function getMaxThreadPerThread(CommentInterface $comment);
/**
* Calculates the page number for the first new comment.
*
* @param int $total_comments
* The total number of comments that the entity has.
* @param int $new_comments
* The number of new comments that the entity has.
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity to which the comments belong.
* @param string $field_name
* The field name on the entity to which comments are attached.
*
* @return array|null
* The page number where first new comment appears. (First page returns 0.)
*/
public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name = 'comment');
/**
* Gets the display ordinal or page number for a comment.
*
* @param \Drupal\comment\CommentInterface $comment
* The comment to use as a reference point.
* @param int $comment_mode
* The comment display mode: CommentManagerInterface::COMMENT_MODE_FLAT or
* CommentManagerInterface::COMMENT_MODE_THREADED.
* @param int $divisor
* Defaults to 1, which returns the display ordinal for a comment. If the
* number of comments per page is provided, the returned value will be the
* page number. (The return value will be divided by $divisor.)
*
* @return int
* The display ordinal or page number for the comment. It is 0-based, so
* will represent the number of items before the given comment/page.
*/
public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $divisor = 1);
/**
* Gets the comment ids of the passed comment entities' children.
*
* @param \Drupal\comment\CommentInterface[] $comments
* An array of comment entities keyed by their ids.
* @return array
* The entity ids of the passed comment entities' children as an array.
*/
public function getChildCids(array $comments);
/**
* Retrieves comments for a thread, sorted in an order suitable for display.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose comment(s) needs rendering.
* @param string $field_name
* The field_name whose comment(s) needs rendering.
* @param int $mode
* The comment display mode: CommentManagerInterface::COMMENT_MODE_FLAT or
* CommentManagerInterface::COMMENT_MODE_THREADED.
* @param int $comments_per_page
* (optional) The amount of comments to display per page.
* Defaults to 0, which means show all comments.
* @param int $pager_id
* (optional) Pager id to use in case of multiple pagers on the one page.
* Defaults to 0; is only used when $comments_per_page is greater than zero.
*
* @return array
* Ordered array of comment objects, keyed by comment id.
*/
public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page = 0, $pager_id = 0);
/**
* Returns the number of unapproved comments.
*
* @return int
* The number of unapproved comments.
*/
public function getUnapprovedCount();
}

View file

@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentStorageSchema.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the comment schema handler.
*/
class CommentStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
$schema['comment_field_data']['indexes'] += array(
'comment__status_pid' => array('pid', 'status'),
'comment__num_new' => array(
'entity_id',
'entity_type',
'comment_type',
'status',
'created',
'cid',
'thread',
),
'comment__entity_langcode' => array(
'entity_id',
'entity_type',
'comment_type',
'default_langcode',
),
);
return $schema;
}
/**
* {@inheritdoc}
*/
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
$field_name = $storage_definition->getName();
if ($table_name == 'comment_field_data') {
// Remove unneeded indexes.
unset($schema['indexes']['comment_field__pid__target_id']);
unset($schema['indexes']['comment_field__entity_id__target_id']);
switch ($field_name) {
case 'thread':
// Improves the performance of the comment__num_new index defined
// in getEntitySchema().
$schema['fields'][$field_name]['not null'] = TRUE;
break;
case 'created':
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
break;
case 'uid':
$this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
}
}
return $schema;
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentTranslationHandler.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for comments.
*/
class CommentTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
if (isset($form['content_translation'])) {
// We do not need to show these values on comment forms: they inherit the
// basic comment property values.
$form['content_translation']['status']['#access'] = FALSE;
$form['content_translation']['name']['#access'] = FALSE;
$form['content_translation']['created']['#access'] = FALSE;
}
}
/**
* {@inheritdoc}
*/
protected function entityFormTitle(EntityInterface $entity) {
return t('Edit comment @subject', array('@subject' => $entity->label()));
}
/**
* {@inheritdoc}
*/
public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
if ($form_state->hasValue('content_translation')) {
$translation = &$form_state->getValue('content_translation');
/** @var \Drupal\comment\CommentInterface $entity */
$translation['status'] = $entity->isPublished();
$translation['name'] = $entity->getAuthorName();
}
parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
}
}

View file

@ -0,0 +1,179 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentTypeForm.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base form controller for category edit forms.
*/
class CommentTypeForm extends EntityForm {
/**
* Entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The comment manager.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('logger.factory')->get('comment'),
$container->get('comment.manager')
);
}
/**
* Constructs a CommentTypeFormController
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* The comment manager.
*/
public function __construct(EntityManagerInterface $entity_manager, LoggerInterface $logger, CommentManagerInterface $comment_manager) {
$this->entityManager = $entity_manager;
$this->logger = $logger;
$this->commentManager = $comment_manager;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$comment_type = $this->entity;
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Label'),
'#maxlength' => 255,
'#default_value' => $comment_type->label(),
'#required' => TRUE,
);
$form['id'] = array(
'#type' => 'machine_name',
'#default_value' => $comment_type->id(),
'#machine_name' => array(
'exists' => '\Drupal\comment\Entity\CommentType::load',
),
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
'#disabled' => !$comment_type->isNew(),
);
$form['description'] = array(
'#type' => 'textarea',
'#default_value' => $comment_type->getDescription(),
'#description' => t('Describe this comment type. The text will be displayed on the <em>Comment types</em> administration overview page'),
'#title' => t('Description'),
);
if ($comment_type->isNew()) {
$options = array();
foreach ($this->entityManager->getDefinitions() as $entity_type) {
// Only expose entities that have field UI enabled, only those can
// get comment fields added in the UI.
if ($entity_type->get('field_ui_base_route')) {
$options[$entity_type->id()] = $entity_type->getLabel();
}
}
$form['target_entity_type_id'] = array(
'#type' => 'select',
'#default_value' => $comment_type->getTargetEntityTypeId(),
'#title' => t('Target entity type'),
'#options' => $options,
'#description' => t('The target entity type can not be changed after the comment type has been created.')
);
}
else {
$form['target_entity_type_id_display'] = array(
'#type' => 'item',
'#markup' => $this->entityManager->getDefinition($comment_type->getTargetEntityTypeId())->getLabel(),
'#title' => t('Target entity type'),
);
}
if ($this->moduleHandler->moduleExists('content_translation')) {
$form['language'] = array(
'#type' => 'details',
'#title' => t('Language settings'),
'#group' => 'additional_settings',
);
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('comment', $comment_type->id());
$form['language']['language_configuration'] = array(
'#type' => 'language_configuration',
'#entity_information' => array(
'entity_type' => 'comment',
'bundle' => $comment_type->id(),
),
'#default_value' => $language_configuration,
);
$form['#submit'][] = 'language_configuration_element_submit';
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$comment_type = $this->entity;
$status = $comment_type->save();
$edit_link = $this->entity->link($this->t('Edit'));
if ($status == SAVED_UPDATED) {
drupal_set_message(t('Comment type %label has been updated.', array('%label' => $comment_type->label())));
$this->logger->notice('Comment type %label has been updated.', array('%label' => $comment_type->label(), 'link' => $edit_link));
}
else {
$this->commentManager->addBodyField($comment_type->id());
drupal_set_message(t('Comment type %label has been added.', array('%label' => $comment_type->label())));
$this->logger->notice('Comment type %label has been added.', array('%label' => $comment_type->label(), 'link' => $edit_link));
}
$form_state->setRedirectUrl($comment_type->urlInfo('collection'));
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentTypeInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a comment type entity.
*/
interface CommentTypeInterface extends ConfigEntityInterface {
/**
* Returns the comment type description.
*
* @return string
* The comment-type description.
*/
public function getDescription();
/**
* Sets the description of the comment type.
*
* @param string $description
* The new description.
*
* @return $this
*/
public function setDescription($description);
/**
* Gets the target entity type id for this comment type.
*
* @return string
* The target entity type id.
*/
public function getTargetEntityTypeId();
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentTypeListBuilder.
*/
namespace Drupal\comment;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
/**
* Defines a class to build a listing of comment type entities.
*
* @see \Drupal\comment\Entity\CommentType
*/
class CommentTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {
$operations['edit']['weight'] = 30;
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['type'] = t('Comment type');
$header['description'] = t('Description');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['type'] = SafeMarkup::checkPlain($entity->label());
$row['description'] = Xss::filterAdmin($entity->getDescription());
return $row + parent::buildRow($entity);
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentViewBuilder.
*/
namespace Drupal\comment;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilder;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Render controller for comments.
*/
class CommentViewBuilder extends EntityViewBuilder {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new CommentViewBuilder.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, AccountInterface $current_user) {
parent::__construct($entity_type, $entity_manager, $language_manager);
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager'),
$container->get('language_manager'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
$build = parent::getBuildDefaults($entity, $view_mode, $langcode);
/** @var \Drupal\comment\CommentInterface $entity */
// Store a threading field setting to use later in self::buildComponents().
$build['#comment_threaded'] = $entity->getCommentedEntity()
->getFieldDefinition($entity->getFieldName())
->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED;
// If threading is enabled, don't render cache individual comments, but do
// keep the cache tags, so they can bubble up.
if ($build['#comment_threaded']) {
$cache_tags = $build['#cache']['tags'];
$build['#cache'] = [];
$build['#cache']['tags'] = $cache_tags;
}
return $build;
}
/**
* {@inheritdoc}
*
* In addition to modifying the content key on entities, this implementation
* will also set the comment entity key which all comments carry.
*
* @throws \InvalidArgumentException
* Thrown when a comment is attached to an entity that no longer exists.
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
/** @var \Drupal\comment\CommentInterface[] $entities */
if (empty($entities)) {
return;
}
// Pre-load associated users into cache to leverage multiple loading.
$uids = array();
foreach ($entities as $entity) {
$uids[] = $entity->getOwnerId();
}
$this->entityManager->getStorage('user')->loadMultiple(array_unique($uids));
parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
// A counter to track the indentation level.
$current_indent = 0;
foreach ($entities as $id => $entity) {
if ($build[$id]['#comment_threaded']) {
$comment_indent = count(explode('.', $entity->getThread())) - 1;
if ($comment_indent > $current_indent) {
// Set 1 to indent this comment from the previous one (its parent).
// Set only one extra level of indenting even if the difference in
// depth is higher.
$build[$id]['#comment_indent'] = 1;
$current_indent++;
}
else {
// Set zero if this comment is on the same level as the previous one
// or negative value to point an amount indents to close.
$build[$id]['#comment_indent'] = $comment_indent - $current_indent;
$current_indent = $comment_indent;
}
}
// Commented entities already loaded after self::getBuildDefaults().
$commented_entity = $entity->getCommentedEntity();
$build[$id]['#entity'] = $entity;
$build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $commented_entity->bundle();
$display = $displays[$entity->bundle()];
if ($display->getComponent('links')) {
$build[$id]['links'] = array(
'#lazy_builder' => ['comment.lazy_builders:renderLinks', [
$entity->id(),
$view_mode,
$langcode,
!empty($entity->in_preview),
]],
'#create_placeholder' => TRUE,
);
}
if (!isset($build[$id]['#attached'])) {
$build[$id]['#attached'] = array();
}
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
if ($this->moduleHandler->moduleExists('history') && $this->currentUser->isAuthenticated()) {
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
// Embed the metadata for the comment "new" indicators on this node.
$build[$id]['history'] = [
'#lazy_builder' => ['history_attach_timestamp', [$commented_entity->id()]],
'#create_placeholder' => TRUE,
];
}
}
if ($build[$id]['#comment_threaded']) {
// The final comment must close up some hanging divs.
$build[$id]['#comment_indent_final'] = $current_indent;
}
}
/**
* {@inheritdoc}
*/
protected function alterBuild(array &$build, EntityInterface $comment, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) {
parent::alterBuild($build, $comment, $display, $view_mode, $langcode);
if (empty($comment->in_preview)) {
$prefix = '';
// Add indentation div or close open divs as needed.
if ($build['#comment_threaded']) {
$prefix .= $build['#comment_indent'] <= 0 ? str_repeat('</div>', abs($build['#comment_indent'])) : "\n" . '<div class="indented">';
}
// Add anchor for each comment.
$prefix .= "<a id=\"comment-{$comment->id()}\"></a>\n";
$build['#prefix'] = $prefix;
// Close all open divs.
if (!empty($build['#comment_indent_final'])) {
$build['#suffix'] = str_repeat('</div>', $build['#comment_indent_final']);
}
}
}
}

View file

@ -0,0 +1,335 @@
<?php
/**
* @file
* Contains \Drupal\comment\CommentViewsData.
*/
namespace Drupal\comment;
use Drupal\views\EntityViewsData;
/**
* Provides views data for the comment entity type.
*/
class CommentViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['comment_field_data']['table']['base']['help'] = t('Comments are responses to content.');
$data['comment_field_data']['table']['base']['access query tag'] = 'comment_access';
$data['comment_field_data']['table']['wizard_id'] = 'comment';
$data['comment_field_data']['subject']['title'] = t('Title');
$data['comment_field_data']['subject']['help'] = t('The title of the comment.');
$data['comment_field_data']['name']['title'] = t('Author');
$data['comment_field_data']['name']['help'] = t("The name of the comment's author. Can be rendered as a link to the author's homepage.");
$data['comment_field_data']['name']['field']['default_formatter'] = 'comment_username';
$data['comment_field_data']['homepage']['title'] = t("Author's website");
$data['comment_field_data']['homepage']['help'] = t("The website address of the comment's author. Can be rendered as a link. Will be empty if the author is a registered user.");
$data['comment_field_data']['mail']['help'] = t('Email of user that posted the comment. Will be empty if the author is a registered user.');
$data['comment_field_data']['created']['title'] = t('Post date');
$data['comment_field_data']['created']['help'] = t('Date and time of when the comment was created.');
$data['comment_field_data']['changed']['title'] = t('Updated date');
$data['comment_field_data']['changed']['help'] = t('Date and time of when the comment was last updated.');
$data['comment_field_data']['changed_fulldata'] = array(
'title' => t('Created date'),
'help' => t('Date in the form of CCYYMMDD.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_fulldate',
),
);
$data['comment_field_data']['changed_year_month'] = array(
'title' => t('Created year + month'),
'help' => t('Date in the form of YYYYMM.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year_month',
),
);
$data['comment_field_data']['changed_year'] = array(
'title' => t('Created year'),
'help' => t('Date in the form of YYYY.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year',
),
);
$data['comment_field_data']['changed_month'] = array(
'title' => t('Created month'),
'help' => t('Date in the form of MM (01 - 12).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_month',
),
);
$data['comment_field_data']['changed_day'] = array(
'title' => t('Created day'),
'help' => t('Date in the form of DD (01 - 31).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_day',
),
);
$data['comment_field_data']['changed_week'] = array(
'title' => t('Created week'),
'help' => t('Date in the form of WW (01 - 53).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_week',
),
);
$data['comment_field_data']['status']['title'] = t('Approved status');
$data['comment_field_data']['status']['help'] = t('Whether the comment is approved (or still in the moderation queue).');
$data['comment_field_data']['status']['filter']['label'] = t('Approved comment status');
$data['comment_field_data']['status']['filter']['type'] = 'yes-no';
$data['comment']['approve_comment'] = array(
'field' => array(
'title' => t('Link to approve comment'),
'help' => t('Provide a simple link to approve the comment.'),
'id' => 'comment_link_approve',
),
);
$data['comment']['replyto_comment'] = array(
'field' => array(
'title' => t('Link to reply-to comment'),
'help' => t('Provide a simple link to reply to the comment.'),
'id' => 'comment_link_reply',
),
);
$data['comment_field_data']['thread']['field'] = array(
'title' => t('Depth'),
'help' => t('Display the depth of the comment if it is threaded.'),
'id' => 'comment_depth',
);
$data['comment_field_data']['thread']['sort'] = array(
'title' => t('Thread'),
'help' => t('Sort by the threaded order. This will keep child comments together with their parents.'),
'id' => 'comment_thread',
);
unset($data['comment_field_data']['thread']['filter']);
unset($data['comment_field_data']['thread']['argument']);
$entities_types = \Drupal::entityManager()->getDefinitions();
// Provide a relationship for each entity type except comment.
foreach ($entities_types as $type => $entity_type) {
if ($type == 'comment' || !$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface') || !$entity_type->getBaseTable()) {
continue;
}
if ($fields = \Drupal::service('comment.manager')->getFields($type)) {
$data['comment_field_data'][$type] = array(
'relationship' => array(
'title' => $entity_type->getLabel(),
'help' => t('The @entity_type to which the comment is a reply to.', array('@entity_type' => $entity_type->getLabel())),
'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
'base field' => $entity_type->getKey('id'),
'relationship field' => 'entity_id',
'id' => 'standard',
'label' => $entity_type->getLabel(),
'extra' => array(
array(
'field' => 'entity_type',
'value' => $type,
'table' => 'comment_field_data'
),
),
),
);
}
}
$data['comment_field_data']['uid']['title'] = t('Author uid');
$data['comment_field_data']['uid']['help'] = t('If you need more fields than the uid add the comment: author relationship');
$data['comment_field_data']['uid']['relationship']['title'] = t('Author');
$data['comment_field_data']['uid']['relationship']['help'] = t("The User ID of the comment's author.");
$data['comment_field_data']['uid']['relationship']['label'] = t('author');
$data['comment_field_data']['pid']['title'] = t('Parent CID');
$data['comment_field_data']['pid']['relationship']['title'] = t('Parent comment');
$data['comment_field_data']['pid']['relationship']['help'] = t('The parent comment');
$data['comment_field_data']['pid']['relationship']['label'] = t('parent');
// Define the base group of this table. Fields that don't have a group defined
// will go into this field by default.
$data['comment_entity_statistics']['table']['group'] = t('Comment Statistics');
// Provide a relationship for each entity type except comment.
foreach ($entities_types as $type => $entity_type) {
if ($type == 'comment' || !$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface') || !$entity_type->getBaseTable()) {
continue;
}
// This relationship does not use the 'field id' column, if the entity has
// multiple comment-fields, then this might introduce duplicates, in which
// case the site-builder should enable aggregation and SUM the comment_count
// field. We cannot create a relationship from the base table to
// {comment_entity_statistics} for each field as multiple joins between
// the same two tables is not supported.
if (\Drupal::service('comment.manager')->getFields($type)) {
$data['comment_entity_statistics']['table']['join'][$entity_type->getDataTable() ?: $entity_type->getBaseTable()] = array(
'type' => 'INNER',
'left_field' => $entity_type->getKey('id'),
'field' => 'entity_id',
'extra' => array(
array(
'field' => 'entity_type',
'value' => $type,
),
),
);
}
}
$data['comment_entity_statistics']['last_comment_timestamp'] = array(
'title' => t('Last comment time'),
'help' => t('Date and time of when the last comment was posted.'),
'field' => array(
'id' => 'comment_last_timestamp',
),
'sort' => array(
'id' => 'date',
),
'filter' => array(
'id' => 'date',
),
);
$data['comment_entity_statistics']['last_comment_name'] = array(
'title' => t("Last comment author"),
'help' => t('The name of the author of the last posted comment.'),
'field' => array(
'id' => 'comment_ces_last_comment_name',
'no group by' => TRUE,
),
'sort' => array(
'id' => 'comment_ces_last_comment_name',
'no group by' => TRUE,
),
);
$data['comment_entity_statistics']['comment_count'] = array(
'title' => t('Comment count'),
'help' => t('The number of comments an entity has.'),
'field' => array(
'id' => 'numeric',
),
'filter' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
'argument' => array(
'id' => 'standard',
),
);
$data['comment_entity_statistics']['last_updated'] = array(
'title' => t('Updated/commented date'),
'help' => t('The most recent of last comment posted or entity updated time.'),
'field' => array(
'id' => 'comment_ces_last_updated',
'no group by' => TRUE,
),
'sort' => array(
'id' => 'comment_ces_last_updated',
'no group by' => TRUE,
),
'filter' => array(
'id' => 'comment_ces_last_updated',
),
);
$data['comment_entity_statistics']['cid'] = array(
'title' => t('Last comment CID'),
'help' => t('Display the last comment of an entity'),
'relationship' => array(
'title' => t('Last comment'),
'help' => t('The last comment of an entity.'),
'group' => t('Comment'),
'base' => 'comment',
'base field' => 'cid',
'id' => 'standard',
'label' => t('Last Comment'),
),
);
$data['comment_entity_statistics']['last_comment_uid'] = array(
'title' => t('Last comment uid'),
'help' => t('The User ID of the author of the last comment of an entity.'),
'relationship' => array(
'title' => t('Last comment author'),
'base' => 'users',
'base field' => 'uid',
'id' => 'standard',
'label' => t('Last comment author'),
),
'filter' => array(
'id' => 'numeric',
),
'argument' => array(
'id' => 'numeric',
),
'field' => array(
'id' => 'numeric',
),
);
$data['comment_entity_statistics']['entity_type'] = array(
'title' => t('Entity type'),
'help' => t('The entity type to which the comment is a reply to.'),
'field' => array(
'id' => 'standard',
),
'filter' => array(
'id' => 'string',
),
'argument' => array(
'id' => 'string',
),
'sort' => array(
'id' => 'standard',
),
);
$data['comment_entity_statistics']['field_name'] = array(
'title' => t('Comment field name'),
'help' => t('The field name from which the comment originated.'),
'field' => array(
'id' => 'standard',
),
'filter' => array(
'id' => 'string',
),
'argument' => array(
'id' => 'string',
),
'sort' => array(
'id' => 'standard',
),
);
return $data;
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\comment\Controller\AdminController.
*/
namespace Drupal\comment\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for comment module administrative routes.
*/
class AdminController extends ControllerBase {
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('form_builder')
);
}
/**
* Constructs an AdminController object.
*
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
public function __construct(FormBuilderInterface $form_builder) {
$this->formBuilder = $form_builder;
}
/**
* Presents an administrative comment listing.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
* @param string $type
* The type of the overview form ('approval' or 'new') default to 'new'.
*
* @return array
* Then comment multiple delete confirmation form or the comments overview
* administration form.
*/
public function adminPage(Request $request, $type = 'new') {
if ($request->request->get('operation') == 'delete' && $request->request->get('comments')) {
return $this->formBuilder->getForm('\Drupal\comment\Form\ConfirmDeleteMultiple', $request);
}
else {
return $this->formBuilder->getForm('\Drupal\comment\Form\CommentAdminOverview', $type);
}
}
}

View file

@ -0,0 +1,338 @@
<?php
/**
* @file
* Contains \Drupal\comment\Controller\CommentController.
*/
namespace Drupal\comment\Controller;
use Drupal\comment\CommentInterface;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Controller for the comment entity.
*
* @see \Drupal\comment\Entity\Comment.
*/
class CommentController extends ControllerBase {
/**
* The HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The comment manager service.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $entityManager;
/**
* Constructs a CommentController object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* HTTP kernel to handle requests.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* The comment manager service.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
*/
public function __construct(HttpKernelInterface $http_kernel, CommentManagerInterface $comment_manager, EntityManagerInterface $entity_manager) {
$this->httpKernel = $http_kernel;
$this->commentManager = $comment_manager;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('http_kernel'),
$container->get('comment.manager'),
$container->get('entity.manager')
);
}
/**
* Publishes the specified comment.
*
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse.
* Redirects to the permalink URL for this comment.
*/
public function commentApprove(CommentInterface $comment) {
$comment->setPublished(TRUE);
$comment->save();
drupal_set_message($this->t('Comment approved.'));
$permalink_uri = $comment->permalink();
$permalink_uri->setAbsolute();
return new RedirectResponse($permalink_uri->toString());
}
/**
* Redirects comment links to the correct page depending on comment settings.
*
* Since comments are paged there is no way to guarantee which page a comment
* appears on. Comment paging and threading settings may be changed at any
* time. With threaded comments, an individual comment may move between pages
* as comments can be added either before or after it in the overall
* discussion. Therefore we use a central routing function for comment links,
* which calculates the page number based on current comment settings and
* returns the full comment view with the pager set dynamically.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*
* @return \Symfony\Component\HttpFoundation\Response
* The comment listing set to the page on which the comment appears.
*/
public function commentPermalink(Request $request, CommentInterface $comment) {
if ($entity = $comment->getCommentedEntity()) {
// Check access permissions for the entity.
if (!$entity->access('view')) {
throw new AccessDeniedHttpException();
}
$field_definition = $this->entityManager()->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
// Find the current display page for this comment.
$page = $this->entityManager()->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
// @todo: Cleaner sub request handling.
$redirect_request = Request::create($entity->url(), 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all());
$redirect_request->query->set('page', $page);
// Carry over the session to the subrequest.
if ($session = $request->getSession()) {
$redirect_request->setSession($session);
}
// @todo: Convert the pager to use the request object.
$request->query->set('page', $page);
return $this->httpKernel->handle($redirect_request, HttpKernelInterface::SUB_REQUEST);
}
throw new NotFoundHttpException();
}
/**
* The _title_callback for the page that renders the comment permalink.
*
* @param \Drupal\comment\CommentInterface $comment
* The current comment.
*
* @return string
* The translated comment subject.
*/
public function commentPermalinkTitle(CommentInterface $comment) {
return $this->entityManager()->getTranslationFromContext($comment)->label();
}
/**
* Redirects legacy node links to the new path.
*
* @param \Drupal\Core\Entity\EntityInterface $node
* The node object identified by the legacy URL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* Redirects user to new url.
*/
public function redirectNode(EntityInterface $node) {
$fields = $this->commentManager->getFields('node');
// Legacy nodes only had a single comment field, so use the first comment
// field on the entity.
if (!empty($fields) && ($field_names = array_keys($fields)) && ($field_name = reset($field_names))) {
return $this->redirect('comment.reply', array(
'entity_type' => 'node',
'entity' => $node->id(),
'field_name' => $field_name,
));
}
throw new NotFoundHttpException();
}
/**
* Form constructor for the comment reply form.
*
* There are several cases that have to be handled, including:
* - replies to comments
* - replies to entities
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity this comment belongs to.
* @param string $field_name
* The field_name to which the comment belongs.
* @param int $pid
* (optional) Some comments are replies to other comments. In those cases,
* $pid is the parent comment's comment ID. Defaults to NULL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* An associative array containing:
* - An array for rendering the entity or parent comment.
* - comment_entity: If the comment is a reply to the entity.
* - comment_parent: If the comment is a reply to another comment.
* - comment_form: The comment form as a renderable array.
*/
public function getReplyForm(Request $request, EntityInterface $entity, $field_name, $pid = NULL) {
$account = $this->currentUser();
$uri = $entity->urlInfo()->setAbsolute();
$build = array();
// The user is not just previewing a comment.
if ($request->request->get('op') != $this->t('Preview')) {
// $pid indicates that this is a reply to a comment.
if ($pid) {
// Load the parent comment.
$comment = $this->entityManager()->getStorage('comment')->load($pid);
// Display the parent comment.
$build['comment_parent'] = $this->entityManager()->getViewBuilder('comment')->view($comment);
}
// The comment is in response to a entity.
elseif ($entity->access('view', $account)) {
// We make sure the field value isn't set so we don't end up with a
// redirect loop.
$entity = clone $entity;
$entity->{$field_name}->status = CommentItemInterface::HIDDEN;
// Render array of the entity full view mode.
$build['commented_entity'] = $this->entityManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, 'full');
unset($build['commented_entity']['#cache']);
}
}
else {
$build['#title'] = $this->t('Preview comment');
}
// Show the actual reply box.
$comment = $this->entityManager()->getStorage('comment')->create(array(
'entity_id' => $entity->id(),
'pid' => $pid,
'entity_type' => $entity->getEntityTypeId(),
'field_name' => $field_name,
));
$build['comment_form'] = $this->entityFormBuilder()->getForm($comment);
return $build;
}
/**
* Access check for the reply form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity this comment belongs to.
* @param string $field_name
* The field_name to which the comment belongs.
* @param int $pid
* (optional) Some comments are replies to other comments. In those cases,
* $pid is the parent comment's comment ID. Defaults to NULL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return \Drupal\Core\Access\AccessResultInterface
* An access result
*/
public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NULL) {
// Check if entity and field exists.
$fields = $this->commentManager->getFields($entity->getEntityTypeId());
if (empty($fields[$field_name])) {
throw new NotFoundHttpException();
}
$account = $this->currentUser();
// Check if the user has the proper permissions.
$access = AccessResult::allowedIfHasPermission($account, 'post comments');
$status = $entity->{$field_name}->status;
$access = $access->andIf(AccessResult::allowedIf($status == CommentItemInterface::OPEN)
->cacheUntilEntityChanges($entity));
// $pid indicates that this is a reply to a comment.
if ($pid) {
// Check if the user has the proper permissions.
$access = $access->andIf(AccessResult::allowedIfHasPermission($account, 'access comments'));
/// Load the parent comment.
$comment = $this->entityManager()->getStorage('comment')->load($pid);
// Check if the parent comment is published and belongs to the entity.
$access = $access->andIf(AccessResult::allowedIf($comment && $comment->isPublished() && $comment->getCommentedEntityId() == $entity->id()));
if ($comment) {
$access->cacheUntilEntityChanges($comment);
}
}
return $access;
}
/**
* Returns a set of nodes' last read timestamps.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*/
public function renderNewCommentsNodeLinks(Request $request) {
if ($this->currentUser()->isAnonymous()) {
throw new AccessDeniedHttpException();
}
$nids = $request->request->get('node_ids');
$field_name = $request->request->get('field_name');
if (!isset($nids)) {
throw new NotFoundHttpException();
}
// Only handle up to 100 nodes.
$nids = array_slice($nids, 0, 100);
$links = array();
foreach ($nids as $nid) {
$node = $this->entityManager->getStorage('node')->load($nid);
$new = $this->commentManager->getCountNewComments($node);
$page_number = $this->entityManager()->getStorage('comment')
->getNewCommentPageNumber($node->{$field_name}->comment_count, $new, $node);
$query = $page_number ? array('page' => $page_number) : NULL;
$links[$nid] = array(
'new_comment_count' => (int) $new,
'first_new_comment_link' => $this->getUrlGenerator()->generateFromPath('node/' . $node->id(), array('query' => $query, 'fragment' => 'new')),
);
}
return new JsonResponse($links);
}
}

View file

@ -0,0 +1,587 @@
<?php
/**
* @file
* Contains \Drupal\comment\Entity\Comment.
*/
namespace Drupal\comment\Entity;
use Drupal\Component\Utility\Number;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\comment\CommentInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* Defines the comment entity class.
*
* @ContentEntityType(
* id = "comment",
* label = @Translation("Comment"),
* bundle_label = @Translation("Content type"),
* handlers = {
* "storage" = "Drupal\comment\CommentStorage",
* "storage_schema" = "Drupal\comment\CommentStorageSchema",
* "access" = "Drupal\comment\CommentAccessControlHandler",
* "view_builder" = "Drupal\comment\CommentViewBuilder",
* "views_data" = "Drupal\comment\CommentViewsData",
* "form" = {
* "default" = "Drupal\comment\CommentForm",
* "delete" = "Drupal\comment\Form\DeleteForm"
* },
* "translation" = "Drupal\comment\CommentTranslationHandler"
* },
* base_table = "comment",
* data_table = "comment_field_data",
* uri_callback = "comment_uri",
* translatable = TRUE,
* entity_keys = {
* "id" = "cid",
* "bundle" = "comment_type",
* "label" = "subject",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/comment/{comment}",
* "delete-form" = "/comment/{comment}/delete",
* "edit-form" = "/comment/{comment}/edit",
* },
* bundle_entity_type = "comment_type",
* field_ui_base_route = "entity.comment_type.edit_form",
* constraints = {
* "CommentName" = {}
* }
* )
*/
class Comment extends ContentEntityBase implements CommentInterface {
use EntityChangedTrait;
/**
* The thread for which a lock was acquired.
*/
protected $threadLock = '';
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
if (is_null($this->get('status')->value)) {
$published = \Drupal::currentUser()->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED;
$this->setPublished($published);
}
if ($this->isNew()) {
// Add the comment to database. This next section builds the thread field.
// Also see the documentation for comment_view().
$thread = $this->getThread();
if (empty($thread)) {
if ($this->threadLock) {
// Thread lock was not released after being set previously.
// This suggests there's a bug in code using this class.
throw new \LogicException('preSave() is called again without calling postSave() or releaseThreadLock()');
}
if (!$this->hasParentComment()) {
// This is a comment with no parent comment (depth 0): we start
// by retrieving the maximum thread level.
$max = $storage->getMaxThread($this);
// Strip the "/" from the end of the thread.
$max = rtrim($max, '/');
// We need to get the value at the correct depth.
$parts = explode('.', $max);
$n = Number::alphadecimalToInt($parts[0]);
$prefix = '';
}
else {
// This is a comment with a parent comment, so increase the part of
// the thread value at the proper depth.
// Get the parent comment:
$parent = $this->getParentComment();
// Strip the "/" from the end of the parent thread.
$parent->setThread((string) rtrim((string) $parent->getThread(), '/'));
$prefix = $parent->getThread() . '.';
// Get the max value in *this* thread.
$max = $storage->getMaxThreadPerThread($this);
if ($max == '') {
// First child of this parent. As the other two cases do an
// increment of the thread number before creating the thread
// string set this to -1 so it requires an increment too.
$n = -1;
}
else {
// Strip the "/" at the end of the thread.
$max = rtrim($max, '/');
// Get the value at the correct depth.
$parts = explode('.', $max);
$parent_depth = count(explode('.', $parent->getThread()));
$n = Number::alphadecimalToInt($parts[$parent_depth]);
}
}
// Finally, build the thread field for this new comment. To avoid
// race conditions, get a lock on the thread. If another process already
// has the lock, just move to the next integer.
do {
$thread = $prefix . Number::intToAlphadecimal(++$n) . '/';
$lock_name = "comment:{$this->getCommentedEntityId()}:$thread";
} while (!\Drupal::lock()->acquire($lock_name));
$this->threadLock = $lock_name;
}
// We test the value with '===' because we need to modify anonymous
// users as well.
if ($this->getOwnerId() === \Drupal::currentUser()->id() && \Drupal::currentUser()->isAuthenticated()) {
$this->setAuthorName(\Drupal::currentUser()->getUsername());
}
// Add the values which aren't passed into the function.
$this->setThread($thread);
$this->setHostname(\Drupal::request()->getClientIP());
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
$this->releaseThreadLock();
// Update the {comment_entity_statistics} table prior to executing the hook.
\Drupal::service('comment.statistics')->update($this);
}
/**
* Release the lock acquired for the thread in preSave().
*/
protected function releaseThreadLock() {
if ($this->threadLock) {
\Drupal::lock()->release($this->threadLock);
$this->threadLock = '';
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$child_cids = $storage->getChildCids($entities);
entity_delete_multiple('comment', $child_cids);
foreach ($entities as $id => $entity) {
\Drupal::service('comment.statistics')->update($entity);
}
}
/**
* {@inheritdoc}
*/
public function referencedEntities() {
$referenced_entities = parent::referencedEntities();
if ($this->getCommentedEntityId()) {
$referenced_entities[] = $this->getCommentedEntity();
}
return $referenced_entities;
}
/**
* {@inheritdoc}
*/
public function permalink() {
$entity = $this->getCommentedEntity();
$uri = $entity->urlInfo();
$uri->setOption('fragment', 'comment-' . $this->id());
return $uri;
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['cid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Comment ID'))
->setDescription(t('The comment ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The comment UUID.'))
->setReadOnly(TRUE);
$fields['pid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Parent ID'))
->setDescription(t('The parent comment ID if this is a reply to a comment.'))
->setSetting('target_type', 'comment');
$fields['entity_id'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Entity ID'))
->setDescription(t('The ID of the entity of which this comment is a reply.'))
->setRequired(TRUE);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The comment language code.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
->setDisplayOptions('form', array(
'type' => 'language_select',
'weight' => 2,
));
$fields['subject'] = BaseFieldDefinition::create('string')
->setLabel(t('Subject'))
->setTranslatable(TRUE)
->setSetting('max_length', 64)
->setDisplayOptions('form', array(
'type' => 'string_textfield',
// Default comment body field has weight 20.
'weight' => 10,
))
->setDisplayConfigurable('form', TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('User ID'))
->setDescription(t('The user ID of the comment author.'))
->setTranslatable(TRUE)
->setSetting('target_type', 'user')
->setDefaultValue(0);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t("The comment author's name."))
->setTranslatable(TRUE)
->setSetting('max_length', 60)
->setDefaultValue('');
$fields['mail'] = BaseFieldDefinition::create('email')
->setLabel(t('Email'))
->setDescription(t("The comment author's email address."))
->setTranslatable(TRUE);
$fields['homepage'] = BaseFieldDefinition::create('uri')
->setLabel(t('Homepage'))
->setDescription(t("The comment author's home page address."))
->setTranslatable(TRUE)
// URIs are not length limited by RFC 2616, but we can only store 255
// characters in our comment DB schema.
->setSetting('max_length', 255);
$fields['hostname'] = BaseFieldDefinition::create('string')
->setLabel(t('Hostname'))
->setDescription(t("The comment author's hostname."))
->setTranslatable(TRUE)
->setSetting('max_length', 128);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the comment was created.'))
->setTranslatable(TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the comment was last edited.'))
->setTranslatable(TRUE);
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the comment is published.'))
->setTranslatable(TRUE)
->setDefaultValue(TRUE);
$fields['thread'] = BaseFieldDefinition::create('string')
->setLabel(t('Thread place'))
->setDescription(t("The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length."))
->setSetting('max_length', 255);
$fields['entity_type'] = BaseFieldDefinition::create('string')
->setLabel(t('Entity type'))
->setDescription(t('The entity type to which this comment is attached.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
$fields['comment_type'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Comment Type'))
->setDescription(t('The comment type.'))
->setSetting('target_type', 'comment_type');
$fields['field_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Comment field name'))
->setDescription(t('The field name through which this comment was added.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH);
return $fields;
}
/**
* {@inheritdoc}
*/
public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
if ($comment_type = CommentType::load($bundle)) {
$fields['entity_id'] = clone $base_field_definitions['entity_id'];
$fields['entity_id']->setSetting('target_type', $comment_type->getTargetEntityTypeId());
return $fields;
}
return array();
}
/**
* {@inheritdoc}
*/
public function hasParentComment() {
return (bool) $this->get('pid')->target_id;
}
/**
* {@inheritdoc}
*/
public function getParentComment() {
return $this->get('pid')->entity;
}
/**
* {@inheritdoc}
*/
public function getCommentedEntity() {
return $this->get('entity_id')->entity;
}
/**
* {@inheritdoc}
*/
public function getCommentedEntityId() {
return $this->get('entity_id')->target_id;
}
/**
* {@inheritdoc}
*/
public function getCommentedEntityTypeId() {
return $this->get('entity_type')->value;
}
/**
* {@inheritdoc}
*/
public function setFieldName($field_name) {
$this->set('field_name', $field_name);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFieldName() {
return $this->get('field_name')->value;
}
/**
* {@inheritdoc}
*/
public function getSubject() {
return $this->get('subject')->value;
}
/**
* {@inheritdoc}
*/
public function setSubject($subject) {
$this->set('subject', $subject);
return $this;
}
/**
* {@inheritdoc}
*/
public function getAuthorName() {
if ($this->get('uid')->target_id) {
return $this->get('uid')->entity->label();
}
return $this->get('name')->value ?: \Drupal::config('user.settings')->get('anonymous');
}
/**
* {@inheritdoc}
*/
public function setAuthorName($name) {
$this->set('name', $name);
return $this;
}
/**
* {@inheritdoc}
*/
public function getAuthorEmail() {
$mail = $this->get('mail')->value;
if ($this->get('uid')->target_id != 0) {
$mail = $this->get('uid')->entity->getEmail();
}
return $mail;
}
/**
* {@inheritdoc}
*/
public function getHomepage() {
return $this->get('homepage')->value;
}
/**
* {@inheritdoc}
*/
public function setHomepage($homepage) {
$this->set('homepage', $homepage);
return $this;
}
/**
* {@inheritdoc}
*/
public function getHostname() {
return $this->get('hostname')->value;
}
/**
* {@inheritdoc}
*/
public function setHostname($hostname) {
$this->set('hostname', $hostname);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
if (isset($this->get('created')->value)) {
return $this->get('created')->value;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($created) {
$this->set('created', $created);
return $this;
}
/**
* {@inheritdoc}
*/
public function isPublished() {
return $this->get('status')->value == CommentInterface::PUBLISHED;
}
/**
* {@inheritdoc}
*/
public function getStatus() {
return $this->get('status')->value;
}
/**
* {@inheritdoc}
*/
public function setPublished($status) {
$this->set('status', $status ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED);
return $this;
}
/**
* {@inheritdoc}
*/
public function getThread() {
$thread = $this->get('thread');
if (!empty($thread->value)) {
return $thread->value;
}
}
/**
* {@inheritdoc}
*/
public function setThread($thread) {
$this->set('thread', $thread);
return $this;
}
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
if (empty($values['comment_type']) && !empty($values['field_name']) && !empty($values['entity_type'])) {
$field_storage = FieldStorageConfig::loadByName($values['entity_type'], $values['field_name']);
$values['comment_type'] = $field_storage->getSetting('comment_type');
}
}
/**
* {@inheritdoc}
*/
public function getOwner() {
$user = $this->get('uid')->entity;
if (!$user || $user->isAnonymous()) {
$user = User::getAnonymousUser();
$user->name = $this->getAuthorName();
$user->homepage = $this->getHomepage();
}
return $user;
}
/**
* {@inheritdoc}
*/
public function getOwnerId() {
return $this->get('uid')->target_id;
}
/**
* {@inheritdoc}
*/
public function setOwnerId($uid) {
$this->set('uid', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public function setOwner(UserInterface $account) {
$this->set('uid', $account->id());
return $this;
}
/**
* Get the comment type ID for this comment.
*
* @return string
* The ID of the comment type.
*/
public function getTypeId() {
return $this->bundle();
}
}

View file

@ -0,0 +1,102 @@
<?php
/**
* @file
* Contains \Drupal\comment\Entity\CommentType.
*/
namespace Drupal\comment\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\comment\CommentTypeInterface;
/**
* Defines the comment type entity.
*
* @ConfigEntityType(
* id = "comment_type",
* label = @Translation("Comment type"),
* handlers = {
* "form" = {
* "default" = "Drupal\comment\CommentTypeForm",
* "add" = "Drupal\comment\CommentTypeForm",
* "edit" = "Drupal\comment\CommentTypeForm",
* "delete" = "Drupal\comment\Form\CommentTypeDeleteForm"
* },
* "list_builder" = "Drupal\comment\CommentTypeListBuilder"
* },
* admin_permission = "administer comment types",
* config_prefix = "type",
* bundle_of = "comment",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* links = {
* "delete-form" = "/admin/structure/comment/manage/{comment_type}/delete",
* "edit-form" = "/admin/structure/comment/manage/{comment_type}",
* "add-form" = "/admin/structure/comment/types/add",
* "collection" = "/admin/structure/comment/types",
* },
* config_export = {
* "id",
* "label",
* "target_entity_type_id",
* "description",
* }
* )
*/
class CommentType extends ConfigEntityBundleBase implements CommentTypeInterface {
/**
* The comment type ID.
*
* @var string
*/
protected $id;
/**
* The comment type label.
*
* @var string
*/
protected $label;
/**
* The description of the comment type.
*
* @var string
*/
protected $description;
/**
* The target entity type.
*
* @var string
*/
protected $target_entity_type_id;
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->target_entity_type_id;
}
}

View file

@ -0,0 +1,282 @@
<?php
/**
* @file
* Contains \Drupal\comment\Form\CommentAdminOverview.
*/
namespace Drupal\comment\Form;
use Drupal\comment\CommentInterface;
use Drupal\comment\CommentStorageInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the comments overview administration form.
*/
class CommentAdminOverview extends FormBase {
/**
* The entity storage.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The comment storage.
*
* @var \Drupal\comment\CommentStorageInterface
*/
protected $commentStorage;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatter
*/
protected $dateFormatter;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Creates a CommentAdminOverview form.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\comment\CommentStorageInterface $comment_storage
* The comment storage.
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
* The date formatter service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(EntityManagerInterface $entity_manager, CommentStorageInterface $comment_storage, DateFormatter $date_formatter, ModuleHandlerInterface $module_handler) {
$this->entityManager = $entity_manager;
$this->commentStorage = $comment_storage;
$this->dateFormatter = $date_formatter;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('entity.manager')->getStorage('comment'),
$container->get('date.formatter'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'comment_admin_overview';
}
/**
* Form constructor for the comment overview administration form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param string $type
* The type of the overview form ('approval' or 'new').
*
* @return array
* The form structure.
*/
public function buildForm(array $form, FormStateInterface $form_state, $type = 'new') {
// Build an 'Update options' form.
$form['options'] = array(
'#type' => 'details',
'#title' => $this->t('Update options'),
'#open' => TRUE,
'#attributes' => array('class' => array('container-inline')),
);
if ($type == 'approval') {
$options['publish'] = $this->t('Publish the selected comments');
}
else {
$options['unpublish'] = $this->t('Unpublish the selected comments');
}
$options['delete'] = $this->t('Delete the selected comments');
$form['options']['operation'] = array(
'#type' => 'select',
'#title' => $this->t('Action'),
'#title_display' => 'invisible',
'#options' => $options,
'#default_value' => 'publish',
);
$form['options']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Update'),
);
// Load the comments that need to be displayed.
$status = ($type == 'approval') ? CommentInterface::NOT_PUBLISHED : CommentInterface::PUBLISHED;
$header = array(
'subject' => array(
'data' => $this->t('Subject'),
'specifier' => 'subject',
),
'author' => array(
'data' => $this->t('Author'),
'specifier' => 'name',
'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
),
'posted_in' => array(
'data' => $this->t('Posted in'),
'class' => array(RESPONSIVE_PRIORITY_LOW),
),
'changed' => array(
'data' => $this->t('Updated'),
'specifier' => 'changed',
'sort' => 'desc',
'class' => array(RESPONSIVE_PRIORITY_LOW),
),
'operations' => $this->t('Operations'),
);
$cids = $this->commentStorage->getQuery()
->condition('status', $status)
->tableSort($header)
->pager(50)
->execute();
/** @var $comments \Drupal\comment\CommentInterface[] */
$comments = $this->commentStorage->loadMultiple($cids);
// Build a table listing the appropriate comments.
$options = array();
$destination = $this->getDestinationArray();
$commented_entity_ids = array();
$commented_entities = array();
foreach ($comments as $comment) {
$commented_entity_ids[$comment->getCommentedEntityTypeId()][] = $comment->getCommentedEntityId();
}
foreach ($commented_entity_ids as $entity_type => $ids) {
$commented_entities[$entity_type] = $this->entityManager->getStorage($entity_type)->loadMultiple($ids);
}
foreach ($comments as $comment) {
/** @var $commented_entity \Drupal\Core\Entity\EntityInterface */
$commented_entity = $commented_entities[$comment->getCommentedEntityTypeId()][$comment->getCommentedEntityId()];
$comment_permalink = $comment->permalink();
if ($comment->hasField('comment_body') && ($body = $comment->get('comment_body')->value)) {
$attributes = $comment_permalink->getOption('attributes') ?: array();
$attributes += array('title' => Unicode::truncate($body, 128));
$comment_permalink->setOption('attributes', $attributes);
}
$options[$comment->id()] = array(
'title' => array('data' => array('#title' => $comment->getSubject() ?: $comment->id())),
'subject' => array(
'data' => array(
'#type' => 'link',
'#title' => $comment->getSubject(),
'#url' => $comment_permalink,
),
),
'author' => array(
'data' => array(
'#theme' => 'username',
'#account' => $comment->getOwner(),
),
),
'posted_in' => array(
'data' => array(
'#type' => 'link',
'#title' => $commented_entity->label(),
'#access' => $commented_entity->access('view'),
'#url' => $commented_entity->urlInfo(),
),
),
'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'),
);
$comment_uri_options = $comment->urlInfo()->getOptions() + ['query' => $destination];
$links = array();
$links['edit'] = array(
'title' => $this->t('Edit'),
'url' => $comment->urlInfo('edit-form', $comment_uri_options),
);
if ($this->moduleHandler->moduleExists('content_translation') && $this->moduleHandler->invoke('content_translation', 'translate_access', array($comment))->isAllowed()) {
$links['translate'] = array(
'title' => $this->t('Translate'),
'url' => $comment->urlInfo('drupal:content-translation-overview', $comment_uri_options),
);
}
$options[$comment->id()]['operations']['data'] = array(
'#type' => 'operations',
'#links' => $links,
);
}
$form['comments'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => $this->t('No comments available.'),
);
$form['pager'] = array('#type' => 'pager');
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$form_state->setValue('comments', array_diff($form_state->getValue('comments'), array(0)));
// We can't execute any 'Update options' if no comments were selected.
if (count($form_state->getValue('comments')) == 0) {
$form_state->setErrorByName('', $this->t('Select one or more comments to perform the update on.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$operation = $form_state->getValue('operation');
$cids = $form_state->getValue('comments');
foreach ($cids as $cid) {
// Delete operation handled in \Drupal\comment\Form\ConfirmDeleteMultiple
// see \Drupal\comment\Controller\AdminController::adminPage().
if ($operation == 'unpublish') {
$comment = $this->commentStorage->load($cid);
$comment->setPublished(FALSE);
$comment->save();
}
elseif ($operation == 'publish') {
$comment = $this->commentStorage->load($cid);
$comment->setPublished(TRUE);
$comment->save();
}
}
drupal_set_message($this->t('The update has been performed.'));
$form_state->setRedirect('comment.admin');
}
}

View file

@ -0,0 +1,120 @@
<?php
/**
* @file
* Contains \Drupal\comment\Form\CommentTypeDeleteForm.
*/
namespace Drupal\comment\Form;
use Drupal\comment\CommentManagerInterface;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a confirmation form for deleting a comment type entity.
*/
class CommentTypeDeleteForm extends EntityDeleteForm {
/**
* The query factory to create entity queries.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
public $queryFactory;
/**
* The comment manager service.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManager
*/
protected $entityManager;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The entity being used by this form.
*
* @var \Drupal\comment\CommentTypeInterface
*/
protected $entity;
/**
* Constructs a query factory object.
*
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
* The entity query object.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* The comment manager service.
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* The entity manager service.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(QueryFactory $query_factory, CommentManagerInterface $comment_manager, EntityManager $entity_manager, LoggerInterface $logger) {
$this->queryFactory = $query_factory;
$this->commentManager = $comment_manager;
$this->entityManager = $entity_manager;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query'),
$container->get('comment.manager'),
$container->get('entity.manager'),
$container->get('logger.factory')->get('comment')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$comments = $this->queryFactory->get('comment')->condition('comment_type', $this->entity->id())->execute();
$entity_type = $this->entity->getTargetEntityTypeId();
$caption = '';
foreach (array_keys($this->commentManager->getFields($entity_type)) as $field_name) {
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
if (($field_storage = FieldStorageConfig::loadByName($entity_type, $field_name)) && $field_storage->getSetting('comment_type') == $this->entity->id() && !$field_storage->isDeleted()) {
$caption .= '<p>' . $this->t('%label is used by the %field field on your site. You can not remove this comment type until you have removed the field.', array(
'%label' => $this->entity->label(),
'%field' => $field_storage->label(),
)) . '</p>';
}
}
if (!empty($comments)) {
$caption .= '<p>' . $this->formatPlural(count($comments), '%label is used by 1 comment on your site. You can not remove this comment type until you have removed all of the %label comments.', '%label is used by @count comments on your site. You may not remove %label until you have removed all of the %label comments.', array('%label' => $this->entity->label())) . '</p>';
}
if ($caption) {
$form['description'] = array('#markup' => $caption);
return $form;
}
else {
return parent::buildForm($form, $form_state);
}
}
}

View file

@ -0,0 +1,130 @@
<?php
/**
* @file
* Contains \Drupal\comment\Form\ConfirmDeleteMultiple.
*/
namespace Drupal\comment\Form;
use Drupal\comment\CommentStorageInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the comment multiple delete confirmation form.
*/
class ConfirmDeleteMultiple extends ConfirmFormBase {
/**
* The comment storage.
*
* @var \Drupal\comment\CommentStorageInterface
*/
protected $commentStorage;
/**
* An array of comments to be deleted.
*
* @var \Drupal\comment\CommentInterface[]
*/
protected $comments;
/**
* Creates an new ConfirmDeleteMultiple form.
*
* @param \Drupal\comment\CommentStorageInterface $comment_storage
* The comment storage.
*/
public function __construct(CommentStorageInterface $comment_storage) {
$this->commentStorage = $comment_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('comment')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'comment_multiple_delete_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete these comments and all their children?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('comment.admin');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete comments');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$edit = $form_state->getUserInput();
$form['comments'] = array(
'#prefix' => '<ul>',
'#suffix' => '</ul>',
'#tree' => TRUE,
);
// array_filter() returns only elements with actual values.
$comment_counter = 0;
$this->comments = $this->commentStorage->loadMultiple(array_keys(array_filter($edit['comments'])));
foreach ($this->comments as $comment) {
$cid = $comment->id();
$form['comments'][$cid] = array(
'#type' => 'hidden',
'#value' => $cid,
'#prefix' => '<li>',
'#suffix' => SafeMarkup::checkPlain($comment->label()) . '</li>'
);
$comment_counter++;
}
$form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
if (!$comment_counter) {
drupal_set_message($this->t('There do not appear to be any comments to delete, or your selected comment was deleted by another administrator.'));
$form_state->setRedirect('comment.admin');
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('confirm')) {
$this->commentStorage->delete($this->comments);
$count = count($form_state->getValue('comments'));
$this->logger('content')->notice('Deleted @count comments.', array('@count' => $count));
drupal_set_message($this->formatPlural($count, 'Deleted 1 comment.', 'Deleted @count comments.'));
}
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\comment\Form\DeleteForm.
*/
namespace Drupal\comment\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
/**
* Provides the comment delete confirmation form.
*/
class DeleteForm extends ContentEntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
// Point to the entity of which this comment is a reply.
return $this->entity->get('entity_id')->entity->urlInfo();
}
/**
* {@inheritdoc}
*/
protected function getRedirectUrl() {
return $this->getCancelUrl();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Any replies to this comment will be lost. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
return $this->t('The comment and all its replies have been deleted.');
}
/**
* {@inheritdoc}
*/
public function logDeletionMessage() {
$this->logger('content')->notice('Deleted comment @cid and its replies.', array('@cid' => $this->entity->id()));
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Action\PublishComment.
*/
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Publishes a comment.
*
* @Action(
* id = "comment_publish_action",
* label = @Translation("Publish comment"),
* type = "comment"
* )
*/
class PublishComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->setPublished(TRUE);
$comment->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
$result = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Action\SaveComment.
*/
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Saves a comment.
*
* @Action(
* id = "comment_save_action",
* label = @Translation("Save comment"),
* type = "comment"
* )
*/
class SaveComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
return $object->access('update', $account, $return_as_object);
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Action\UnpublishByKeywordComment.
*/
namespace Drupal\comment\Plugin\Action;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a comment containing certain keywords.
*
* @Action(
* id = "comment_unpublish_by_keyword_action",
* label = @Translation("Unpublish comment containing keyword(s)"),
* type = "comment"
* )
*/
class UnpublishByKeywordComment extends ConfigurableActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$build = comment_view($comment);
$text = \Drupal::service('renderer')->renderPlain($build);
foreach ($this->configuration['keywords'] as $keyword) {
if (strpos($text, $keyword) !== FALSE) {
$comment->setPublished(FALSE);
$comment->save();
break;
}
}
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'keywords' => array(),
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['keywords'] = array(
'#title' => t('Keywords'),
'#type' => 'textarea',
'#description' => t('The comment will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
'#default_value' => Tags::implode($this->configuration['keywords']),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Action\UnpublishComment.
*/
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a comment.
*
* @Action(
* id = "comment_unpublish_action",
* label = @Translation("Unpublish comment"),
* type = "comment"
* )
*/
class UnpublishComment extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($comment = NULL) {
$comment->setPublished(FALSE);
$comment->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $object */
$result = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\EntityReferenceSelection\CommentSelection.
*/
namespace Drupal\comment\Plugin\EntityReferenceSelection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase;
use Drupal\comment\CommentInterface;
/**
* Provides specific access control for the comment entity type.
*
* @EntityReferenceSelection(
* id = "default:comment",
* label = @Translation("Comment selection"),
* entity_types = {"comment"},
* group = "default",
* weight = 1
* )
*/
class CommentSelection extends SelectionBase {
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
// Adding the 'comment_access' tag is sadly insufficient for comments:
// core requires us to also know about the concept of 'published' and
// 'unpublished'.
if (!$this->currentUser->hasPermission('administer comments')) {
$query->condition('status', CommentInterface::PUBLISHED);
}
return $query;
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) {
$tables = $query->getTables();
$data_table = 'comment_field_data';
if (!isset($tables['comment_field_data']['alias'])) {
// If no conditions join against the comment data table, it should be
// joined manually to allow node access processing.
$query->innerJoin($data_table, NULL, "base_table.cid = $data_table.cid AND $data_table.default_langcode = 1");
}
// The Comment module doesn't implement any proper comment access,
// and as a consequence doesn't make sure that comments cannot be viewed
// when the user doesn't have access to the node.
$node_alias = $query->innerJoin('node_field_data', 'n', '%alias.nid = ' . $data_table . '.entity_id AND ' . $data_table . ".entity_type = 'node'");
// Pass the query to the node access control.
$this->reAlterQuery($query, 'node_access', $node_alias);
// Passing the query to node_query_node_access_alter() is sadly
// insufficient for nodes.
// @see SelectionEntityTypeNode::entityQueryAlter()
if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) {
$query->condition($node_alias . '.status', 1);
}
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Field\FieldFormatter\AuthorNameFormatter.
*/
namespace Drupal\comment\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'comment_username' formatter.
*
* @FieldFormatter(
* id = "comment_username",
* label = @Translation("Author name"),
* description = @Translation("Display the author name."),
* field_types = {
* "string"
* }
* )
*/
class AuthorNameFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
$elements = array();
foreach ($items as $delta => $item) {
/** @var $comment \Drupal\comment\CommentInterface */
$comment = $item->getEntity();
$account = $comment->getOwner();
$elements[$delta] = array(
'#theme' => 'username',
'#account' => $account,
'#cache' => array(
'tags' => $account->getCacheTags() + $comment->getCacheTags(),
),
);
}
return $elements;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return $field_definition->getName() === 'name' && $field_definition->getTargetEntityTypeId() === 'comment';
}
}

View file

@ -0,0 +1,254 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter.
*/
namespace Drupal\comment\Plugin\Field\FieldFormatter;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\CommentStorageInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a default comment formatter.
*
* @FieldFormatter(
* id = "comment_default",
* module = "comment",
* label = @Translation("Comment list"),
* field_types = {
* "comment"
* },
* quickedit = {
* "editor" = "disabled"
* }
* )
*/
class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'pager_id' => 0,
) + parent::defaultSettings();
}
/**
* The comment storage.
*
* @var \Drupal\comment\CommentStorageInterface
*/
protected $storage;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The comment render controller.
*
* @var \Drupal\Core\Entity\EntityViewBuilderInterface
*/
protected $viewBuilder;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity form builder.
*
* @var \Drupal\Core\Entity\EntityFormBuilderInterface
*/
protected $entityFormBuilder;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('current_user'),
$container->get('entity.manager'),
$container->get('entity.form_builder')
);
}
/**
* Constructs a new CommentDefaultFormatter.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Third party settings.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager
* @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
* The entity form builder.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityManagerInterface $entity_manager, EntityFormBuilderInterface $entity_form_builder) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->viewBuilder = $entity_manager->getViewBuilder('comment');
$this->storage = $entity_manager->getStorage('comment');
$this->currentUser = $current_user;
$this->entityManager = $entity_manager;
$this->entityFormBuilder = $entity_form_builder;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
$elements = array();
$output = array();
$field_name = $this->fieldDefinition->getName();
$entity = $items->getEntity();
$status = $items->status;
if ($status != CommentItemInterface::HIDDEN && empty($entity->in_preview) &&
// Comments are added to the search results and search index by
// comment_node_update_index() instead of by this formatter, so don't
// return anything if the view mode is search_index or search_result.
!in_array($this->viewMode, array('search_result', 'search_index'))) {
$comment_settings = $this->getFieldSettings();
// Only attempt to render comments if the entity has visible comments.
// Unpublished comments are not included in
// $entity->get($field_name)->comment_count, but unpublished comments
// should display if the user is an administrator.
$elements['#cache']['contexts'][] = 'user.permissions';
if ($this->currentUser->hasPermission('access comments') || $this->currentUser->hasPermission('administer comments')) {
// This is a listing of Comment entities, so associate its list cache
// tag for correct invalidation.
$output['comments']['#cache']['tags'] = $this->entityManager->getDefinition('comment')->getListCacheTags();
if ($entity->get($field_name)->comment_count || $this->currentUser->hasPermission('administer comments')) {
$mode = $comment_settings['default_mode'];
$comments_per_page = $comment_settings['per_page'];
$comments = $this->storage->loadThread($entity, $field_name, $mode, $comments_per_page, $this->getSetting('pager_id'));
if ($comments) {
$build = $this->viewBuilder->viewMultiple($comments);
$build['pager']['#type'] = 'pager';
if ($this->getSetting('pager_id')) {
$build['pager']['#element'] = $this->getSetting('pager_id');
}
$output['comments'] += $build;
}
}
}
// 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'] == CommentItemInterface::FORM_BELOW && $this->viewMode != 'print') {
// Only show the add comment form if the user has permission.
$elements['#cache']['contexts'][] = 'user.roles';
if ($this->currentUser->hasPermission('post comments')) {
// All users in the "anonymous" role can use the same form: it is fine
// for this form to be stored in the render cache.
if ($this->currentUser->isAnonymous()) {
$comment = $this->storage->create(array(
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'field_name' => $field_name,
'comment_type' => $this->getFieldSetting('comment_type'),
'pid' => NULL,
));
$output['comment_form'] = $this->entityFormBuilder->getForm($comment);
}
// All other users need a user-specific form, which would break the
// render cache: hence use a #lazy_builder callback.
else {
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', [
$entity->getEntityTypeId(),
$entity->id(),
$field_name,
$this->getFieldSetting('comment_type'),
]],
'#create_placeholder' => TRUE,
];
}
}
}
$elements[] = $output + array(
'#comment_type' => $this->getFieldSetting('comment_type'),
'#comment_display_mode' => $this->getFieldSetting('default_mode'),
'comments' => array(),
'comment_form' => array(),
);
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = array();
$element['pager_id'] = array(
'#type' => 'select',
'#title' => $this->t('Pager ID'),
'#options' => range(0, 10),
'#default_value' => $this->getSetting('pager_id'),
'#description' => $this->t("Unless you're experiencing problems with pagers related to this field, you should leave this at 0. If using multiple pagers on one page you may need to set this number to a higher value so as not to conflict within the ?page= array. Large values will add a lot of commas to your URLs, so avoid if possible."),
);
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
// Only show a summary if we're using a non-standard pager id.
if ($this->getSetting('pager_id')) {
return array($this->t('Pager ID: @id', array(
'@id' => $this->getSetting('pager_id'),
)));
}
return array();
}
}

View file

@ -0,0 +1,211 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Field\FieldType\CommentItem.
*/
namespace Drupal\comment\Plugin\Field\FieldType;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\Entity\CommentType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Session\AnonymousUserSession;
/**
* Plugin implementation of the 'comment' field type.
*
* @FieldType(
* id = "comment",
* label = @Translation("Comments"),
* description = @Translation("This field manages configuration and presentation of comments on an entity."),
* list_class = "\Drupal\comment\CommentFieldItemList",
* default_widget = "comment_default",
* default_formatter = "comment_default"
* )
*/
class CommentItem extends FieldItemBase implements CommentItemInterface {
use UrlGeneratorTrait;
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return array(
'comment_type' => '',
) + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return array(
'default_mode' => CommentManagerInterface::COMMENT_MODE_THREADED,
'per_page' => 50,
'form_location' => CommentItemInterface::FORM_BELOW,
'anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
'preview' => DRUPAL_OPTIONAL,
) + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['status'] = DataDefinition::create('integer')
->setLabel(t('Comment status'))
->setRequired(TRUE);
$properties['cid'] = DataDefinition::create('integer')
->setLabel(t('Last comment ID'));
$properties['last_comment_timestamp'] = DataDefinition::create('integer')
->setLabel(t('Last comment timestamp'))
->setDescription(t('The time that the last comment was created.'));
$properties['last_comment_name'] = DataDefinition::create('string')
->setLabel(t('Last comment name'))
->setDescription(t('The name of the user posting the last comment.'));
$properties['last_comment_uid'] = DataDefinition::create('integer')
->setLabel(t('Last comment user ID'));
$properties['comment_count'] = DataDefinition::create('integer')
->setLabel(t('Number of comments'))
->setDescription(t('The number of comments.'));
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return array(
'columns' => array(
'status' => array(
'description' => 'Whether comments are allowed on this entity: 0 = no, 1 = closed (read only), 2 = open (read/write).',
'type' => 'int',
'default' => 0,
),
),
'indexes' => array(),
'foreign keys' => array(),
);
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$element = array();
$settings = $this->getSettings();
$anonymous_user = new AnonymousUserSession();
$element['default_mode'] = array(
'#type' => 'checkbox',
'#title' => t('Threading'),
'#default_value' => $settings['default_mode'],
'#description' => t('Show comment replies in a threaded list.'),
);
$element['per_page'] = array(
'#type' => 'number',
'#title' => t('Comments per page'),
'#default_value' => $settings['per_page'],
'#required' => TRUE,
'#min' => 10,
'#max' => 1000,
'#step' => 10,
);
$element['anonymous'] = array(
'#type' => 'select',
'#title' => t('Anonymous commenting'),
'#default_value' => $settings['anonymous'],
'#options' => array(
COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'),
COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'),
COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information'),
),
'#access' => $anonymous_user->hasPermission('post comments'),
);
$element['form_location'] = array(
'#type' => 'checkbox',
'#title' => t('Show reply form on the same page as comments'),
'#default_value' => $settings['form_location'],
);
$element['preview'] = array(
'#type' => 'radios',
'#title' => t('Preview comment'),
'#default_value' => $settings['preview'],
'#options' => array(
DRUPAL_DISABLED => t('Disabled'),
DRUPAL_OPTIONAL => t('Optional'),
DRUPAL_REQUIRED => t('Required'),
),
);
return $element;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
// There is always a value for this field, it is one of
// CommentItemInterface::OPEN, CommentItemInterface::CLOSED or
// CommentItemInterface::HIDDEN.
return FALSE;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = array();
// @todo Inject entity storage once typed-data supports container injection.
// See https://www.drupal.org/node/2053415 for more details.
$comment_types = CommentType::loadMultiple();
$options = array();
$entity_type = $this->getEntity()->getEntityTypeId();
foreach ($comment_types as $comment_type) {
if ($comment_type->getTargetEntityTypeId() == $entity_type) {
$options[$comment_type->id()] = $comment_type->label();
}
}
$element['comment_type'] = array(
'#type' => 'select',
'#title' => t('Comment type'),
'#options' => $options,
'#required' => TRUE,
'#description' => $this->t('Select the Comment type to use for this comment field. Manage the comment types from the <a href="@url">administration overview page</a>.', array('@url' => $this->url('entity.comment_type.collection'))),
'#default_value' => $this->getSetting('comment_type'),
'#disabled' => $has_data,
);
return $element;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$statuses = [
CommentItemInterface::HIDDEN,
CommentItemInterface::CLOSED,
CommentItemInterface::OPEN,
];
return [
'status' => $statuses[mt_rand(0, count($statuses) - 1)],
];
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Field\FieldType\CommentItemInterface.
*/
namespace Drupal\comment\Plugin\Field\FieldType;
/**
* Interface definition for Comment items.
*/
interface CommentItemInterface {
/**
* Comments for this entity are hidden.
*/
const HIDDEN = 0;
/**
* Comments for this entity are closed.
*/
const CLOSED = 1;
/**
* Comments for this entity are open.
*/
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;
}

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Field\FieldWidget\CommentWidget.
*/
namespace Drupal\comment\Plugin\Field\FieldWidget;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a default comment widget.
*
* @FieldWidget(
* id = "comment_default",
* label = @Translation("Comment"),
* field_types = {
* "comment"
* }
* )
*/
class CommentWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$entity = $items->getEntity();
$element['status'] = array(
'#type' => 'radios',
'#title' => t('Comments'),
'#title_display' => 'invisible',
'#default_value' => $items->status,
'#options' => array(
CommentItemInterface::OPEN => t('Open'),
CommentItemInterface::CLOSED => t('Closed'),
CommentItemInterface::HIDDEN => t('Hidden'),
),
CommentItemInterface::OPEN => array(
'#description' => t('Users with the "Post comments" permission can post comments.'),
),
CommentItemInterface::CLOSED => array(
'#description' => t('Users cannot post comments, but existing comments will be displayed.'),
),
CommentItemInterface::HIDDEN => array(
'#description' => t('Comments are hidden from view.'),
),
);
// If the entity doesn't have any comments, the "hidden" option makes no
// sense, so don't even bother presenting it to the user unless this is the
// default value widget on the field settings form.
if (!$this->isDefaultValueWidget($form_state) && !$items->comment_count) {
$element['status'][CommentItemInterface::HIDDEN]['#access'] = FALSE;
// Also adjust the description of the "closed" option.
$element['status'][CommentItemInterface::CLOSED]['#description'] = t('Users cannot post comments.');
}
// If the advanced settings tabs-set is available (normally rendered in the
// second column on wide-resolutions), place the field as a details element
// in this tab-set.
if (isset($form['advanced'])) {
// Get default value from the field.
$field_default_values = $this->fieldDefinition->getDefaultValue($entity);
// Override widget title to be helpful for end users.
$element['#title'] = $this->t('Comment settings');
$element += array(
'#type' => 'details',
// Open the details when the selected value is different to the stored
// default values for the field.
'#open' => ($items->status != $field_default_values[0]['status']),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('comment-' . Html::getClass($entity->getEntityTypeId()) . '-settings-form'),
),
'#attached' => array(
'library' => array('comment/drupal.comment'),
),
);
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// Add default values for statistics properties because we don't want to
// have them in form.
foreach ($values as &$value) {
$value += array(
'cid' => 0,
'last_comment_timestamp' => 0,
'last_comment_name' => '',
'last_comment_uid' => 0,
'comment_count' => 0,
);
}
return $values;
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Menu\LocalTask\UnapprovedComments.
*/
namespace Drupal\comment\Plugin\Menu\LocalTask;
use Drupal\comment\CommentStorageInterface;
use Drupal\Core\Menu\LocalTaskDefault;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a local task that shows the amount of unapproved comments.
*/
class UnapprovedComments extends LocalTaskDefault implements ContainerFactoryPluginInterface {
/**
* The comment storage service.
*
* @var \Drupal\comment\CommentStorageInterface
*/
protected $commentStorage;
/**
* Construct the UnapprovedComments object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\comment\CommentStorageInterface $comment_storage
* The comment storage service.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, CommentStorageInterface $comment_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->commentStorage = $comment_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('comment')
);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return t('Unapproved comments (@count)', array('@count' => $this->commentStorage->getUnapprovedCount()));
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Validation\Constraint\CommentNameConstraint.
*/
namespace Drupal\comment\Plugin\Validation\Constraint;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Supports validating comment author names.
*
* @Constraint(
* id = "CommentName",
* label = @Translation("Comment author name", context = "Validation"),
* type = "entity:comment"
* )
*/
class CommentNameConstraint extends CompositeConstraintBase {
/**
* Message shown when an anonymous user comments using a registered name.
*
* @var string
*/
public $messageNameTaken = 'The name you used (%name) belongs to a registered user.';
/**
* Message shown when an admin changes the comment-author to an invalid user.
*
* @var string
*/
public $messageRequired = 'You have to specify a valid author.';
/**
* Message shown when the name doesn't match the author's name.
*
* @var string
*/
public $messageMatch = 'The specified author name does not match the comment author.';
/**
* {@inheritdoc}
*/
public function coversFields() {
return ['name', 'uid'];
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\Validation\Constraint\CommentNameConstraintValidator.
*/
namespace Drupal\comment\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\comment\CommentInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the CommentName constraint.
*/
class CommentNameConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* Validator 2.5 and upwards compatible execution context.
*
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
*/
protected $context;
/**
* User storage handler.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new CommentNameConstraintValidator.
*
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage handler.
*/
public function __construct(UserStorageInterface $user_storage) {
$this->userStorage = $user_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager')->getStorage('user'));
}
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
$author_name = $entity->name->value;
$owner_id = (int) $entity->uid->target_id;
// Do not allow unauthenticated comment authors to use a name that is
// taken by a registered user.
if (isset($author_name) && $author_name !== '' && $owner_id === 0) {
$users = $this->userStorage->loadByProperties(array('name' => $author_name));
if (!empty($users)) {
$this->context->buildViolation($constraint->messageNameTaken, array('%name' => $author_name))
->atPath('name')
->addViolation();
}
}
// If an author name and owner are given, make sure they match.
elseif (isset($author_name) && $author_name !== '' && $owner_id) {
$owner = $this->userStorage->load($owner_id);
if ($owner->getUsername() != $author_name) {
$this->context->buildViolation($constraint->messageMatch)
->atPath('name')
->addViolation();
}
}
// Anonymous account might be required - depending on field settings.
if ($owner_id === 0 && empty($author_name) &&
$this->getAnonymousContactDetailsSetting($entity) === COMMENT_ANONYMOUS_MUST_CONTACT) {
$this->context->buildViolation($constraint->messageRequired)
->atPath('name')
->addViolation();
}
}
/**
* Gets the anonymous contact details setting from the comment.
*
* @param \Drupal\comment\CommentInterface $comment
* The entity.
*
* @return int
* The anonymous contact setting.
*/
protected function getAnonymousContactDetailsSetting(CommentInterface $comment) {
return $comment
->getCommentedEntity()
->get($comment->getFieldName())
->getFieldDefinition()
->getSetting('anonymous');
}
}

View file

@ -0,0 +1,114 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\argument\UserUid.
*/
namespace Drupal\comment\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Connection;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler to accept a user id to check for nodes that
* user posted or commented on.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("argument_comment_user_uid")
*/
class UserUid extends ArgumentPluginBase {
/**
* Database Service Object.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $database
* Database Service Object.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'));
}
function title() {
if (!$this->argument) {
$title = \Drupal::config('user.settings')->get('anonymous');
}
else {
$title = $this->database->query('SELECT name FROM {users_field_data} WHERE uid = :uid AND default_langcode = 1', array(':uid' => $this->argument))->fetchField();
}
if (empty($title)) {
return $this->t('No user');
}
return SafeMarkup::checkPlain($title);
}
protected function defaultActions($which = NULL) {
// Disallow summary views on this argument.
if (!$which) {
$actions = parent::defaultActions();
unset($actions['summary asc']);
unset($actions['summary desc']);
return $actions;
}
if ($which != 'summary asc' && $which != 'summary desc') {
return parent::defaultActions($which);
}
}
public function query($group_by = FALSE) {
$this->ensureMyTable();
// Use the table definition to correctly add this user ID condition.
if ($this->table != 'comment_field_data') {
$subselect = $this->database->select('comment_field_data', 'c');
$subselect->addField('c', 'cid');
$subselect->condition('c.uid', $this->argument);
$entity_id = $this->definition['entity_id'];
$entity_type = $this->definition['entity_type'];
$subselect->where("c.entity_id = $this->tableAlias.$entity_id");
$subselect->condition('c.entity_type', $entity_type);
$condition = db_or()
->condition("$this->tableAlias.uid", $this->argument, '=')
->exists($subselect);
$this->query->addWhere(0, $condition);
}
}
/**
* {@inheritdoc}
*/
public function getSortName() {
return $this->t('Numerical', array(), array('context' => 'Sort order'));
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\Depth.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\ResultRow;
/**
* Field handler to display the depth of a comment.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_depth")
*/
class Depth extends Field {
/**
* {@inheritdoc}
*/
public function getItems(ResultRow $values) {
$items = parent::getItems($values);
foreach ($items as &$item) {
// Work out the depth of this comment.
$comment_thread = $item['rendered']['#markup'];
$item['rendered']['#markup'] = count(explode('.', $comment_thread)) - 1;
}
return $items;
}
}

View file

@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\EntityLink.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
/**
* Handler for showing comment module's entity links.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_entity_link")
*/
class EntityLink extends FieldPluginBase {
/**
* Stores the result of node_view_multiple for all rows to reuse it later.
*
* @var array
*/
protected $build;
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['teaser'] = array('default' => FALSE);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['teaser'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Show teaser-style link'),
'#default_value' => $this->options['teaser'],
'#description' => $this->t('Show the comment link in the form used on standard entity teasers, rather than the full entity form.'),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function query() {}
/**
* {@inheritdoc}
*/
public function preRender(&$values) {
// Render all nodes, so you can grep the comment links.
$entities = array();
foreach ($values as $row) {
$entity = $row->_entity;
$entities[$entity->id()] = $entity;
}
if ($entities) {
$this->build = entity_view_multiple($entities, $this->options['teaser'] ? 'teaser' : 'full');
}
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$entity = $this->getEntity($values);
// Only render the links, if they are defined.
return !empty($this->build[$entity->id()]['links']['comment__comment']) ? drupal_render($this->build[$entity->id()]['links']['comment__comment']) : '';
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\LastTimestamp.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\views\Plugin\views\field\Date;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Field handler to display the timestamp of a comment with the count of comments.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_last_timestamp")
*/
class LastTimestamp extends Date {
/**
* Overrides Drupal\views\Plugin\views\field\FieldPluginBase::init().
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->additional_fields['comment_count'] = 'comment_count';
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$comment_count = $this->getValue($values, 'comment_count');
if (empty($this->options['empty_zero']) || $comment_count) {
return parent::render($values);
}
else {
return NULL;
}
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\LinkApprove.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\field\LinkBase;
use Drupal\views\ResultRow;
/**
* Provides a comment approve link.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_link_approve")
*/
class LinkApprove extends LinkBase {
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
return Url::fromRoute('comment.approve', ['comment' => $this->getEntity($row)->id()]);
}
/**
* {@inheritdoc}
*/
protected function renderLink(ResultRow $row) {
$this->options['alter']['query'] = $this->getDestinationArray();
return parent::renderLink($row);
}
/**
* {@inheritdoc}
*/
protected function getDefaultLabel() {
return $this->t('Approve');
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\LinkReply.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\field\LinkBase;
use Drupal\views\ResultRow;
/**
* Field handler to present a link to reply to a comment.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_link_reply")
*/
class LinkReply extends LinkBase {
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
/** @var \Drupal\comment\CommentInterface $comment */
$comment = $this->getEntity($row);
return Url::fromRoute('comment.reply', [
'entity_type' => $comment->getCommentedEntityTypeId(),
'entity' => $comment->getCommentedEntityId(),
'field_name' => $comment->getFieldName(),
'pid' => $comment->id(),
]);
}
/**
* {@inheritdoc}
*/
protected function getDefaultLabel() {
return $this->t('Reply');
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\NodeNewComments.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\Core\Database\Connection;
use Drupal\comment\CommentInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\field\NumericField;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Field handler to display the number of new comments.
*
* @ingroup views_field_handlers
*
* @ViewsField("node_new_comments")
*/
class NodeNewComments extends NumericField {
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
/**
* Database Service Object.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $database
* Database Service Object.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'));
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->additional_fields['entity_id'] = 'nid';
$this->additional_fields['type'] = 'type';
$this->additional_fields['comment_count'] = array('table' => 'comment_entity_statistics', 'field' => 'comment_count');
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['link_to_comment'] = array('default' => TRUE);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['link_to_comment'] = array(
'#title' => $this->t('Link this field to new comments'),
'#description' => $this->t("Enable to override this field's links."),
'#type' => 'checkbox',
'#default_value' => $this->options['link_to_comment'],
);
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function query() {
$this->ensureMyTable();
$this->addAdditionalFields();
$this->field_alias = $this->table . '_' . $this->field;
}
/**
* {@inheritdoc}
*/
public function preRender(&$values) {
$user = \Drupal::currentUser();
if ($user->isAnonymous() || empty($values)) {
return;
}
$nids = array();
$ids = array();
foreach ($values as $id => $result) {
$nids[] = $result->{$this->aliases['nid']};
$values[$id]->{$this->field_alias} = 0;
// Create a reference so we can find this record in the values again.
if (empty($ids[$result->{$this->aliases['nid']}])) {
$ids[$result->{$this->aliases['nid']}] = array();
}
$ids[$result->{$this->aliases['nid']}][] = $id;
}
if ($nids) {
$result = $this->database->query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment_field_data} c ON n.nid = c.entity_id AND c.entity_type = 'node' AND c.default_langcode = 1
LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = :h_uid WHERE n.nid IN ( :nids[] )
AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp1), :timestamp2) AND c.status = :status GROUP BY n.nid", array(
':status' => CommentInterface::PUBLISHED,
':h_uid' => $user->id(),
':nids[]' => $nids,
':timestamp1' => HISTORY_READ_LIMIT,
':timestamp2' => HISTORY_READ_LIMIT,
));
foreach ($result as $node) {
foreach ($ids[$node->id()] as $id) {
$values[$id]->{$this->field_alias} = $node->num_comments;
}
}
}
}
/**
* Prepares the link to the first new comment.
*
* @param string $data
* The XSS safe string for the link text.
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* Returns a string for the link text.
*/
protected function renderLink($data, ResultRow $values) {
if (!empty($this->options['link_to_comment']) && $data !== NULL && $data !== '') {
$node = entity_create('node', array(
'nid' => $this->getValue($values, 'nid'),
'type' => $this->getValue($values, 'type'),
));
$page_number = \Drupal::entityManager()->getStorage('comment')
->getNewCommentPageNumber($this->getValue($values, 'comment_count'), $this->getValue($values), $node);
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['url'] = $node->urlInfo();
$this->options['alter']['query'] = $page_number ? array('page' => $page_number) : NULL;
$this->options['alter']['fragment'] = 'new';
}
return $data;
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$value = $this->getValue($values);
if (!empty($value)) {
return $this->renderLink(parent::render($values), $values);
}
else {
$this->options['alter']['make_link'] = FALSE;
}
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\StatisticsLastCommentName.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
/**
* Field handler to present the name of the last comment poster.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_ces_last_comment_name")
*/
class StatisticsLastCommentName extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function query() {
// last_comment_name only contains data if the user is anonymous. So we
// have to join in a specially related user table.
$this->ensureMyTable();
// join 'users' to this table via vid
$definition = array(
'table' => 'users_field_data',
'field' => 'uid',
'left_table' => 'comment_entity_statistics',
'left_field' => 'last_comment_uid',
'extra' => array(
array(
'field' => 'uid',
'operator' => '!=',
'value' => '0'
)
)
);
$join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $definition);
// nes_user alias so this can work with the sort handler, below.
$this->user_table = $this->query->ensureTable('ces_users', $this->relationship, $join);
$this->field_alias = $this->query->addField(NULL, "COALESCE($this->user_table.name, $this->tableAlias.$this->field)", $this->tableAlias . '_' . $this->field);
$this->user_field = $this->query->addField($this->user_table, 'name');
$this->uid = $this->query->addField($this->tableAlias, 'last_comment_uid');
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['link_to_user'] = array('default' => TRUE);
return $options;
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
if (!empty($this->options['link_to_user'])) {
$account = entity_create('user');
$account->name = $this->getValue($values);
$account->uid = $values->{$this->uid};
$username = array(
'#theme' => 'username',
'#account' => $account,
);
return drupal_render($username);
}
else {
return $this->sanitizeValue($this->getValue($values));
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\field\StatisticsLastUpdated.
*/
namespace Drupal\comment\Plugin\views\field;
use Drupal\views\Plugin\views\field\Date;
/**
* Field handler to display the newer of last comment / node updated.
*
* @ingroup views_field_handlers
*
* @ViewsField("comment_ces_last_updated")
*/
class StatisticsLastUpdated extends Date {
public function query() {
$this->ensureMyTable();
$this->node_table = $this->query->ensureTable('node_field_data', $this->relationship);
$this->field_alias = $this->query->addField(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)", $this->tableAlias . '_' . $this->field);
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\filter\NodeComment.
*/
namespace Drupal\comment\Plugin\views\filter;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\views\Plugin\views\filter\InOperator;
/**
* Filter based on comment node status.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("node_comment")
*/
class NodeComment extends InOperator {
public function getValueOptions() {
$this->valueOptions = array(
CommentItemInterface::HIDDEN => $this->t('Hidden'),
CommentItemInterface::CLOSED => $this->t('Closed'),
CommentItemInterface::OPEN => $this->t('Open'),
);
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\filter\StatisticsLastUpdated.
*/
namespace Drupal\comment\Plugin\views\filter;
use Drupal\views\Plugin\views\filter\Date;
/**
* Filter handler for the newer of last comment / node updated.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("comment_ces_last_updated")
*/
class StatisticsLastUpdated extends Date {
public function query() {
$this->ensureMyTable();
$this->node_table = $this->query->ensureTable('node', $this->relationship);
$field = "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)";
$info = $this->operators();
if (!empty($info[$this->operator]['method'])) {
$this->{$info[$this->operator]['method']}($field);
}
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\filter\UserUid.
*/
namespace Drupal\comment\Plugin\views\filter;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
/**
* Filter handler to accept a user id to check for nodes that user posted or
* commented on.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("comment_user_uid")
*/
class UserUid extends FilterPluginBase {
public function query() {
$this->ensureMyTable();
$subselect = db_select('comment_field_data', 'c');
$subselect->addField('c', 'cid');
$subselect->condition('c.uid', $this->value, $this->operator);
$entity_id = $this->definition['entity_id'];
$entity_type = $this->definition['entity_type'];
$subselect->where("c.entity_id = $this->tableAlias.$entity_id");
$subselect->condition('c.entity_type', $entity_type);
$condition = db_or()
->condition("$this->tableAlias.uid", $this->value, $this->operator)
->exists($subselect);
$this->query->addWhere($this->options['group'], $condition);
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\row\Rss.
*/
namespace Drupal\comment\Plugin\views\row;
use Drupal\views\Plugin\views\row\RssPluginBase;
/**
* Plugin which formats the comments as RSS items.
*
* @ViewsRow(
* id = "comment_rss",
* title = @Translation("Comment"),
* help = @Translation("Display the comment as RSS."),
* theme = "views_view_row_rss",
* register_theme = FALSE,
* base = {"comment_field_data"},
* display_types = {"feed"}
* )
*/
class Rss extends RssPluginBase {
/**
* {@inheritdoc}
*/
protected $base_table = 'comment_field_data';
/**
* {@inheritdoc}
*/
protected $base_field = 'cid';
/**
* @var \Drupal\comment\CommentInterface[]
*/
protected $comments;
/**
* {@inheritdoc}
*/
protected $entityTypeId = 'comment';
public function preRender($result) {
$cids = array();
foreach ($result as $row) {
$cids[] = $row->cid;
}
$this->comments = $this->entityManager->getStorage('comment')->loadMultiple($cids);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm_summary_options() {
$options = parent::buildOptionsForm_summary_options();
$options['title'] = $this->t('Title only');
$options['default'] = $this->t('Use site default RSS settings');
return $options;
}
public function render($row) {
global $base_url;
$cid = $row->{$this->field_alias};
if (!is_numeric($cid)) {
return;
}
$view_mode = $this->options['view_mode'];
if ($view_mode == 'default') {
$view_mode = \Drupal::config('system.rss')->get('items.view_mode');
}
// Load the specified comment and its associated node:
/** @var $comment \Drupal\comment\CommentInterface */
$comment = $this->comments[$cid];
if (empty($comment)) {
return;
}
$description_build = [];
$comment->link = $comment->url('canonical', array('absolute' => TRUE));
$comment->rss_namespaces = array();
$comment->rss_elements = array(
array(
'key' => 'pubDate',
'value' => gmdate('r', $comment->getCreatedTime()),
),
array(
'key' => 'dc:creator',
'value' => $comment->getAuthorName(),
),
array(
'key' => 'guid',
'value' => 'comment ' . $comment->id() . ' at ' . $base_url,
'attributes' => array('isPermaLink' => 'false'),
),
);
// The comment gets built and modules add to or modify
// $comment->rss_elements and $comment->rss_namespaces.
$build = comment_view($comment, 'rss');
unset($build['#theme']);
if (!empty($comment->rss_namespaces)) {
$this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $comment->rss_namespaces);
}
if ($view_mode != 'title') {
// We render comment contents.
$description_build = $build;
}
$item = new \stdClass();
$item->description = $description_build;
$item->title = $comment->label();
$item->link = $comment->link;
// Provide a reference so that the render call in
// template_preprocess_views_view_row_rss() can still access it.
$item->elements = &$comment->rss_elements;
$item->cid = $comment->id();
$build = array(
'#theme' => $this->themeFunctions(),
'#view' => $this->view,
'#options' => $this->options,
'#row' => $item,
);
return $build;
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\sort\StatisticsLastCommentName.
*/
namespace Drupal\comment\Plugin\views\sort;
use Drupal\views\Plugin\views\sort\SortPluginBase;
/**
* Sort handler to sort by last comment name which might be in 2 different
* fields.
*
* @ingroup views_sort_handlers
*
* @ViewsSort("comment_ces_last_comment_name")
*/
class StatisticsLastCommentName extends SortPluginBase {
public function query() {
$this->ensureMyTable();
$definition = array(
'table' => 'users_field_data',
'field' => 'uid',
'left_table' => 'comment_entity_statistics',
'left_field' => 'last_comment_uid',
);
$join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $definition);
// @todo this might be safer if we had an ensure_relationship rather than guessing
// the table alias. Though if we did that we'd be guessing the relationship name
// so that doesn't matter that much.
$this->user_table = $this->query->ensureTable('ces_users', $this->relationship, $join);
$this->user_field = $this->query->addField($this->user_table, 'name');
// Add the field.
$this->query->addOrderBy(NULL, "LOWER(COALESCE($this->user_table.name, $this->tableAlias.$this->field))", $this->options['order'], $this->tableAlias . '_' . $this->field);
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\sort\StatisticsLastUpdated.
*/
namespace Drupal\comment\Plugin\views\sort;
use Drupal\views\Plugin\views\sort\Date;
/**
* Sort handler for the newer of last comment / entity updated.
*
* @ingroup views_sort_handlers
*
* @ViewsSort("comment_ces_last_updated")
*/
class StatisticsLastUpdated extends Date {
public function query() {
$this->ensureMyTable();
$this->node_table = $this->query->ensureTable('node', $this->relationship);
$this->field_alias = $this->query->addOrderBy(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)", $this->options['order'], $this->tableAlias . '_' . $this->field);
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\sort\Thread.
*/
namespace Drupal\comment\Plugin\views\sort;
use Drupal\views\Plugin\views\sort\SortPluginBase;
/**
* Sort handler for ordering by thread.
*
* @ingroup views_sort_handlers
*
* @ViewsSort("comment_thread")
*/
class Thread extends SortPluginBase {
public function query() {
$this->ensureMyTable();
//Read comment_render() in comment.module for an explanation of the
//thinking behind this sort.
if ($this->options['order'] == 'DESC') {
$this->query->addOrderBy($this->tableAlias, $this->realField, $this->options['order']);
}
else {
$alias = $this->tableAlias . '_' . $this->realField . 'asc';
//@todo is this secure?
$this->query->addOrderBy(NULL, "SUBSTRING({$this->tableAlias}.{$this->realField}, 1, (LENGTH({$this->tableAlias}.{$this->realField}) - 1))", $this->options['order'], $alias);
}
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* @file
* Contains \Drupal\comment\Plugin\views\wizard\Comment.
*/
namespace Drupal\comment\Plugin\views\wizard;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* @todo: replace numbers with constants.
*/
/**
* Tests creating comment views with the wizard.
*
* @ViewsWizard(
* id = "comment",
* base_table = "comment_field_data",
* title = @Translation("Comments")
* )
*/
class Comment extends WizardPluginBase {
/**
* Set the created column.
*/
protected $createdColumn = 'created';
/**
* Set default values for the filters.
*/
protected $filters = array(
'status' => array(
'value' => TRUE,
'table' => 'comment_field_data',
'field' => 'status',
'plugin_id' => 'boolean',
'entity_type' => 'comment',
'entity_field' => 'status',
),
'status_node' => array(
'value' => TRUE,
'table' => 'node_field_data',
'field' => 'status',
'plugin_id' => 'boolean',
'relationship' => 'node',
'entity_type' => 'node',
'entity_field' => 'status',
),
);
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
*/
protected function rowStyleOptions() {
$options = array();
$options['entity:comment'] = $this->t('comments');
$options['fields'] = $this->t('fields');
return $options;
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayOptions().
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'access comments';
// Add a relationship to nodes.
$display_options['relationships']['node']['id'] = 'node';
$display_options['relationships']['node']['table'] = 'comment_field_data';
$display_options['relationships']['node']['field'] = 'node';
$display_options['relationships']['node']['entity_type'] = 'comment_field_data';
$display_options['relationships']['node']['required'] = 1;
$display_options['relationships']['node']['plugin_id'] = 'standard';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
/* Field: Comment: Title */
$display_options['fields']['subject']['id'] = 'subject';
$display_options['fields']['subject']['table'] = 'comment_field_data';
$display_options['fields']['subject']['field'] = 'subject';
$display_options['fields']['subject']['entity_type'] = 'comment';
$display_options['fields']['subject']['entity_field'] = 'subject';
$display_options['fields']['subject']['label'] = '';
$display_options['fields']['subject']['alter']['alter_text'] = 0;
$display_options['fields']['subject']['alter']['make_link'] = 0;
$display_options['fields']['subject']['alter']['absolute'] = 0;
$display_options['fields']['subject']['alter']['trim'] = 0;
$display_options['fields']['subject']['alter']['word_boundary'] = 0;
$display_options['fields']['subject']['alter']['ellipsis'] = 0;
$display_options['fields']['subject']['alter']['strip_tags'] = 0;
$display_options['fields']['subject']['alter']['html'] = 0;
$display_options['fields']['subject']['hide_empty'] = 0;
$display_options['fields']['subject']['empty_zero'] = 0;
$display_options['fields']['subject']['plugin_id'] = 'field';
$display_options['fields']['subject']['type'] = 'string';
$display_options['fields']['subject']['settings'] = ['link_to_entity' => TRUE];
return $display_options;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentActionsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\Comment;
/**
* Tests actions provided by the Comment module.
*
* @group comment
*/
class CommentActionsTest extends CommentTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('dblog', 'action');
/**
* Tests comment publish and unpublish actions.
*/
function testCommentPublishUnpublishActions() {
$this->drupalLogin($this->webUser);
$comment_text = $this->randomMachineName();
$subject = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text, $subject);
// Unpublish a comment.
$action = entity_load('action', 'comment_unpublish_action');
$action->execute(array($comment));
$this->assertTrue($comment->isPublished() === FALSE, 'Comment was unpublished');
// Publish a comment.
$action = entity_load('action', 'comment_publish_action');
$action->execute(array($comment));
$this->assertTrue($comment->isPublished() === TRUE, 'Comment was published');
}
/**
* Tests the unpublish comment by keyword action.
*/
function testCommentUnpublishByKeyword() {
$this->drupalLogin($this->adminUser);
$keyword_1 = $this->randomMachineName();
$keyword_2 = $this->randomMachineName();
$action = entity_create('action', array(
'id' => 'comment_unpublish_by_keyword_action',
'label' => $this->randomMachineName(),
'type' => 'comment',
'configuration' => array(
'keywords' => array($keyword_1, $keyword_2),
),
'plugin' => 'comment_unpublish_by_keyword_action',
));
$action->save();
$comment = $this->postComment($this->node, $keyword_2, $this->randomMachineName());
// Load the full comment so that status is available.
$comment = Comment::load($comment->id());
$this->assertTrue($comment->isPublished() === TRUE, 'The comment status was set to published.');
$action->execute(array($comment));
$this->assertTrue($comment->isPublished() === FALSE, 'The comment status was set to not published.');
}
}

View file

@ -0,0 +1,214 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentAdminTest.
*/
namespace Drupal\comment\Tests;
use Drupal\user\RoleInterface;
/**
* Tests comment approval functionality.
*
* @group comment
*/
class CommentAdminTest extends CommentTestBase {
/**
* Test comment approval functionality through admin/content/comment.
*/
function testApprovalAdminInterface() {
// Set anonymous comments to require approval.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => TRUE,
'skip comment approval' => FALSE,
));
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous('0'); // Ensure that doesn't require contact info.
// Test that the comments page loads correctly when there are no comments
$this->drupalGet('admin/content/comment');
$this->assertText(t('No comments available.'));
$this->drupalLogout();
// Post anonymous comment without contact info.
$subject = $this->randomMachineName();
$body = $this->randomMachineName();
$this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message.
$this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), 'Comment requires approval.');
// Get unapproved comment id.
$this->drupalLogin($this->adminUser);
$anonymous_comment4 = $this->getUnapprovedComment($subject);
$anonymous_comment4 = entity_create('comment', array(
'cid' => $anonymous_comment4,
'subject' => $subject,
'comment_body' => $body,
'entity_id' => $this->node->id(),
'entity_type' => 'node',
'field_name' => 'comment'
));
$this->drupalLogout();
$this->assertFalse($this->commentExists($anonymous_comment4), 'Anonymous comment was not published.');
// Approve comment.
$this->drupalLogin($this->adminUser);
$this->performCommentOperation($anonymous_comment4, 'publish', TRUE);
$this->drupalLogout();
$this->drupalGet('node/' . $this->node->id());
$this->assertTrue($this->commentExists($anonymous_comment4), 'Anonymous comment visible.');
// Post 2 anonymous comments without contact info.
$comments[] = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Publish multiple comments in one operation.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content/comment/approval');
$this->assertText(t('Unapproved comments (@count)', array('@count' => 2)), 'Two unapproved comments waiting for approval.');
$edit = array(
"comments[{$comments[0]->id()}]" => 1,
"comments[{$comments[1]->id()}]" => 1,
);
$this->drupalPostForm(NULL, $edit, t('Update'));
$this->assertText(t('Unapproved comments (@count)', array('@count' => 0)), 'All comments were approved.');
// Delete multiple comments in one operation.
$edit = array(
'operation' => 'delete',
"comments[{$comments[0]->id()}]" => 1,
"comments[{$comments[1]->id()}]" => 1,
"comments[{$anonymous_comment4->id()}]" => 1,
);
$this->drupalPostForm(NULL, $edit, t('Update'));
$this->assertText(t('Are you sure you want to delete these comments and all their children?'), 'Confirmation required.');
$this->drupalPostForm(NULL, $edit, t('Delete comments'));
$this->assertText(t('No comments available.'), 'All comments were deleted.');
// Test message when no comments selected.
$edit = array(
'operation' => 'delete',
);
$this->drupalPostForm(NULL, $edit, t('Update'));
$this->assertText(t('Select one or more comments to perform the update on.'));
}
/**
* Tests comment approval functionality through the node interface.
*/
function testApprovalNodeInterface() {
// Set anonymous comments to require approval.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => TRUE,
'skip comment approval' => FALSE,
));
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous('0'); // Ensure that doesn't require contact info.
$this->drupalLogout();
// Post anonymous comment without contact info.
$subject = $this->randomMachineName();
$body = $this->randomMachineName();
$this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message.
$this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), 'Comment requires approval.');
// Get unapproved comment id.
$this->drupalLogin($this->adminUser);
$anonymous_comment4 = $this->getUnapprovedComment($subject);
$anonymous_comment4 = entity_create('comment', array(
'cid' => $anonymous_comment4,
'subject' => $subject,
'comment_body' => $body,
'entity_id' => $this->node->id(),
'entity_type' => 'node',
'field_name' => 'comment'
));
$this->drupalLogout();
$this->assertFalse($this->commentExists($anonymous_comment4), 'Anonymous comment was not published.');
// Approve comment.
$this->drupalLogin($this->adminUser);
$this->drupalGet('comment/1/approve');
$this->assertResponse(403, 'Forged comment approval was denied.');
$this->drupalGet('comment/1/approve', array('query' => array('token' => 'forged')));
$this->assertResponse(403, 'Forged comment approval was denied.');
$this->drupalGet('comment/1/edit');
$this->assertFieldChecked('edit-status-0');
$this->drupalGet('node/' . $this->node->id());
$this->clickLink(t('Approve'));
$this->drupalLogout();
$this->drupalGet('node/' . $this->node->id());
$this->assertTrue($this->commentExists($anonymous_comment4), 'Anonymous comment visible.');
}
/**
* Tests comment bundle admin.
*/
public function testCommentAdmin() {
// Login.
$this->drupalLogin($this->adminUser);
// Browse to comment bundle overview.
$this->drupalGet('admin/structure/comment');
$this->assertResponse(200);
// Make sure titles visible.
$this->assertText('Comment type');
$this->assertText('Description');
// Make sure the description is present.
$this->assertText('Default comment field');
// Manage fields.
$this->clickLink('Manage fields');
$this->assertResponse(200);
// Make sure comment_body field is shown.
$this->assertText('comment_body');
// Rest from here on in is field_ui.
}
/**
* Tests editing a comment as an admin.
*/
public function testEditComment() {
// Enable anonymous user comments.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments',
'post comments',
'skip comment approval',
));
// Login as a web user.
$this->drupalLogin($this->webUser);
// Post a comment.
$comment = $this->postComment($this->node, $this->randomMachineName());
$this->drupalLogout();
// Post anonymous comment.
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous('2'); // Ensure that we need email id before posting comment.
$this->drupalLogout();
// Post comment with contact info (required).
$author_name = $this->randomMachineName();
$author_mail = $this->randomMachineName() . '@example.com';
$anonymous_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), array('name' => $author_name, 'mail' => $author_mail));
// Login as an admin user.
$this->drupalLogin($this->adminUser);
// Make sure the comment field is not visible when
// the comment was posted by an authenticated user.
$this->drupalGet('comment/' . $comment->id() . '/edit');
$this->assertNoFieldById('edit-mail', $comment->getAuthorEmail());
// Make sure the comment field is visible when
// the comment was posted by an anonymous user.
$this->drupalGet('comment/' . $anonymous_comment->id() . '/edit');
$this->assertFieldById('edit-mail', $anonymous_comment->getAuthorEmail());
}
}

View file

@ -0,0 +1,170 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentAnonymousTest.
*/
namespace Drupal\comment\Tests;
use Drupal\user\RoleInterface;
/**
* Tests anonymous commenting.
*
* @group comment
*/
class CommentAnonymousTest extends CommentTestBase {
protected function setUp() {
parent::setUp();
// Enable anonymous and authenticated user comments.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments',
'post comments',
'skip comment approval',
));
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, array(
'access comments',
'post comments',
'skip comment approval',
));
}
/**
* Tests anonymous comment functionality.
*/
function testAnonymous() {
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous(COMMENT_ANONYMOUS_MAYNOT_CONTACT);
$this->drupalLogout();
// Post anonymous comment without contact info.
$anonymous_comment1 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($anonymous_comment1), 'Anonymous comment without contact info found.');
// Allow contact info.
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous(COMMENT_ANONYMOUS_MAY_CONTACT);
// Attempt to edit anonymous comment.
$this->drupalGet('comment/' . $anonymous_comment1->id() . '/edit');
$edited_comment = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($edited_comment, FALSE), 'Modified reply found.');
$this->drupalLogout();
// Post anonymous comment with contact info (optional).
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.');
// Check the presence of expected cache tags.
$this->assertCacheTag('config:field.field.node.article.comment');
$this->assertCacheTag('config:user.settings');
$anonymous_comment2 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($anonymous_comment2), 'Anonymous comment with contact info (optional) found.');
// Ensure anonymous users cannot post in the name of registered users.
$edit = array(
'name' => $this->adminUser->getUsername(),
'mail' => $this->randomMachineName() . '@example.com',
'subject[0][value]' => $this->randomMachineName(),
'comment_body[0][value]' => $this->randomMachineName(),
);
$this->drupalPostForm('comment/reply/node/' . $this->node->id() . '/comment', $edit, t('Save'));
$this->assertRaw(t('The name you used (%name) belongs to a registered user.', [
'%name' => $this->adminUser->getUsername(),
]));
// Require contact info.
$this->drupalLogin($this->adminUser);
$this->setCommentAnonymous(COMMENT_ANONYMOUS_MUST_CONTACT);
$this->drupalLogout();
// Try to post comment with contact info (required).
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.');
$anonymous_comment3 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Name should have 'Anonymous' for value by default.
$this->assertText(t('Email field is required.'), 'Email required.');
$this->assertFalse($this->commentExists($anonymous_comment3), 'Anonymous comment with contact info (required) not found.');
// Post comment with contact info (required).
$author_name = $this->randomMachineName();
$author_mail = $this->randomMachineName() . '@example.com';
$anonymous_comment3 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), array('name' => $author_name, 'mail' => $author_mail));
$this->assertTrue($this->commentExists($anonymous_comment3), 'Anonymous comment with contact info (required) found.');
// Make sure the user data appears correctly when editing the comment.
$this->drupalLogin($this->adminUser);
$this->drupalGet('comment/' . $anonymous_comment3->id() . '/edit');
$this->assertRaw($author_name, "The anonymous user's name is correct when editing the comment.");
$this->assertRaw($author_mail, "The anonymous user's email address is correct when editing the comment.");
// Unpublish comment.
$this->performCommentOperation($anonymous_comment3, 'unpublish');
$this->drupalGet('admin/content/comment/approval');
$this->assertRaw('comments[' . $anonymous_comment3->id() . ']', 'Comment was unpublished.');
// Publish comment.
$this->performCommentOperation($anonymous_comment3, 'publish', TRUE);
$this->drupalGet('admin/content/comment');
$this->assertRaw('comments[' . $anonymous_comment3->id() . ']', 'Comment was published.');
// Delete comment.
$this->performCommentOperation($anonymous_comment3, 'delete');
$this->drupalGet('admin/content/comment');
$this->assertNoRaw('comments[' . $anonymous_comment3->id() . ']', 'Comment was deleted.');
$this->drupalLogout();
// Comment 3 was deleted.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $anonymous_comment3->id());
$this->assertResponse(403);
// Reset.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => FALSE,
'post comments' => FALSE,
'skip comment approval' => FALSE,
));
// Attempt to view comments while disallowed.
// 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->id());
$this->assertNoPattern('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
$this->assertNoLink('Add new comment', 'Link to add comment was found.');
// Attempt to view node-comment form while disallowed.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertResponse(403);
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => FALSE,
'skip comment approval' => FALSE,
));
$this->drupalGet('node/' . $this->node->id());
$this->assertPattern('@<h2[^>]*>Comments</h2>@', 'Comments were displayed.');
$this->assertLink('Log in', 1, 'Link to log in was found.');
$this->assertLink('register', 1, 'Link to register was found.');
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => FALSE,
'post comments' => TRUE,
'skip comment approval' => TRUE,
));
$this->drupalGet('node/' . $this->node->id());
$this->assertNoPattern('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
$this->assertFieldByName('subject[0][value]', '', 'Subject field found.');
$this->assertFieldByName('comment_body[0][value]', '', 'Comment field found.');
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $anonymous_comment2->id());
$this->assertResponse(403);
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentBlockTest.
*/
namespace Drupal\comment\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\user\RoleInterface;
/**
* Tests comment block functionality.
*
* @group comment
*/
class CommentBlockTest extends CommentTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'views');
protected function setUp() {
parent::setUp();
// Update admin user to have the 'administer blocks' permission.
$this->adminUser = $this->drupalCreateUser(array(
'administer content types',
'administer comments',
'skip comment approval',
'post comments',
'access comments',
'access content',
'administer blocks',
));
}
/**
* Tests the recent comments block.
*/
function testRecentCommentBlock() {
$this->drupalLogin($this->adminUser);
$block = $this->drupalPlaceBlock('views_block:comments_recent-block_1');
// Add some test comments, with and without subjects. Because the 10 newest
// comments should be shown by the block, we create 11 to test that behavior
// below.
$timestamp = REQUEST_TIME;
for ($i = 0; $i < 11; ++$i) {
$subject = ($i % 2) ? $this->randomMachineName() : '';
$comments[$i] = $this->postComment($this->node, $this->randomMachineName(), $subject);
$comments[$i]->created->value = $timestamp--;
$comments[$i]->save();
}
// Test that a user without the 'access comments' permission cannot see the
// block.
$this->drupalLogout();
user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, array('access comments'));
$this->drupalGet('');
$this->assertNoText(t('Recent comments'));
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access comments'));
// Test that a user with the 'access comments' permission can see the
// block.
$this->drupalLogin($this->webUser);
$this->drupalGet('');
$this->assertText(t('Recent comments'));
// Test the only the 10 latest comments are shown and in the proper order.
$this->assertNoText($comments[10]->getSubject(), 'Comment 11 not found in block.');
for ($i = 0; $i < 10; $i++) {
$this->assertText($comments[$i]->getSubject(), SafeMarkup::format('Comment @number found in block.', array('@number' => 10 - $i)));
if ($i > 1) {
$previous_position = $position;
$position = strpos($this->getRawContent(), $comments[$i]->getSubject());
$this->assertTrue($position > $previous_position, SafeMarkup::format('Comment @a appears after comment @b', array('@a' => 10 - $i, '@b' => 11 - $i)));
}
$position = strpos($this->getRawContent(), $comments[$i]->getSubject());
}
// Test that links to comments work when comments are across pages.
$this->setCommentsPerPage(1);
for ($i = 0; $i < 10; $i++) {
$this->clickLink($comments[$i]->getSubject());
$this->assertText($comments[$i]->getSubject(), 'Comment link goes to correct page.');
$this->assertRaw('<link rel="canonical"', 'Canonical URL was found in the HTML head');
}
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentBookTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests visibility of comments on book pages.
*
* @group comment
*/
class CommentBookTest extends WebTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('book', 'comment');
protected function setUp() {
parent::setUp();
// Create comment field on book.
$this->addDefaultCommentField('node', 'book');
}
/**
* Tests comments in book export.
*/
public function testBookCommentPrint() {
$book_node = entity_create('node', array(
'type' => 'book',
'title' => 'Book title',
'body' => 'Book body',
));
$book_node->book['bid'] = 'new';
$book_node->save();
$comment_subject = $this->randomMachineName(8);
$comment_body = $this->randomMachineName(8);
$comment = entity_create('comment', array(
'subject' => $comment_subject,
'comment_body' => $comment_body,
'entity_id' => $book_node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'status' => CommentInterface::PUBLISHED,
));
$comment->save();
$commenting_user = $this->drupalCreateUser(array('access printer-friendly version', 'access comments', 'post comments'));
$this->drupalLogin($commenting_user);
$this->drupalGet('node/' . $book_node->id());
$this->assertText($comment_subject, 'Comment subject found');
$this->assertText($comment_body, 'Comment body found');
$this->assertText(t('Add new comment'), 'Comment form found');
$this->assertField('subject[0][value]', 'Comment form subject found');
$this->drupalGet('book/export/html/' . $book_node->id());
$this->assertText(t('Comments'), 'Comment thread found');
$this->assertText($comment_subject, 'Comment subject found');
$this->assertText($comment_body, 'Comment body found');
$this->assertNoText(t('Add new comment'), 'Comment form not found');
$this->assertNoField('subject[0][value]', 'Comment form subject not found');
}
}

View file

@ -0,0 +1,138 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentCSSTest.
*/
namespace Drupal\comment\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\comment\CommentInterface;
use Drupal\user\RoleInterface;
/**
* Tests CSS classes on comments.
*
* @group comment
*/
class CommentCSSTest extends CommentTestBase {
protected function setUp() {
parent::setUp();
// Allow anonymous users to see comments.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments',
'access content'
));
}
/**
* Tests CSS classes on comments.
*/
function testCommentClasses() {
// Create all permutations for comments, users, and nodes.
$parameters = array(
'node_uid' => array(0, $this->webUser->id()),
'comment_uid' => array(0, $this->webUser->id(), $this->adminUser->id()),
'comment_status' => array(CommentInterface::PUBLISHED, CommentInterface::NOT_PUBLISHED),
'user' => array('anonymous', 'authenticated', 'admin'),
);
$permutations = $this->generatePermutations($parameters);
foreach ($permutations as $case) {
// Create a new node.
$node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid']));
// Add a comment.
/** @var \Drupal\comment\CommentInterface $comment */
$comment = entity_create('comment', array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'uid' => $case['comment_uid'],
'status' => $case['comment_status'],
'subject' => $this->randomMachineName(),
'language' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'comment_body' => array(LanguageInterface::LANGCODE_NOT_SPECIFIED => array($this->randomMachineName())),
));
$comment->save();
// Adjust the current/viewing user.
switch ($case['user']) {
case 'anonymous':
if ($this->loggedInUser) {
$this->drupalLogout();
}
$case['user_uid'] = 0;
break;
case 'authenticated':
$this->drupalLogin($this->webUser);
$case['user_uid'] = $this->webUser->id();
break;
case 'admin':
$this->drupalLogin($this->adminUser);
$case['user_uid'] = $this->adminUser->id();
break;
}
// Request the node with the comment.
$this->drupalGet('node/' . $node->id());
$settings = $this->getDrupalSettings();
// Verify the data-history-node-id attribute, which is necessary for the
// by-viewer class and the "new" indicator, see below.
$this->assertIdentical(1, count($this->xpath('//*[@data-history-node-id="' . $node->id() . '"]')), 'data-history-node-id attribute is set on node.');
// Verify classes if the comment is visible for the current user.
if ($case['comment_status'] == CommentInterface::PUBLISHED || $case['user'] == 'admin') {
// Verify the by-anonymous class.
$comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-anonymous")]');
if ($case['comment_uid'] == 0) {
$this->assertTrue(count($comments) == 1, 'by-anonymous class found.');
}
else {
$this->assertFalse(count($comments), 'by-anonymous class not found.');
}
// Verify the by-node-author class.
$comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-node-author")]');
if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['node_uid']) {
$this->assertTrue(count($comments) == 1, 'by-node-author class found.');
}
else {
$this->assertFalse(count($comments), 'by-node-author class not found.');
}
// Verify the data-comment-user-id attribute, which is used by the
// drupal.comment-by-viewer library to add a by-viewer when the current
// user (the viewer) was the author of the comment. We do this in Java-
// Script to prevent breaking the render cache.
$this->assertIdentical(1, count($this->xpath('//*[contains(@class, "comment") and @data-comment-user-id="' . $case['comment_uid'] . '"]')), 'data-comment-user-id attribute is set on comment.');
$this->assertRaw(drupal_get_path('module', 'comment') . '/js/comment-by-viewer.js', 'drupal.comment-by-viewer library is present.');
}
// Verify the unpublished class.
$comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "unpublished")]');
if ($case['comment_status'] == CommentInterface::NOT_PUBLISHED && $case['user'] == 'admin') {
$this->assertTrue(count($comments) == 1, 'unpublished class found.');
}
else {
$this->assertFalse(count($comments), 'unpublished class not found.');
}
// Verify the data-comment-timestamp attribute, which is used by the
// drupal.comment-new-indicator library to add a "new" indicator to each
// comment that was created or changed after the last time the current
// user read the corresponding node.
if ($case['comment_status'] == CommentInterface::PUBLISHED || $case['user'] == 'admin') {
$this->assertIdentical(1, count($this->xpath('//*[contains(@class, "comment")]/*[@data-comment-timestamp="' . $comment->getChangedTime() . '"]')), 'data-comment-timestamp attribute is set on comment');
$expectedJS = ($case['user'] !== 'anonymous');
$this->assertIdentical($expectedJS, isset($settings['ajaxPageState']['libraries']) && in_array('comment/drupal.comment-new-indicator', explode(',', $settings['ajaxPageState']['libraries'])), 'drupal.comment-new-indicator library is present.');
}
}
}
}

View file

@ -0,0 +1,110 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentCacheTagsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the Comment entity's cache tags.
*
* @group comment
*/
class CommentCacheTagsTest extends EntityWithUriCacheTagsTestBase {
use CommentTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = array('comment');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Give anonymous users permission to view comments, so that we can verify
// the cache tags of cached versions of comment pages.
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
$user_role->grantPermission('access comments');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "bar" bundle for the "entity_test" entity type and create.
$bundle = 'bar';
entity_test_create_bundle($bundle, NULL, 'entity_test');
// Create a comment field on this bundle.
$this->addDefaultCommentField('entity_test', 'bar', 'comment');
// Display comments in a flat list; threaded comments are not render cached.
$field = FieldConfig::loadByName('entity_test', 'bar', 'comment');
$field->setSetting('default_mode', CommentManagerInterface::COMMENT_MODE_FLAT);
$field->save();
// Create a "Camelids" test entity.
$entity_test = entity_create('entity_test', array(
'name' => 'Camelids',
'type' => 'bar',
));
$entity_test->save();
// Create a "Llama" comment.
$comment = entity_create('comment', array(
'subject' => 'Llama',
'comment_body' => array(
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
),
'entity_id' => $entity_test->id(),
'entity_type' => 'entity_test',
'field_name' => 'comment',
'status' => \Drupal\comment\CommentInterface::PUBLISHED,
));
$comment->save();
return $comment;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return [
// Field access for the user picture rendered as part of the node that
// this comment is created on.
'user.permissions',
];
}
/**
* {@inheritdoc}
*
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
/** @var \Drupal\comment\CommentInterface $entity */
return array(
'config:filter.format.plain_text',
'user:' . $entity->getOwnerId(),
'user_view',
);
}
}

View file

@ -0,0 +1,133 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentDefaultFormatterCacheTagsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\Core\Session\UserSession;
use Drupal\comment\CommentInterface;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests the bubbling up of comment cache tags when using the Comment list
* formatter on an entity.
*
* @group comment
*/
class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('entity_test', 'comment');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set the current user to one that can access comments. Specifically, this
// user does not have access to the 'administer comments' permission, to
// ensure only published comments are visible to the end user.
$current_user = $this->container->get('current_user');
$current_user->setAccount($this->createUser(array(), array('access comments')));
// Install tables and config needed to render comments.
$this->installSchema('comment', array('comment_entity_statistics'));
$this->installConfig(array('system', 'filter', 'comment'));
// Comment rendering generates links, so build the router.
$this->installSchema('system', array('router'));
$this->container->get('router.builder')->rebuild();
// Set up a field, so that the entity that'll be referenced bubbles up a
// cache tag when rendering it entirely.
$this->addDefaultCommentField('entity_test', 'entity_test');
}
/**
* Tests the bubbling of cache tags.
*/
public function testCacheTags() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
// Create the entity that will be commented upon.
$commented_entity = entity_create('entity_test', array('name' => $this->randomMachineName()));
$commented_entity->save();
// Verify cache tags on the rendered entity before it has comments.
$build = \Drupal::entityManager()
->getViewBuilder('entity_test')
->view($commented_entity);
$renderer->renderRoot($build);
$expected_cache_tags = array(
'entity_test_view',
'entity_test:' . $commented_entity->id(),
'comment_list',
'config:core.entity_form_display.comment.comment.default',
'config:field.field.comment.comment.comment_body',
'config:field.field.entity_test.entity_test.comment',
'config:field.storage.comment.comment_body',
'config:user.settings',
);
sort($expected_cache_tags);
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.');
// Create a comment on that entity. Comment loading requires that the uid
// also exists in the {users} table.
$user = $this->createUser();
$user->save();
$comment = entity_create('comment', array(
'subject' => 'Llama',
'comment_body' => array(
'value' => 'Llamas are cool!',
'format' => 'plain_text',
),
'entity_id' => $commented_entity->id(),
'entity_type' => 'entity_test',
'field_name' => 'comment',
'comment_type' => 'comment',
'status' => CommentInterface::PUBLISHED,
'uid' => $user->id(),
));
$comment->save();
// Load commented entity so comment_count gets computed.
// @todo Remove the $reset = TRUE parameter after
// https://www.drupal.org/node/597236 lands. It's a temporary work-around.
$commented_entity = entity_load('entity_test', $commented_entity->id(), TRUE);
// Verify cache tags on the rendered entity when it has comments.
$build = \Drupal::entityManager()
->getViewBuilder('entity_test')
->view($commented_entity);
$renderer->renderRoot($build);
$expected_cache_tags = array(
'entity_test_view',
'entity_test:' . $commented_entity->id(),
'comment_list',
'comment_view',
'comment:' . $comment->id(),
'config:filter.format.plain_text',
'user_view',
'user:2',
'config:core.entity_form_display.comment.comment.default',
'config:field.field.comment.comment.comment_body',
'config:field.field.entity_test.entity_test.comment',
'config:field.storage.comment.comment_body',
'config:user.settings',
);
sort($expected_cache_tags);
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags when it has comments.');
}
}

View file

@ -0,0 +1,283 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentFieldAccessTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Entity\CommentType;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\simpletest\TestBase;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests comment field level access.
*
* @group comment
* @group Access
*/
class CommentFieldAccessTest extends EntityUnitTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('comment', 'entity_test', 'user');
/**
* Fields that only users with administer comments permissions can change.
*
* @var array
*/
protected $administrativeFields = array(
'uid',
'status',
'created',
);
/**
* These fields are automatically managed and can not be changed by any user.
*
* @var array
*/
protected $readOnlyFields = array(
'changed',
'hostname',
'uuid',
'cid',
'thread',
'comment_type',
'pid',
'entity_id',
'entity_type',
'field_name',
);
/**
* These fields can only be edited by the admin or anonymous users if allowed.
*
* @var array
*/
protected $contactFields = array(
'name',
'mail',
'homepage',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('user', 'comment'));
$this->installSchema('comment', array('comment_entity_statistics'));
}
/**
* Test permissions on comment fields.
*/
public function testAccessToAdministrativeFields() {
// Create a comment type.
$comment_type = CommentType::create([
'id' => 'comment',
'label' => 'Default comments',
'description' => 'Default comment field',
'target_entity_type_id' => 'entity_test',
]);
$comment_type->save();
// Create a comment against a test entity.
$host = EntityTest::create();
$host->save();
// An administrator user. No user exists yet, ensure that the first user
// does not have UID 1.
$comment_admin_user = $this->createUser(['uid' => 2, 'name' => 'admin'], [
'administer comments',
'access comments',
]);
// Two comment enabled users, one with edit access.
$comment_enabled_user = $this->createUser(['name' => 'enabled'], [
'post comments',
'skip comment approval',
'edit own comments',
'access comments',
]);
$comment_no_edit_user = $this->createUser(['name' => 'no edit'], [
'post comments',
'skip comment approval',
'access comments',
]);
// An unprivileged user.
$comment_disabled_user = $this->createUser(['name' => 'disabled'], ['access content']);
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$role->grantPermission('post comments')
->save();
$anonymous_user = new AnonymousUserSession();
// Add two fields.
$this->addDefaultCommentField('entity_test', 'entity_test', 'comment');
$this->addDefaultCommentField('entity_test', 'entity_test', 'comment_other');
// Change the second field's anonymous contact setting.
$instance = FieldConfig::loadByName('entity_test', 'entity_test', 'comment_other');
// Default is 'May not contact', for this field - they may contact.
$instance->setSetting('anonymous', COMMENT_ANONYMOUS_MAY_CONTACT);
$instance->save();
// Create three "Comments". One is owned by our edit-enabled user.
$comment1 = Comment::create([
'entity_type' => 'entity_test',
'name' => 'Tony',
'hostname' => 'magic.example.com',
'mail' => 'tonythemagicalpony@example.com',
'subject' => 'Bruce the Mesopotamian moose',
'entity_id' => $host->id(),
'comment_type' => 'comment',
'field_name' => 'comment',
'pid' => 0,
'uid' => 0,
'status' => 1,
]);
$comment1->save();
$comment2 = Comment::create([
'entity_type' => 'entity_test',
'hostname' => 'magic.example.com',
'subject' => 'Brian the messed up lion',
'entity_id' => $host->id(),
'comment_type' => 'comment',
'field_name' => 'comment',
'status' => 1,
'pid' => 0,
'uid' => $comment_enabled_user->id(),
]);
$comment2->save();
$comment3 = Comment::create([
'entity_type' => 'entity_test',
'hostname' => 'magic.example.com',
// Unpublished.
'status' => 0,
'subject' => 'Gail the minky whale',
'entity_id' => $host->id(),
'comment_type' => 'comment',
'field_name' => 'comment_other',
'pid' => $comment2->id(),
'uid' => $comment_no_edit_user->id(),
]);
$comment3->save();
// Note we intentionally don't save this comment so it remains 'new'.
$comment4 = Comment::create([
'entity_type' => 'entity_test',
'hostname' => 'magic.example.com',
// Unpublished.
'status' => 0,
'subject' => 'Daniel the Cocker-Spaniel',
'entity_id' => $host->id(),
'comment_type' => 'comment',
'field_name' => 'comment_other',
'pid' => 0,
'uid' => $anonymous_user->id(),
]);
// Generate permutations.
$combinations = [
'comment' => [$comment1, $comment2, $comment3, $comment4],
'user' => [$comment_admin_user, $comment_enabled_user, $comment_no_edit_user, $comment_disabled_user, $anonymous_user]
];
$permutations = TestBase::generatePermutations($combinations);
// Check access to administrative fields.
foreach ($this->administrativeFields as $field) {
foreach ($permutations as $set) {
$may_view = $set['comment']->{$field}->access('view', $set['user']);
$may_update = $set['comment']->{$field}->access('edit', $set['user']);
$this->assertEqual($may_view, $set['user']->hasPermission('administer comments') || ($set['comment']->isPublished() && $set['user']->hasPermission('access comments')), SafeMarkup::format('User @user !state view field !field on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_update ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
'!field' => $field,
]));
$this->assertEqual($may_update, $set['user']->hasPermission('administer comments'), SafeMarkup::format('User @user !state update field !field on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_update ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
'!field' => $field,
]));
}
}
// Check access to normal field.
foreach ($permutations as $set) {
$may_update = $set['comment']->access('update', $set['user']) && $set['comment']->subject->access('edit', $set['user']);
$this->assertEqual($may_update, $set['user']->hasPermission('administer comments') || ($set['user']->hasPermission('edit own comments') && $set['user']->id() == $set['comment']->getOwnerId()), SafeMarkup::format('User @user !state update field subject on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_update ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
]));
}
// Check read-only fields.
foreach ($this->readOnlyFields as $field) {
// Check view operation.
foreach ($permutations as $set) {
$may_view = $set['comment']->{$field}->access('view', $set['user']);
$may_update = $set['comment']->{$field}->access('edit', $set['user']);
$this->assertEqual($may_view, $field != 'hostname' && ($set['user']->hasPermission('administer comments') ||
($set['comment']->isPublished() && $set['user']->hasPermission('access comments'))), SafeMarkup::format('User @user !state view field !field on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_view ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
'!field' => $field,
]));
$this->assertFalse($may_update, SafeMarkup::format('User @user !state update field !field on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_update ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
'!field' => $field,
]));
}
}
// Check contact fields.
foreach ($this->contactFields as $field) {
// Check view operation.
foreach ($permutations as $set) {
$may_update = $set['comment']->{$field}->access('edit', $set['user']);
// To edit the 'mail' or 'name' field, either the user has the
// "administer comments" permissions or the user is anonymous and
// adding a new comment using a field that allows contact details.
$this->assertEqual($may_update, $set['user']->hasPermission('administer comments') || (
$set['user']->isAnonymous() &&
$set['comment']->isNew() &&
$set['user']->hasPermission('post comments') &&
$set['comment']->getFieldName() == 'comment_other'
), SafeMarkup::format('User @user !state update field !field on comment @comment', [
'@user' => $set['user']->getUsername(),
'!state' => $may_update ? 'can' : 'cannot',
'@comment' => $set['comment']->getSubject(),
'!field' => $field,
]));
}
}
foreach ($permutations as $set) {
// Check no view-access to mail field for other than admin.
$may_view = $set['comment']->mail->access('view', $set['user']);
$this->assertEqual($may_view, $set['user']->hasPermission('administer comments'));
}
}
}

View file

@ -0,0 +1,191 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentFieldsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\comment\Entity\CommentType;
/**
* Tests fields on comments.
*
* @group comment
*/
class CommentFieldsTest extends CommentTestBase {
/**
* Install the field UI.
*
* @var array
*/
public static $modules = array('field_ui');
/**
* Tests that the default 'comment_body' field is correctly added.
*/
function testCommentDefaultFields() {
// Do not make assumptions on default node types created by the test
// installation profile, and create our own.
$this->drupalCreateContentType(array('type' => 'test_node_type'));
$this->addDefaultCommentField('node', 'test_node_type');
// Check that the 'comment_body' field is present on the comment bundle.
$field = FieldConfig::loadByName('comment', 'comment', 'comment_body');
$this->assertTrue(!empty($field), 'The comment_body field is added when a comment bundle is created');
$field->delete();
// Check that the 'comment_body' field is not deleted since it is persisted
// even if it has no fields.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$this->assertTrue($field_storage, 'The comment_body field storage was not deleted');
// Create a new content type.
$type_name = 'test_node_type_2';
$this->drupalCreateContentType(array('type' => $type_name));
$this->addDefaultCommentField('node', $type_name);
// Check that the 'comment_body' field exists and has an instance on the
// new comment bundle.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$this->assertTrue($field_storage, 'The comment_body field exists');
$field = FieldConfig::loadByName('comment', 'comment', 'comment_body');
$this->assertTrue(isset($field), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name)));
// Test adding a field that defaults to CommentItemInterface::CLOSED.
$this->addDefaultCommentField('node', 'test_node_type', 'who_likes_ponies', CommentItemInterface::CLOSED, 'who_likes_ponies');
$field = FieldConfig::load('node.test_node_type.who_likes_ponies');
$this->assertEqual($field->default_value[0]['status'], CommentItemInterface::CLOSED);
}
/**
* Tests that you can remove a comment field.
*/
public function testCommentFieldDelete() {
$this->drupalCreateContentType(array('type' => 'test_node_type'));
$this->addDefaultCommentField('node', 'test_node_type');
// We want to test the handling of removing the primary comment field, so we
// ensure there is at least one other comment field attached to a node type
// so that comment_entity_load() runs for nodes.
$this->addDefaultCommentField('node', 'test_node_type', 'comment2');
// Create a sample node.
$node = $this->drupalCreateNode(array(
'title' => 'Baloney',
'type' => 'test_node_type',
));
$this->drupalLogin($this->webUser);
$this->drupalGet('node/' . $node->nid->value);
$elements = $this->cssSelect('.field-type-comment');
$this->assertEqual(2, count($elements), 'There are two comment fields on the node.');
// Delete the first comment field.
FieldStorageConfig::loadByName('node', 'comment')->delete();
$this->drupalGet('node/' . $node->nid->value);
$elements = $this->cssSelect('.field-type-comment');
$this->assertEqual(1, count($elements), 'There is one comment field on the node.');
}
/**
* Tests creating a comment field through the interface.
*/
public function testCommentFieldCreate() {
// Create user who can administer user fields.
$user = $this->drupalCreateUser(array(
'administer user fields',
));
$this->drupalLogin($user);
// Create comment field in account settings.
$edit = array(
'new_storage_type' => 'comment',
'label' => 'User comment',
'field_name' => 'user_comment',
);
$this->drupalPostForm('admin/config/people/accounts/fields/add-field', $edit, 'Save and continue');
// Try to save the comment field without selecting a comment type.
$edit = array();
$this->drupalPostForm('admin/config/people/accounts/fields/user.user.field_user_comment/storage', $edit, t('Save field settings'));
// We should get an error message.
$this->assertText(t('An illegal choice has been detected. Please contact the site administrator.'));
// Create a comment type for users.
$bundle = CommentType::create(array(
'id' => 'user_comment_type',
'label' => 'user_comment_type',
'description' => '',
'target_entity_type_id' => 'user',
));
$bundle->save();
// Select a comment type and try to save again.
$edit = array(
'settings[comment_type]' => 'user_comment_type',
);
$this->drupalPostForm('admin/config/people/accounts/fields/user.user.field_user_comment/storage', $edit, t('Save field settings'));
// We shouldn't get an error message.
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
}
/**
* Tests that comment module works when installed after a content module.
*/
function testCommentInstallAfterContentModule() {
// Create a user to do module administration.
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
$this->drupalLogin($this->adminUser);
// Drop default comment field added in CommentTestBase::setup().
FieldStorageConfig::loadByName('node', 'comment')->delete();
if ($field_storage = FieldStorageConfig::loadByName('node', 'comment_forum')) {
$field_storage->delete();
}
// Purge field data now to allow comment module to be uninstalled once the
// field has been deleted.
field_purge_batch(10);
// Uninstall the comment module.
$edit = array();
$edit['uninstall[comment]'] = TRUE;
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
$this->drupalPostForm(NULL, array(), t('Uninstall'));
$this->rebuildContainer();
$this->assertFalse($this->container->get('module_handler')->moduleExists('comment'), 'Comment module uninstalled.');
// Install core content type module (book).
$edit = array();
$edit['modules[Core][book][enable]'] = 'book';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
// Now install the comment module.
$edit = array();
$edit['modules[Core][comment][enable]'] = 'comment';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->rebuildContainer();
$this->assertTrue($this->container->get('module_handler')->moduleExists('comment'), 'Comment module enabled.');
// Create nodes of each type.
$this->addDefaultCommentField('node', 'book');
$book_node = $this->drupalCreateNode(array('type' => 'book'));
$this->drupalLogout();
// Try to post a comment on each node. A failure will be triggered if the
// comment body is missing on one of these forms, due to postComment()
// asserting that the body is actually posted correctly.
$this->webUser = $this->drupalCreateUser(array('access content', 'access comments', 'post comments', 'skip comment approval'));
$this->drupalLogin($this->webUser);
$this->postComment($book_node, $this->randomMachineName(), $this->randomMachineName());
}
}

View file

@ -0,0 +1,284 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentInterfaceTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\comment\Entity\Comment;
use Drupal\user\RoleInterface;
use Drupal\filter\Entity\FilterFormat;
/**
* Tests comment user interfaces.
*
* @group comment
*/
class CommentInterfaceTest extends CommentTestBase {
/**
* Set up comments to have subject and preview disabled.
*/
public function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(FALSE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
}
/**
* Tests the comment interface.
*/
public function testCommentInterface() {
// Post comment #1 without subject or preview.
$this->drupalLogin($this->webUser);
$comment_text = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text);
$this->assertTrue($this->commentExists($comment), 'Comment found.');
// Set comments to have subject and preview to required.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->setCommentSubject(TRUE);
$this->setCommentPreview(DRUPAL_REQUIRED);
$this->drupalLogout();
// Create comment #2 that allows subject and requires preview.
$this->drupalLogin($this->webUser);
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
$this->assertTrue($this->commentExists($comment), 'Comment found.');
// Comment as anonymous with preview required.
$this->drupalLogout();
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access content', 'access comments', 'post comments', 'skip comment approval'));
$anonymous_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->assertTrue($this->commentExists($anonymous_comment), 'Comment found.');
$anonymous_comment->delete();
// Check comment display.
$this->drupalLogin($this->webUser);
$this->drupalGet('node/' . $this->node->id());
$this->assertText($subject_text, 'Individual comment subject found.');
$this->assertText($comment_text, 'Individual comment body found.');
// Set comments to have subject and preview to optional.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->setCommentSubject(TRUE);
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->drupalGet('comment/' . $comment->id() . '/edit');
$this->assertTitle(t('Edit comment @title | Drupal', array(
'@title' => $comment->getSubject(),
)));
// Test changing the comment author to "Anonymous".
$comment = $this->postComment(NULL, $comment->comment_body->value, $comment->getSubject(), array('name' => ''));
$this->assertTrue($comment->getAuthorName() == t('Anonymous') && $comment->getOwnerId() == 0, 'Comment author successfully changed to anonymous.');
// Test changing the comment author to an unverified user.
$random_name = $this->randomMachineName();
$this->drupalGet('comment/' . $comment->id() . '/edit');
$comment = $this->postComment(NULL, $comment->comment_body->value, $comment->getSubject(), array('name' => $random_name));
$this->drupalGet('node/' . $this->node->id());
$this->assertText($random_name . ' (' . t('not verified') . ')', 'Comment author successfully changed to an unverified user.');
// Test changing the comment author to a verified user.
$this->drupalGet('comment/' . $comment->id() . '/edit');
$comment = $this->postComment(NULL, $comment->comment_body->value, $comment->getSubject(), array('name' => $this->webUser->getUsername()));
$this->assertTrue($comment->getAuthorName() == $this->webUser->getUsername() && $comment->getOwnerId() == $this->webUser->id(), 'Comment author successfully changed to a registered user.');
$this->drupalLogout();
// Reply to comment #2 creating comment #3 with optional preview and no
// subject though field enabled.
$this->drupalLogin($this->webUser);
// Deliberately use the wrong url to test
// \Drupal\comment\Controller\CommentController::redirectNode().
$this->drupalGet('comment/' . $this->node->id() . '/reply');
// Verify we were correctly redirected.
$this->assertUrl(\Drupal::url('comment.reply', array('entity_type' => 'node', 'entity' => $this->node->id(), 'field_name' => 'comment'), array('absolute' => TRUE)));
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id());
$this->assertText($subject_text, 'Individual comment-reply subject found.');
$this->assertText($comment_text, 'Individual comment-reply body found.');
$reply = $this->postComment(NULL, $this->randomMachineName(), '', TRUE);
$reply_loaded = Comment::load($reply->id());
$this->assertTrue($this->commentExists($reply, TRUE), 'Reply found.');
$this->assertEqual($comment->id(), $reply_loaded->getParentComment()->id(), 'Pid of a reply to a comment is set correctly.');
// Check the thread of reply grows correctly.
$this->assertEqual(rtrim($comment->getThread(), '/') . '.00/', $reply_loaded->getThread());
// Second reply to comment #2 creating comment #4.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id());
$this->assertText($comment->getSubject(), 'Individual comment-reply subject found.');
$this->assertText($comment->comment_body->value, 'Individual comment-reply body found.');
$reply = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$reply_loaded = Comment::load($reply->id());
$this->assertTrue($this->commentExists($reply, TRUE), 'Second reply found.');
// Check the thread of second reply grows correctly.
$this->assertEqual(rtrim($comment->getThread(), '/') . '.01/', $reply_loaded->getThread());
// Reply to comment #4 creating comment #5.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $reply_loaded->id());
$this->assertText($reply_loaded->getSubject(), 'Individual comment-reply subject found.');
$this->assertText($reply_loaded->comment_body->value, 'Individual comment-reply body found.');
$reply = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$reply_loaded = Comment::load($reply->id());
$this->assertTrue($this->commentExists($reply, TRUE), 'Second reply found.');
// Check the thread of reply to second reply grows correctly.
$this->assertEqual(rtrim($comment->getThread(), '/') . '.01.00/', $reply_loaded->getThread());
// Edit reply.
$this->drupalGet('comment/' . $reply->id() . '/edit');
$reply = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->assertTrue($this->commentExists($reply, TRUE), 'Modified reply found.');
// Confirm a new comment is posted to the correct page.
$this->setCommentsPerPage(2);
$comment_new_page = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->assertTrue($this->commentExists($comment_new_page), 'Page one exists. %s');
$this->drupalGet('node/' . $this->node->id(), array('query' => array('page' => 2)));
$this->assertTrue($this->commentExists($reply, TRUE), 'Page two exists. %s');
$this->setCommentsPerPage(50);
// Attempt to reply to an unpublished comment.
$reply_loaded->setPublished(FALSE);
$reply_loaded->save();
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $reply_loaded->id());
$this->assertResponse(403);
// Attempt to post to node with comments disabled.
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => CommentItemInterface::HIDDEN))));
$this->assertTrue($this->node, 'Article node created.');
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertResponse(403);
$this->assertNoField('edit-comment', 'Comment body field found.');
// Attempt to post to node with read-only comments.
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => CommentItemInterface::CLOSED))));
$this->assertTrue($this->node, 'Article node created.');
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertResponse(403);
$this->assertNoField('edit-comment', 'Comment body field found.');
// Attempt to post to node with comments enabled (check field names etc).
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => CommentItemInterface::OPEN))));
$this->assertTrue($this->node, 'Article node created.');
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$this->assertNoText('This discussion is closed', 'Posting to node with comments enabled');
$this->assertField('edit-comment-body-0-value', 'Comment body field found.');
// Delete comment and make sure that reply is also removed.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->deleteComment($comment);
$this->deleteComment($comment_new_page);
$this->drupalGet('node/' . $this->node->id());
$this->assertFalse($this->commentExists($comment), 'Comment not found.');
$this->assertFalse($this->commentExists($reply, TRUE), 'Reply not found.');
// Enabled comment form on node page.
$this->drupalLogin($this->adminUser);
$this->setCommentForm(TRUE);
$this->drupalLogout();
// Submit comment through node form.
$this->drupalLogin($this->webUser);
$this->drupalGet('node/' . $this->node->id());
$form_comment = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->assertTrue($this->commentExists($form_comment), 'Form comment found.');
// Disable comment form on node page.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->setCommentForm(FALSE);
}
/**
* Test that the subject is automatically filled if disabled or left blank.
*
* When the subject field is blank or disabled, the first 29 characters of the
* comment body are used for the subject. If this would break within a word,
* then the break is put at the previous word boundary instead.
*/
public function testAutoFilledSubject() {
$this->drupalLogin($this->webUser);
$this->drupalGet('node/' . $this->node->id());
// Break when there is a word boundary before 29 characters.
$body_text = 'Lorem ipsum Lorem ipsum Loreming ipsum Lorem ipsum';
$comment1 = $this->postComment(NULL, $body_text, '', TRUE);
$this->assertTrue($this->commentExists($comment1), 'Form comment found.');
$this->assertEqual('Lorem ipsum Lorem ipsum…', $comment1->getSubject());
// Break at 29 characters where there's no boundary before that.
$body_text2 = 'LoremipsumloremipsumLoremingipsumLoremipsum';
$comment2 = $this->postComment(NULL, $body_text2, '', TRUE);
$this->assertEqual('LoremipsumloremipsumLoreming…', $comment2->getSubject());
}
/**
* Test that automatic subject is correctly created from HTML comment text.
*
* This is the same test as in CommentInterfaceTest::testAutoFilledSubject()
* with the additional check that HTML is stripped appropriately prior to
* character-counting.
*/
public function testAutoFilledHtmlSubject() {
// Set up two default (i.e. filtered HTML) input formats, because then we
// can select one of them. Then create a user that can use these formats,
// log the user in, and then GET the node page on which to test the
// comments.
$filtered_html_format = FilterFormat::create(array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
));
$filtered_html_format->save();
$full_html_format = FilterFormat::create(array(
'format' => 'full_html',
'name' => 'Full HTML',
));
$full_html_format->save();
$html_user = $this->drupalCreateUser(array(
'access comments',
'post comments',
'edit own comments',
'skip comment approval',
'access content',
$filtered_html_format->getPermissionName(),
$full_html_format->getPermissionName(),
));
$this->drupalLogin($html_user);
$this->drupalGet('node/' . $this->node->id());
// HTML should not be included in the character count.
$body_text1 = '<span></span><strong> </strong><span> </span><strong></strong>Hello World<br />';
$edit1 = array(
'comment_body[0][value]' => $body_text1,
'comment_body[0][format]' => 'filtered_html',
);
$this->drupalPostForm(NULL, $edit1, t('Save'));
$this->assertEqual('Hello World', Comment::load(1)->getSubject());
// If there's nothing other than HTML, the subject should be '(No subject)'.
$body_text2 = '<span></span><strong> </strong><span> </span><strong></strong> <br />';
$edit2 = array(
'comment_body[0][value]' => $body_text2,
'comment_body[0][format]' => 'filtered_html',
);
$this->drupalPostForm(NULL, $edit2, t('Save'));
$this->assertEqual('(No subject)', Comment::load(2)->getSubject());
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentItemTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\field\Tests\FieldUnitTestBase;
/**
* Tests the new entity API for the comment field type.
*
* @group comment
*/
class CommentItemTest extends FieldUnitTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['comment', 'entity_test', 'user'];
protected function setUp() {
parent::setUp();
$this->installSchema('comment', ['comment_entity_statistics']);
$this->installConfig(['comment']);
}
/**
* Tests using entity fields of the comment field type.
*/
public function testCommentItem() {
$this->addDefaultCommentField('entity_test', 'entity_test', 'comment');
// Verify entity creation.
$entity = entity_create('entity_test');
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id, TRUE);
$this->assertTrue($entity->comment instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->comment[0] instanceof CommentItemInterface, 'Field item implements interface.');
// Test sample item generation.
/** @var \Drupal\entity_test\Entity\EntityTest $entity */
$entity = entity_create('entity_test');
$entity->comment->generateSampleItems();
$this->entityValidateAndSave($entity);
$this->assertTrue(in_array($entity->get('comment')->status, [
CommentItemInterface::HIDDEN,
CommentItemInterface::CLOSED,
CommentItemInterface::OPEN,
]), 'Comment status value in defined range');
}
}

View file

@ -0,0 +1,140 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentLanguageTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests for comment language.
*
* @group comment
*/
class CommentLanguageTest extends WebTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* We also use the language_test module here to be able to turn on content
* language negotiation. Drupal core does not provide a way in itself to do
* that.
*
* @var array
*/
public static $modules = array('node', 'language', 'language_test', 'comment_test');
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Create and login user.
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'administer comments', 'create article content', 'access comments', 'post comments', 'skip comment approval'));
$this->drupalLogin($admin_user);
// Add language.
$edit = array('predefined_langcode' => 'fr');
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Set "Article" content type to use multilingual support.
$edit = array('language_configuration[language_alterable]' => TRUE);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Enable content language negotiation UI.
\Drupal::state()->set('language_test.content_language_type', TRUE);
// Set interface language detection to user and content language detection
// to URL. Disable inheritance from interface language to ensure content
// language will fall back to the default language if no URL language can be
// detected.
$edit = array(
'language_interface[enabled][language-user]' => TRUE,
'language_content[enabled][language-url]' => TRUE,
'language_content[enabled][language-interface]' => FALSE,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Change user language preference, this way interface language is always
// French no matter what path prefix the URLs have.
$edit = array('preferred_langcode' => 'fr');
$this->drupalPostForm("user/" . $admin_user->id() . "/edit", $edit, t('Save'));
// Create comment field on article.
$this->addDefaultCommentField('node', 'article');
// Make comment body translatable.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
$this->assertTrue($field_storage->isTranslatable(), 'Comment body is translatable.');
}
/**
* Test that comment language is properly set.
*/
function testCommentLanguage() {
// Create two nodes, one for english and one for french, and comment each
// node using both english and french as content language by changing URL
// language prefixes. Meanwhile interface language is always French, which
// is the user language preference. This way we can ensure that node
// language and interface language do not influence comment language, as
// only content language has to.
foreach ($this->container->get('language_manager')->getLanguages() as $node_langcode => $node_language) {
// Create "Article" content.
$title = $this->randomMachineName();
$edit = array(
'title[0][value]' => $title,
'body[0][value]' => $this->randomMachineName(),
'langcode[0][value]' => $node_langcode,
'comment[0][status]' => CommentItemInterface::OPEN,
);
$this->drupalPostForm("node/add/article", $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($title);
$prefixes = language_negotiation_url_prefixes();
foreach ($this->container->get('language_manager')->getLanguages() as $langcode => $language) {
// Post a comment with content language $langcode.
$prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/';
$comment_values[$node_langcode][$langcode] = $this->randomMachineName();
$edit = array(
'subject[0][value]' => $this->randomMachineName(),
'comment_body[0][value]' => $comment_values[$node_langcode][$langcode],
);
$this->drupalPostForm($prefix . 'node/' . $node->id(), $edit, t('Preview'));
$this->drupalPostForm(NULL, $edit, t('Save'));
// Check that comment language matches the current content language.
$cids = \Drupal::entityQuery('comment')
->condition('entity_id', $node->id())
->condition('entity_type', 'node')
->condition('field_name', 'comment')
->sort('cid', 'DESC')
->range(0, 1)
->execute();
$comment = Comment::load(reset($cids));
$args = array('%node_language' => $node_langcode, '%comment_language' => $comment->langcode->value, '%langcode' => $langcode);
$this->assertEqual($comment->langcode->value, $langcode, format_string('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args));
$this->assertEqual($comment->comment_body->value, $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.');
}
}
// Check that comment bodies appear in the administration UI.
$this->drupalGet('admin/content/comment');
foreach ($comment_values as $node_values) {
foreach ($node_values as $value) {
$this->assertRaw($value);
}
}
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentLinksAlterTest.
*/
namespace Drupal\comment\Tests;
/**
* Tests comment links altering.
*
* @group comment
*/
class CommentLinksAlterTest extends CommentTestBase {
public static $modules = array('comment_test');
protected function setUp() {
parent::setUp();
// Enable comment_test.module's hook_comment_links_alter() implementation.
$this->container->get('state')->set('comment_test_links_alter_enabled', TRUE);
}
/**
* Tests comment links altering.
*/
public function testCommentLinksAlter() {
$this->drupalLogin($this->webUser);
$comment_text = $this->randomMachineName();
$subject = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text, $subject);
$this->drupalGet('node/' . $this->node->id());
$this->assertLink(t('Report'));
}
}

View file

@ -0,0 +1,130 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentLinksTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\comment\CommentInterface;
use Drupal\user\RoleInterface;
/**
* 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.
*
* @var array
*
* @todo Remove this dependency.
*/
public static $modules = array('views');
/**
* Tests that comment links are output and can be hidden.
*/
public function testCommentLinks() {
// Bartik theme alters comment links, so use a different theme.
\Drupal::service('theme_handler')->install(array('stark'));
$this->config('system.theme')
->set('default', 'stark')
->save();
// Remove additional user permissions from $this->webUser added by setUp(),
// since this test is limited to anonymous and authenticated roles only.
$roles = $this->webUser->getRoles();
entity_delete_multiple('user_role', array(reset($roles)));
// 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', CommentItemInterface::FORM_BELOW, 'Set comment form location');
$this->setCommentAnonymous(TRUE);
$this->node->comment = CommentItemInterface::OPEN;
$this->node->save();
// Change user permissions.
$perms = array(
'access comments' => 1,
'post comments' => 1,
'skip comment approval' => 1,
'edit own comments' => 1,
);
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, $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);
// 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');
}
// Make sure we can hide node links.
entity_get_display('node', $this->node->bundle(), 'default')
->removeComponent('links')
->save();
$this->drupalGet($this->node->urlInfo());
$this->assertNoLink('1 comment');
$this->assertNoLink('Add new comment');
// Visit the full node, make sure there are links for the comment.
$this->drupalGet('node/' . $this->node->id());
$this->assertText($comment->getSubject());
$this->assertLink('Reply');
// Make sure we can hide comment links.
entity_get_display('comment', 'comment', 'default')
->removeComponent('links')
->save();
$this->drupalGet('node/' . $this->node->id());
$this->assertText($comment->getSubject());
$this->assertNoLink('Reply');
}
}

View file

@ -0,0 +1,154 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentNewIndicatorTest.
*/
namespace Drupal\comment\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Language\LanguageInterface;
use Drupal\comment\CommentInterface;
use Drupal\Core\Url;
/**
* Tests the 'new' indicator posted on comments.
*
* @group comment
*/
class CommentNewIndicatorTest extends CommentTestBase {
/**
* Use the main node listing to test rendering on teasers.
*
* @var array
*
* @todo Remove this dependency.
*/
public static $modules = array('views');
/**
* Get node "x new comments" metadata from the server for the current user.
*
* @param array $node_ids
* An array of node IDs.
*
* @return string
* The response body.
*/
protected function renderNewCommentsNodeLinks(array $node_ids) {
// Build POST values.
$post = array();
for ($i = 0; $i < count($node_ids); $i++) {
$post['node_ids[' . $i . ']'] = $node_ids[$i];
}
$post['field_name'] = 'comment';
// Serialize POST values.
foreach ($post as $key => $value) {
// Encode according to application/x-www-form-urlencoded
// Both names and values needs to be urlencoded, according to
// http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
$post[$key] = urlencode($key) . '=' . urlencode($value);
}
$post = implode('&', $post);
// Perform HTTP request.
return $this->curlExec(array(
CURLOPT_URL => \Drupal::url('comment.new_comments_node_links', array(), array('absolute' => TRUE)),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => array(
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
),
));
}
/**
* Tests new comment marker.
*/
public function testCommentNewCommentsIndicator() {
// Test if the right links are displayed when no comment is present for the
// node.
$this->drupalLogin($this->adminUser);
$this->drupalGet('node');
$this->assertNoLink(t('@count comments', array('@count' => 0)));
$this->assertLink(t('Read more'));
// Verify the data-history-node-last-comment-timestamp attribute, which is
// used by the drupal.node-new-comments-link library to determine whether
// a "x new comments" link might be necessary or not. We do this in
// JavaScript to prevent breaking the render cache.
$this->assertIdentical(0, count($this->xpath('//*[@data-history-node-last-comment-timestamp]')), 'data-history-node-last-comment-timestamp attribute is not set.');
// Create a new comment. This helper function may be run with different
// comment settings so use $comment->save() to avoid complex setup.
/** @var \Drupal\comment\CommentInterface $comment */
$comment = entity_create('comment', array(
'cid' => NULL,
'entity_id' => $this->node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'pid' => 0,
'uid' => $this->loggedInUser->id(),
'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->drupalLogout();
// Log in with 'web user' and check comment links.
$this->drupalLogin($this->webUser);
$this->drupalGet('node');
// Verify the data-history-node-last-comment-timestamp attribute. Given its
// value, the drupal.node-new-comments-link library would determine that the
// node received a comment after the user last viewed it, and hence it would
// perform an HTTP request to render the "new comments" node link.
$this->assertIdentical(1, count($this->xpath('//*[@data-history-node-last-comment-timestamp="' . $comment->getChangedTime() . '"]')), 'data-history-node-last-comment-timestamp attribute is set to the correct value.');
$this->assertIdentical(1, count($this->xpath('//*[@data-history-node-field-name="comment"]')), 'data-history-node-field-name attribute is set to the correct value.');
// The data will be pre-seeded on this particular page in drupalSettings, to
// avoid the need for the client to make a separate request to the server.
$settings = $this->getDrupalSettings();
$this->assertEqual($settings['history'], ['lastReadTimestamps' => [1 => 0]]);
$this->assertEqual($settings['comment'], [
'newCommentsLinks' => [
'node' => [
'comment' => [
1 => [
'new_comment_count' => 1,
'first_new_comment_link' => Url::fromRoute('entity.node.canonical', ['node' => 1])->setOptions([
'fragment' => 'new',
])->toString(),
],
],
],
],
]);
// Pretend the data was not present in drupalSettings, i.e. test the
// separate request to the server.
$response = $this->renderNewCommentsNodeLinks(array($this->node->id()));
$this->assertResponse(200);
$json = Json::decode($response);
$expected = array($this->node->id() => array(
'new_comment_count' => 1,
'first_new_comment_link' => $this->node->url('canonical', array('fragment' => 'new')),
));
$this->assertIdentical($expected, $json);
// Failing to specify node IDs for the endpoint should return a 404.
$this->renderNewCommentsNodeLinks(array());
$this->assertResponse(404);
// Accessing the endpoint as the anonymous user should return a 403.
$this->drupalLogout();
$this->renderNewCommentsNodeLinks(array($this->node->id()));
$this->assertResponse(403);
$this->renderNewCommentsNodeLinks(array());
$this->assertResponse(403);
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentNodeAccessTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
/**
* Tests comments with node access.
*
* Verifies there is no PostgreSQL error when viewing a node with threaded
* comments (a comment and a reply), if a node access module is in use.
*
* @group comment
*/
class CommentNodeAccessTest extends CommentTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('node_access_test');
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Re-create user.
$this->webUser = $this->drupalCreateUser(array(
'access comments',
'post comments',
'create article content',
'edit own comments',
'node test view',
'skip comment approval',
));
// Set the author of the created node to the web_user uid.
$this->node->setOwnerId($this->webUser->id())->save();
}
/**
* Test that threaded comments can be viewed.
*/
function testThreadedCommentView() {
// Set comments to have subject required and preview disabled.
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
// Post comment.
$this->drupalLogin($this->webUser);
$comment_text = $this->randomMachineName();
$comment_subject = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text, $comment_subject);
$this->assertTrue($this->commentExists($comment), 'Comment found.');
// Check comment display.
$this->drupalGet('node/' . $this->node->id());
$this->assertText($comment_subject, 'Individual comment subject found.');
$this->assertText($comment_text, 'Individual comment body found.');
// Reply to comment, creating second comment.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id());
$reply_text = $this->randomMachineName();
$reply_subject = $this->randomMachineName();
$reply = $this->postComment(NULL, $reply_text, $reply_subject, TRUE);
$this->assertTrue($this->commentExists($reply, TRUE), 'Reply found.');
// Go to the node page and verify comment and reply are visible.
$this->drupalGet('node/' . $this->node->id());
$this->assertText($comment_text);
$this->assertText($comment_subject);
$this->assertText($reply_text);
$this->assertText($reply_subject);
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentNodeChangesTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests that comments behave correctly when the node is changed.
*
* @group comment
*/
class CommentNodeChangesTest extends CommentTestBase {
/**
* Tests that comments are deleted with the node.
*/
function testNodeDeletion() {
$this->drupalLogin($this->webUser);
$comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($comment->id(), 'The comment could be loaded.');
$this->node->delete();
$this->assertFalse(Comment::load($comment->id()), 'The comment could not be loaded after the node was deleted.');
// Make sure the comment field storage and all its fields are deleted when
// the node type is deleted.
$this->assertNotNull(FieldStorageConfig::load('node.comment'), 'Comment field storage exists');
$this->assertNotNull(FieldConfig::load('node.article.comment'), 'Comment field exists');
// Delete the node type.
entity_delete_multiple('node_type', array($this->node->bundle()));
$this->assertNull(FieldStorageConfig::load('node.comment'), 'Comment field storage deleted');
$this->assertNull(FieldConfig::load('node.article.comment'), 'Comment field deleted');
}
}

View file

@ -0,0 +1,496 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentNonNodeTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Entity\CommentType;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field_ui\Tests\FieldUiTestTrait;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\RoleInterface;
/**
* Tests commenting on a test entity.
*
* @group comment
*/
class CommentNonNodeTest extends WebTestBase {
use FieldUiTestTrait;
use CommentTestTrait;
public static $modules = array('comment', 'user', 'field_ui', 'entity_test', 'block');
/**
* An administrative user with permission to configure comment settings.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* The entity to use within tests.
*
* @var \Drupal\entity_test\Entity\EntityTest
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
// Create a bundle for entity_test.
entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test');
entity_create('comment_type', array(
'id' => 'comment',
'label' => 'Comment settings',
'description' => 'Comment settings',
'target_entity_type_id' => 'entity_test',
))->save();
// Create comment field on entity_test bundle.
$this->addDefaultCommentField('entity_test', 'entity_test');
// Verify that bundles are defined correctly.
$bundles = \Drupal::entityManager()->getBundleInfo('comment');
$this->assertEqual($bundles['comment']['label'], 'Comment settings');
// Create test user.
$this->adminUser = $this->drupalCreateUser(array(
'administer comments',
'skip comment approval',
'post comments',
'access comments',
'view test entity',
'administer entity_test content',
));
// Enable anonymous and authenticated user comments.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments',
'post comments',
'skip comment approval',
));
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, array(
'access comments',
'post comments',
'skip comment approval',
));
// Create a test entity.
$random_label = $this->randomMachineName();
$data = array('type' => 'entity_test', 'name' => $random_label);
$this->entity = entity_create('entity_test', $data);
$this->entity->save();
}
/**
* Posts a comment.
*
* @param \Drupal\Core\Entity\EntityInterface|null $entity
* Entity to post comment on or NULL to post to the previously loaded page.
* @param string $comment
* Comment body.
* @param string $subject
* Comment subject.
* @param mixed $contact
* Set to NULL for no contact info, TRUE to ignore success checking, and
* array of values to set contact info.
*
* @return \Drupal\comment\CommentInterface
* The new comment entity.
*/
function postComment(EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
$edit = array();
$edit['comment_body[0][value]'] = $comment;
$field = FieldConfig::loadByName('entity_test', 'entity_test', 'comment');
$preview_mode = $field->getSetting('preview');
// Must get the page before we test for fields.
if ($entity !== NULL) {
$this->drupalGet('comment/reply/entity_test/' . $entity->id() . '/comment');
}
// Determine the visibility of subject form field.
if (entity_get_form_display('comment', 'comment', 'default')->getComponent('subject')) {
// Subject input allowed.
$edit['subject[0][value]'] = $subject;
}
else {
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
}
if ($contact !== NULL && is_array($contact)) {
$edit += $contact;
}
switch ($preview_mode) {
case DRUPAL_REQUIRED:
// Preview required so no save button should be found.
$this->assertNoFieldByName('op', t('Save'), 'Save button not found.');
$this->drupalPostForm(NULL, $edit, t('Preview'));
// Don't break here so that we can test post-preview field presence and
// function below.
case DRUPAL_OPTIONAL:
$this->assertFieldByName('op', t('Preview'), 'Preview button found.');
$this->assertFieldByName('op', t('Save'), 'Save button found.');
$this->drupalPostForm(NULL, $edit, t('Save'));
break;
case DRUPAL_DISABLED:
$this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.');
$this->assertFieldByName('op', t('Save'), 'Save button found.');
$this->drupalPostForm(NULL, $edit, t('Save'));
break;
}
$match = array();
// Get comment ID
preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
// Get comment.
if ($contact !== TRUE) { // If true then attempting to find error message.
if ($subject) {
$this->assertText($subject, 'Comment subject posted.');
}
$this->assertText($comment, 'Comment body posted.');
$this->assertTrue((!empty($match) && !empty($match[1])), 'Comment ID found.');
}
if (isset($match[1])) {
return Comment::load($match[1]);
}
}
/**
* Checks current page for specified comment.
*
* @param \Drupal\comment\CommentInterface $comment
* The comment object.
* @param bool $reply
* Boolean indicating whether the comment is a reply to another comment.
*
* @return boolean
* Boolean indicating whether the comment was found.
*/
function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
if ($comment) {
$regex = '/' . ($reply ? '<div class="indented">(.*?)' : '');
$regex .= '<a id="comment-' . $comment->id() . '"(.*?)';
$regex .= $comment->getSubject() . '(.*?)';
$regex .= $comment->comment_body->value . '(.*?)';
$regex .= '/s';
return (boolean) preg_match($regex, $this->getRawContent());
}
else {
return FALSE;
}
}
/**
* Checks whether the commenter's contact information is displayed.
*
* @return boolean
* Contact info is available.
*/
function commentContactInfoAvailable() {
return preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->getRawContent());
}
/**
* Performs the specified operation on the specified comment.
*
* @param object $comment
* Comment to perform operation on.
* @param string $operation
* Operation to perform.
* @param bool $approval
* Operation is found on approval page.
*/
function performCommentOperation($comment, $operation, $approval = FALSE) {
$edit = array();
$edit['operation'] = $operation;
$edit['comments[' . $comment->id() . ']'] = TRUE;
$this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
if ($operation == 'delete') {
$this->drupalPostForm(NULL, array(), t('Delete comments'));
$this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation)));
}
else {
$this->assertText(t('The update has been performed.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation)));
}
}
/**
* Gets the comment ID for an unapproved comment.
*
* @param string $subject
* Comment subject to find.
*
* @return integer
* Comment ID.
*/
function getUnapprovedComment($subject) {
$this->drupalGet('admin/content/comment/approval');
preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>(' . $subject . ')/', $this->getRawContent(), $match);
return $match[2];
}
/**
* Tests anonymous comment functionality.
*/
function testCommentFunctionality() {
$limited_user = $this->drupalCreateUser(array(
'administer entity_test fields'
));
$this->drupalLogin($limited_user);
// Test that default field exists.
$this->drupalGet('entity_test/structure/entity_test/fields');
$this->assertText(t('Comments'));
$this->assertLinkByHref('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
// Test widget hidden option is not visible when there's no comments.
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
$this->assertResponse(200);
$this->assertNoField('edit-default-value-input-comment-und-0-status-0');
// Test that field to change cardinality is not available.
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment/storage');
$this->assertResponse(200);
$this->assertNoField('cardinality_number');
$this->assertNoField('cardinality');
$this->drupalLogin($this->adminUser);
// Test breadcrumb on comment add page.
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual(current($this->xpath($xpath)), $this->entity->label(), 'Last breadcrumb item is equal to node title on comment reply page.');
// Post a comment.
/** @var \Drupal\comment\CommentInterface $comment1 */
$comment1 = $this->postComment($this->entity, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($comment1), 'Comment on test entity exists.');
// Test breadcrumb on comment reply page.
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1->id());
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual(current($this->xpath($xpath)), $comment1->getSubject(), 'Last breadcrumb item is equal to comment title on comment reply page.');
// Test breadcrumb on comment edit page.
$this->drupalGet('comment/' . $comment1->id() . '/edit');
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual(current($this->xpath($xpath)), $comment1->getSubject(), 'Last breadcrumb item is equal to comment subject on edit page.');
// Test breadcrumb on comment delete page.
$this->drupalGet('comment/' . $comment1->id() . '/delete');
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual(current($this->xpath($xpath)), $comment1->getSubject(), 'Last breadcrumb item is equal to comment subject on delete confirm page.');
// Unpublish the comment.
$this->performCommentOperation($comment1, 'unpublish');
$this->drupalGet('admin/content/comment/approval');
$this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was unpublished.');
// Publish the comment.
$this->performCommentOperation($comment1, 'publish', TRUE);
$this->drupalGet('admin/content/comment');
$this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.');
// Delete the comment.
$this->performCommentOperation($comment1, 'delete');
$this->drupalGet('admin/content/comment');
$this->assertNoRaw('comments[' . $comment1->id() . ']', 'Comment was deleted.');
// Post another comment.
$comment1 = $this->postComment($this->entity, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($comment1), 'Comment on test entity exists.');
// Check that the comment was found.
$this->drupalGet('admin/content/comment');
$this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.');
// Check that entity access applies to administrative page.
$this->assertText($this->entity->label(), 'Name of commented account found.');
$limited_user = $this->drupalCreateUser(array(
'administer comments',
));
$this->drupalLogin($limited_user);
$this->drupalGet('admin/content/comment');
$this->assertNoText($this->entity->label(), 'No commented account name found.');
$this->drupalLogout();
// Deny anonymous users access to comments.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => FALSE,
'post comments' => FALSE,
'skip comment approval' => FALSE,
'view test entity' => TRUE,
));
// Attempt to view comments while disallowed.
$this->drupalGet('entity-test/' . $this->entity->id());
$this->assertNoPattern('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
$this->assertNoLink('Add new comment', 'Link to add comment was found.');
// Attempt to view test entity comment form while disallowed.
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
$this->assertResponse(403);
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
$this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field not found.');
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => FALSE,
'view test entity' => TRUE,
'skip comment approval' => FALSE,
));
$this->drupalGet('entity_test/' . $this->entity->id());
$this->assertPattern('@<h2[^>]*>Comments</h2>@', 'Comments were displayed.');
$this->assertLink('Log in', 0, 'Link to log in was found.');
$this->assertLink('register', 0, 'Link to register was found.');
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
$this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field not found.');
// Test the combination of anonymous users being able to post, but not view
// comments, to ensure that access to post comments doesn't grant access to
// view them.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => FALSE,
'post comments' => TRUE,
'skip comment approval' => TRUE,
'view test entity' => TRUE,
));
$this->drupalGet('entity_test/' . $this->entity->id());
$this->assertNoPattern('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
$this->assertFieldByName('subject[0][value]', '', 'Subject field found.');
$this->assertFieldByName('comment_body[0][value]', '', 'Comment field found.');
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1->id());
$this->assertResponse(403);
$this->assertNoText($comment1->getSubject(), 'Comment not displayed.');
// Test comment field widget changes.
$limited_user = $this->drupalCreateUser(array(
'administer entity_test fields',
'view test entity',
'administer entity_test content',
));
$this->drupalLogin($limited_user);
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
$this->assertNoFieldChecked('edit-default-value-input-comment-0-status-0');
$this->assertNoFieldChecked('edit-default-value-input-comment-0-status-1');
$this->assertFieldChecked('edit-default-value-input-comment-0-status-2');
// Test comment option change in field settings.
$edit = array(
'default_value_input[comment][0][status]' => CommentItemInterface::CLOSED,
'settings[anonymous]' => COMMENT_ANONYMOUS_MAY_CONTACT,
);
$this->drupalPostForm(NULL, $edit, t('Save settings'));
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
$this->assertNoFieldChecked('edit-default-value-input-comment-0-status-0');
$this->assertFieldChecked('edit-default-value-input-comment-0-status-1');
$this->assertNoFieldChecked('edit-default-value-input-comment-0-status-2');
$this->assertFieldByName('settings[anonymous]', COMMENT_ANONYMOUS_MAY_CONTACT);
// Add a new comment-type.
$bundle = CommentType::create(array(
'id' => 'foobar',
'label' => 'Foobar',
'description' => '',
'target_entity_type_id' => 'entity_test',
));
$bundle->save();
// Add a new comment field.
$storage_edit = array(
'settings[comment_type]' => 'foobar',
);
$this->fieldUIAddNewField('entity_test/structure/entity_test', 'foobar', 'Foobar', 'comment', $storage_edit);
// Add a third comment field.
$this->fieldUIAddNewField('entity_test/structure/entity_test', 'barfoo', 'BarFoo', 'comment', $storage_edit);
// Check the field contains the correct comment type.
$field_storage = FieldStorageConfig::load('entity_test.field_barfoo');
$this->assertTrue($field_storage);
$this->assertEqual($field_storage->getSetting('comment_type'), 'foobar');
$this->assertEqual($field_storage->getCardinality(), 1);
// Test the new entity commenting inherits default.
$random_label = $this->randomMachineName();
$data = array('bundle' => 'entity_test', 'name' => $random_label);
$new_entity = entity_create('entity_test', $data);
$new_entity->save();
$this->drupalGet('entity_test/manage/' . $new_entity->id());
$this->assertNoFieldChecked('edit-field-foobar-0-status-1');
$this->assertFieldChecked('edit-field-foobar-0-status-2');
$this->assertNoField('edit-field-foobar-0-status-0');
// @todo Check proper url and form https://www.drupal.org/node/2458323
$this->drupalGet('comment/reply/entity_test/comment/' . $new_entity->id());
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field found.');
$this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field found.');
// Test removal of comment_body field.
$limited_user = $this->drupalCreateUser(array(
'administer entity_test fields',
'post comments',
'administer comment fields',
'administer comment types',
));
$this->drupalLogin($limited_user);
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
$this->assertFieldByName('comment_body[0][value]', '', 'Comment body field found.');
$this->fieldUIDeleteField('admin/structure/comment/manage/comment', 'comment.comment.comment_body', 'Comment', 'Comment settings');
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
$this->assertNoFieldByName('comment_body[0][value]', '', 'Comment body field not found.');
// Set subject field to autogenerate it.
$edit = ['subject[0][value]' => ''];
$this->drupalPostForm(NULL, $edit, t('Save'));
}
/**
* Tests comment fields cannot be added to entity types without integer IDs.
*/
public function testsNonIntegerIdEntities() {
// Create a bundle for entity_test_string_id.
entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test_string_id');
$limited_user = $this->drupalCreateUser(array(
'administer entity_test_string_id fields',
));
$this->drupalLogin($limited_user);
// Visit the Field UI field add page.
$this->drupalGet('entity_test_string_id/structure/entity_test/fields/add-field');
// Ensure field isn't shown for string IDs.
$this->assertNoOption('edit-new-storage-type', 'comment');
// Ensure a core field type shown.
$this->assertOption('edit-new-storage-type', 'boolean');
// Create a bundle for entity_test_no_id.
entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test_no_id');
$this->drupalLogin($this->drupalCreateUser(array(
'administer entity_test_no_id fields',
)));
// Visit the Field UI field add page.
$this->drupalGet('entity_test_no_id/structure/entity_test/fields/add-field');
// Ensure field isn't shown for empty IDs.
$this->assertNoOption('edit-new-storage-type', 'comment');
// Ensure a core field type shown.
$this->assertOption('edit-new-storage-type', 'boolean');
}
}

View file

@ -0,0 +1,392 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentPagerTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\node\Entity\Node;
/**
* Tests paging of comments and their settings.
*
* @group comment
*/
class CommentPagerTest extends CommentTestBase {
/**
* Confirms comment paging works correctly with flat and threaded comments.
*/
function testCommentPaging() {
$this->drupalLogin($this->adminUser);
// Set comment variables.
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentPreview(DRUPAL_DISABLED);
// Create a node and three comments.
$node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
$comments = array();
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_FLAT, 'Comment paging changed.');
// Set comments to one per page so that we are able to test paging without
// needing to insert large numbers of comments.
$this->setCommentsPerPage(1);
// Check the first page of the node, and confirm the correct comments are
// shown.
$this->drupalGet('node/' . $node->id());
$this->assertRaw(t('next'), 'Paging links found.');
$this->assertTrue($this->commentExists($comments[0]), 'Comment 1 appears on page 1.');
$this->assertFalse($this->commentExists($comments[1]), 'Comment 2 does not appear on page 1.');
$this->assertFalse($this->commentExists($comments[2]), 'Comment 3 does not appear on page 1.');
// Check the second page.
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 1)));
$this->assertTrue($this->commentExists($comments[1]), 'Comment 2 appears on page 2.');
$this->assertFalse($this->commentExists($comments[0]), 'Comment 1 does not appear on page 2.');
$this->assertFalse($this->commentExists($comments[2]), 'Comment 3 does not appear on page 2.');
// Check the third page.
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 2)));
$this->assertTrue($this->commentExists($comments[2]), 'Comment 3 appears on page 3.');
$this->assertFalse($this->commentExists($comments[0]), 'Comment 1 does not appear on page 3.');
$this->assertFalse($this->commentExists($comments[1]), 'Comment 2 does not appear on page 3.');
// Post a reply to the oldest comment and test again.
$oldest_comment = reset($comments);
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $oldest_comment->id());
$reply = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->setCommentsPerPage(2);
// We are still in flat view - the replies should not be on the first page,
// even though they are replies to the oldest comment.
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 0)));
$this->assertFalse($this->commentExists($reply, TRUE), 'In flat mode, reply does not appear on page 1.');
// If we switch to threaded mode, the replies on the oldest comment
// should be bumped to the first page and comment 6 should be bumped
// to the second page.
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Switched to threaded mode.');
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 0)));
$this->assertTrue($this->commentExists($reply, TRUE), 'In threaded mode, reply appears on page 1.');
$this->assertFalse($this->commentExists($comments[1]), 'In threaded mode, comment 2 has been bumped off of page 1.');
// If (# replies > # comments per page) in threaded expanded view,
// the overage should be bumped.
$reply2 = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 0)));
$this->assertFalse($this->commentExists($reply2, TRUE), 'In threaded mode where # replies > # comments per page, the newest reply does not appear on page 1.');
// Test that the page build process does not somehow generate errors when
// # comments per page is set to 0.
$this->setCommentsPerPage(0);
$this->drupalGet('node/' . $node->id(), array('query' => array('page' => 0)));
$this->assertFalse($this->commentExists($reply2, TRUE), 'Threaded mode works correctly when comments per page is 0.');
$this->drupalLogout();
}
/**
* Tests comment ordering and threading.
*/
function testCommentOrderingThreading() {
$this->drupalLogin($this->adminUser);
// Set comment variables.
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentPreview(DRUPAL_DISABLED);
// Display all the comments on the same page.
$this->setCommentsPerPage(1000);
// Create a node and three comments.
$node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
$comments = array();
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the second comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[1]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the first comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[0]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the last comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[2]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the second comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[3]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// At this point, the comment tree is:
// - 0
// - 4
// - 1
// - 3
// - 6
// - 2
// - 5
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_FLAT, 'Comment paging changed.');
$expected_order = array(
0,
1,
2,
3,
4,
5,
6,
);
$this->drupalGet('node/' . $node->id());
$this->assertCommentOrder($comments, $expected_order);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Switched to threaded mode.');
$expected_order = array(
0,
4,
1,
3,
6,
2,
5,
);
$this->drupalGet('node/' . $node->id());
$this->assertCommentOrder($comments, $expected_order);
}
/**
* Asserts that the comments are displayed in the correct order.
*
* @param $comments
* And array of comments.
* @param $expected_order
* An array of keys from $comments describing the expected order.
*/
function assertCommentOrder(array $comments, array $expected_order) {
$expected_cids = array();
// First, rekey the expected order by cid.
foreach ($expected_order as $key) {
$expected_cids[] = $comments[$key]->id();
}
$comment_anchors = $this->xpath('//a[starts-with(@id,"comment-")]');
$result_order = array();
foreach ($comment_anchors as $anchor) {
$result_order[] = substr($anchor['id'], 8);
}
return $this->assertEqual($expected_cids, $result_order, format_string('Comment order: expected @expected, returned @returned.', array('@expected' => implode(',', $expected_cids), '@returned' => implode(',', $result_order))));
}
/**
* Tests calculation of first page with new comment.
*/
function testCommentNewPageIndicator() {
$this->drupalLogin($this->adminUser);
// Set comment variables.
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentPreview(DRUPAL_DISABLED);
// Set comments to one per page so that we are able to test paging without
// needing to insert large numbers of comments.
$this->setCommentsPerPage(1);
// Create a node and three comments.
$node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
$comments = array();
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comments[] = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the second comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[1]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the first comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[0]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the last comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[2]->id());
$comments[] = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// At this point, the comment tree is:
// - 0
// - 4
// - 1
// - 3
// - 2
// - 5
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_FLAT, 'Comment paging changed.');
$expected_pages = array(
1 => 5, // Page of comment 5
2 => 4, // Page of comment 4
3 => 3, // Page of comment 3
4 => 2, // Page of comment 2
5 => 1, // Page of comment 1
6 => 0, // Page of comment 0
);
$node = Node::load($node->id());
foreach ($expected_pages as $new_replies => $expected_page) {
$returned_page = \Drupal::entityManager()->getStorage('comment')
->getNewCommentPageNumber($node->get('comment')->comment_count, $new_replies, $node);
$this->assertIdentical($expected_page, $returned_page, format_string('Flat mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page)));
}
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Switched to threaded mode.');
$expected_pages = array(
1 => 5, // Page of comment 5
2 => 1, // Page of comment 4
3 => 1, // Page of comment 4
4 => 1, // Page of comment 4
5 => 1, // Page of comment 4
6 => 0, // Page of comment 0
);
\Drupal::entityManager()->getStorage('node')->resetCache(array($node->id()));
$node = Node::load($node->id());
foreach ($expected_pages as $new_replies => $expected_page) {
$returned_page = \Drupal::entityManager()->getStorage('comment')
->getNewCommentPageNumber($node->get('comment')->comment_count, $new_replies, $node);
$this->assertEqual($expected_page, $returned_page, format_string('Threaded mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page)));
}
}
/**
* Confirms comment paging works correctly with two pagers.
*/
function testTwoPagers() {
// Add another field to article content-type.
$this->addDefaultCommentField('node', 'article', 'comment_2');
// Set default to display comment list with unique pager id.
entity_get_display('node', 'article', 'default')
->setComponent('comment_2', array(
'label' => 'hidden',
'type' => 'comment_default',
'weight' => 30,
'settings' => array(
'pager_id' => 1,
)
))
->save();
// Make sure pager appears in formatter summary and settings form.
$account = $this->drupalCreateUser(array('administer node display'));
$this->drupalLogin($account);
$this->drupalGet('admin/structure/types/manage/article/display');
$this->assertNoText(t('Pager ID: @id', array('@id' => 0)), 'No summary for standard pager');
$this->assertText(t('Pager ID: @id', array('@id' => 1)));
$this->drupalPostAjaxForm(NULL, array(), 'comment_settings_edit');
// Change default pager to 2.
$this->drupalPostForm(NULL, array('fields[comment][settings_edit_form][settings][pager_id]' => 2), t('Save'));
$this->assertText(t('Pager ID: @id', array('@id' => 2)));
// Revert the changes.
$this->drupalPostAjaxForm(NULL, array(), 'comment_settings_edit');
$this->drupalPostForm(NULL, array('fields[comment][settings_edit_form][settings][pager_id]' => 0), t('Save'));
$this->assertNoText(t('Pager ID: @id', array('@id' => 0)), 'No summary for standard pager');
$this->drupalLogin($this->adminUser);
// Add a new node with both comment fields open.
$node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()));
// Set comment options.
$comments = array();
foreach (array('comment', 'comment_2') as $field_name) {
$this->setCommentForm(TRUE, $field_name);
$this->setCommentPreview(DRUPAL_OPTIONAL, $field_name);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_FLAT, 'Comment paging changed.', $field_name);
// Set comments to one per page so that we are able to test paging without
// needing to insert large numbers of comments.
$this->setCommentsPerPage(1, $field_name);
for ($i = 0; $i < 3; $i++) {
$comment = t('Comment @count on field @field', array(
'@count' => $i + 1,
'@field' => $field_name,
));
$comments[] = $this->postComment($node, $comment, $comment, TRUE, $field_name);
}
}
// Check the first page of the node, and confirm the correct comments are
// shown.
$this->drupalGet('node/' . $node->id());
$this->assertRaw(t('next'), 'Paging links found.');
$this->assertRaw('Comment 1 on field comment');
$this->assertRaw('Comment 1 on field comment_2');
// Navigate to next page of field 1.
$this->clickLinkWithXPath('//h3/a[normalize-space(text())=:label]/ancestor::section[1]//a[@rel="next"]', array(':label' => 'Comment 1 on field comment'));
// Check only one pager updated.
$this->assertRaw('Comment 2 on field comment');
$this->assertRaw('Comment 1 on field comment_2');
// Return to page 1.
$this->drupalGet('node/' . $node->id());
// Navigate to next page of field 2.
$this->clickLinkWithXPath('//h3/a[normalize-space(text())=:label]/ancestor::section[1]//a[@rel="next"]', array(':label' => 'Comment 1 on field comment_2'));
// Check only one pager updated.
$this->assertRaw('Comment 1 on field comment');
$this->assertRaw('Comment 2 on field comment_2');
// Navigate to next page of field 1.
$this->clickLinkWithXPath('//h3/a[normalize-space(text())=:label]/ancestor::section[1]//a[@rel="next"]', array(':label' => 'Comment 1 on field comment'));
// Check only one pager updated.
$this->assertRaw('Comment 2 on field comment');
$this->assertRaw('Comment 2 on field comment_2');
}
/**
* Follows a link found at a give xpath query.
*
* Will click the first link found with the given xpath query by default,
* or a later one if an index is given.
*
* If the link is discovered and clicked, the test passes. Fail otherwise.
*
* @param string $xpath
* Xpath query that targets an anchor tag, or set of anchor tags.
* @param array $arguments
* An array of arguments with keys in the form ':name' matching the
* placeholders in the query. The values may be either strings or numeric
* values.
* @param int $index
* Link position counting from zero.
*
* @return string|false
* Page contents on success, or FALSE on failure.
*
* @see WebTestBase::clickLink()
*/
protected function clickLinkWithXPath($xpath, $arguments = array(), $index = 0) {
$url_before = $this->getUrl();
$urls = $this->xpath($xpath, $arguments);
if (isset($urls[$index])) {
$url_target = $this->getAbsoluteUrl($urls[$index]['href']);
$this->pass(SafeMarkup::format('Clicked link %label (@url_target) from @url_before', array('%label' => $xpath, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
return $this->drupalGet($url_target);
}
$this->fail(SafeMarkup::format('Link %label does not exist on @url_before', array('%label' => $xpath, '@url_before' => $url_before)), 'Browser');
return FALSE;
}
}

View file

@ -0,0 +1,195 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentPreviewTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\comment\Entity\Comment;
/**
* Tests comment preview.
*
* @group comment
*/
class CommentPreviewTest extends CommentTestBase {
/**
* The profile to install as a basis for testing.
*
* Using the standard profile to test user picture display in comments.
*
* @var string
*/
protected $profile = 'standard';
/**
* Tests comment preview.
*/
function testCommentPreview() {
// As admin user, configure comment settings.
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
// Login as web user and add a user picture.
$this->drupalLogin($this->webUser);
$image = current($this->drupalGetTestFiles('image'));
$edit['files[user_picture_0]'] = drupal_realpath($image->uri);
$this->drupalPostForm('user/' . $this->webUser->id() . '/edit', $edit, t('Save'));
// As the web user, fill in the comment form and preview the comment.
$edit = array();
$edit['subject[0][value]'] = $this->randomMachineName(8);
$edit['comment_body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/' . $this->node->id(), $edit, t('Preview'));
// Check that the preview is displaying the title and body.
$this->assertTitle(t('Preview comment | Drupal'), 'Page title is "Preview comment".');
$this->assertText($edit['subject[0][value]'], 'Subject displayed.');
$this->assertText($edit['comment_body[0][value]'], 'Comment displayed.');
// Check that the title and body fields are displayed with the correct values.
$this->assertFieldByName('subject[0][value]', $edit['subject[0][value]'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[0][value]', $edit['comment_body[0][value]'], 'Comment field displayed.');
// Check that the user picture is displayed.
$this->assertFieldByXPath("//article[contains(@class, 'preview')]//div[contains(@class, 'user-picture')]//img", NULL, 'User picture displayed.');
}
/**
* Tests comment preview.
*/
public function testCommentPreviewDuplicateSubmission() {
// As admin user, configure comment settings.
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
// Login as web user.
$this->drupalLogin($this->webUser);
// As the web user, fill in the comment form and preview the comment.
$edit = array();
$edit['subject[0][value]'] = $this->randomMachineName(8);
$edit['comment_body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/' . $this->node->id(), $edit, t('Preview'));
// Check that the preview is displaying the title and body.
$this->assertTitle(t('Preview comment | Drupal'), 'Page title is "Preview comment".');
$this->assertText($edit['subject[0][value]'], 'Subject displayed.');
$this->assertText($edit['comment_body[0][value]'], 'Comment displayed.');
// Check that the title and body fields are displayed with the correct values.
$this->assertFieldByName('subject[0][value]', $edit['subject[0][value]'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[0][value]', $edit['comment_body[0][value]'], 'Comment field displayed.');
// Store the content of this page.
$content = $this->getRawContent();
$this->drupalPostForm(NULL, [], 'Save');
$this->assertText('Your comment has been posted.');
$elements = $this->xpath('//section[contains(@class, "comment-wrapper")]/article');
$this->assertEqual(1, count($elements));
// Reset the content of the page to simulate the browser's back button, and
// re-submit the form.
$this->setRawContent($content);
$this->drupalPostForm(NULL, [], 'Save');
$this->assertText('Your comment has been posted.');
$elements = $this->xpath('//section[contains(@class, "comment-wrapper")]/article');
$this->assertEqual(2, count($elements));
}
/**
* Tests comment edit, preview, and save.
*/
function testCommentEditPreviewSave() {
$web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval', 'edit own comments'));
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$edit = array();
$date = new DrupalDateTime('2008-03-02 17:23');
$edit['subject[0][value]'] = $this->randomMachineName(8);
$edit['comment_body[0][value]'] = $this->randomMachineName(16);
$edit['name'] = $web_user->getUsername();
$edit['date[date]'] = $date->format('Y-m-d');
$edit['date[time]'] = $date->format('H:i:s');
$raw_date = $date->getTimestamp();
$expected_text_date = format_date($raw_date);
$expected_form_date = $date->format('Y-m-d');
$expected_form_time = $date->format('H:i:s');
$comment = $this->postComment($this->node, $edit['subject[0][value]'], $edit['comment_body[0][value]'], TRUE);
$this->drupalPostForm('comment/' . $comment->id() . '/edit', $edit, t('Preview'));
// Check that the preview is displaying the subject, comment, author and date correctly.
$this->assertTitle(t('Preview comment | Drupal'), 'Page title is "Preview comment".');
$this->assertText($edit['subject[0][value]'], 'Subject displayed.');
$this->assertText($edit['comment_body[0][value]'], 'Comment displayed.');
$this->assertText($edit['name'], 'Author displayed.');
$this->assertText($expected_text_date, 'Date displayed.');
// Check that the subject, comment, author and date fields are displayed with the correct values.
$this->assertFieldByName('subject[0][value]', $edit['subject[0][value]'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[0][value]', $edit['comment_body[0][value]'], 'Comment field displayed.');
$this->assertFieldByName('name', $edit['name'], 'Author field displayed.');
$this->assertFieldByName('date[date]', $edit['date[date]'], 'Date field displayed.');
$this->assertFieldByName('date[time]', $edit['date[time]'], 'Time field displayed.');
// Check that saving a comment produces a success message.
$this->drupalPostForm('comment/' . $comment->id() . '/edit', $edit, t('Save'));
$this->assertText(t('Your comment has been posted.'), 'Comment posted.');
// Check that the comment fields are correct after loading the saved comment.
$this->drupalGet('comment/' . $comment->id() . '/edit');
$this->assertFieldByName('subject[0][value]', $edit['subject[0][value]'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[0][value]', $edit['comment_body[0][value]'], 'Comment field displayed.');
$this->assertFieldByName('name', $edit['name'], 'Author field displayed.');
$this->assertFieldByName('date[date]', $expected_form_date, 'Date field displayed.');
$this->assertFieldByName('date[time]', $expected_form_time, 'Time field displayed.');
// Submit the form using the displayed values.
$displayed = array();
$displayed['subject[0][value]'] = (string) current($this->xpath("//input[@id='edit-subject-0-value']/@value"));
$displayed['comment_body[0][value]'] = (string) current($this->xpath("//textarea[@id='edit-comment-body-0-value']"));
$displayed['name'] = (string) current($this->xpath("//input[@id='edit-name']/@value"));
$displayed['date[date]'] = (string) current($this->xpath("//input[@id='edit-date-date']/@value"));
$displayed['date[time]'] = (string) current($this->xpath("//input[@id='edit-date-time']/@value"));
$this->drupalPostForm('comment/' . $comment->id() . '/edit', $displayed, t('Save'));
// Check that the saved comment is still correct.
$comment_storage = \Drupal::entityManager()->getStorage('comment');
$comment_storage->resetCache(array($comment->id()));
$comment_loaded = Comment::load($comment->id());
$this->assertEqual($comment_loaded->getSubject(), $edit['subject[0][value]'], 'Subject loaded.');
$this->assertEqual($comment_loaded->comment_body->value, $edit['comment_body[0][value]'], 'Comment body loaded.');
$this->assertEqual($comment_loaded->getAuthorName(), $edit['name'], 'Name loaded.');
$this->assertEqual($comment_loaded->getCreatedTime(), $raw_date, 'Date loaded.');
$this->drupalLogout();
// Check that the date and time of the comment are correct when edited by
// non-admin users.
$user_edit = array();
$expected_created_time = $comment_loaded->getCreatedTime();
$this->drupalLogin($web_user);
$this->drupalPostForm('comment/' . $comment->id() . '/edit', $user_edit, t('Save'));
$comment_storage->resetCache(array($comment->id()));
$comment_loaded = Comment::load($comment->id());
$this->assertEqual($comment_loaded->getCreatedTime(), $expected_created_time, 'Expected date and time for comment edited.');
$this->drupalLogout();
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentRssTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* Tests comments as part of an RSS feed.
*
* @group comment
*/
class CommentRssTest extends CommentTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('views');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Setup the rss view display.
EntityViewDisplay::create([
'status' => TRUE,
'targetEntityType' => 'node',
'bundle' => 'article',
'mode' => 'rss',
'content' => ['links' => ['weight' => 100]],
])->save();
}
/**
* Tests comments as part of an RSS feed.
*/
function testCommentRss() {
// Find comment in RSS feed.
$this->drupalLogin($this->webUser);
$this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
$this->drupalGet('rss.xml');
$this->assertCacheTags([
'config:views.view.frontpage', 'node:1', 'node_list', 'node_view', 'user:3',
]);
$this->assertCacheContexts([
'languages:language_interface',
'theme',
'user.node_grants:view',
'user.permissions',
'timezone',
]);
$raw = '<comments>' . $this->node->url('canonical', array('fragment' => 'comments', 'absolute' => TRUE)) . '</comments>';
$this->assertRaw($raw, 'Comments as part of RSS feed.');
// Hide comments from RSS feed and check presence.
$this->node->set('comment', CommentItemInterface::HIDDEN);
$this->node->save();
$this->drupalGet('rss.xml');
$this->assertNoRaw($raw, 'Hidden comments is not a part of RSS feed.');
}
}

View file

@ -0,0 +1,123 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentStatisticsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\Entity\Comment;
use Drupal\user\RoleInterface;
/**
* Tests comment statistics on nodes.
*
* @group comment
*/
class CommentStatisticsTest extends CommentTestBase {
/**
* A secondary user for posting comments.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser2;
protected function setUp() {
parent::setUp();
// Create a second user to post comments.
$this->webUser2 = $this->drupalCreateUser(array(
'post comments',
'create article content',
'edit own comments',
'post comments',
'skip comment approval',
'access comments',
'access content',
));
}
/**
* Tests the node comment statistics.
*/
function testCommentNodeCommentStatistics() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Set comments to have subject and preview disabled.
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(FALSE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
// Checks the initial values of node comment statistics with no comment.
$node = $node_storage->load($this->node->id());
$this->assertEqual($node->get('comment')->last_comment_timestamp, $this->node->getCreatedTime(), 'The initial value of node last_comment_timestamp is the node created date.');
$this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The initial value of node last_comment_name is NULL.');
$this->assertEqual($node->get('comment')->last_comment_uid, $this->webUser->id(), 'The initial value of node last_comment_uid is the node uid.');
$this->assertEqual($node->get('comment')->comment_count, 0, 'The initial value of node comment_count is zero.');
// Post comment #1 as web_user2.
$this->drupalLogin($this->webUser2);
$comment_text = $this->randomMachineName();
$this->postComment($this->node, $comment_text);
// Checks the new values of node comment statistics with comment #1.
// The node cache needs to be reset before reload.
$node_storage->resetCache(array($this->node->id()));
$node = $node_storage->load($this->node->id());
$this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The value of node last_comment_name is NULL.');
$this->assertEqual($node->get('comment')->last_comment_uid, $this->webUser2->id(), 'The value of node last_comment_uid is the comment #1 uid.');
$this->assertEqual($node->get('comment')->comment_count, 1, 'The value of node comment_count is 1.');
// Prepare for anonymous comment submission (comment approval enabled).
$this->drupalLogin($this->adminUser);
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => TRUE,
'skip comment approval' => FALSE,
));
// Ensure that the poster can leave some contact info.
$this->setCommentAnonymous('1');
$this->drupalLogout();
// Post comment #2 as anonymous (comment approval enabled).
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$anonymous_comment = $this->postComment($this->node, $this->randomMachineName(), '', TRUE);
// Checks the new values of node comment statistics with comment #2 and
// ensure they haven't changed since the comment has not been moderated.
// The node needs to be reloaded with the cache reset.
$node_storage->resetCache(array($this->node->id()));
$node = $node_storage->load($this->node->id());
$this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The value of node last_comment_name is still NULL.');
$this->assertEqual($node->get('comment')->last_comment_uid, $this->webUser2->id(), 'The value of node last_comment_uid is still the comment #1 uid.');
$this->assertEqual($node->get('comment')->comment_count, 1, 'The value of node comment_count is still 1.');
// Prepare for anonymous comment submission (no approval required).
$this->drupalLogin($this->adminUser);
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
'access comments' => TRUE,
'post comments' => TRUE,
'skip comment approval' => TRUE,
));
$this->drupalLogout();
// Post comment #3 as anonymous.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment');
$anonymous_comment = $this->postComment($this->node, $this->randomMachineName(), '', array('name' => $this->randomMachineName()));
$comment_loaded = Comment::load($anonymous_comment->id());
// Checks the new values of node comment statistics with comment #3.
// The node needs to be reloaded with the cache reset.
$node_storage->resetCache(array($this->node->id()));
$node = $node_storage->load($this->node->id());
$this->assertEqual($node->get('comment')->last_comment_name, $comment_loaded->getAuthorName(), 'The value of node last_comment_name is the name of the anonymous user.');
$this->assertEqual($node->get('comment')->last_comment_uid, 0, 'The value of node last_comment_uid is zero.');
$this->assertEqual($node->get('comment')->comment_count, 2, 'The value of node comment_count is 2.');
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentStringIdEntitiesTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\CommentType;
use Drupal\simpletest\KernelTestBase;
/**
* Tests that comment fields cannot be added to entities with non-integer IDs.
*
* @group comment
*/
class CommentStringIdEntitiesTest extends KernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array(
'comment',
'user',
'field',
'field_ui',
'entity_test',
'text',
);
protected function setUp() {
parent::setUp();
$this->installEntitySchema('comment');
$this->installSchema('comment', array('comment_entity_statistics'));
// Create the comment body field storage.
$this->installConfig(array('field'));
}
/**
* Tests that comment fields cannot be added entities with non-integer IDs.
*/
public function testCommentFieldNonStringId() {
try {
$bundle = CommentType::create(array(
'id' => 'foo',
'label' => 'foo',
'description' => '',
'target_entity_type_id' => 'entity_test_string_id',
));
$bundle->save();
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'foo',
'entity_type' => 'entity_test_string_id',
'settings' => array(
'comment_type' => 'entity_test_string_id',
),
'type' => 'comment',
));
$field_storage->save();
$this->fail('Did not throw an exception as expected.');
}
catch (\UnexpectedValueException $e) {
$this->pass('Exception thrown when trying to create comment field on Entity Type with string ID.');
}
}
}

View file

@ -0,0 +1,394 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTestBase.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\CommentType;
use Drupal\comment\Entity\Comment;
use Drupal\comment\CommentInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\WebTestBase;
/**
* Provides setup and helper methods for comment tests.
*/
abstract class CommentTestBase extends WebTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('comment', 'node', 'history', 'field_ui', 'datetime');
/**
* An administrative user with permission to configure comment settings.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A normal user with permission to post comments.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A test node to which comments will be posted.
*
* @var \Drupal\node\NodeInterface
*/
protected $node;
protected function setUp() {
parent::setUp();
// Create an article content type only if it does not yet exist, so that
// child classes may specify the standard profile.
$types = NodeType::loadMultiple();
if (empty($types['article'])) {
$this->drupalCreateContentType(array('type' => 'article', 'name' => t('Article')));
}
// Create two test users.
$this->adminUser = $this->drupalCreateUser(array(
'administer content types',
'administer comments',
'administer comment types',
'administer comment fields',
'administer comment display',
'skip comment approval',
'post comments',
'access comments',
'access content',
));
$this->webUser = $this->drupalCreateUser(array(
'access comments',
'post comments',
'create article content',
'edit own comments',
'skip comment approval',
'access content',
));
// Create comment field on article.
$this->addDefaultCommentField('node', 'article');
// Create a test node authored by the web user.
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()));
}
/**
* Posts a comment.
*
* @param \Drupal\Core\Entity\EntityInterface|null $entity
* Node to post comment on or NULL to post to the previously loaded page.
* @param string $comment
* Comment body.
* @param string $subject
* Comment subject.
* @param string $contact
* Set to NULL for no contact info, TRUE to ignore success checking, and
* array of values to set contact info.
* @param string $field_name
* (optional) Field name through which the comment should be posted.
* Defaults to 'comment'.
*
* @return \Drupal\comment\CommentInterface|null
* The posted comment or NULL when posted comment was not found.
*/
public function postComment($entity, $comment, $subject = '', $contact = NULL, $field_name = 'comment') {
$edit = array();
$edit['comment_body[0][value]'] = $comment;
if ($entity !== NULL) {
$field = FieldConfig::loadByName('node', $entity->bundle(), $field_name);
}
else {
$field = FieldConfig::loadByName('node', 'article', $field_name);
}
$preview_mode = $field->getSetting('preview');
// Must get the page before we test for fields.
if ($entity !== NULL) {
$this->drupalGet('comment/reply/node/' . $entity->id() . '/' . $field_name);
}
// Determine the visibility of subject form field.
if (entity_get_form_display('comment', 'comment', 'default')->getComponent('subject')) {
// Subject input allowed.
$edit['subject[0][value]'] = $subject;
}
else {
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
}
if ($contact !== NULL && is_array($contact)) {
$edit += $contact;
}
switch ($preview_mode) {
case DRUPAL_REQUIRED:
// Preview required so no save button should be found.
$this->assertNoFieldByName('op', t('Save'), 'Save button not found.');
$this->drupalPostForm(NULL, $edit, t('Preview'));
// Don't break here so that we can test post-preview field presence and
// function below.
case DRUPAL_OPTIONAL:
$this->assertFieldByName('op', t('Preview'), 'Preview button found.');
$this->assertFieldByName('op', t('Save'), 'Save button found.');
$this->drupalPostForm(NULL, $edit, t('Save'));
break;
case DRUPAL_DISABLED:
$this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.');
$this->assertFieldByName('op', t('Save'), 'Save button found.');
$this->drupalPostForm(NULL, $edit, t('Save'));
break;
}
$match = array();
// Get comment ID
preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
// Get comment.
if ($contact !== TRUE) { // If true then attempting to find error message.
if ($subject) {
$this->assertText($subject, 'Comment subject posted.');
}
$this->assertText($comment, 'Comment body posted.');
$this->assertTrue((!empty($match) && !empty($match[1])), 'Comment id found.');
}
if (isset($match[1])) {
\Drupal::entityManager()->getStorage('comment')->resetCache(array($match[1]));
return Comment::load($match[1]);
}
}
/**
* Checks current page for specified comment.
*
* @param \Drupal\comment\CommentInterface $comment
* The comment object.
* @param bool $reply
* Boolean indicating whether the comment is a reply to another comment.
*
* @return boolean
* Boolean indicating whether the comment was found.
*/
function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
if ($comment) {
$regex = '!' . ($reply ? '<div class="indented">(.*?)' : '');
$regex .= '<a id="comment-' . $comment->id() . '"(.*?)';
$regex .= $comment->getSubject() . '(.*?)';
$regex .= $comment->comment_body->value . '(.*?)';
$regex .= ($reply ? '</article>\s</div>(.*?)' : '');
$regex .= '!s';
return (boolean) preg_match($regex, $this->getRawContent());
}
else {
return FALSE;
}
}
/**
* Deletes a comment.
*
* @param \Drupal\comment\CommentInterface $comment
* Comment to delete.
*/
function deleteComment(CommentInterface $comment) {
$this->drupalPostForm('comment/' . $comment->id() . '/delete', array(), t('Delete'));
$this->assertText(t('The comment and all its replies have been deleted.'), 'Comment deleted.');
}
/**
* Sets the value governing whether the subject field should be enabled.
*
* @param bool $enabled
* Boolean specifying whether the subject field should be enabled.
*/
public function setCommentSubject($enabled) {
$form_display = entity_get_form_display('comment', 'comment', 'default');
if ($enabled) {
$form_display->setComponent('subject', array(
'type' => 'string_textfield',
));
}
else {
$form_display->removeComponent('subject');
}
$form_display->save();
// Display status message.
$this->pass('Comment subject ' . ($enabled ? 'enabled' : 'disabled') . '.');
}
/**
* Sets the value governing the previewing mode for the comment form.
*
* @param int $mode
* The preview mode: DRUPAL_DISABLED, DRUPAL_OPTIONAL or DRUPAL_REQUIRED.
* @param string $field_name
* (optional) Field name through which the comment should be posted.
* Defaults to 'comment'.
*/
public function setCommentPreview($mode, $field_name = 'comment') {
switch ($mode) {
case DRUPAL_DISABLED:
$mode_text = 'disabled';
break;
case DRUPAL_OPTIONAL:
$mode_text = 'optional';
break;
case DRUPAL_REQUIRED:
$mode_text = 'required';
break;
}
$this->setCommentSettings('preview', $mode, format_string('Comment preview @mode_text.', array('@mode_text' => $mode_text)), $field_name);
}
/**
* Sets the value governing whether the comment form is on its own page.
*
* @param bool $enabled
* TRUE if the comment form should be displayed on the same page as the
* comments; FALSE if it should be displayed on its own page.
* @param string $field_name
* (optional) Field name through which the comment should be posted.
* Defaults to 'comment'.
*/
public function setCommentForm($enabled, $field_name = 'comment') {
$this->setCommentSettings('form_location', ($enabled ? CommentItemInterface::FORM_BELOW : CommentItemInterface::FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.', $field_name);
}
/**
* Sets the value governing restrictions on anonymous comments.
*
* @param integer $level
* The level of the contact information allowed for anonymous comments:
* - 0: No contact information allowed.
* - 1: Contact information allowed but not required.
* - 2: Contact information required.
*/
function setCommentAnonymous($level) {
$this->setCommentSettings('anonymous', $level, format_string('Anonymous commenting set to level @level.', array('@level' => $level)));
}
/**
* Sets the value specifying the default number of comments per page.
*
* @param int $number
* Comments per page value.
* @param string $field_name
* (optional) Field name through which the comment should be posted.
* Defaults to 'comment'.
*/
public function setCommentsPerPage($number, $field_name = 'comment') {
$this->setCommentSettings('per_page', $number, format_string('Number of comments per page set to @number.', array('@number' => $number)), $field_name);
}
/**
* Sets a comment settings variable for the article content type.
*
* @param string $name
* Name of variable.
* @param string $value
* Value of variable.
* @param string $message
* Status message to display.
* @param string $field_name
* (optional) Field name through which the comment should be posted.
* Defaults to 'comment'.
*/
public function setCommentSettings($name, $value, $message, $field_name = 'comment') {
$field = FieldConfig::loadByName('node', 'article', $field_name);
$field->setSetting($name, $value);
$field->save();
// Display status message.
$this->pass($message);
}
/**
* Checks whether the commenter's contact information is displayed.
*
* @return boolean
* Contact info is available.
*/
function commentContactInfoAvailable() {
return preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->getRawContent());
}
/**
* Performs the specified operation on the specified comment.
*
* @param \Drupal\comment\CommentInterface $comment
* Comment to perform operation on.
* @param string $operation
* Operation to perform.
* @param bool $approval
* Operation is found on approval page.
*/
function performCommentOperation(CommentInterface $comment, $operation, $approval = FALSE) {
$edit = array();
$edit['operation'] = $operation;
$edit['comments[' . $comment->id() . ']'] = TRUE;
$this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
if ($operation == 'delete') {
$this->drupalPostForm(NULL, array(), t('Delete comments'));
$this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation)));
}
else {
$this->assertText(t('The update has been performed.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation)));
}
}
/**
* Gets the comment ID for an unapproved comment.
*
* @param string $subject
* Comment subject to find.
*
* @return integer
* Comment id.
*/
function getUnapprovedComment($subject) {
$this->drupalGet('admin/content/comment/approval');
preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>(' . $subject . ')/', $this->getRawContent(), $match);
return $match[2];
}
/**
* Creates a comment comment type (bundle).
*
* @param string $label
* The comment type label.
*
* @return \Drupal\comment\Entity\CommentType
* Created comment type.
*/
protected function createCommentType($label) {
$bundle = CommentType::create(array(
'id' => $label,
'label' => $label,
'description' => '',
'target_entity_type_id' => 'node',
));
$bundle->save();
return $bundle;
}
}

View file

@ -0,0 +1,130 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTestTrait.
*/
namespace Drupal\comment\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
/**
* Provides common functionality for the Comment test classes.
*/
trait CommentTestTrait {
/**
* Adds the default comment field to an entity.
*
* Attaches a comment field named 'comment' to the given entity type and
* bundle. Largely replicates the default behavior in Drupal 7 and earlier.
*
* @param string $entity_type
* The entity type to attach the default comment field to.
* @param string $bundle
* The bundle to attach the default comment field to.
* @param string $field_name
* (optional) Field name to use for the comment field. Defaults to
* 'comment'.
* @param int $default_value
* (optional) Default value, one of CommentItemInterface::HIDDEN,
* CommentItemInterface::OPEN, CommentItemInterface::CLOSED. Defaults to
* CommentItemInterface::OPEN.
* @param string $comment_type_id
* (optional) ID of comment type to use. Defaults to 'comment'.
*/
public function addDefaultCommentField($entity_type, $bundle, $field_name = 'comment', $default_value = CommentItemInterface::OPEN, $comment_type_id = 'comment') {
$entity_manager = \Drupal::entityManager();
// Create the comment type if needed.
$comment_type_storage = $entity_manager->getStorage('comment_type');
if ($comment_type = $comment_type_storage->load($comment_type_id)) {
if ($comment_type->getTargetEntityTypeId() !== $entity_type) {
throw new \InvalidArgumentException(SafeMarkup::format('The given comment type id %id can only be used with the %entity_type entity type', array(
'%id' => $comment_type_id,
'%entity_type' => $entity_type,
)));
}
}
else {
$comment_type_storage->create(array(
'id' => $comment_type_id,
'label' => Unicode::ucfirst($comment_type_id),
'target_entity_type_id' => $entity_type,
'description' => 'Default comment field',
))->save();
}
// Add a body field to the comment type.
\Drupal::service('comment.manager')->addBodyField($comment_type_id);
// Add a comment field to the host entity type. Create the field storage if
// needed.
if (!array_key_exists($field_name, $entity_manager->getFieldStorageDefinitions($entity_type))) {
$entity_manager->getStorage('field_storage_config')->create(array(
'entity_type' => $entity_type,
'field_name' => $field_name,
'type' => 'comment',
'translatable' => TRUE,
'settings' => array(
'comment_type' => $comment_type_id,
),
))->save();
}
// Create the field if needed, and configure its form and view displays.
if (!array_key_exists($field_name, $entity_manager->getFieldDefinitions($entity_type, $bundle))) {
$entity_manager->getStorage('field_config')->create(array(
'label' => 'Comments',
'description' => '',
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'required' => 1,
'default_value' => array(
array(
'status' => $default_value,
'cid' => 0,
'last_comment_name' => '',
'last_comment_timestamp' => 0,
'last_comment_uid' => 0,
),
),
))->save();
// Entity form displays: assign widget settings for the 'default' form
// mode, and hide the field in all other form modes.
entity_get_form_display($entity_type, $bundle, 'default')
->setComponent($field_name, array(
'type' => 'comment_default',
'weight' => 20,
))
->save();
foreach ($entity_manager->getFormModes($entity_type) as $id => $form_mode) {
$display = entity_get_form_display($entity_type, $bundle, $id);
// Only update existing displays.
if ($display && !$display->isNew()) {
$display->removeComponent($field_name)->save();
}
}
// Entity view displays: assign widget settings for the 'default' view
// mode, and hide the field in all other view modes.
entity_get_display($entity_type, $bundle, 'default')
->setComponent($field_name, array(
'label' => 'above',
'type' => 'comment_default',
'weight' => 20,
))
->save();
foreach ($entity_manager->getViewModes($entity_type) as $id => $view_mode) {
$display = entity_get_display($entity_type, $bundle, $id);
// Only update existing displays.
if ($display && !$display->isNew()) {
$display->removeComponent($field_name)->save();
}
}
}
}
}

View file

@ -0,0 +1,176 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentThreadingTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentManagerInterface;
/**
* Tests to make sure the comment number increments properly.
*
* @group comment
*/
class CommentThreadingTest extends CommentTestBase {
/**
* Tests the comment threading.
*/
function testCommentThreading() {
// Set comments to have a subject with preview disabled.
$this->drupalLogin($this->adminUser);
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
$this->setCommentSettings('default_mode', CommentManagerInterface::COMMENT_MODE_THREADED, 'Comment paging changed.');
$this->drupalLogout();
// Create a node.
$this->drupalLogin($this->webUser);
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()));
// Post comment #1.
$this->drupalLogin($this->webUser);
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$comment1 = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment1), 'Comment #1. Comment found.');
$this->assertEqual($comment1->getThread(), '01/');
// Confirm that there is no reference to a parent comment.
$this->assertNoParentLink($comment1->id());
// Post comment #2 following the comment #1 to test if it correctly jumps
// out the indentation in case there is a thread above.
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$this->postComment($this->node, $comment_text, $subject_text, TRUE);
// Reply to comment #1 creating comment #1_3.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment1->id());
$comment1_3 = $this->postComment(NULL, $this->randomMachineName(), '', TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment1_3, TRUE), 'Comment #1_3. Reply found.');
$this->assertEqual($comment1_3->getThread(), '01.00/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment1_3->id(), $comment1->id());
// Reply to comment #1_3 creating comment #1_3_4.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment1_3->id());
$comment1_3_4 = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment1_3_4, TRUE), 'Comment #1_3_4. Second reply found.');
$this->assertEqual($comment1_3_4->getThread(), '01.00.00/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment1_3_4->id(), $comment1_3->id());
// Reply to comment #1 creating comment #1_5.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment1->id());
$comment1_5 = $this->postComment(NULL, $this->randomMachineName(), '', TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment1_5), 'Comment #1_5. Third reply found.');
$this->assertEqual($comment1_5->getThread(), '01.01/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment1_5->id(), $comment1->id());
// Post comment #3 overall comment #5.
$this->drupalLogin($this->webUser);
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$comment5 = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment5), 'Comment #5. Second comment found.');
$this->assertEqual($comment5->getThread(), '03/');
// Confirm that there is no link to a parent comment.
$this->assertNoParentLink($comment5->id());
// Reply to comment #5 creating comment #5_6.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment5->id());
$comment5_6 = $this->postComment(NULL, $this->randomMachineName(), '', TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment5_6, TRUE), 'Comment #6. Reply found.');
$this->assertEqual($comment5_6->getThread(), '03.00/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment5_6->id(), $comment5->id());
// Reply to comment #5_6 creating comment #5_6_7.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment5_6->id());
$comment5_6_7 = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment5_6_7, TRUE), 'Comment #5_6_7. Second reply found.');
$this->assertEqual($comment5_6_7->getThread(), '03.00.00/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment5_6_7->id(), $comment5_6->id());
// Reply to comment #5 creating comment #5_8.
$this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment5->id());
$comment5_8 = $this->postComment(NULL, $this->randomMachineName(), '', TRUE);
// Confirm that the comment was created and has the correct threading.
$this->assertTrue($this->commentExists($comment5_8), 'Comment #5_8. Third reply found.');
$this->assertEqual($comment5_8->getThread(), '03.01/');
// Confirm that there is a link to the parent comment.
$this->assertParentLink($comment5_8->id(), $comment5->id());
}
/**
* Asserts that the link to the specified parent comment is present.
*
* @param int $cid
* The comment ID to check.
* @param int $pid
* The expected parent comment ID.
*/
protected function assertParentLink($cid, $pid) {
// This pattern matches a markup structure like:
// <a id="comment-2"></a>
// <article>
// <p class="parent">
// <a href="...comment-1"></a>
// </p>
// </article>
$pattern = "//a[@id='comment-$cid']/following-sibling::article//p[contains(@class, 'parent')]//a[contains(@href, 'comment-$pid')]";
$this->assertFieldByXpath($pattern, NULL, format_string(
'Comment %cid has a link to parent %pid.',
array(
'%cid' => $cid,
'%pid' => $pid,
)
));
}
/**
* Asserts that the specified comment does not have a link to a parent.
*
* @param int $cid
* The comment ID to check.
*/
protected function assertNoParentLink($cid) {
// This pattern matches a markup structure like:
// <a id="comment-2"></a>
// <article>
// <p class="parent"></p>
// </article>
$pattern = "//a[@id='comment-$cid']/following-sibling::article//p[contains(@class, 'parent')]";
$this->assertNoFieldByXpath($pattern, NULL, format_string(
'Comment %cid does not have a link to a parent.',
array(
'%cid' => $cid,
)
));
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTitleTest.
*/
namespace Drupal\comment\Tests;
/**
* Tests to ensure that appropriate and accessible markup is created for comment
* titles.
*
* @group comment
*/
class CommentTitleTest extends CommentTestBase {
/**
* Tests markup for comments with empty titles.
*/
public function testCommentEmptyTitles() {
// Installs module that sets comments to an empty string.
\Drupal::service('module_installer')->install(array('comment_empty_title_test'));
// Set comments to have a subject with preview disabled.
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
// Create a node.
$this->drupalLogin($this->webUser);
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()));
// Post comment #1 and verify that h3's are not rendered.
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
// Confirm that the comment was created.
$regex = '/<a id="comment-' . $comment->id() . '"(.*?)';
$regex .= $comment->comment_body->value . '(.*?)';
$regex .= '/s';
$this->assertPattern($regex, 'Comment is created successfully');
// Tests that markup is not generated for the comment without header.
$this->assertNoPattern('|<h3[^>]*></h3>|', 'Comment title H3 element not found when title is an empty string.');
}
/**
* Tests markup for comments with populated titles.
*/
public function testCommentPopulatedTitles() {
// Set comments to have a subject with preview disabled.
$this->setCommentPreview(DRUPAL_DISABLED);
$this->setCommentForm(TRUE);
$this->setCommentSubject(TRUE);
// Create a node.
$this->drupalLogin($this->webUser);
$this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()));
// Post comment #1 and verify that title is rendered in h3.
$subject_text = $this->randomMachineName();
$comment_text = $this->randomMachineName();
$comment1 = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
// Confirm that the comment was created.
$this->assertTrue($this->commentExists($comment1), 'Comment #1. Comment found.');
// Tests that markup is created for comment with heading.
$this->assertPattern('|<h3[^>]*><a[^>]*>' . $subject_text . '</a></h3>|', 'Comment title is rendered in h3 when title populated.');
}
}

View file

@ -0,0 +1,122 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTokenReplaceTest.
*/
namespace Drupal\comment\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\comment\Entity\Comment;
use Drupal\node\Entity\Node;
/**
* Generates text using placeholders for dummy content to check comment token
* replacement.
*
* @group comment
*/
class CommentTokenReplaceTest extends CommentTestBase {
/**
* Creates a comment, then tests the tokens generated from it.
*/
function testCommentTokenReplacement() {
$token_service = \Drupal::token();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$url_options = array(
'absolute' => TRUE,
'language' => $language_interface,
);
$this->drupalLogin($this->adminUser);
// Set comment variables.
$this->setCommentSubject(TRUE);
// Create a node and a comment.
$node = $this->drupalCreateNode(array('type' => 'article'));
$parent_comment = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the comment.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $parent_comment->id());
$child_comment = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName());
$comment = Comment::load($child_comment->id());
$comment->setHomepage('http://example.org/');
// Add HTML to ensure that sanitation of some fields tested directly.
$comment->setSubject('<blink>Blinking Comment</blink>');
// Generate and test sanitized tokens.
$tests = array();
$tests['[comment:cid]'] = $comment->id();
$tests['[comment:hostname]'] = SafeMarkup::checkPlain($comment->getHostname());
$tests['[comment:author]'] = Xss::filter($comment->getAuthorName());
$tests['[comment:mail]'] = SafeMarkup::checkPlain($this->adminUser->getEmail());
$tests['[comment:homepage]'] = check_url($comment->getHomepage());
$tests['[comment:title]'] = Xss::filter($comment->getSubject());
$tests['[comment:body]'] = $comment->comment_body->processed;
$tests['[comment:langcode]'] = SafeMarkup::checkPlain($comment->language()->getId());
$tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
$tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
$tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getCreatedTime(), array('langcode' => $language_interface->getId()));
$tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), array('langcode' => $language_interface->getId()));
$tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
$tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject());
$tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle());
// Test node specific tokens.
$tests['[comment:entity:nid]'] = $comment->getCommentedEntityId();
$tests['[comment:entity:title]'] = SafeMarkup::checkPlain($node->getTitle());
$tests['[comment:author:uid]'] = $comment->getOwnerId();
$tests['[comment:author:name]'] = SafeMarkup::checkPlain($this->adminUser->getUsername());
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, $expected, format_string('Sanitized comment token %token replaced.', array('%token' => $input)));
}
// Generate and test unsanitized tokens.
$tests['[comment:hostname]'] = $comment->getHostname();
$tests['[comment:author]'] = $comment->getAuthorName();
$tests['[comment:mail]'] = $this->adminUser->getEmail();
$tests['[comment:homepage]'] = $comment->getHomepage();
$tests['[comment:title]'] = $comment->getSubject();
$tests['[comment:body]'] = $comment->comment_body->value;
$tests['[comment:langcode]'] = $comment->language()->getId();
$tests['[comment:parent:title]'] = $parent_comment->getSubject();
$tests['[comment:entity]'] = $node->getTitle();
$tests['[comment:author:name]'] = $this->adminUser->getUsername();
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
$this->assertEqual($output, $expected, format_string('Unsanitized comment token %token replaced.', array('%token' => $input)));
}
// Test anonymous comment author.
$author_name = $this->randomString();
$comment->setOwnerId(0)->setAuthorName($author_name);
$input = '[comment:author]';
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, Xss::filter($author_name), format_string('Sanitized comment author token %token replaced.', array('%token' => $input)));
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
$this->assertEqual($output, $author_name, format_string('Unsanitized comment author token %token replaced.', array('%token' => $input)));
// Load node so comment_count gets computed.
$node = Node::load($node->id());
// Generate comment tokens for the node (it has 2 comments, both new).
$tests = array();
$tests['[entity:comment-count]'] = 2;
$tests['[entity:comment-count-new]'] = 2;
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('entity' => $node, 'node' => $node), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, $expected, format_string('Node comment token %token replaced.', array('%token' => $input)));
}
}
}

View file

@ -0,0 +1,209 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTranslationUITest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\content_translation\Tests\ContentTranslationUITestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the Comment Translation UI.
*
* @group comment
*/
class CommentTranslationUITest extends ContentTranslationUITestBase {
use CommentTestTrait;
/**
* The subject of the test comment.
*/
protected $subject;
/**
* An administrative user with permission to administer comments.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('language', 'content_translation', 'node', 'comment');
protected function setUp() {
$this->entityTypeId = 'comment';
$this->nodeBundle = 'article';
$this->bundle = 'comment_article';
$this->testLanguageSelector = FALSE;
$this->subject = $this->randomMachineName();
parent::setUp();
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::setupBundle().
*/
function setupBundle() {
parent::setupBundle();
$this->drupalCreateContentType(array('type' => $this->nodeBundle, 'name' => $this->nodeBundle));
// Add a comment field to the article content type.
$this->addDefaultCommentField('node', 'article', 'comment_article', CommentItemInterface::OPEN, 'comment_article');
// Create a page content type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'page'));
// Add a comment field to the page content type - this one won't be
// translatable.
$this->addDefaultCommentField('node', 'page', 'comment');
// Mark this bundle as translatable.
$this->container->get('content_translation.manager')->setEnabled('comment', 'comment_article', TRUE);
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array('post comments', 'administer comments', 'access comments'));
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::createEntity().
*/
protected function createEntity($values, $langcode, $comment_type = 'comment_article') {
if ($comment_type == 'comment_article') {
// This is the article node type, with the 'comment_article' field.
$node_type = 'article';
$field_name = 'comment_article';
}
else {
// This is the page node type with the non-translatable 'comment' field.
$node_type = 'page';
$field_name = 'comment';
}
$node = $this->drupalCreateNode(array(
'type' => $node_type,
$field_name => array(
array('status' => CommentItemInterface::OPEN)
),
));
$values['entity_id'] = $node->id();
$values['entity_type'] = 'node';
$values['field_name'] = $field_name;
$values['uid'] = $node->getOwnerId();
return parent::createEntity($values, $langcode, $comment_type);
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getNewEntityValues().
*/
protected function getNewEntityValues($langcode) {
// Comment subject is not translatable hence we use a fixed value.
return array(
'subject' => array(array('value' => $this->subject)),
'comment_body' => array(array('value' => $this->randomMachineName(16))),
) + parent::getNewEntityValues($langcode);
}
/**
* {@inheritdoc}
*/
protected function doTestPublishedStatus() {
$entity_manager = \Drupal::entityManager();
$storage = $entity_manager->getStorage($this->entityTypeId);
$storage->resetCache();
$entity = $storage->load($this->entityId);
// Unpublish translations.
foreach ($this->langcodes as $index => $langcode) {
if ($index > 0) {
$edit = array('status' => 0);
$url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
$this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
$storage->resetCache();
$entity = $storage->load($this->entityId);
$this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestAuthoringInfo() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$languages = $this->container->get('language_manager')->getLanguages();
$values = array();
// Post different authoring information for each translation.
foreach ($this->langcodes as $langcode) {
$url = $entity->urlInfo('edit-form', ['language' => $languages[$langcode]]);
$user = $this->drupalCreateUser();
$values[$langcode] = array(
'uid' => $user->id(),
'created' => REQUEST_TIME - mt_rand(0, 1000),
);
$edit = array(
'name' => $user->getUsername(),
'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
);
$this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
}
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
foreach ($this->langcodes as $langcode) {
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
}
}
/**
* Tests translate link on comment content admin page.
*/
function testTranslateLinkCommentAdminPage() {
$this->adminUser = $this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), array('access administration pages', 'administer comments', 'skip comment approval')));
$this->drupalLogin($this->adminUser);
$cid_translatable = $this->createEntity(array(), $this->langcodes[0]);
$cid_untranslatable = $this->createEntity(array(), $this->langcodes[0], 'comment');
// Verify translation links.
$this->drupalGet('admin/content/comment');
$this->assertResponse(200);
$this->assertLinkByHref('comment/' . $cid_translatable . '/translations');
$this->assertNoLinkByHref('comment/' . $cid_untranslatable . '/translations');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = array('language' => $languages[$langcode]);
$url = $entity->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('Edit @type @title [%language translation]', array(
'@type' => $this->entityTypeId,
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
));
$this->assertRaw($title);
}
}
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentTypeTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Entity\CommentType;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\Node;
/**
* Ensures that comment type functions work correctly.
*
* @group comment
*/
class CommentTypeTest extends CommentTestBase {
/**
* Admin user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = array(
'administer comments',
'administer comment fields',
'administer comment types',
);
/**
* Sets the test up.
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser($this->permissions);
}
/**
* Tests creating a comment type programmatically and via a form.
*/
public function testCommentTypeCreation() {
// Create a comment type programmatically.
$type = $this->createCommentType('other');
$comment_type = CommentType::load('other');
$this->assertTrue($comment_type, 'The new comment type has been created.');
// Login a test user.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/comment/manage/' . $type->id());
$this->assertResponse(200, 'The new comment type can be accessed at the edit form.');
// Create a comment type via the user interface.
$edit = array(
'id' => 'foo',
'label' => 'title for foo',
'description' => '',
'target_entity_type_id' => 'node',
);
$this->drupalPostForm('admin/structure/comment/types/add', $edit, t('Save'));
$comment_type = CommentType::load('foo');
$this->assertTrue($comment_type, 'The new comment type has been created.');
// Check that the comment type was created in site default language.
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->assertEqual($comment_type->language()->getId(), $default_langcode);
// Edit the comment-type and ensure that we cannot change the entity-type.
$this->drupalGet('admin/structure/comment/manage/foo');
$this->assertNoField('target_entity_type_id', 'Entity type file not present');
$this->assertText(t('Target entity type'));
// Save the form and ensure the entity-type value is preserved even though
// the field isn't present.
$this->drupalPostForm(NULL, array(), t('Save'));
\Drupal::entityManager()->getStorage('comment_type')->resetCache(array('foo'));
$comment_type = CommentType::load('foo');
$this->assertEqual($comment_type->getTargetEntityTypeId(), 'node');
}
/**
* Tests editing a comment type using the UI.
*/
public function testCommentTypeEditing() {
$this->drupalLogin($this->adminUser);
$field = FieldConfig::loadByName('comment', 'comment', 'comment_body');
$this->assertEqual($field->getLabel(), 'Comment', 'Comment body field was found.');
// Change the comment type name.
$this->drupalGet('admin/structure/comment');
$edit = array(
'label' => 'Bar',
);
$this->drupalPostForm('admin/structure/comment/manage/comment', $edit, t('Save'));
$this->drupalGet('admin/structure/comment');
$this->assertRaw('Bar', 'New name was displayed.');
$this->clickLink('Manage fields');
$this->assertUrl(\Drupal::url('entity.comment.field_ui_fields', ['comment_type' => 'comment'], ['absolute' => TRUE]), [], 'Original machine name was used in URL.');
$this->assertTrue($this->cssSelect('tr#comment-body'), 'Body field exists.');
// Remove the body field.
$this->drupalPostForm('admin/structure/comment/manage/comment/fields/comment.comment.comment_body/delete', array(), t('Delete'));
// Resave the settings for this type.
$this->drupalPostForm('admin/structure/comment/manage/comment', array(), t('Save'));
// Check that the body field doesn't exist.
$this->drupalGet('admin/structure/comment/manage/comment/fields');
$this->assertFalse($this->cssSelect('tr#comment-body'), 'Body field does not exist.');
}
/**
* Tests deleting a comment type that still has content.
*/
public function testCommentTypeDeletion() {
// Create a comment type programmatically.
$type = $this->createCommentType('foo');
$this->drupalCreateContentType(array('type' => 'page'));
$this->addDefaultCommentField('node', 'page', 'foo', CommentItemInterface::OPEN, 'foo');
$field_storage = FieldStorageConfig::loadByName('node', 'foo');
$this->drupalLogin($this->adminUser);
// Create a node.
$node = Node::create(array(
'type' => 'page',
'title' => 'foo',
));
$node->save();
// Add a new comment of this type.
$comment = Comment::create(array(
'comment_type' => 'foo',
'entity_type' => 'node',
'field_name' => 'foo',
'entity_id' => $node->id(),
));
$comment->save();
// Attempt to delete the comment type, which should not be allowed.
$this->drupalGet('admin/structure/comment/manage/' . $type->id() . '/delete');
$this->assertRaw(
t('%label is used by 1 comment on your site. You can not remove this comment type until you have removed all of the %label comments.', array('%label' => $type->label())),
'The comment type will not be deleted until all comments of that type are removed.'
);
$this->assertRaw(
t('%label is used by the %field field on your site. You can not remove this comment type until you have removed the field.', array(
'%label' => 'foo',
'%field' => 'node.foo',
)),
'The comment type will not be deleted until all fields of that type are removed.'
);
$this->assertNoText(t('This action cannot be undone.'), 'The comment type deletion confirmation form is not available.');
// Delete the comment and the field.
$comment->delete();
$field_storage->delete();
// Attempt to delete the comment type, which should now be allowed.
$this->drupalGet('admin/structure/comment/manage/' . $type->id() . '/delete');
$this->assertRaw(
t('Are you sure you want to delete the comment type %type?', array('%type' => $type->id())),
'The comment type is available for deletion.'
);
$this->assertText(t('This action cannot be undone.'), 'The comment type deletion confirmation form is available.');
// Test exception thrown when re-using an existing comment type.
try {
$this->addDefaultCommentField('comment', 'comment', 'bar');
$this->fail('Exception not thrown.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Exception thrown if attempting to re-use comment-type from another entity type.');
}
// Delete the comment type.
$this->drupalPostForm('admin/structure/comment/manage/' . $type->id() . '/delete', array(), t('Delete'));
$this->assertNull(CommentType::load($type->id()), 'Comment type deleted.');
$this->assertRaw(t('The comment type %label has been deleted.', array('%label' => $type->label())));
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentUninstallTest.
*/
namespace Drupal\comment\Tests;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Core\Extension\ModuleUninstallValidatorException;
use Drupal\simpletest\WebTestBase;
/**
* Tests comment module uninstallation.
*
* @group comment
*/
class CommentUninstallTest extends WebTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('comment', 'node');
protected function setUp() {
parent::setup();
// Create an article content type.
$this->drupalCreateContentType(array('type' => 'article', 'name' => t('Article')));
// Create comment field on article so that adds 'comment_body' field.
$this->addDefaultCommentField('node', 'article');
}
/**
* Tests if comment module uninstallation fails if the field exists.
*
* @throws \Drupal\Core\Extension\ModuleUninstallValidatorException
*/
function testCommentUninstallWithField() {
// Ensure that the field exists before uninstallation.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$this->assertNotNull($field_storage, 'The comment_body field exists.');
// Uninstall the comment module which should trigger an exception.
try {
$this->container->get('module_installer')->uninstall(array('comment'));
$this->fail("Expected an exception when uninstall was attempted.");
}
catch (ModuleUninstallValidatorException $e) {
$this->pass("Caught an exception when uninstall was attempted.");
}
}
/**
* Tests if uninstallation succeeds if the field has been deleted beforehand.
*/
function testCommentUninstallWithoutField() {
// Manually delete the comment_body field before module uninstallation.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$this->assertNotNull($field_storage, 'The comment_body field exists.');
$field_storage->delete();
// Check that the field is now deleted.
$field_storage = FieldStorageConfig::loadByName('comment', 'comment_body');
$this->assertNull($field_storage, 'The comment_body field has been deleted.');
// Manually delete the comment field on the node before module uninstallation.
$field_storage = FieldStorageConfig::loadByName('node', 'comment');
$this->assertNotNull($field_storage, 'The comment field exists.');
$field_storage->delete();
// Check that the field is now deleted.
$field_storage = FieldStorageConfig::loadByName('node', 'comment');
$this->assertNull($field_storage, 'The comment field has been deleted.');
field_purge_batch(10);
// Ensure that uninstallation succeeds even if the field has already been
// deleted manually beforehand.
$this->container->get('module_installer')->uninstall(array('comment'));
}
}

View file

@ -0,0 +1,206 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentValidationTest.
*/
namespace Drupal\comment\Tests;
use Drupal\comment\CommentInterface;
use Drupal\node\Entity\Node;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
use Drupal\user\Entity\User;
/**
* Tests comment validation constraints.
*
* @group comment
*/
class CommentValidationTest extends EntityUnitTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('comment', 'node');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('comment', array('comment_entity_statistics'));
}
/**
* Tests the comment validation constraints.
*/
public function testValidation() {
// Add a user.
$user = User::create(array('name' => 'test'));
$user->save();
// Add comment type.
$this->entityManager->getStorage('comment_type')->create(array(
'id' => 'comment',
'label' => 'comment',
'target_entity_type_id' => 'node',
))->save();
// Add comment field to content.
$this->entityManager->getStorage('field_storage_config')->create(array(
'entity_type' => 'node',
'field_name' => 'comment',
'type' => 'comment',
'settings' => array(
'comment_type' => 'comment',
)
))->save();
// Create a page node type.
$this->entityManager->getStorage('node_type')->create(array(
'type' => 'page',
'name' => 'page',
))->save();
// Add comment field to page content.
/** @var \Drupal\field\FieldConfigInterface $field */
$field = $this->entityManager->getStorage('field_config')->create(array(
'field_name' => 'comment',
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Comment settings',
));
$field->save();
$node = $this->entityManager->getStorage('node')->create(array(
'type' => 'page',
'title' => 'test',
));
$node->save();
$comment = $this->entityManager->getStorage('comment')->create(array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'comment_body' => $this->randomMachineName(),
));
$violations = $comment->validate();
$this->assertEqual(count($violations), 0, 'No violations when validating a default comment.');
$comment->set('subject', $this->randomString(65));
$this->assertLengthViolation($comment, 'subject', 64);
// Make the subject valid.
$comment->set('subject', $this->randomString());
$comment->set('name', $this->randomString(61));
$this->assertLengthViolation($comment, 'name', 60);
// Validate a name collision between an anonymous comment author name and an
// existing user account name.
$comment->set('name', 'test');
$comment->set('uid', 0);
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, "Violation found on author name collision");
$this->assertEqual($violations[0]->getPropertyPath(), "name");
$this->assertEqual($violations[0]->getMessage(), t('The name you used (%name) belongs to a registered user.', array('%name' => 'test')));
// Make the name valid.
$comment->set('name', 'valid unused name');
$comment->set('mail', 'invalid');
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, 'Violation found when email is invalid');
$this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
$this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.'));
$comment->set('mail', NULL);
$comment->set('homepage', 'http://example.com/' . $this->randomMachineName(237));
$this->assertLengthViolation($comment, 'homepage', 255);
$comment->set('homepage', 'invalid');
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, 'Violation found when homepage is invalid');
$this->assertEqual($violations[0]->getPropertyPath(), 'homepage.0.value');
// @todo This message should be improved in
// https://www.drupal.org/node/2012690.
$this->assertEqual($violations[0]->getMessage(), t('This value should be of the correct primitive type.'));
$comment->set('homepage', NULL);
$comment->set('hostname', $this->randomString(129));
$this->assertLengthViolation($comment, 'hostname', 128);
$comment->set('hostname', NULL);
$comment->set('thread', $this->randomString(256));
$this->assertLengthViolation($comment, 'thread', 255);
$comment->set('thread', NULL);
// Force anonymous users to enter contact details.
$field->setSetting('anonymous', COMMENT_ANONYMOUS_MUST_CONTACT);
$field->save();
// Reset the node entity.
\Drupal::entityManager()->getStorage('node')->resetCache([$node->id()]);
$node = Node::load($node->id());
// Create a new comment with the new field.
$comment = $this->entityManager->getStorage('comment')->create(array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'comment_body' => $this->randomMachineName(),
'uid' => 0,
'name' => '',
));
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, 'Violation found when name is required, but empty and UID is anonymous.');
$this->assertEqual($violations[0]->getPropertyPath(), 'name');
$this->assertEqual($violations[0]->getMessage(), t('You have to specify a valid author.'));
// Test creating a default comment with a given user id works.
$comment = $this->entityManager->getStorage('comment')->create(array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'comment_body' => $this->randomMachineName(),
'uid' => $user->id(),
));
$violations = $comment->validate();
$this->assertEqual(count($violations), 0, 'No violations when validating a default comment with an author.');
// Test specifying a wrong author name does not work.
$comment = $this->entityManager->getStorage('comment')->create(array(
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'comment_body' => $this->randomMachineName(),
'uid' => $user->id(),
'name' => 'not-test',
));
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, 'Violation found when author name and comment author do not match.');
$this->assertEqual($violations[0]->getPropertyPath(), 'name');
$this->assertEqual($violations[0]->getMessage(), t('The specified author name does not match the comment author.'));
}
/**
* Verifies that a length violation exists for the given field.
*
* @param \Drupal\comment\CommentInterface $comment
* The comment object to validate.
* @param string $field_name
* The field that violates the maximum length.
* @param int $length
* Number of characters that was exceeded.
*/
protected function assertLengthViolation(CommentInterface $comment, $field_name, $length) {
$violations = $comment->validate();
$this->assertEqual(count($violations), 1, "Violation found when $field_name is too long.");
$this->assertEqual($violations[0]->getPropertyPath(), "$field_name.0.value");
$field_label = $comment->get($field_name)->getFieldDefinition()->getLabel();
$this->assertEqual($violations[0]->getMessage(), t('%name: may not be longer than @max characters.', array('%name' => $field_label, '@max' => $length)));
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\ArgumentUserUIDTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Entity\Comment;
use Drupal\user\Entity\User;
use Drupal\views\Views;
/**
* Tests the user posted or commented argument handler.
*
* @group comment
*/
class ArgumentUserUIDTest extends CommentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_comment_user_uid');
function testCommentUserUIDTest() {
// Add an additional comment which is not created by the user.
$new_user = User::create(['name' => 'new user']);
$new_user->save();
$comment = Comment::create([
'uid' => $new_user->uid->value,
'entity_id' => $this->nodeUserCommented->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'subject' => 'if a woodchuck could chuck wood.',
]);
$comment->save();
$view = Views::getView('test_comment_user_uid');
$this->executeView($view, array($this->account->id()));
$result_set = array(
array(
'nid' => $this->nodeUserPosted->id(),
),
array(
'nid' => $this->nodeUserCommented->id(),
),
);
$column_map = array('nid' => 'nid');
$this->assertIdenticalResultset($view, $result_set, $column_map);
}
}

View file

@ -0,0 +1,123 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentFieldFilterTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests comment field filters with translations.
*
* @group comment
*/
class CommentFieldFilterTest extends CommentTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('language');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_field_filters');
/**
* List of comment titles by language.
*
* @var array
*/
public $commentTitles = array();
function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['access comments']));
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Set up comment titles.
$this->commentTitles = array(
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nouriture en Paris',
);
// Create a new comment. Using the one created earlier will not work,
// as it predates the language set-up.
$comment = array(
'uid' => $this->loggedInUser->id(),
'entity_id' => $this->nodeUserCommented->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'cid' => '',
'pid' => '',
'node_type' => '',
);
$this->comment = entity_create('comment', $comment);
// Add field values and translate the comment.
$this->comment->subject->value = $this->commentTitles['en'];
$this->comment->comment_body->value = $this->commentTitles['en'];
$this->comment->langcode = 'en';
$this->comment->save();
foreach (array('es', 'fr') as $langcode) {
$translation = $this->comment->addTranslation($langcode, array());
$translation->comment_body->value = $this->commentTitles[$langcode];
$translation->subject->value = $this->commentTitles[$langcode];
}
$this->comment->save();
}
/**
* Tests body and title filters.
*/
public function testFilters() {
// Test the title filter page, which filters for title contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-title-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida title filter');
// Test the body filter page, which filters for body contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-body-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida body filter');
// Test the title Paris filter page, which filters for title contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-title-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris title filter');
// Test the body Paris filter page, which filters for body contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-body-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris body filter');
}
/**
* Asserts that the given comment translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*/
protected function assertPageCounts($path, $counts, $message) {
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
// Check the counts. Note that the title and body are both shown on the
// page, and they are the same. So the title/body string should appear on
// the page twice as many times as the input count.
foreach ($counts as $langcode => $count) {
$this->assertEqual(substr_count($text, $this->commentTitles[$langcode]), 2 * $count, 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentFieldNameTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Entity\Comment;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\user\RoleInterface;
use Drupal\views\Views;
/**
* Tests the comment field name field.
*
* @group comment
*/
class CommentFieldNameTest extends CommentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_comment_field_name'];
/**
* The second comment entity used by this test.
*
* @var \Drupal\comment\CommentInterface
*/
protected $customComment;
/**
* The comment field name used by this test.
*
* @var string
*/
protected $fieldName = 'comment_custom';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->addDefaultCommentField('node', 'page', $this->fieldName);
$this->customComment = Comment::create([
'entity_id' => $this->nodeUserCommented->id(),
'entity_type' => 'node',
'field_name' => $this->fieldName,
]);
$this->customComment->save();
}
/**
* Test comment field name.
*/
public function testCommentFieldName() {
$view = Views::getView('test_comment_field_name');
$this->executeView($view);
$expected_result = [
[
'cid' => $this->comment->id(),
'field_name' => $this->comment->getFieldName(),
],
[
'cid' => $this->customComment->id(),
'field_name' => $this->customComment->getFieldName(),
],
];
$column_map = [
'cid' => 'cid',
'comment_field_data_field_name' => 'field_name',
];
$this->assertIdenticalResultset($view, $expected_result, $column_map);
// Test that no data can be rendered.
$this->assertIdentical(FALSE, isset($view->field['field_name']));
// Grant permission to properly check view access on render.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['access comments']);
$this->container->get('account_switcher')->switchTo(new AnonymousUserSession());
$view = Views::getView('test_comment_field_name');
$this->executeView($view);
// Test that data rendered.
$this->assertIdentical($this->comment->getFieldName(), $view->field['field_name']->advancedRender($view->result[0]));
$this->assertIdentical($this->customComment->getFieldName(), $view->field['field_name']->advancedRender($view->result[1]));
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentRestExportTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\Component\Serialization\Json;
/**
* Tests a comment rest export view.
*
* @group comment
*/
class CommentRestExportTest extends CommentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_comment_rest'];
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'comment', 'comment_test_views', 'rest', 'hal'];
protected function setUp() {
parent::setUp();
// Add another anonymous comment.
$comment = array(
'uid' => 0,
'entity_id' => $this->nodeUserCommented->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'subject' => 'A lot, apparently',
'cid' => '',
'pid' => $this->comment->id(),
'mail' => 'someone@example.com',
'name' => 'bobby tables',
'hostname' => 'public.example.com',
);
$this->comment = entity_create('comment', $comment);
$this->comment->save();
$user = $this->drupalCreateUser(['access comments']);
$this->drupalLogin($user);
}
/**
* Test comment row.
*/
public function testCommentRestExport() {
$this->drupalGetWithFormat(sprintf('node/%d/comments', $this->nodeUserCommented->id()), 'hal_json');
$this->assertResponse(200);
$contents = Json::decode($this->getRawContent());
$this->assertEqual($contents[0]['subject'], 'How much wood would a woodchuck chuck');
$this->assertEqual($contents[1]['subject'], 'A lot, apparently');
$this->assertEqual(count($contents), 2);
// Ensure field-level access is respected - user shouldn't be able to see
// mail or hostname fields.
$this->assertNoText('someone@example.com');
$this->assertNoText('public.example.com');
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentRowTest.
*/
namespace Drupal\comment\Tests\Views;
/**
* Tests the comment row plugin.
*
* @group comment
*/
class CommentRowTest extends CommentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_comment_row');
/**
* Test comment row.
*/
public function testCommentRow() {
$this->drupalGet('test-comment-row');
$result = $this->xpath('//article[contains(@class, "comment")]');
$this->assertEqual(1, count($result), 'One rendered comment found.');
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentTestBase.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the argument_comment_user_uid handler.
*/
abstract class CommentTestBase extends ViewTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('node', 'comment', 'comment_test_views');
/**
* A normal user with permission to post comments (without approval).
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* A second normal user that will author a node for $account to comment on.
*
* @var \Drupal\user\UserInterface
*/
protected $account2;
/**
* Stores a node posted by the user created as $account.
*
* @var \Drupal\node\NodeInterface
*/
protected $nodeUserPosted;
/**
* Stores a node posted by the user created as $account2.
*
* @var \Drupal\node\NodeInterface
*/
protected $nodeUserCommented;
/**
* Stores a comment used by the tests.
*
* @var \Drupal\comment\Entity\Comment
*/
protected $comment;
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('comment_test_views'));
// Add two users, create a node with the user1 as author and another node
// with user2 as author. For the second node add a comment from user1.
$this->account = $this->drupalCreateUser(array('skip comment approval'));
$this->account2 = $this->drupalCreateUser();
$this->drupalLogin($this->account);
$this->drupalCreateContentType(array('type' => 'page', 'name' => t('Basic page')));
$this->addDefaultCommentField('node', 'page');
$this->nodeUserPosted = $this->drupalCreateNode();
$this->nodeUserCommented = $this->drupalCreateNode(array('uid' => $this->account2->id()));
$comment = array(
'uid' => $this->loggedInUser->id(),
'entity_id' => $this->nodeUserCommented->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'subject' => 'How much wood would a woodchuck chuck',
'cid' => '',
'pid' => '',
'mail' => 'someone@example.com',
);
$this->comment = entity_create('comment', $comment);
$this->comment->save();
}
}

View file

@ -0,0 +1,169 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentUserNameTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Entity\Comment;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Views;
/**
* Tests comment user name field
*
* @group comment
*/
class CommentUserNameTest extends ViewUnitTestBase {
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
public static $modules = ['user', 'comment', 'entity_test'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('user');
$this->installEntitySchema('comment');
// Create the anonymous role.
$this->installConfig(['user']);
// Create an anonymous user.
$storage = \Drupal::entityManager()->getStorage('user');
// Insert a row for the anonymous user.
$storage
->create(array(
'uid' => 0,
'status' => 0,
))
->save();
$admin_role = Role::create([
'id' => 'admin',
'permissions' => ['administer comments', 'access user profiles'],
]);
$admin_role->save();
/* @var \Drupal\user\RoleInterface $anonymous_role */
$anonymous_role = Role::load(Role::ANONYMOUS_ID);
$anonymous_role->grantPermission('access comments');
$anonymous_role->save();
$this->adminUser = User::create([
'name' => $this->randomMachineName(),
'roles' => [$admin_role->id()],
]);
$this->adminUser->save();
// Create some comments.
$comment = Comment::create([
'subject' => 'My comment title',
'uid' => $this->adminUser->id(),
'entity_type' => 'entity_test',
'comment_type' => 'entity_test',
'status' => 1,
]);
$comment->save();
$comment_anonymous = Comment::create([
'subject' => 'Anonymous comment title',
'uid' => 0,
'name' => 'barry',
'mail' => 'test@example.com',
'homepage' => 'https://example.com',
'entity_type' => 'entity_test',
'comment_type' => 'entity_test',
'created' => 123456,
'status' => 1,
]);
$comment_anonymous->save();
}
/**
* Test the username formatter.
*/
public function testUsername() {
$view_id = $this->randomMachineName();
$view = View::create([
'id' => $view_id,
'base_table' => 'comment_field_data',
'display' => [
'default' => [
'display_plugin' => 'default',
'id' => 'default',
'display_options' => [
'fields' => [
'name' => [
'table' => 'comment_field_data',
'field' => 'name',
'id' => 'name',
'plugin_id' => 'field',
'type' => 'comment_username'
],
'subject' => [
'table' => 'comment_field_data',
'field' => 'subject',
'id' => 'subject',
'plugin_id' => 'field',
'type' => 'string',
'settings' => [
'link_to_entity' => TRUE,
],
],
],
],
],
],
]);
$view->save();
/* @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
$account_switcher = \Drupal::service('account_switcher');
/* @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$account_switcher->switchTo($this->adminUser);
$executable = Views::getView($view_id);
$build = $executable->preview();
$this->setRawContent($renderer->renderRoot($build));
$this->verbose($this->getRawContent());
$this->assertLink('My comment title');
$this->assertLink('Anonymous comment title');
$this->assertLink($this->adminUser->label());
$this->assertLink('barry (not verified)');
$account_switcher->switchTo(new AnonymousUserSession());
$executable = Views::getView($view_id);
$executable->storage->invalidateCaches();
$build = $executable->preview();
$this->setRawContent($renderer->renderRoot($build));
// No access to user-profiles, so shouldn't be able to see links.
$this->assertNoLink($this->adminUser->label());
// Note: External users aren't pointing to drupal user profiles.
$this->assertLink('barry (not verified)');
$this->verbose($this->getRawContent());
$this->assertLink('My comment title');
$this->assertLink('Anonymous comment title');
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentViewsFieldAccessTest.
*/
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Entity\Comment;
use Drupal\user\Entity\User;
use Drupal\views\Tests\Handler\FieldFieldAccessTestBase;
/**
* Tests base field access in Views for the comment entity.
*
* @group comment
*/
class CommentViewsFieldAccessTest extends FieldFieldAccessTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'entity_test'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('comment');
}
/**
* Check access for comment fields.
*/
public function testCommentFields() {
$user = User::create([
'name' => 'test user',
]);
$user->save();
$comment = Comment::create([
'subject' => 'My comment title',
'uid' => $user->id(),
'entity_type' => 'entity_test',
'comment_type' => 'entity_test',
]);
$comment->save();
$comment_anonymous = Comment::create([
'subject' => 'Anonymous comment title',
'uid' => 0,
'name' => 'anonymous',
'mail' => 'test@example.com',
'homepage' => 'https://example.com',
'entity_type' => 'entity_test',
'comment_type' => 'entity_test',
'created' => 123456,
'status' => 1,
]);
$comment_anonymous->save();
// @todo Expand the test coverage in https://www.drupal.org/node/2464635
$this->assertFieldAccess('comment', 'cid', $comment->id());
$this->assertFieldAccess('comment', 'cid', $comment_anonymous->id());
$this->assertFieldAccess('comment', 'uuid', $comment->uuid());
$this->assertFieldAccess('comment', 'subject', 'My comment title');
$this->assertFieldAccess('comment', 'subject', 'Anonymous comment title');
$this->assertFieldAccess('comment', 'name', 'anonymous');
$this->assertFieldAccess('comment', 'mail', 'test@example.com');
$this->assertFieldAccess('comment', 'homepage', 'https://example.com');
$this->assertFieldAccess('comment', 'uid', $user->getUsername());
// $this->assertFieldAccess('comment', 'created', \Drupal::service('date.formatter')->format(123456));
// $this->assertFieldAccess('comment', 'changed', \Drupal::service('date.formatter')->format(REQUEST_TIME));
$this->assertFieldAccess('comment', 'status', 'On');
}
}

Some files were not shown because too many files have changed in this diff Show more