See Release Notes
Long Term Support Release
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Forum search unit tests. 19 * 20 * @package mod_forum 21 * @category test 22 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace mod_forum\search; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 global $CFG; 31 require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php'); 32 require_once($CFG->dirroot . '/mod/forum/tests/generator/lib.php'); 33 require_once($CFG->dirroot . '/mod/forum/lib.php'); 34 35 /** 36 * Provides the unit tests for forum search. 37 * 38 * @package mod_forum 39 * @category test 40 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class search_test extends \advanced_testcase { 44 45 /** 46 * @var string Area id 47 */ 48 protected $forumpostareaid = null; 49 50 public function setUp(): void { 51 $this->resetAfterTest(true); 52 set_config('enableglobalsearch', true); 53 54 $this->forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post'); 55 56 // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this. 57 $search = \testable_core_search::instance(); 58 } 59 60 /** 61 * Availability. 62 * 63 * @return void 64 */ 65 public function test_search_enabled() { 66 67 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 68 list($componentname, $varname) = $searcharea->get_config_var_name(); 69 70 // Enabled by default once global search is enabled. 71 $this->assertTrue($searcharea->is_enabled()); 72 73 set_config($varname . '_enabled', 0, $componentname); 74 $this->assertFalse($searcharea->is_enabled()); 75 76 set_config($varname . '_enabled', 1, $componentname); 77 $this->assertTrue($searcharea->is_enabled()); 78 } 79 80 /** 81 * Indexing mod forum contents. 82 * 83 * @return void 84 */ 85 public function test_posts_indexing() { 86 global $DB; 87 88 // Returns the instance as long as the area is supported. 89 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 90 $this->assertInstanceOf('\mod_forum\search\post', $searcharea); 91 92 $user1 = self::getDataGenerator()->create_user(); 93 $user2 = self::getDataGenerator()->create_user(); 94 95 $course1 = self::getDataGenerator()->create_course(); 96 $course2 = self::getDataGenerator()->create_course(); 97 98 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student'); 99 $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student'); 100 101 $record = new \stdClass(); 102 $record->course = $course1->id; 103 104 // Available for both student and teacher. 105 $forum1 = self::getDataGenerator()->create_module('forum', $record); 106 107 // Create discussion1. 108 $record = new \stdClass(); 109 $record->course = $course1->id; 110 $record->userid = $user1->id; 111 $record->forum = $forum1->id; 112 $record->message = 'discussion'; 113 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 114 115 // Create post1 in discussion1. 116 $record = new \stdClass(); 117 $record->discussion = $discussion1->id; 118 $record->parent = $discussion1->firstpost; 119 $record->userid = $user2->id; 120 $record->message = 'post2'; 121 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 122 123 // All records. 124 $recordset = $searcharea->get_recordset_by_timestamp(0); 125 $this->assertTrue($recordset->valid()); 126 $nrecords = 0; 127 foreach ($recordset as $record) { 128 $this->assertInstanceOf('stdClass', $record); 129 $doc = $searcharea->get_document($record); 130 $this->assertInstanceOf('\core_search\document', $doc); 131 132 // Static caches are working. 133 $dbreads = $DB->perf_get_reads(); 134 $doc = $searcharea->get_document($record); 135 $this->assertEquals($dbreads, $DB->perf_get_reads()); 136 $this->assertInstanceOf('\core_search\document', $doc); 137 $nrecords++; 138 } 139 // If there would be an error/failure in the foreach above the recordset would be closed on shutdown. 140 $recordset->close(); 141 $this->assertEquals(2, $nrecords); 142 143 // The +2 is to prevent race conditions. 144 $recordset = $searcharea->get_recordset_by_timestamp(time() + 2); 145 146 // No new records. 147 $this->assertFalse($recordset->valid()); 148 $recordset->close(); 149 150 // Context test: create another forum with 1 post. 151 $forum2 = self::getDataGenerator()->create_module('forum', ['course' => $course1->id]); 152 $record = new \stdClass(); 153 $record->course = $course1->id; 154 $record->userid = $user1->id; 155 $record->forum = $forum2->id; 156 $record->message = 'discussion'; 157 self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 158 159 // Test indexing with each forum then combined course context. 160 $rs = $searcharea->get_document_recordset(0, \context_module::instance($forum1->cmid)); 161 $this->assertEquals(2, iterator_count($rs)); 162 $rs->close(); 163 $rs = $searcharea->get_document_recordset(0, \context_module::instance($forum2->cmid)); 164 $this->assertEquals(1, iterator_count($rs)); 165 $rs->close(); 166 $rs = $searcharea->get_document_recordset(0, \context_course::instance($course1->id)); 167 $this->assertEquals(3, iterator_count($rs)); 168 $rs->close(); 169 } 170 171 /** 172 * Document contents. 173 * 174 * @return void 175 */ 176 public function test_posts_document() { 177 global $DB; 178 179 // Returns the instance as long as the area is supported. 180 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 181 $this->assertInstanceOf('\mod_forum\search\post', $searcharea); 182 183 $user = self::getDataGenerator()->create_user(); 184 $course1 = self::getDataGenerator()->create_course(); 185 $this->getDataGenerator()->enrol_user($user->id, $course1->id, 'teacher'); 186 187 $record = new \stdClass(); 188 $record->course = $course1->id; 189 $forum1 = self::getDataGenerator()->create_module('forum', $record); 190 191 // Teacher only. 192 $forum2 = self::getDataGenerator()->create_module('forum', $record); 193 set_coursemodule_visible($forum2->cmid, 0); 194 195 // Create discussion1. 196 $record = new \stdClass(); 197 $record->course = $course1->id; 198 $record->userid = $user->id; 199 $record->forum = $forum1->id; 200 $record->message = 'discussion'; 201 $record->groupid = 0; 202 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 203 204 // Create post1 in discussion1. 205 $record = new \stdClass(); 206 $record->discussion = $discussion1->id; 207 $record->parent = $discussion1->firstpost; 208 $record->userid = $user->id; 209 $record->subject = 'subject1'; 210 $record->message = 'post1'; 211 $record->groupid = -1; 212 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 213 214 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1reply1->id)); 215 $post1->forumid = $forum1->id; 216 $post1->courseid = $forum1->course; 217 $post1->groupid = -1; 218 219 $doc = $searcharea->get_document($post1); 220 $this->assertInstanceOf('\core_search\document', $doc); 221 $this->assertEquals($discussion1reply1->id, $doc->get('itemid')); 222 $this->assertEquals($this->forumpostareaid . '-' . $discussion1reply1->id, $doc->get('id')); 223 $this->assertEquals($course1->id, $doc->get('courseid')); 224 $this->assertEquals($user->id, $doc->get('userid')); 225 $this->assertEquals($discussion1reply1->subject, $doc->get('title')); 226 $this->assertEquals($discussion1reply1->message, $doc->get('content')); 227 } 228 229 /** 230 * Group support for forum posts. 231 */ 232 public function test_posts_group_support() { 233 // Get the search area and test generators. 234 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 235 $generator = $this->getDataGenerator(); 236 $forumgenerator = $generator->get_plugin_generator('mod_forum'); 237 238 // Create a course, a user, and two groups. 239 $course = $generator->create_course(); 240 $user = $generator->create_user(); 241 $generator->enrol_user($user->id, $course->id, 'teacher'); 242 $group1 = $generator->create_group(['courseid' => $course->id]); 243 $group2 = $generator->create_group(['courseid' => $course->id]); 244 245 // Separate groups forum. 246 $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 247 'groupmode' => SEPARATEGROUPS]); 248 249 // Create discussion with each group and one for all groups. One has a post in. 250 $discussion1 = $forumgenerator->create_discussion(['course' => $course->id, 251 'userid' => $user->id, 'forum' => $forum->id, 'message' => 'd1', 252 'groupid' => $group1->id]); 253 $forumgenerator->create_discussion(['course' => $course->id, 254 'userid' => $user->id, 'forum' => $forum->id, 'message' => 'd2', 255 'groupid' => $group2->id]); 256 $forumgenerator->create_discussion(['course' => $course->id, 257 'userid' => $user->id, 'forum' => $forum->id, 'message' => 'd3']); 258 259 // Create a reply in discussion1. 260 $forumgenerator->create_post(['discussion' => $discussion1->id, 'parent' => $discussion1->firstpost, 261 'userid' => $user->id, 'message' => 'p1']); 262 263 // Do the indexing of all 4 posts. 264 $rs = $searcharea->get_recordset_by_timestamp(0); 265 $results = []; 266 foreach ($rs as $rec) { 267 $results[$rec->message] = $rec; 268 } 269 $rs->close(); 270 $this->assertCount(4, $results); 271 272 // Check each document has the correct groupid. 273 $doc = $searcharea->get_document($results['d1']); 274 $this->assertTrue($doc->is_set('groupid')); 275 $this->assertEquals($group1->id, $doc->get('groupid')); 276 $doc = $searcharea->get_document($results['d2']); 277 $this->assertTrue($doc->is_set('groupid')); 278 $this->assertEquals($group2->id, $doc->get('groupid')); 279 $doc = $searcharea->get_document($results['d3']); 280 $this->assertFalse($doc->is_set('groupid')); 281 $doc = $searcharea->get_document($results['p1']); 282 $this->assertTrue($doc->is_set('groupid')); 283 $this->assertEquals($group1->id, $doc->get('groupid')); 284 285 // While we're here, also test that the search area requests restriction by group. 286 $modinfo = get_fast_modinfo($course); 287 $this->assertTrue($searcharea->restrict_cm_access_by_group($modinfo->get_cm($forum->cmid))); 288 289 // In visible groups mode, it won't request restriction by group. 290 set_coursemodule_groupmode($forum->cmid, VISIBLEGROUPS); 291 $modinfo = get_fast_modinfo($course); 292 $this->assertFalse($searcharea->restrict_cm_access_by_group($modinfo->get_cm($forum->cmid))); 293 } 294 295 /** 296 * Document accesses. 297 * 298 * @return void 299 */ 300 public function test_posts_access() { 301 global $DB; 302 303 // Returns the instance as long as the area is supported. 304 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 305 306 $user1 = self::getDataGenerator()->create_user(); 307 $user2 = self::getDataGenerator()->create_user(); 308 309 $course1 = self::getDataGenerator()->create_course(); 310 $course2 = self::getDataGenerator()->create_course(); 311 312 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher'); 313 $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student'); 314 315 $record = new \stdClass(); 316 $record->course = $course1->id; 317 318 // Available for both student and teacher. 319 $forum1 = self::getDataGenerator()->create_module('forum', $record); 320 321 // Teacher only. 322 $forum2 = self::getDataGenerator()->create_module('forum', $record); 323 set_coursemodule_visible($forum2->cmid, 0); 324 325 // Create discussion1. 326 $record = new \stdClass(); 327 $record->course = $course1->id; 328 $record->userid = $user1->id; 329 $record->forum = $forum1->id; 330 $record->message = 'discussion'; 331 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 332 333 // Create post1 in discussion1. 334 $record = new \stdClass(); 335 $record->discussion = $discussion1->id; 336 $record->parent = $discussion1->firstpost; 337 $record->userid = $user2->id; 338 $record->message = 'post1'; 339 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 340 341 // Create discussion2 only visible to teacher. 342 $record = new \stdClass(); 343 $record->course = $course1->id; 344 $record->userid = $user1->id; 345 $record->forum = $forum2->id; 346 $record->message = 'discussion'; 347 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 348 349 // Create post2 in discussion2. 350 $record = new \stdClass(); 351 $record->discussion = $discussion2->id; 352 $record->parent = $discussion2->firstpost; 353 $record->userid = $user1->id; 354 $record->message = 'post2'; 355 $discussion2reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 356 357 $this->setUser($user2); 358 $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($discussion1reply1->id)); 359 $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($discussion2reply1->id)); 360 } 361 362 /** 363 * Test for post attachments. 364 * 365 * @return void 366 */ 367 public function test_attach_files() { 368 global $DB; 369 370 $fs = get_file_storage(); 371 372 // Returns the instance as long as the area is supported. 373 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 374 $this->assertInstanceOf('\mod_forum\search\post', $searcharea); 375 376 $user1 = self::getDataGenerator()->create_user(); 377 $user2 = self::getDataGenerator()->create_user(); 378 379 $course1 = self::getDataGenerator()->create_course(); 380 381 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student'); 382 $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student'); 383 384 $record = new \stdClass(); 385 $record->course = $course1->id; 386 387 $forum1 = self::getDataGenerator()->create_module('forum', $record); 388 389 // Create discussion1. 390 $record = new \stdClass(); 391 $record->course = $course1->id; 392 $record->userid = $user1->id; 393 $record->forum = $forum1->id; 394 $record->message = 'discussion'; 395 $record->attachemt = 1; 396 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 397 398 // Attach 2 file to the discussion post. 399 $post = $DB->get_record('forum_posts', array('discussion' => $discussion1->id)); 400 $filerecord = array( 401 'contextid' => \context_module::instance($forum1->cmid)->id, 402 'component' => 'mod_forum', 403 'filearea' => 'attachment', 404 'itemid' => $post->id, 405 'filepath' => '/', 406 'filename' => 'myfile1' 407 ); 408 $file1 = $fs->create_file_from_string($filerecord, 'Some contents 1'); 409 $filerecord['filename'] = 'myfile2'; 410 $file2 = $fs->create_file_from_string($filerecord, 'Some contents 2'); 411 412 // Create post1 in discussion1. 413 $record = new \stdClass(); 414 $record->discussion = $discussion1->id; 415 $record->parent = $discussion1->firstpost; 416 $record->userid = $user2->id; 417 $record->message = 'post2'; 418 $record->attachemt = 1; 419 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 420 421 $filerecord['itemid'] = $discussion1reply1->id; 422 $filerecord['filename'] = 'myfile3'; 423 $file3 = $fs->create_file_from_string($filerecord, 'Some contents 3'); 424 425 // Create post2 in discussion1. 426 $record = new \stdClass(); 427 $record->discussion = $discussion1->id; 428 $record->parent = $discussion1->firstpost; 429 $record->userid = $user2->id; 430 $record->message = 'post3'; 431 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 432 433 // Now get all the posts and see if they have the right files attached. 434 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 435 $recordset = $searcharea->get_recordset_by_timestamp(0); 436 $nrecords = 0; 437 foreach ($recordset as $record) { 438 $doc = $searcharea->get_document($record); 439 $searcharea->attach_files($doc); 440 $files = $doc->get_files(); 441 // Now check that each doc has the right files on it. 442 switch ($doc->get('itemid')) { 443 case ($post->id): 444 $this->assertCount(2, $files); 445 $this->assertEquals($file1->get_id(), $files[$file1->get_id()]->get_id()); 446 $this->assertEquals($file2->get_id(), $files[$file2->get_id()]->get_id()); 447 break; 448 case ($discussion1reply1->id): 449 $this->assertCount(1, $files); 450 $this->assertEquals($file3->get_id(), $files[$file3->get_id()]->get_id()); 451 break; 452 case ($discussion1reply2->id): 453 $this->assertCount(0, $files); 454 break; 455 default: 456 $this->fail('Unexpected post returned'); 457 break; 458 } 459 $nrecords++; 460 } 461 $recordset->close(); 462 $this->assertEquals(3, $nrecords); 463 } 464 465 /** 466 * Tests that reindexing works in order starting from the forum with most recent discussion. 467 */ 468 public function test_posts_get_contexts_to_reindex() { 469 global $DB; 470 471 $generator = $this->getDataGenerator(); 472 $adminuser = get_admin(); 473 474 $course1 = $generator->create_course(); 475 $course2 = $generator->create_course(); 476 477 $time = time() - 1000; 478 479 // Create 3 forums (two in course 1, one in course 2 - doesn't make a difference). 480 $forum1 = $generator->create_module('forum', ['course' => $course1->id]); 481 $forum2 = $generator->create_module('forum', ['course' => $course1->id]); 482 $forum3 = $generator->create_module('forum', ['course' => $course2->id]); 483 $forum4 = $generator->create_module('forum', ['course' => $course2->id]); 484 485 // Hack added time for the course_modules entries. These should not be used (they would 486 // be used by the base class implementation). We are setting this so that the order would 487 // be 4, 3, 2, 1 if this ordering were used (newest first). 488 $DB->set_field('course_modules', 'added', $time + 100, ['id' => $forum1->cmid]); 489 $DB->set_field('course_modules', 'added', $time + 110, ['id' => $forum2->cmid]); 490 $DB->set_field('course_modules', 'added', $time + 120, ['id' => $forum3->cmid]); 491 $DB->set_field('course_modules', 'added', $time + 130, ['id' => $forum4->cmid]); 492 493 $forumgenerator = $generator->get_plugin_generator('mod_forum'); 494 495 // Create one discussion in forums 1 and 3, three in forum 2, and none in forum 4. 496 $forumgenerator->create_discussion(['course' => $course1->id, 497 'forum' => $forum1->id, 'userid' => $adminuser->id, 'timemodified' => $time + 20]); 498 499 $forumgenerator->create_discussion(['course' => $course1->id, 500 'forum' => $forum2->id, 'userid' => $adminuser->id, 'timemodified' => $time + 10]); 501 $forumgenerator->create_discussion(['course' => $course1->id, 502 'forum' => $forum2->id, 'userid' => $adminuser->id, 'timemodified' => $time + 30]); 503 $forumgenerator->create_discussion(['course' => $course1->id, 504 'forum' => $forum2->id, 'userid' => $adminuser->id, 'timemodified' => $time + 11]); 505 506 $forumgenerator->create_discussion(['course' => $course2->id, 507 'forum' => $forum3->id, 'userid' => $adminuser->id, 'timemodified' => $time + 25]); 508 509 // Get the contexts in reindex order. 510 $area = \core_search\manager::get_search_area($this->forumpostareaid); 511 $contexts = iterator_to_array($area->get_contexts_to_reindex(), false); 512 513 // We expect them in order of newest discussion. Forum 4 is not included at all (which is 514 // correct because it has no content). 515 $expected = [ 516 \context_module::instance($forum2->cmid), 517 \context_module::instance($forum3->cmid), 518 \context_module::instance($forum1->cmid) 519 ]; 520 $this->assertEquals($expected, $contexts); 521 } 522 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body