Issue #1986606 by jibran, pcambra, vijaycs85, dawehner, andypost, mbovan, Arla, adnen, vprocessor, tkuldeep17, xjm, kim.pepper, dobe, snig, larowlan, miro_dietiker, tim.plunkett, Berdir, Lendude, plach, olli, damiankloip: Convert the comments administration screen to a view

8.5.x
Lee Rowlands 2017-07-29 16:55:37 +10:00
parent bcc7d03859
commit 04d69926b4
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
25 changed files with 2125 additions and 137 deletions

View File

@ -2,6 +2,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
@ -116,8 +117,8 @@ class StringFormatter extends FormatterBase implements ContainerFactoryPluginInt
$elements = [];
$url = NULL;
if ($this->getSetting('link_to_entity')) {
// For the default revision this falls back to 'canonical'
$url = $items->getEntity()->urlInfo('revision');
// For the default revision this falls back to 'canonical'.
$url = $this->getEntityUrl($items->getEntity());
}
foreach ($items as $delta => $item) {
@ -155,4 +156,18 @@ class StringFormatter extends FormatterBase implements ContainerFactoryPluginInt
];
}
/**
* Gets the URI elements of the entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*
* @return \Drupal\Core\Url
* The URI elements of the entity.
*/
protected function getEntityUrl(EntityInterface $entity) {
// For the default revision this falls back to 'canonical'.
return $entity->toUrl('revision');
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @file
* Post update functions for the comment module.
*/
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
/**
* Enable the comment admin view.
*/
function comment_post_update_enable_comment_admin_view() {
$module_handler = \Drupal::moduleHandler();
$entity_type_manager = \Drupal::entityTypeManager();
// Save the comment delete action to config.
$config_install_path = $module_handler->getModule('comment')->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
$storage = new FileStorage($config_install_path);
$entity_type_manager
->getStorage('action')
->create($storage->read('system.action.comment_delete_action'))
->save();
// Only create if the views module is enabled.
if (!$module_handler->moduleExists('views')) {
return;
}
// Save the comment admin view to config.
$optional_install_path = $module_handler->getModule('comment')->getPath() . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$storage = new FileStorage($optional_install_path);
$entity_type_manager
->getStorage('view')
->create($storage->read('views.view.comment'))
->save();
}

View File

@ -2,7 +2,7 @@ comment.admin:
path: '/admin/content/comment'
defaults:
_title: 'Comments'
_controller: '\Drupal\comment\Controller\AdminController::adminPage'
_form: '\Drupal\comment\Form\CommentAdminOverview'
type: 'new'
requirements:
_permission: 'administer comments'
@ -11,7 +11,7 @@ comment.admin_approval:
path: '/admin/content/comment/approval'
defaults:
_title: 'Unapproved comments'
_controller: '\Drupal\comment\Controller\AdminController::adminPage'
_form: '\Drupal\comment\Form\CommentAdminOverview'
type: 'approval'
requirements:
_permission: 'administer comments'
@ -54,6 +54,14 @@ entity.comment.delete_form:
_entity_access: 'comment.delete'
comment: \d+
comment.multiple_delete_confirm:
path: '/admin/content/comment/delete'
defaults:
_title: 'Delete'
_form: '\Drupal\comment\Form\ConfirmDeleteMultiple'
requirements:
_permission: 'administer comments'
comment.reply:
path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}'
defaults:

View File

@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- comment
id: comment_delete_action
label: 'Delete comment'
type: comment
plugin: comment_delete_action
configuration: { }

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,10 @@ action.configuration.comment_unpublish_action:
type: action_configuration_default
label: 'Unpublish comment configuration'
action.configuration.comment_delete_action:
type: action_configuration_default
label: 'Delete comment configuration'
comment.type.*:
type: config_entity
label: 'Comment type settings'
@ -105,3 +109,6 @@ field.field_settings.comment:
preview:
type: integer
label: 'Preview comment'
field.formatter.settings.comment_permalink:
type: field.formatter.settings.string

View File

@ -16,6 +16,14 @@ views.field.comment_entity_link:
type: boolean
label: 'Show teaser-style link'
views.field.comment_bulk_form:
type: views_field_bulk_form
label: 'Comment bulk form'
views.field.commented_entity:
type: views.field.field
label: 'Commented entity'
views.field.comment_last_timestamp:
type: views.field.date
label: 'Last comment date'

View File

@ -23,6 +23,7 @@ class CommentViewsData extends EntityViewsData {
$data['comment_field_data']['subject']['title'] = $this->t('Title');
$data['comment_field_data']['subject']['help'] = $this->t('The title of the comment.');
$data['comment_field_data']['subject']['field']['default_formatter'] = 'comment_permalink';
$data['comment_field_data']['name']['title'] = $this->t('Author');
$data['comment_field_data']['name']['help'] = $this->t("The name of the comment's author. Can be rendered as a link to the author's homepage.");
@ -168,6 +169,17 @@ class CommentViewsData extends EntityViewsData {
],
];
$data['comment_field_data']['entity_id']['field']['id'] = 'commented_entity';
unset($data['comment_field_data']['entity_id']['relationship']);
$data['comment']['comment_bulk_form'] = [
'title' => $this->t('Comment operations bulk form'),
'help' => $this->t('Add a form element that lets you run operations on multiple comments.'),
'field' => [
'id' => 'comment_bulk_form',
],
];
$data['comment_field_data']['thread']['field'] = [
'title' => $this->t('Depth'),
'help' => $this->t('Display the depth of the comment if it is threaded.'),

View File

@ -1,62 +0,0 @@
<?php
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

@ -3,13 +3,13 @@
namespace Drupal\comment\Form;
use Drupal\comment\CommentInterface;
use Drupal\comment\CommentStorageInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -18,11 +18,11 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class CommentAdminOverview extends FormBase {
/**
* The entity storage.
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityManager;
protected $entityTypeManager;
/**
* The comment storage.
@ -45,23 +45,31 @@ class CommentAdminOverview extends FormBase {
*/
protected $moduleHandler;
/**
* The tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Creates a CommentAdminOverview form.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager service.
* @param \Drupal\comment\CommentStorageInterface $comment_storage
* The comment storage.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
*/
public function __construct(EntityManagerInterface $entity_manager, CommentStorageInterface $comment_storage, DateFormatterInterface $date_formatter, ModuleHandlerInterface $module_handler) {
$this->entityManager = $entity_manager;
$this->commentStorage = $comment_storage;
public function __construct(EntityTypeManagerInterface $entity_type_manager, DateFormatterInterface $date_formatter, ModuleHandlerInterface $module_handler, PrivateTempStoreFactory $temp_store_factory) {
$this->entityTypeManager = $entity_type_manager;
$this->commentStorage = $entity_type_manager->getStorage('comment');
$this->dateFormatter = $date_formatter;
$this->moduleHandler = $module_handler;
$this->tempStoreFactory = $temp_store_factory;
}
/**
@ -69,10 +77,10 @@ class CommentAdminOverview extends FormBase {
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('entity.manager')->getStorage('comment'),
$container->get('entity_type.manager'),
$container->get('date.formatter'),
$container->get('module_handler')
$container->get('module_handler'),
$container->get('user.private_tempstore')
);
}
@ -171,7 +179,9 @@ class CommentAdminOverview extends FormBase {
}
foreach ($commented_entity_ids as $entity_type => $ids) {
$commented_entities[$entity_type] = $this->entityManager->getStorage($entity_type)->loadMultiple($ids);
$commented_entities[$entity_type] = $this->entityTypeManager
->getStorage($entity_type)
->loadMultiple($ids);
}
foreach ($comments as $comment) {
@ -255,23 +265,33 @@ class CommentAdminOverview extends FormBase {
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);
/** @var \Drupal\comment\CommentInterface[] $comments */
$comments = $this->commentStorage->loadMultiple($cids);
if ($operation != 'delete') {
foreach ($comments as $comment) {
if ($operation == 'unpublish') {
$comment->setUnpublished();
}
elseif ($operation == 'publish') {
$comment->setPublished();
}
$comment->save();
}
drupal_set_message($this->t('The update has been performed.'));
$form_state->setRedirect('comment.admin');
}
else {
$info = [];
/** @var \Drupal\comment\CommentInterface $comment */
foreach ($comments as $comment) {
$langcode = $comment->language()->getId();
$info[$comment->id()][$langcode] = $langcode;
}
$this->tempStoreFactory
->get('comment_multiple_delete_confirm')
->set($this->currentUser()->id(), $info);
$form_state->setRedirect('comment.multiple_delete_confirm');
}
drupal_set_message($this->t('The update has been performed.'));
$form_state->setRedirect('comment.admin');
}
}

View File

@ -3,7 +3,7 @@
namespace Drupal\comment\Form;
use Drupal\comment\CommentStorageInterface;
use Drupal\Component\Utility\Html;
use Drupal\user\PrivateTempStoreFactory;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
@ -14,6 +14,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class ConfirmDeleteMultiple extends ConfirmFormBase {
/**
* The tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The comment storage.
*
@ -24,18 +31,21 @@ class ConfirmDeleteMultiple extends ConfirmFormBase {
/**
* An array of comments to be deleted.
*
* @var \Drupal\comment\CommentInterface[]
* @var string[][]
*/
protected $comments;
protected $commentInfo;
/**
* Creates an new ConfirmDeleteMultiple form.
*
* @param \Drupal\comment\CommentStorageInterface $comment_storage
* The comment storage.
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
*/
public function __construct(CommentStorageInterface $comment_storage) {
public function __construct(CommentStorageInterface $comment_storage, PrivateTempStoreFactory $temp_store_factory) {
$this->commentStorage = $comment_storage;
$this->tempStoreFactory = $temp_store_factory;
}
/**
@ -43,7 +53,8 @@ class ConfirmDeleteMultiple extends ConfirmFormBase {
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('comment')
$container->get('entity.manager')->getStorage('comment'),
$container->get('user.private_tempstore')
);
}
@ -58,7 +69,7 @@ class ConfirmDeleteMultiple extends ConfirmFormBase {
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete these comments and all their children?');
return $this->formatPlural(count($this->commentInfo), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?');
}
/**
@ -72,39 +83,56 @@ class ConfirmDeleteMultiple extends ConfirmFormBase {
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete comments');
return $this->t('Delete');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$edit = $form_state->getUserInput();
$this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id());
if (empty($this->commentInfo)) {
return $this->redirect('comment.admin');
}
/** @var \Drupal\comment\CommentInterface[] $comments */
$comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo));
$items = [];
foreach ($this->commentInfo as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$comment = $comments[$id]->getTranslation($langcode);
$key = $id . ':' . $langcode;
$default_key = $id . ':' . $comment->getUntranslated()->language()->getId();
// If we have a translated entity we build a nested list of translations
// that will be deleted.
$languages = $comment->getTranslationLanguages();
if (count($languages) > 1 && $comment->isDefaultTranslation()) {
$names = [];
foreach ($languages as $translation_langcode => $language) {
$names[] = $language->getName();
unset($items[$id . ':' . $translation_langcode]);
}
$items[$default_key] = [
'label' => [
'#markup' => $this->t('@label (Original translation) - <em>The following comment translations will be deleted:</em>', ['@label' => $comment->label()]),
],
'deleted_translations' => [
'#theme' => 'item_list',
'#items' => $names,
],
];
}
elseif (!isset($items[$default_key])) {
$items[$key] = $comment->label();
}
}
}
$form['comments'] = [
'#prefix' => '<ul>',
'#suffix' => '</ul>',
'#tree' => TRUE,
'#theme' => 'item_list',
'#items' => $items,
];
// 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] = [
'#type' => 'hidden',
'#value' => $cid,
'#prefix' => '<li>',
'#suffix' => Html::escape($comment->label()) . '</li>'
];
$comment_counter++;
}
$form['operation'] = ['#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);
}
@ -113,12 +141,56 @@ class ConfirmDeleteMultiple extends ConfirmFormBase {
* {@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('comment')->notice('Deleted @count comments.', ['@count' => $count]);
drupal_set_message($this->formatPlural($count, 'Deleted 1 comment.', 'Deleted @count comments.'));
if ($form_state->getValue('confirm') && !empty($this->commentInfo)) {
$total_count = 0;
$delete_comments = [];
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
$delete_translations = [];
/** @var \Drupal\comment\CommentInterface[] $comments */
$comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo));
foreach ($this->commentInfo as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$comment = $comments[$id]->getTranslation($langcode);
if ($comment->isDefaultTranslation()) {
$delete_comments[$id] = $comment;
unset($delete_translations[$id]);
$total_count += count($comment->getTranslationLanguages());
}
elseif (!isset($delete_comments[$id])) {
$delete_translations[$id][] = $comment;
}
}
}
if ($delete_comments) {
$this->commentStorage->delete($delete_comments);
$this->logger('content')->notice('Deleted @count comments.', ['@count' => count($delete_comments)]);
}
if ($delete_translations) {
$count = 0;
foreach ($delete_translations as $id => $translations) {
$comment = $comments[$id]->getUntranslated();
foreach ($translations as $translation) {
$comment->removeTranslation($translation->language()->getId());
}
$comment->save();
$count += count($translations);
}
if ($count) {
$total_count += $count;
$this->logger('content')->notice('Deleted @count comment translations.', ['@count' => $count]);
}
}
if ($total_count) {
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 comment.', 'Deleted @count comments.'));
}
$this->tempStoreFactory->get('comment_multiple_delete_confirm')->delete($this->currentUser()->id());
}
$form_state->setRedirectUrl($this->getCancelUrl());
}

View File

@ -0,0 +1,98 @@
<?php
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deletes a comment.
*
* @Action(
* id = "comment_delete_action",
* label = @Translation("Delete comment"),
* type = "comment",
* confirm_form_route_name = "comment.multiple_delete_confirm"
* )
*/
class DeleteComment extends ActionBase implements ContainerFactoryPluginInterface {
/**
* The tempstore object.
*
* @var \Drupal\user\PrivateTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new DeleteComment 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\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->tempStore = $temp_store_factory->get('comment_multiple_delete_confirm');
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('user.private_tempstore'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
$info = [];
/** @var \Drupal\comment\CommentInterface $comment */
foreach ($entities as $comment) {
$langcode = $comment->language()->getId();
$info[$comment->id()][$langcode] = $langcode;
}
$this->tempStore->set($this->currentUser->id(), $info);
}
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$this->executeMultiple([$entity]);
}
/**
* {@inheritdoc}
*/
public function access($comment, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\comment\CommentInterface $comment */
return $comment->access('delete', $account, $return_as_object);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Drupal\comment\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\StringFormatter;
/**
* Plugin implementation of the 'comment_permalink' formatter.
*
* All the other entities use 'canonical' or 'revision' links to link the entity
* to itself but comments use permalink URL.
*
* @FieldFormatter(
* id = "comment_permalink",
* label = @Translation("Comment Permalink"),
* field_types = {
* "string",
* "uri",
* },
* quickedit = {
* "editor" = "plain_text"
* }
* )
*/
class CommentPermalinkFormatter extends StringFormatter {
/**
* {@inheritdoc}
*/
protected function getEntityUrl(EntityInterface $comment) {
/* @var $comment \Drupal\comment\CommentInterface */
$comment_permalink = $comment->permalink();
if ($comment->hasField('comment_body') && ($body = $comment->get('comment_body')->value)) {
$attributes = $comment_permalink->getOption('attributes') ?: [];
$attributes += ['title' => Unicode::truncate($body, 128)];
$comment_permalink->setOption('attributes', $attributes);
}
return $comment_permalink;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return parent::isApplicable($field_definition) && $field_definition->getTargetEntityTypeId() === 'comment' && $field_definition->getName() === 'subject';
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Drupal\comment\Plugin\views\field;
use Drupal\system\Plugin\views\field\BulkForm;
/**
* Defines a comment operations bulk form element.
*
* @ViewsField("comment_bulk_form")
*/
class CommentBulkForm extends BulkForm {
/**
* {@inheritdoc}
*/
protected function emptySelectedMessage() {
return $this->t('Select one or more comments to perform the update on.');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Drupal\comment\Plugin\views\field;
use Drupal\views\Plugin\views\field\EntityField;
use Drupal\views\ResultRow;
/**
* Views field display for commented entity.
*
* @ViewsField("commented_entity")
*/
class CommentedEntity extends EntityField {
/**
* Array of entities that has comments.
*
* We use this to load all the commented entities of same entity type at once
* to the EntityStorageController static cache.
*
* @var array
*/
protected $loadedCommentedEntities = [];
/**
* {@inheritdoc}
*/
public function getItems(ResultRow $values) {
if (empty($this->loadedCommentedEntities)) {
$result = $this->view->result;
$entity_ids_per_type = [];
foreach ($result as $value) {
/** @var \Drupal\comment\CommentInterface $comment */
if ($comment = $this->getEntity($value)) {
$entity_ids_per_type[$comment->getCommentedEntityTypeId()][] = $comment->getCommentedEntityId();
}
}
foreach ($entity_ids_per_type as $type => $ids) {
$this->loadedCommentedEntities[$type] = $this->entityManager->getStorage($type)->loadMultiple($ids);
}
}
return parent::getItems($values);
}
}

View File

@ -2,6 +2,9 @@
namespace Drupal\comment\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\RoleInterface;
use Drupal\comment\Entity\Comment;
@ -90,7 +93,7 @@ class CommentAdminTest extends CommentTestBase {
];
$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->drupalPostForm(NULL, [], t('Delete'));
$this->assertText(t('No comments available.'), 'All comments were deleted.');
// Test message when no comments selected.
$edit = [
@ -98,6 +101,15 @@ class CommentAdminTest extends CommentTestBase {
];
$this->drupalPostForm(NULL, $edit, t('Update'));
$this->assertText(t('Select one or more comments to perform the update on.'));
// Make sure the label of unpublished node is not visible on listing page.
$this->drupalGet('admin/content/comment');
$this->postComment($this->node, $this->randomMachineName());
$this->drupalGet('admin/content/comment');
$this->assertText(Html::escape($this->node->label()));
$this->node->setUnpublished()->save();
$this->drupalGet('admin/content/comment');
$this->assertNoText(Html::escape($this->node->label()));
}
/**
@ -215,4 +227,51 @@ class CommentAdminTest extends CommentTestBase {
$this->assertFieldById('edit-mail', $anonymous_comment->getAuthorEmail());
}
/**
* Tests commented translation deletion admin view.
*/
public function testCommentedTranslationDeletion() {
\Drupal::service('module_installer')->install([
'language',
'locale',
]);
\Drupal::service('router.builder')->rebuildIfNeeded();
ConfigurableLanguage::createFromLangcode('ur')->save();
// Rebuild the container to update the default language container variable.
$this->rebuildContainer();
// Ensure that doesn't require contact info.
$this->setCommentAnonymous('0');
$this->drupalLogin($this->webUser);
$count_query = \Drupal::entityTypeManager()
->getStorage('comment')
->getQuery()
->count();
$before_count = $count_query->execute();
// Post 2 anonymous comments without contact info.
$comment1 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comment2 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$comment1->addTranslation('ur', ['subject' => 'ur ' . $comment1->label()])
->save();
$comment2->addTranslation('ur', ['subject' => 'ur ' . $comment1->label()])
->save();
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
// Delete multiple comments in one operation.
$edit = [
'operation' => 'delete',
"comments[{$comment1->id()}]" => 1,
"comments[{$comment2->id()}]" => 1,
];
$this->drupalPostForm('admin/content/comment', $edit, t('Update'));
$this->assertRaw(new FormattableMarkup('@label (Original translation) - <em>The following comment translations will be deleted:</em>', ['@label' => $comment1->label()]));
$this->assertRaw(new FormattableMarkup('@label (Original translation) - <em>The following comment translations will be deleted:</em>', ['@label' => $comment2->label()]));
$this->assertText('English');
$this->assertText('Urdu');
$this->drupalPostForm(NULL, [], t('Delete'));
$after_count = $count_query->execute();
$this->assertEqual($after_count, $before_count, 'No comment or translation found.');
}
}

View File

@ -222,7 +222,7 @@ class CommentNonNodeTest extends WebTestBase {
$this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
if ($operation == 'delete') {
$this->drupalPostForm(NULL, [], t('Delete comments'));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
}
else {

View File

@ -354,7 +354,7 @@ abstract class CommentTestBase extends WebTestBase {
$this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
if ($operation == 'delete') {
$this->drupalPostForm(NULL, [], t('Delete comments'));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
}
else {

View File

@ -0,0 +1,51 @@
<?php
namespace Drupal\comment\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests that comment admin view is enabled after update.
*
* @see comment_post_update_enable_comment_admin_view()
*
* @group Update
*/
class CommentAdminViewUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['comment', 'views'];
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
}
/**
* Tests that comment admin view is enabled after update.
*/
public function testCommentAdminPostUpdateHook() {
$this->runUpdates();
// Ensure we can load the view from the storage after the update and it's
// enabled.
$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\views\ViewEntityInterface $comment_admin_view */
$comment_admin_view = $entity_type_manager->getStorage('view')->load('comment');
$this->assertNotNull($comment_admin_view, 'Comment admin view exist in storage.');
$this->assertTrue($comment_admin_view->enable(), 'Comment admin view is enabled.');
$comment_delete_action = $entity_type_manager->getStorage('action')->load('comment_delete_action');
$this->assertNotNull($comment_delete_action, 'Comment delete action imported');
// Verify comment admin page is working after updates.
$account = $this->drupalCreateUser(['administer comments']);
$this->drupalLogin($account);
$this->drupalGet('admin/content/comment');
$this->assertText(t('No comments available.'));
}
}

View File

@ -0,0 +1,211 @@
<?php
namespace Drupal\comment\Tests\Views;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\comment\Tests\CommentTestBase as CommentWebTestBase;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\user\RoleInterface;
use Drupal\views\Views;
/**
* Tests comment approval functionality.
*
* @group comment
*/
class CommentAdminTest extends CommentWebTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::service('module_installer')->install(['views']);
$view = Views::getView('comment');
$view->storage->enable()->save();
\Drupal::service('router.builder')->rebuildIfNeeded();
}
/**
* Test comment approval functionality through admin/content/comment.
*/
public function testApprovalAdminInterface() {
// Set anonymous comments to require approval.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
'access comments' => TRUE,
'post comments' => TRUE,
'skip comment approval' => FALSE,
]);
$this->drupalPlaceBlock('page_title_block');
$this->drupalLogin($this->adminUser);
// Ensure that doesn't require contact info.
$this->setCommentAnonymous('0');
// 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.
$body = $this->getRandomGenerator()->sentences(4);
$subject = Unicode::truncate(trim(Html::decodeEntities(strip_tags($body))), 29, TRUE, TRUE);
$author_name = $this->randomMachineName();
$this->drupalPostForm('comment/reply/node/' . $this->node->id() . '/comment', [
'name' => $author_name,
'comment_body[0][value]' => $body,
], t('Save'));
$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 = Comment::create([
'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);
$edit = [];
$edit['action'] = 'comment_publish_action';
$edit['comment_bulk_form[0]'] = $anonymous_comment4->id();
$this->drupalPostForm('admin/content/comment/approval', $edit, t('Apply to selected items'));
$this->assertText('Publish comment was applied to 1 item.', new FormattableMarkup('Operation "@operation" was performed on comment.', ['@operation' => 'publish']));
$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)', ['@count' => 2]), 'Two unapproved comments waiting for approval.');
$edit = [
"action" => 'comment_publish_action',
"comment_bulk_form[1]" => $comments[0]->id(),
"comment_bulk_form[0]" => $comments[1]->id(),
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$this->assertText(t('Unapproved comments (@count)', ['@count' => 0]), 'All comments were approved.');
// Test message when no comments selected.
$this->drupalPostForm('admin/content/comment', [], t('Apply to selected items'));
$this->assertText(t('Select one or more comments to perform the update on.'));
$subject_link = $this->xpath('//table/tbody/tr/td/a[contains(@href, :href) and contains(@title, :title) and text()=:text]', [
':href' => $comments[0]->permalink()->toString(),
':title' => Unicode::truncate($comments[0]->get('comment_body')->value, 128),
':text' => $comments[0]->getSubject(),
]);
$this->assertTrue(!empty($subject_link), 'Comment listing shows the correct subject link.');
$this->assertText($author_name . ' (not verified)', 'Anonymous author name is displayed correctly.');
$subject_link = $this->xpath('//table/tbody/tr/td/a[contains(@href, :href) and contains(@title, :title) and text()=:text]', [
':href' => $anonymous_comment4->permalink()->toString(),
':title' => Unicode::truncate($body, 128),
':text' => $subject,
]);
$this->assertTrue(!empty($subject_link), 'Comment listing shows the correct subject link.');
$this->assertText($author_name . ' (not verified)', 'Anonymous author name is displayed correctly.');
// Delete multiple comments in one operation.
$edit = [
'action' => 'comment_delete_action',
"comment_bulk_form[1]" => $comments[0]->id(),
"comment_bulk_form[0]" => $comments[1]->id(),
"comment_bulk_form[2]" => $anonymous_comment4->id(),
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$this->assertText(t('Are you sure you want to delete these comments and all their children?'), 'Confirmation required.');
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertText(t('No comments available.'), 'All comments were deleted.');
// Make sure the label of unpublished node is not visible on listing page.
$this->drupalGet('admin/content/comment');
$this->postComment($this->node, $this->randomMachineName());
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content/comment');
$this->assertText(Html::escape($this->node->label()), 'Comment admin can see the title of a published node');
$this->node->setUnpublished()->save();
$this->assertFalse($this->node->isPublished(), 'Node is unpublished now.');
$this->drupalGet('admin/content/comment');
$this->assertNoText(Html::escape($this->node->label()), 'Comment admin cannot see the title of an unpublished node');
$this->drupalLogout();
$node_access_user = $this->drupalCreateUser([
'administer comments',
'bypass node access',
]);
$this->drupalLogin($node_access_user);
$this->drupalGet('admin/content/comment');
$this->assertText(Html::escape($this->node->label()), 'Comment admin with bypass node access permissions can still see the title of a published node');
}
/**
* Tests commented entity label of admin view.
*/
public function testCommentedEntityLabel() {
\Drupal::service('module_installer')->install(['block_content']);
\Drupal::service('router.builder')->rebuildIfNeeded();
$bundle = BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => FALSE,
]);
$bundle->save();
$block_content = BlockContent::create([
'type' => 'basic',
'label' => 'Some block title',
'info' => 'Test block',
]);
$block_content->save();
// Create comment field on block_content.
$this->addDefaultCommentField('block_content', 'basic', 'block_comment', CommentItemInterface::OPEN, 'block_comment');
$this->drupalLogin($this->webUser);
// Post a comment to node.
$node_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a comment to block content.
$block_content_comment = $this->postComment($block_content, $this->randomMachineName(), $this->randomMachineName(), TRUE, 'block_comment');
$this->drupalLogout();
// Login as admin to test the admin comment page.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content/comment');
$comment_author_link = $this->xpath('//table/tbody/tr[1]/td/a[contains(@href, :href) and text()=:text]', [
':href' => $this->webUser->toUrl()->toString(),
':text' => $this->webUser->label(),
]);
$this->assertTrue(!empty($comment_author_link), 'Comment listing links to comment author.');
$comment_author_link = $this->xpath('//table/tbody/tr[2]/td/a[contains(@href, :href) and text()=:text]', [
':href' => $this->webUser->toUrl()->toString(),
':text' => $this->webUser->label(),
]);
$this->assertTrue(!empty($comment_author_link), 'Comment listing links to comment author.');
// Admin page contains label of both entities.
$this->assertText(Html::escape($this->node->label()), 'Node title is visible.');
$this->assertText(Html::escape($block_content->label()), 'Block content label is visible.');
// Admin page contains subject of both entities.
$this->assertText(Html::escape($node_comment->label()), 'Node comment is visible.');
$this->assertText(Html::escape($block_content_comment->label()), 'Block content comment is visible.');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Drupal\comment\Tests\Views;
use Drupal\comment\Tests\CommentTestBase as CommentWebTestBase;
/**
* Tests comment edit functionality.
*
* @group comment
*/
class CommentEditTest extends CommentWebTestBase {
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* Tests comment label in admin view.
*/
public function testCommentEdit() {
$this->drupalLogin($this->adminUser);
// Post a comment to node.
$node_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
$this->drupalGet('admin/content/comment');
$this->assertText($this->adminUser->label());
$this->drupalGet($node_comment->toUrl('edit-form')->toString());
$edit = [
'comment_body[0][value]' => $this->randomMachineName(),
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/content/comment');
$this->assertText($this->adminUser->label());
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Drupal\Tests\comment\Unit\Plugin\views\field;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\comment\Plugin\views\field\CommentBulkForm;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\comment\Plugin\views\field\CommentBulkForm
* @group comment
*/
class CommentBulkFormTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function tearDown() {
parent::tearDown();
$container = new ContainerBuilder();
\Drupal::setContainer($container);
}
/**
* Tests the constructor assignment of actions.
*/
public function testConstructor() {
$actions = [];
for ($i = 1; $i <= 2; $i++) {
$action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
$action->expects($this->any())
->method('getType')
->will($this->returnValue('comment'));
$actions[$i] = $action;
}
$action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
$action->expects($this->any())
->method('getType')
->will($this->returnValue('user'));
$actions[] = $action;
$entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_storage->expects($this->any())
->method('loadMultiple')
->will($this->returnValue($actions));
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$entity_manager->expects($this->once())
->method('getStorage')
->with('action')
->will($this->returnValue($entity_storage));
$language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$views_data = $this->getMockBuilder('Drupal\views\ViewsData')
->disableOriginalConstructor()
->getMock();
$views_data->expects($this->any())
->method('get')
->with('comment')
->will($this->returnValue(['table' => ['entity type' => 'comment']]));
$container = new ContainerBuilder();
$container->set('views.views_data', $views_data);
$container->set('string_translation', $this->getStringTranslationStub());
\Drupal::setContainer($container);
$storage = $this->getMock('Drupal\views\ViewEntityInterface');
$storage->expects($this->any())
->method('get')
->with('base_table')
->will($this->returnValue('comment'));
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$executable->storage = $storage;
$display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
->disableOriginalConstructor()
->getMock();
$definition['title'] = '';
$options = [];
$comment_bulk_form = new CommentBulkForm([], 'comment_bulk_form', $definition, $entity_manager, $language_manager);
$comment_bulk_form->init($executable, $display, $options);
$this->assertAttributeEquals(array_slice($actions, 0, -1, TRUE), 'actions', $comment_bulk_form);
}
}

View File

@ -57,7 +57,7 @@ class MigrateUpgrade6Test extends MigrateUpgradeTestBase {
'search_page' => 2,
'shortcut' => 2,
'shortcut_set' => 1,
'action' => 22,
'action' => 23,
'menu' => 8,
'taxonomy_term' => 7,
'taxonomy_vocabulary' => 6,
@ -65,7 +65,7 @@ class MigrateUpgrade6Test extends MigrateUpgradeTestBase {
'user' => 7,
'user_role' => 6,
'menu_link_content' => 4,
'view' => 15,
'view' => 16,
'date_format' => 11,
'entity_form_display' => 19,
'entity_form_mode' => 1,

View File

@ -63,7 +63,7 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
'search_page' => 2,
'shortcut' => 6,
'shortcut_set' => 2,
'action' => 16,
'action' => 17,
'menu' => 6,
'taxonomy_term' => 18,
'taxonomy_vocabulary' => 4,
@ -71,7 +71,7 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
'user' => 4,
'user_role' => 3,
'menu_link_content' => 7,
'view' => 15,
'view' => 16,
'date_format' => 11,
'entity_form_display' => 18,
'entity_form_mode' => 1,

View File

@ -100,6 +100,15 @@ class DefaultViewsTest extends ViewTestBase {
'field_name' => 'comment'
];
Comment::create($comment)->save();
$unpublished_comment = [
'uid' => $user->id(),
'status' => CommentInterface::NOT_PUBLISHED,
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
];
Comment::create($unpublished_comment)->save();
}
// Some views, such as the "Who's Online" view, only return results if at