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