Verified Commit b4533402 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3332546 by fjgarlin, Berdir, djsagar, larowlan, smustgrave:...

Issue #3332546 by fjgarlin, Berdir, djsagar, larowlan, smustgrave: CommentSelection::entityQueryAlter() fails on validate when referencing entity is not a comment

(cherry picked from commit 13955d6b)
parent d1b9e9b3
Loading
Loading
Loading
Loading
+30 −2
Original line number Diff line number Diff line
@@ -63,6 +63,27 @@ public function validateReferenceableNewEntities(array $entities) {
    return $entities;
  }

  /**
   * {@inheritdoc}
   */
  public function validateReferenceableEntities(array $ids) {
    $result = [];
    if ($ids) {
      $target_type = $this->configuration['target_type'];
      $entity_type = $this->entityTypeManager->getDefinition($target_type);
      $query = $this->buildEntityQuery();
      // Mirror the conditions checked in buildEntityQuery().
      if (!$this->currentUser->hasPermission('administer comments')) {
        $query->condition('status', 1);
      }
      $result = $query
        ->condition($entity_type->getKey('id'), $ids, 'IN')
        ->execute();
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
@@ -77,9 +98,16 @@ public function entityQueryAlter(SelectInterface $query) {
      $query->innerJoin($data_table, NULL, "[base_table].[cid] = [$data_table].[cid] AND [$data_table].[default_langcode] = 1");
    }

    // Find the host entity type the comment field is on.
    // Historically, comments were always linked to 'node' entities, but that is
    // no longer the case, as the 'node' module might not even be enabled.
    // Comments can now be linked to any entity and they can also be referenced
    // by other entities, so we won't have a single table to join to. That
    // actually means that we can no longer optimize the query on those cases.
    // However, the most common case remains to be comment replies, and in this
    // case, we can get the host entity type if the 'entity' value is present
    // and perform the extra joins and alterations needed.
    $comment = $this->getConfiguration()['entity'];
    if ($comment) {
    if ($comment instanceof CommentInterface) {
      $host_entity_type_id = $comment->getCommentedEntityTypeId();

      /** @var \Drupal\Core\Entity\EntityTypeInterface $host_entity_type */
+124 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\comment\Functional;

use Drupal\comment\Entity\Comment;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;

/**
 * Tests that comments behave correctly when added as entity references.
 *
 * @group comment
 */
class CommentEntityReferenceTest extends CommentTestBase {

  use EntityReferenceTestTrait;

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * A second test node containing references to comments.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $node2;

  /**
   * A comment linked to a node.
   *
   * @var \Drupal\comment\CommentInterface
   */
  protected $comment;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->createEntityReferenceField(
      'node',
      'article',
      'entity_reference_comment',
      'Entity Reference Comment',
      'comment',
      'default',
      ['target_bundles' => ['comment']]
    );
    \Drupal::service('entity_display.repository')
      ->getFormDisplay('node', 'article')
      ->setComponent('entity_reference_comment', ['type' => 'options_select'])
      ->save();
    \Drupal::service('entity_display.repository')
      ->getViewDisplay('node', 'article')
      ->setComponent('entity_reference_comment', ['type' => 'entity_reference_label'])
      ->save();

    $administratorUser = $this->drupalCreateUser([
      'skip comment approval',
      'post comments',
      'access comments',
      'access content',
      'administer nodes',
      'administer comments',
      'bypass node access',
    ]);
    $this->drupalLogin($administratorUser);

    $this->node = $this->drupalCreateNode(['type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()]);
    $this->comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
    $this->assertInstanceOf(Comment::class, $this->comment);

    $this->node2 = $this->drupalCreateNode([
      'title' => $this->randomMachineName(),
      'type' => 'article',
    ]);
  }

  /**
   * Tests that comments are correctly saved as entity references.
   */
  public function testCommentAsEntityReference() {
    // Load the node and save it.
    $edit = [
      'entity_reference_comment' => $this->comment->id(),
    ];
    $this->drupalGet('node/' . $this->node2->id() . '/edit');
    $this->submitForm($edit, 'Save');
    $this->assertSession()->statusCodeEquals(200);
    $this->assertSession()->pageTextContains('has been updated');

    // Make sure the comment is linked.
    $this->assertSession()->pageTextContains($this->comment->label());
  }

  /**
   * Tests that comments of unpublished are not shown.
   */
  public function testCommentOfUnpublishedNodeBypassAccess() {
    // Unpublish the node that has the comment.
    $this->node->setUnpublished()->save();

    // When the user has 'bypass node access' permission, they can still set it.
    $edit = [
      'entity_reference_comment' => $this->comment->id(),
    ];
    $this->drupalGet('node/' . $this->node2->id() . '/edit');
    $this->submitForm($edit, 'Save');
    $this->assertSession()->statusCodeEquals(200);
    $this->assertSession()->pageTextContains('has been updated');

    // Comment is seen as administrator user.
    $this->assertSession()->pageTextContains($this->comment->label());

    // But not as anonymous.
    $this->drupalLogout();
    $this->drupalGet('node/' . $this->node2->id());
    $this->assertSession()->pageTextContains($this->node2->label());
    $this->assertSession()->pageTextNotContains($this->comment->label());
  }

}
+103 −0
Original line number Diff line number Diff line
@@ -3,8 +3,12 @@
namespace Drupal\Tests\comment\Kernel;

use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Entity\CommentType;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
use Drupal\user\Entity\User;

/**
@@ -13,6 +17,8 @@
 * @group comment
 */
class CommentValidationTest extends EntityKernelTestBase {
  use CommentTestTrait;
  use EntityReferenceTestTrait;

  /**
   * Modules to install.
@@ -27,6 +33,7 @@ class CommentValidationTest extends EntityKernelTestBase {
  protected function setUp(): void {
    parent::setUp();
    $this->installSchema('comment', ['comment_entity_statistics']);
    $this->installConfig(['comment']);
  }

  /**
@@ -180,6 +187,102 @@ public function testValidation() {
    $this->assertEquals('The specified author name does not match the comment author.', $violations[0]->getMessage());
  }

  /**
   * Tests that comments of unpublished nodes are not valid.
   */
  public function testValidationOfCommentOfUnpublishedNode() {
    // Create a page node type.
    $this->entityTypeManager->getStorage('node_type')->create([
      'type' => 'page',
      'name' => 'page',
    ])->save();

    // Create a comment type.
    CommentType::create([
      'id' => 'comment',
      'label' => 'Default comments',
      'description' => 'Default comment field',
      'target_entity_type_id' => 'node',
    ])->save();

    // Add comment and entity reference comment fields.
    $this->addDefaultCommentField('node', 'page', 'comment');
    $this->createEntityReferenceField(
      'node',
      'page',
      'entity_reference_comment',
      'Entity Reference Comment',
      'comment',
      'default',
      ['target_bundles' => ['comment']]
    );

    $comment_admin_user = $this->drupalCreateUser([
      'skip comment approval',
      'post comments',
      'access comments',
      'access content',
      'administer nodes',
      'administer comments',
      'bypass node access',
    ]);
    $comment_non_admin_user = $this->drupalCreateUser([
      'access comments',
      'post comments',
      'create page content',
      'edit own comments',
      'skip comment approval',
      'access content',
    ]);

    // Create a node with a comment and make it unpublished.
    $node1 = $this->entityTypeManager->getStorage('node')->create([
      'type' => 'page',
      'title' => 'test 1',
      'promote' => 1,
      'status' => 0,
      'uid' => $comment_non_admin_user->id(),
    ]);
    $node1->save();
    $comment1 = $this->entityTypeManager->getStorage('comment')->create([
      'entity_id' => $node1->id(),
      'entity_type' => 'node',
      'field_name' => 'comment',
      'comment_body' => $this->randomMachineName(),
    ]);
    $comment1->save();
    $this->assertInstanceOf(Comment::class, $comment1);

    // Create a second published node.
    /** @var \Drupal\node\Entity\Node $node2 */
    $node2 = $this->entityTypeManager->getStorage('node')->create([
      'type' => 'page',
      'title' => 'test 2',
      'promote' => 1,
      'status' => 1,
      'uid' => $comment_non_admin_user->id(),
    ]);
    $node2->save();

    // Test the validation API directly.
    $this->drupalSetCurrentUser($comment_non_admin_user);
    $this->assertEquals(\Drupal::currentUser()->id(), $comment_non_admin_user->id());
    $node2->set('entity_reference_comment', $comment1->id());
    $violations = $node2->validate();
    $this->assertCount(1, $violations);
    $this->assertEquals('entity_reference_comment.0.target_id', $violations[0]->getPropertyPath());
    $this->assertEquals(t('This entity (%type: %name) cannot be referenced.', [
      '%type' => $comment1->getEntityTypeId(),
      '%name' => $comment1->id(),
    ]), $violations[0]->getMessage());

    $this->drupalSetCurrentUser($comment_admin_user);
    $this->assertEquals(\Drupal::currentUser()->id(), $comment_admin_user->id());
    $node2->set('entity_reference_comment', $comment1->id());
    $violations = $node2->validate();
    $this->assertCount(0, $violations);
  }

  /**
   * Verifies that a length violation exists for the given field.
   *