From 3f9d9b449fc10a752cd7edc8ec1a7d78faebe68a Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Thu, 14 Jul 2022 23:10:45 +0900 Subject: [PATCH] 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 --- .../CommentSelection.php | 81 ++++++++++++++++--- .../src/Functional/CommentNonNodeTest.php | 14 +++- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php index 2c37f34125fc..7446b125904c 100644 --- a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php +++ b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php @@ -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); } } diff --git a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php index 9820d2553405..faf48498796f 100644 --- a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php +++ b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php @@ -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'); -- GitLab