a77d7357426f232489aacc55b5be4f6d034086b4
[yaffs-website] / web / core / modules / search / tests / src / Functional / SearchCommentTest.php
1 <?php
2
3 namespace Drupal\Tests\search\Functional;
4
5 use Behat\Mink\Exception\ResponseTextException;
6 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
7 use Drupal\comment\Tests\CommentTestTrait;
8 use Drupal\field\Entity\FieldConfig;
9 use Drupal\Tests\BrowserTestBase;
10 use Drupal\Tests\Traits\Core\CronRunTrait;
11 use Drupal\user\RoleInterface;
12 use Drupal\filter\Entity\FilterFormat;
13
14 /**
15  * Tests integration searching comments.
16  *
17  * @group search
18  */
19 class SearchCommentTest extends BrowserTestBase {
20
21   use CommentTestTrait;
22   use CronRunTrait;
23
24   /**
25    * {@inheritdoc}
26    */
27   protected static $modules = ['filter', 'node', 'comment', 'search'];
28
29   /**
30    * Test subject for comments.
31    *
32    * @var string
33    */
34   protected $commentSubject;
35
36   /**
37    * ID for the administrator role.
38    *
39    * @var string
40    */
41   protected $adminRole;
42
43   /**
44    * A user with various administrative permissions.
45    *
46    * @var \Drupal\user\UserInterface
47    */
48   protected $adminUser;
49
50   /**
51    * Test node for searching.
52    *
53    * @var \Drupal\node\NodeInterface
54    */
55   protected $node;
56
57   protected function setUp() {
58     parent::setUp();
59
60     $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
61     $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
62
63     $full_html_format = FilterFormat::create([
64       'format' => 'full_html',
65       'name' => 'Full HTML',
66       'weight' => 1,
67       'filters' => [],
68     ]);
69     $full_html_format->save();
70
71     // Create and log in an administrative user having access to the Full HTML
72     // text format.
73     $permissions = [
74       'administer filters',
75       $full_html_format->getPermissionName(),
76       'administer permissions',
77       'create page content',
78       'post comments',
79       'skip comment approval',
80       'access comments',
81     ];
82     $this->adminUser = $this->drupalCreateUser($permissions);
83     $this->drupalLogin($this->adminUser);
84     // Add a comment field.
85     $this->addDefaultCommentField('node', 'article');
86   }
87
88   /**
89    * Verify that comments are rendered using proper format in search results.
90    */
91   public function testSearchResultsComment() {
92     $node_storage = $this->container->get('entity.manager')->getStorage('node');
93     // Create basic_html format that escapes all HTML.
94     $basic_html_format = FilterFormat::create([
95       'format' => 'basic_html',
96       'name' => 'Basic HTML',
97       'weight' => 1,
98       'filters' => [
99         'filter_html_escape' => ['status' => 1],
100       ],
101       'roles' => [RoleInterface::AUTHENTICATED_ID],
102     ]);
103     $basic_html_format->save();
104
105     $comment_body = 'Test comment body';
106
107     // Make preview optional.
108     $field = FieldConfig::loadByName('node', 'article', 'comment');
109     $field->setSetting('preview', DRUPAL_OPTIONAL);
110     $field->save();
111
112     // Allow anonymous users to search content.
113     $edit = [
114       RoleInterface::ANONYMOUS_ID . '[search content]' => 1,
115       RoleInterface::ANONYMOUS_ID . '[access comments]' => 1,
116       RoleInterface::ANONYMOUS_ID . '[post comments]' => 1,
117     ];
118     $this->drupalPostForm('admin/people/permissions', $edit, t('Save permissions'));
119
120     // Create a node.
121     $node = $this->drupalCreateNode(['type' => 'article']);
122     // Post a comment using 'Full HTML' text format.
123     $edit_comment = [];
124     $edit_comment['subject[0][value]'] = 'Test comment subject';
125     $edit_comment['comment_body[0][value]'] = '<h1>' . $comment_body . '</h1>';
126     $full_html_format_id = 'full_html';
127     $edit_comment['comment_body[0][format]'] = $full_html_format_id;
128     $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit_comment, t('Save'));
129
130     // Post a comment with an evil script tag in the comment subject and a
131     // script tag nearby a keyword in the comment body. Use the 'FULL HTML' text
132     // format so the script tag stored.
133     $edit_comment2 = [];
134     $edit_comment2['subject[0][value]'] = "<script>alert('subjectkeyword');</script>";
135     $edit_comment2['comment_body[0][value]'] = "nearbykeyword<script>alert('somethinggeneric');</script>";
136     $edit_comment2['comment_body[0][format]'] = $full_html_format_id;
137     $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit_comment2, t('Save'));
138
139     // Post a comment with a keyword inside an evil script tag in the comment
140     // body. Use the 'FULL HTML' text format so the script tag is stored.
141     $edit_comment3 = [];
142     $edit_comment3['subject[0][value]'] = 'asubject';
143     $edit_comment3['comment_body[0][value]'] = "<script>alert('insidekeyword');</script>";
144     $edit_comment3['comment_body[0][format]'] = $full_html_format_id;
145     $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit_comment3, t('Save'));
146
147     // Invoke search index update.
148     $this->drupalLogout();
149     $this->cronRun();
150
151     // Search for the comment subject.
152     $edit = [
153       'keys' => "'" . $edit_comment['subject[0][value]'] . "'",
154     ];
155     $this->drupalPostForm('search/node', $edit, t('Search'));
156     $node_storage->resetCache([$node->id()]);
157     $node2 = $node_storage->load($node->id());
158     $this->assertText($node2->label(), 'Node found in search results.');
159     $this->assertText($edit_comment['subject[0][value]'], 'Comment subject found in search results.');
160
161     // Search for the comment body.
162     $edit = [
163       'keys' => "'" . $comment_body . "'",
164     ];
165     $this->drupalPostForm(NULL, $edit, t('Search'));
166     $this->assertText($node2->label(), 'Node found in search results.');
167
168     // Verify that comment is rendered using proper format.
169     $this->assertText($comment_body, 'Comment body text found in search results.');
170     $this->assertNoRaw(t('n/a'), 'HTML in comment body is not hidden.');
171     $this->assertNoEscaped($edit_comment['comment_body[0][value]'], 'HTML in comment body is not escaped.');
172
173     // Search for the evil script comment subject.
174     $edit = [
175       'keys' => 'subjectkeyword',
176     ];
177     $this->drupalPostForm('search/node', $edit, t('Search'));
178
179     // Verify the evil comment subject is escaped in search results.
180     $this->assertRaw('&lt;script&gt;alert(&#039;<strong>subjectkeyword</strong>&#039;);');
181     $this->assertNoRaw('<script>');
182
183     // Search for the keyword near the evil script tag in the comment body.
184     $edit = [
185       'keys' => 'nearbykeyword',
186     ];
187     $this->drupalPostForm('search/node', $edit, t('Search'));
188
189     // Verify that nearby script tag in the evil comment body is stripped from
190     // search results.
191     $this->assertRaw('<strong>nearbykeyword</strong>');
192     $this->assertNoRaw('<script>');
193
194     // Search for contents inside the evil script tag in the comment body.
195     $edit = [
196       'keys' => 'insidekeyword',
197     ];
198     $this->drupalPostForm('search/node', $edit, t('Search'));
199
200     // @todo Verify the actual search results.
201     //   https://www.drupal.org/node/2551135
202
203     // Verify there is no script tag in search results.
204     $this->assertNoRaw('<script>');
205
206     // Hide comments.
207     $this->drupalLogin($this->adminUser);
208     $node->set('comment', CommentItemInterface::HIDDEN);
209     $node->save();
210
211     // Invoke search index update.
212     $this->drupalLogout();
213     $this->cronRun();
214
215     // Search for $title.
216     $this->drupalPostForm('search/node', $edit, t('Search'));
217     $this->assertText(t('Your search yielded no results.'));
218   }
219
220   /**
221    * Verify access rules for comment indexing with different permissions.
222    */
223   public function testSearchResultsCommentAccess() {
224     $comment_body = 'Test comment body';
225     $this->commentSubject = 'Test comment subject';
226     $roles = $this->adminUser->getRoles(TRUE);
227     $this->adminRole = $roles[0];
228
229     // Create a node.
230     // Make preview optional.
231     $field = FieldConfig::loadByName('node', 'article', 'comment');
232     $field->setSetting('preview', DRUPAL_OPTIONAL);
233     $field->save();
234     $this->node = $this->drupalCreateNode(['type' => 'article']);
235
236     // Post a comment using 'Full HTML' text format.
237     $edit_comment = [];
238     $edit_comment['subject[0][value]'] = $this->commentSubject;
239     $edit_comment['comment_body[0][value]'] = '<h1>' . $comment_body . '</h1>';
240     $this->drupalPostForm('comment/reply/node/' . $this->node->id() . '/comment', $edit_comment, t('Save'));
241
242     $this->drupalLogout();
243     $this->setRolePermissions(RoleInterface::ANONYMOUS_ID);
244     $this->assertCommentAccess(FALSE, 'Anon user has search permission but no access comments permission, comments should not be indexed');
245
246     $this->setRolePermissions(RoleInterface::ANONYMOUS_ID, TRUE);
247     $this->assertCommentAccess(TRUE, 'Anon user has search permission and access comments permission, comments should be indexed');
248
249     $this->drupalLogin($this->adminUser);
250     $this->drupalGet('admin/people/permissions');
251
252     // Disable search access for authenticated user to test admin user.
253     $this->setRolePermissions(RoleInterface::AUTHENTICATED_ID, FALSE, FALSE);
254
255     $this->setRolePermissions($this->adminRole);
256     $this->assertCommentAccess(FALSE, 'Admin user has search permission but no access comments permission, comments should not be indexed');
257
258     $this->drupalGet('node/' . $this->node->id());
259     $this->setRolePermissions($this->adminRole, TRUE);
260     $this->assertCommentAccess(TRUE, 'Admin user has search permission and access comments permission, comments should be indexed');
261
262     $this->setRolePermissions(RoleInterface::AUTHENTICATED_ID);
263     $this->assertCommentAccess(FALSE, 'Authenticated user has search permission but no access comments permission, comments should not be indexed');
264
265     $this->setRolePermissions(RoleInterface::AUTHENTICATED_ID, TRUE);
266     $this->assertCommentAccess(TRUE, 'Authenticated user has search permission and access comments permission, comments should be indexed');
267
268     // Verify that access comments permission is inherited from the
269     // authenticated role.
270     $this->setRolePermissions(RoleInterface::AUTHENTICATED_ID, TRUE, FALSE);
271     $this->setRolePermissions($this->adminRole);
272     $this->assertCommentAccess(TRUE, 'Admin user has search permission and no access comments permission, but comments should be indexed because admin user inherits authenticated user\'s permission to access comments');
273
274     // Verify that search content permission is inherited from the authenticated
275     // role.
276     $this->setRolePermissions(RoleInterface::AUTHENTICATED_ID, TRUE, TRUE);
277     $this->setRolePermissions($this->adminRole, TRUE, FALSE);
278     $this->assertCommentAccess(TRUE, 'Admin user has access comments permission and no search permission, but comments should be indexed because admin user inherits authenticated user\'s permission to search');
279   }
280
281   /**
282    * Set permissions for role.
283    */
284   public function setRolePermissions($rid, $access_comments = FALSE, $search_content = TRUE) {
285     $permissions = [
286       'access comments' => $access_comments,
287       'search content' => $search_content,
288     ];
289     user_role_change_permissions($rid, $permissions);
290   }
291
292   /**
293    * Update search index and search for comment.
294    */
295   public function assertCommentAccess($assume_access, $message) {
296     // Invoke search index update.
297     search_mark_for_reindex('node_search', $this->node->id());
298     $this->cronRun();
299
300     // Search for the comment subject.
301     $edit = [
302       'keys' => "'" . $this->commentSubject . "'",
303     ];
304     $this->drupalPostForm('search/node', $edit, t('Search'));
305
306     try {
307       if ($assume_access) {
308         $this->assertSession()->pageTextContains($this->node->label());
309         $this->assertSession()->pageTextContains($this->commentSubject);
310       }
311       else {
312         $this->assertSession()->pageTextContains(t('Your search yielded no results.'));
313       }
314     }
315     catch (ResponseTextException $exception) {
316       $this->fail($message);
317     }
318   }
319
320   /**
321    * Verify that 'add new comment' does not appear in search results or index.
322    */
323   public function testAddNewComment() {
324     // Create a node with a short body.
325     $settings = [
326       'type' => 'article',
327       'title' => 'short title',
328       'body' => [['value' => 'short body text']],
329     ];
330
331     $user = $this->drupalCreateUser([
332       'search content',
333       'create article content',
334       'access content',
335       'post comments',
336       'access comments',
337     ]);
338     $this->drupalLogin($user);
339
340     $node = $this->drupalCreateNode($settings);
341     // Verify that if you view the node on its own page, 'add new comment'
342     // is there.
343     $this->drupalGet('node/' . $node->id());
344     $this->assertText(t('Add new comment'));
345
346     // Run cron to index this page.
347     $this->drupalLogout();
348     $this->cronRun();
349
350     // Search for 'comment'. Should be no results.
351     $this->drupalLogin($user);
352     $this->drupalPostForm('search/node', ['keys' => 'comment'], t('Search'));
353     $this->assertText(t('Your search yielded no results'));
354
355     // Search for the node title. Should be found, and 'Add new comment' should
356     // not be part of the search snippet.
357     $this->drupalPostForm('search/node', ['keys' => 'short'], t('Search'));
358     $this->assertText($node->label(), 'Search for keyword worked');
359     $this->assertNoText(t('Add new comment'));
360   }
361
362 }