CommentNonNodeTest.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <?php
  2. namespace Drupal\Tests\comment\Functional;
  3. use Drupal\Component\Render\FormattableMarkup;
  4. use Drupal\comment\CommentInterface;
  5. use Drupal\comment\Entity\Comment;
  6. use Drupal\comment\Entity\CommentType;
  7. use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
  8. use Drupal\comment\Tests\CommentTestTrait;
  9. use Drupal\entity_test\Entity\EntityTest;
  10. use Drupal\field\Entity\FieldConfig;
  11. use Drupal\field\Entity\FieldStorageConfig;
  12. use Drupal\Tests\BrowserTestBase;
  13. use Drupal\Core\Entity\EntityInterface;
  14. use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
  15. use Drupal\user\RoleInterface;
  16. /**
  17. * Tests commenting on a test entity.
  18. *
  19. * @group comment
  20. */
  21. class CommentNonNodeTest extends BrowserTestBase {
  22. use FieldUiTestTrait;
  23. use CommentTestTrait;
  24. public static $modules = [
  25. 'comment',
  26. 'user',
  27. 'field_ui',
  28. 'entity_test',
  29. 'block',
  30. ];
  31. /**
  32. * {@inheritdoc}
  33. */
  34. protected $defaultTheme = 'classy';
  35. /**
  36. * An administrative user with permission to configure comment settings.
  37. *
  38. * @var \Drupal\user\UserInterface
  39. */
  40. protected $adminUser;
  41. /**
  42. * The entity to use within tests.
  43. *
  44. * @var \Drupal\entity_test\Entity\EntityTest
  45. */
  46. protected $entity;
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected function setUp() {
  51. parent::setUp();
  52. $this->drupalPlaceBlock('system_breadcrumb_block');
  53. $this->drupalPlaceBlock('page_title_block');
  54. // Create a bundle for entity_test.
  55. entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test');
  56. CommentType::create([
  57. 'id' => 'comment',
  58. 'label' => 'Comment settings',
  59. 'description' => 'Comment settings',
  60. 'target_entity_type_id' => 'entity_test',
  61. ])->save();
  62. // Create comment field on entity_test bundle.
  63. $this->addDefaultCommentField('entity_test', 'entity_test');
  64. // Verify that bundles are defined correctly.
  65. $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('comment');
  66. $this->assertEqual($bundles['comment']['label'], 'Comment settings');
  67. // Create test user.
  68. $this->adminUser = $this->drupalCreateUser([
  69. 'administer comments',
  70. 'skip comment approval',
  71. 'post comments',
  72. 'access comments',
  73. 'view test entity',
  74. 'administer entity_test content',
  75. ]);
  76. // Enable anonymous and authenticated user comments.
  77. user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [
  78. 'access comments',
  79. 'post comments',
  80. 'skip comment approval',
  81. ]);
  82. user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, [
  83. 'access comments',
  84. 'post comments',
  85. 'skip comment approval',
  86. ]);
  87. // Create a test entity.
  88. $random_label = $this->randomMachineName();
  89. $data = ['type' => 'entity_test', 'name' => $random_label];
  90. $this->entity = EntityTest::create($data);
  91. $this->entity->save();
  92. }
  93. /**
  94. * Posts a comment.
  95. *
  96. * @param \Drupal\Core\Entity\EntityInterface|null $entity
  97. * Entity to post comment on or NULL to post to the previously loaded page.
  98. * @param string $comment
  99. * Comment body.
  100. * @param string $subject
  101. * Comment subject.
  102. * @param mixed $contact
  103. * Set to NULL for no contact info, TRUE to ignore success checking, and
  104. * array of values to set contact info.
  105. *
  106. * @return \Drupal\comment\CommentInterface
  107. * The new comment entity.
  108. */
  109. public function postComment(EntityInterface $entity, $comment, $subject = '', $contact = NULL) {
  110. $edit = [];
  111. $edit['comment_body[0][value]'] = $comment;
  112. $field = FieldConfig::loadByName('entity_test', 'entity_test', 'comment');
  113. $preview_mode = $field->getSetting('preview');
  114. // Must get the page before we test for fields.
  115. if ($entity !== NULL) {
  116. $this->drupalGet('comment/reply/entity_test/' . $entity->id() . '/comment');
  117. }
  118. // Determine the visibility of subject form field.
  119. $display_repository = $this->container->get('entity_display.repository');
  120. if ($display_repository->getFormDisplay('comment', 'comment')->getComponent('subject')) {
  121. // Subject input allowed.
  122. $edit['subject[0][value]'] = $subject;
  123. }
  124. else {
  125. $this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
  126. }
  127. if ($contact !== NULL && is_array($contact)) {
  128. $edit += $contact;
  129. }
  130. switch ($preview_mode) {
  131. case DRUPAL_REQUIRED:
  132. // Preview required so no save button should be found.
  133. $this->assertNoFieldByName('op', t('Save'), 'Save button not found.');
  134. $this->drupalPostForm(NULL, $edit, t('Preview'));
  135. // Don't break here so that we can test post-preview field presence and
  136. // function below.
  137. case DRUPAL_OPTIONAL:
  138. $this->assertFieldByName('op', t('Preview'), 'Preview button found.');
  139. $this->assertFieldByName('op', t('Save'), 'Save button found.');
  140. $this->drupalPostForm(NULL, $edit, t('Save'));
  141. break;
  142. case DRUPAL_DISABLED:
  143. $this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.');
  144. $this->assertFieldByName('op', t('Save'), 'Save button found.');
  145. $this->drupalPostForm(NULL, $edit, t('Save'));
  146. break;
  147. }
  148. $match = [];
  149. // Get comment ID
  150. preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
  151. // Get comment.
  152. if ($contact !== TRUE) {
  153. // If true then attempting to find error message.
  154. if ($subject) {
  155. $this->assertText($subject, 'Comment subject posted.');
  156. }
  157. $this->assertText($comment, 'Comment body posted.');
  158. $this->assertTrue((!empty($match) && !empty($match[1])), 'Comment ID found.');
  159. }
  160. if (isset($match[1])) {
  161. return Comment::load($match[1]);
  162. }
  163. }
  164. /**
  165. * Checks current page for specified comment.
  166. *
  167. * @param \Drupal\comment\CommentInterface $comment
  168. * The comment object.
  169. * @param bool $reply
  170. * Boolean indicating whether the comment is a reply to another comment.
  171. *
  172. * @return bool
  173. * Boolean indicating whether the comment was found.
  174. */
  175. public function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
  176. if ($comment) {
  177. $regex = '/' . ($reply ? '<div class="indented">(.*?)' : '');
  178. $regex .= '<article(.*?)id="comment-' . $comment->id() . '"(.*?)';
  179. $regex .= $comment->getSubject() . '(.*?)';
  180. $regex .= $comment->comment_body->value . '(.*?)';
  181. $regex .= '/s';
  182. return (boolean) preg_match($regex, $this->getSession()->getPage()->getContent());
  183. }
  184. else {
  185. return FALSE;
  186. }
  187. }
  188. /**
  189. * Checks whether the commenter's contact information is displayed.
  190. *
  191. * @return bool
  192. * Contact info is available.
  193. */
  194. public function commentContactInfoAvailable() {
  195. return (bool) preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->getSession()->getPage()->getContent());
  196. }
  197. /**
  198. * Performs the specified operation on the specified comment.
  199. *
  200. * @param object $comment
  201. * Comment to perform operation on.
  202. * @param string $operation
  203. * Operation to perform.
  204. * @param bool $approval
  205. * Operation is found on approval page.
  206. */
  207. public function performCommentOperation($comment, $operation, $approval = FALSE) {
  208. $edit = [];
  209. $edit['operation'] = $operation;
  210. $edit['comments[' . $comment->id() . ']'] = TRUE;
  211. $this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
  212. if ($operation == 'delete') {
  213. $this->drupalPostForm(NULL, [], t('Delete'));
  214. $this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), new FormattableMarkup('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
  215. }
  216. else {
  217. $this->assertText(t('The update has been performed.'), new FormattableMarkup('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
  218. }
  219. }
  220. /**
  221. * Gets the comment ID for an unapproved comment.
  222. *
  223. * @param string $subject
  224. * Comment subject to find.
  225. *
  226. * @return int
  227. * Comment ID.
  228. */
  229. public function getUnapprovedComment($subject) {
  230. $this->drupalGet('admin/content/comment/approval');
  231. preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>(' . $subject . ')/', $this->getSession()->getPage()->getContent(), $match);
  232. return $match[2];
  233. }
  234. /**
  235. * Tests anonymous comment functionality.
  236. */
  237. public function testCommentFunctionality() {
  238. $limited_user = $this->drupalCreateUser([
  239. 'administer entity_test fields',
  240. ]);
  241. $this->drupalLogin($limited_user);
  242. // Test that default field exists.
  243. $this->drupalGet('entity_test/structure/entity_test/fields');
  244. $this->assertText(t('Comments'));
  245. $this->assertLinkByHref('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
  246. // Test widget hidden option is not visible when there's no comments.
  247. $this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
  248. $this->assertSession()->statusCodeEquals(200);
  249. $this->assertNoField('edit-default-value-input-comment-und-0-status-0');
  250. // Test that field to change cardinality is not available.
  251. $this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment/storage');
  252. $this->assertSession()->statusCodeEquals(200);
  253. $this->assertNoField('cardinality_number');
  254. $this->assertNoField('cardinality');
  255. $this->drupalLogin($this->adminUser);
  256. // Test breadcrumb on comment add page.
  257. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
  258. $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
  259. $this->assertEqual(current($this->xpath($xpath))->getText(), $this->entity->label(), 'Last breadcrumb item is equal to node title on comment reply page.');
  260. // Post a comment.
  261. /** @var \Drupal\comment\CommentInterface $comment1 */
  262. $comment1 = $this->postComment($this->entity, $this->randomMachineName(), $this->randomMachineName());
  263. $this->assertTrue($this->commentExists($comment1), 'Comment on test entity exists.');
  264. // Test breadcrumb on comment reply page.
  265. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1->id());
  266. $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
  267. $this->assertEqual(current($this->xpath($xpath))->getText(), $comment1->getSubject(), 'Last breadcrumb item is equal to comment title on comment reply page.');
  268. // Test breadcrumb on comment edit page.
  269. $this->drupalGet('comment/' . $comment1->id() . '/edit');
  270. $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
  271. $this->assertEqual(current($this->xpath($xpath))->getText(), $comment1->getSubject(), 'Last breadcrumb item is equal to comment subject on edit page.');
  272. // Test breadcrumb on comment delete page.
  273. $this->drupalGet('comment/' . $comment1->id() . '/delete');
  274. $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
  275. $this->assertEqual(current($this->xpath($xpath))->getText(), $comment1->getSubject(), 'Last breadcrumb item is equal to comment subject on delete confirm page.');
  276. // Unpublish the comment.
  277. $this->performCommentOperation($comment1, 'unpublish');
  278. $this->drupalGet('admin/content/comment/approval');
  279. $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was unpublished.');
  280. // Publish the comment.
  281. $this->performCommentOperation($comment1, 'publish', TRUE);
  282. $this->drupalGet('admin/content/comment');
  283. $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.');
  284. // Delete the comment.
  285. $this->performCommentOperation($comment1, 'delete');
  286. $this->drupalGet('admin/content/comment');
  287. $this->assertNoRaw('comments[' . $comment1->id() . ']', 'Comment was deleted.');
  288. // Post another comment.
  289. $comment1 = $this->postComment($this->entity, $this->randomMachineName(), $this->randomMachineName());
  290. $this->assertTrue($this->commentExists($comment1), 'Comment on test entity exists.');
  291. // Check that the comment was found.
  292. $this->drupalGet('admin/content/comment');
  293. $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.');
  294. // Check that entity access applies to administrative page.
  295. $this->assertText($this->entity->label(), 'Name of commented account found.');
  296. $limited_user = $this->drupalCreateUser([
  297. 'administer comments',
  298. ]);
  299. $this->drupalLogin($limited_user);
  300. $this->drupalGet('admin/content/comment');
  301. $this->assertNoText($this->entity->label(), 'No commented account name found.');
  302. $this->drupalLogout();
  303. // Deny anonymous users access to comments.
  304. user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
  305. 'access comments' => FALSE,
  306. 'post comments' => FALSE,
  307. 'skip comment approval' => FALSE,
  308. 'view test entity' => TRUE,
  309. ]);
  310. // Attempt to view comments while disallowed.
  311. $this->drupalGet('entity-test/' . $this->entity->id());
  312. $this->assertSession()->responseNotMatches('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
  313. $this->assertSession()->linkNotExists('Add new comment', 'Link to add comment was found.');
  314. // Attempt to view test entity comment form while disallowed.
  315. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
  316. $this->assertSession()->statusCodeEquals(403);
  317. $this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
  318. $this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field not found.');
  319. user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
  320. 'access comments' => TRUE,
  321. 'post comments' => FALSE,
  322. 'view test entity' => TRUE,
  323. 'skip comment approval' => FALSE,
  324. ]);
  325. $this->drupalGet('entity_test/' . $this->entity->id());
  326. // Verify that the comment field title is displayed.
  327. $this->assertPattern('@<h2[^>]*>Comments</h2>@');
  328. $this->assertSession()->linkExists('Log in', 0, 'Link to login was found.');
  329. $this->assertSession()->linkExists('register', 0, 'Link to register was found.');
  330. $this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
  331. $this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field not found.');
  332. // Test the combination of anonymous users being able to post, but not view
  333. // comments, to ensure that access to post comments doesn't grant access to
  334. // view them.
  335. user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
  336. 'access comments' => FALSE,
  337. 'post comments' => TRUE,
  338. 'skip comment approval' => TRUE,
  339. 'view test entity' => TRUE,
  340. ]);
  341. $this->drupalGet('entity_test/' . $this->entity->id());
  342. $this->assertSession()->responseNotMatches('@<h2[^>]*>Comments</h2>@', 'Comments were not displayed.');
  343. $this->assertFieldByName('subject[0][value]', '', 'Subject field found.');
  344. $this->assertFieldByName('comment_body[0][value]', '', 'Comment field found.');
  345. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment/' . $comment1->id());
  346. $this->assertSession()->statusCodeEquals(403);
  347. $this->assertNoText($comment1->getSubject(), 'Comment not displayed.');
  348. // Test comment field widget changes.
  349. $limited_user = $this->drupalCreateUser([
  350. 'administer entity_test fields',
  351. 'view test entity',
  352. 'administer entity_test content',
  353. 'administer comments',
  354. ]);
  355. $this->drupalLogin($limited_user);
  356. $this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
  357. $this->assertNoFieldChecked('edit-default-value-input-comment-0-status-0');
  358. $this->assertNoFieldChecked('edit-default-value-input-comment-0-status-1');
  359. $this->assertFieldChecked('edit-default-value-input-comment-0-status-2');
  360. // Test comment option change in field settings.
  361. $edit = [
  362. 'default_value_input[comment][0][status]' => CommentItemInterface::CLOSED,
  363. 'settings[anonymous]' => CommentInterface::ANONYMOUS_MAY_CONTACT,
  364. ];
  365. $this->drupalPostForm(NULL, $edit, t('Save settings'));
  366. $this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment');
  367. $this->assertNoFieldChecked('edit-default-value-input-comment-0-status-0');
  368. $this->assertFieldChecked('edit-default-value-input-comment-0-status-1');
  369. $this->assertNoFieldChecked('edit-default-value-input-comment-0-status-2');
  370. $this->assertFieldByName('settings[anonymous]', CommentInterface::ANONYMOUS_MAY_CONTACT);
  371. // Add a new comment-type.
  372. $bundle = CommentType::create([
  373. 'id' => 'foobar',
  374. 'label' => 'Foobar',
  375. 'description' => '',
  376. 'target_entity_type_id' => 'entity_test',
  377. ]);
  378. $bundle->save();
  379. // Add a new comment field.
  380. $storage_edit = [
  381. 'settings[comment_type]' => 'foobar',
  382. ];
  383. $this->fieldUIAddNewField('entity_test/structure/entity_test', 'foobar', 'Foobar', 'comment', $storage_edit);
  384. // Add a third comment field.
  385. $this->fieldUIAddNewField('entity_test/structure/entity_test', 'barfoo', 'BarFoo', 'comment', $storage_edit);
  386. // Check the field contains the correct comment type.
  387. $field_storage = FieldStorageConfig::load('entity_test.field_barfoo');
  388. $this->assertInstanceOf(FieldStorageConfig::class, $field_storage);
  389. $this->assertEqual($field_storage->getSetting('comment_type'), 'foobar');
  390. $this->assertEqual($field_storage->getCardinality(), 1);
  391. // Test the new entity commenting inherits default.
  392. $random_label = $this->randomMachineName();
  393. $data = ['bundle' => 'entity_test', 'name' => $random_label];
  394. $new_entity = EntityTest::create($data);
  395. $new_entity->save();
  396. $this->drupalGet('entity_test/manage/' . $new_entity->id() . '/edit');
  397. $this->assertNoFieldChecked('edit-field-foobar-0-status-1');
  398. $this->assertFieldChecked('edit-field-foobar-0-status-2');
  399. $this->assertNoField('edit-field-foobar-0-status-0');
  400. // @todo Check proper url and form https://www.drupal.org/node/2458323
  401. $this->drupalGet('comment/reply/entity_test/comment/' . $new_entity->id());
  402. $this->assertNoFieldByName('subject[0][value]', '', 'Subject field found.');
  403. $this->assertNoFieldByName('comment_body[0][value]', '', 'Comment field found.');
  404. // Test removal of comment_body field.
  405. $limited_user = $this->drupalCreateUser([
  406. 'administer entity_test fields',
  407. 'post comments',
  408. 'administer comment fields',
  409. 'administer comment types',
  410. 'view test entity',
  411. ]);
  412. $this->drupalLogin($limited_user);
  413. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
  414. $this->assertFieldByName('comment_body[0][value]', '', 'Comment body field found.');
  415. $this->fieldUIDeleteField('admin/structure/comment/manage/comment', 'comment.comment.comment_body', 'Comment', 'Comment settings');
  416. $this->drupalGet('comment/reply/entity_test/' . $this->entity->id() . '/comment');
  417. $this->assertNoFieldByName('comment_body[0][value]', '', 'Comment body field not found.');
  418. // Set subject field to autogenerate it.
  419. $edit = ['subject[0][value]' => ''];
  420. $this->drupalPostForm(NULL, $edit, t('Save'));
  421. }
  422. /**
  423. * Tests comment fields cannot be added to entity types without integer IDs.
  424. */
  425. public function testsNonIntegerIdEntities() {
  426. // Create a bundle for entity_test_string_id.
  427. entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test_string_id');
  428. $limited_user = $this->drupalCreateUser([
  429. 'administer entity_test_string_id fields',
  430. ]);
  431. $this->drupalLogin($limited_user);
  432. // Visit the Field UI field add page.
  433. $this->drupalGet('entity_test_string_id/structure/entity_test/fields/add-field');
  434. // Ensure field isn't shown for string IDs.
  435. $this->assertNoOption('edit-new-storage-type', 'comment');
  436. // Ensure a core field type shown.
  437. $this->assertOption('edit-new-storage-type', 'boolean');
  438. // Create a bundle for entity_test_no_id.
  439. entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test_no_id');
  440. $this->drupalLogin($this->drupalCreateUser([
  441. 'administer entity_test_no_id fields',
  442. ]));
  443. // Visit the Field UI field add page.
  444. $this->drupalGet('entity_test_no_id/structure/entity_test/fields/add-field');
  445. // Ensure field isn't shown for empty IDs.
  446. $this->assertNoOption('edit-new-storage-type', 'comment');
  447. // Ensure a core field type shown.
  448. $this->assertOption('edit-new-storage-type', 'boolean');
  449. }
  450. }