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
							
								
									c78d1a8987
								
							
						
					
					
						commit
						3f9d9b449f
					
				| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Drupal\comment\Plugin\EntityReferenceSelection;
 | 
					namespace Drupal\comment\Plugin\EntityReferenceSelection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Drupal\Component\Utility\Html;
 | 
				
			||||||
use Drupal\Core\Database\Query\SelectInterface;
 | 
					use Drupal\Core\Database\Query\SelectInterface;
 | 
				
			||||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
 | 
					use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
 | 
				
			||||||
use Drupal\comment\CommentInterface;
 | 
					use Drupal\comment\CommentInterface;
 | 
				
			||||||
| 
						 | 
					@ -76,19 +77,75 @@ class CommentSelection extends DefaultSelection {
 | 
				
			||||||
      $query->innerJoin($data_table, NULL, "[base_table].[cid] = [$data_table].[cid] AND [$data_table].[default_langcode] = 1");
 | 
					      $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,
 | 
					    // Find the host entity type the comment field is on.
 | 
				
			||||||
    // and as a consequence doesn't make sure that comments cannot be viewed
 | 
					    $comment = $this->getConfiguration()['entity'];
 | 
				
			||||||
    // when the user doesn't have access to the node.
 | 
					    if ($comment) {
 | 
				
			||||||
    $node_alias = $query->innerJoin('node_field_data', 'n', "[%alias].[nid] = [$data_table].[entity_id] AND [$data_table].[entity_type] = 'node'");
 | 
					      $host_entity_type_id = $comment->getCommentedEntityTypeId();
 | 
				
			||||||
    // 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
 | 
					      /** @var \Drupal\Core\Entity\EntityTypeInterface $host_entity_type */
 | 
				
			||||||
    // insufficient for nodes.
 | 
					      $host_entity_type = $this->entityTypeManager->getDefinition($host_entity_type_id);
 | 
				
			||||||
    // @see \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection::buildEntityQuery()
 | 
					      $host_entity_field_data_table = $host_entity_type->getDataTable();
 | 
				
			||||||
    if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) {
 | 
					
 | 
				
			||||||
      $query->condition($node_alias . '.status', 1);
 | 
					      // 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 @@ class CommentNonNodeTest extends BrowserTestBase {
 | 
				
			||||||
   * @return \Drupal\comment\CommentInterface
 | 
					   * @return \Drupal\comment\CommentInterface
 | 
				
			||||||
   *   The new comment entity.
 | 
					   *   The new comment entity.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public function postComment(EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
 | 
					  public function postComment(?EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
 | 
				
			||||||
    $edit = [];
 | 
					    $edit = [];
 | 
				
			||||||
    $edit['comment_body[0][value]'] = $comment;
 | 
					    $edit['comment_body[0][value]'] = $comment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -309,6 +309,18 @@ class CommentNonNodeTest extends BrowserTestBase {
 | 
				
			||||||
    $xpath = '//nav[@aria-labelledby="system-breadcrumb"]/ol/li[last()]/a';
 | 
					    $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.');
 | 
					    $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.
 | 
					    // Unpublish the comment.
 | 
				
			||||||
    $this->performCommentOperation($comment1, 'unpublish');
 | 
					    $this->performCommentOperation($comment1, 'unpublish');
 | 
				
			||||||
    $this->drupalGet('admin/content/comment/approval');
 | 
					    $this->drupalGet('admin/content/comment/approval');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue