Skip to content
Snippets Groups Projects
Commit 3f9d9b44 authored by catch's avatar catch
Browse files

Issue #2958241 by fjgarlin, thetwentyseven, Wim Leers, joachim,...

Issue #2958241 by fjgarlin, thetwentyseven, Wim Leers, joachim, immaculatexavier, fulgent, larowlan, lastlink, andypost, nnevill: Impossible to reply to comments: commented entity considered unreferencable because CommentSelection::entityQueryAlter() joins on {node_field_data} table
parent c78d1a89
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@
namespace Drupal\comment\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\comment\CommentInterface;
......@@ -76,19 +77,75 @@ public function entityQueryAlter(SelectInterface $query) {
$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 \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection::buildEntityQuery()
if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) {
$query->condition($node_alias . '.status', 1);
// Find the host entity type the comment field is on.
$comment = $this->getConfiguration()['entity'];
if ($comment) {
$host_entity_type_id = $comment->getCommentedEntityTypeId();
/** @var \Drupal\Core\Entity\EntityTypeInterface $host_entity_type */
$host_entity_type = $this->entityTypeManager->getDefinition($host_entity_type_id);
$host_entity_field_data_table = $host_entity_type->getDataTable();
// Not all entities have a data table, so check first.
if ($host_entity_field_data_table) {
$id_key = $host_entity_type->getKey('id');
// The Comment module doesn't implement per-comment access, so it
// checks instead that the user has access to the host entity.
$entity_alias = $query->innerJoin($host_entity_field_data_table, 'n', "[%alias].[$id_key] = [$data_table].[entity_id] AND [$data_table].[entity_type] = '$host_entity_type_id'");
// Pass the query to the entity access control.
$this->reAlterQuery($query, $host_entity_type_id . '_access', $entity_alias);
// Additional checks for "node" entities.
if ($host_entity_type_id === 'node') {
// Passing the query to node_query_node_access_alter() is sadly
// insufficient for nodes.
// @see \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection::buildEntityQuery()
if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) {
$query->condition($entity_alias . '.status', 1);
}
}
}
}
}
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$target_type = $this->getConfiguration()['target_type'];
$query = $this->buildEntityQuery($match, $match_operator);
if ($limit > 0) {
$query->range(0, $limit);
}
$result = $query->execute();
if (empty($result)) {
return [];
}
$options = [];
$entities = $this->entityTypeManager->getStorage($target_type)->loadMultiple($result);
foreach ($entities as $entity_id => $entity) {
// Additional access check as comments might be attached to entities
// which the current user does not have access to.
if ($entity->access('view', $this->currentUser)) {
$bundle = $entity->bundle();
$options[$bundle][$entity_id] = Html::escape($this->entityRepository->getTranslationFromContext($entity)->label() ?? '');
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$options = $this->getReferenceableEntities($match, $match_operator);
return count($options, COUNT_RECURSIVE) - count($options);
}
}
......@@ -120,7 +120,7 @@ protected function setUp(): void {
* @return \Drupal\comment\CommentInterface
* The new comment entity.
*/
public function postComment(EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
public function postComment(?EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
$edit = [];
$edit['comment_body[0][value]'] = $comment;
......@@ -309,6 +309,18 @@ public function testCommentFunctionality() {
$xpath = '//nav[@aria-labelledby="system-breadcrumb"]/ol/li[last()]/a';
$this->assertEquals($comment1->getSubject(), current($this->xpath($xpath))->getText(), 'Last breadcrumb item is equal to comment subject on delete confirm page.');
// Test threading replying to comment #1 creating comment #1_2.
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1->id());
$comment1_2 = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($comment1_2, TRUE), 'Comment #1_2. Reply found.');
$this->assertEquals('01.00/', $comment1_2->getThread());
// Test nested threading replying to comment #1_2 creating comment #1_2_3.
$this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1_2->id());
$comment1_2_3 = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName());
$this->assertTrue($this->commentExists($comment1_2_3, TRUE), 'Comment #1_2_3. Reply found.');
$this->assertEquals('01.00.00/', $comment1_2_3->getThread());
// Unpublish the comment.
$this->performCommentOperation($comment1, 'unpublish');
$this->drupalGet('admin/content/comment/approval');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment