See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 * Search manager unit tests. 19 * 20 * @package core_search 21 * @category phpunit 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 defined('MOODLE_INTERNAL') || die(); 27 28 require_once (__DIR__ . '/fixtures/testable_core_search.php'); 29 require_once (__DIR__ . '/fixtures/mock_search_area.php'); 30 31 /** 32 * Unit tests for search manager. 33 * 34 * @package core_search 35 * @category phpunit 36 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com} 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class search_manager_testcase extends advanced_testcase { 40 41 /** 42 * Forum area id. 43 * 44 * @var string 45 */ 46 47 protected $forumpostareaid = null; 48 49 /** 50 * Courses area id. 51 * 52 * @var string 53 */ 54 protected $coursesareaid = null; 55 56 public function setUp() { 57 $this->forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post'); 58 $this->coursesareaid = \core_search\manager::generate_areaid('core_course', 'course'); 59 } 60 61 protected function tearDown() { 62 // Stop it from faking time in the search manager (if set by test). 63 testable_core_search::fake_current_time(); 64 parent::tearDown(); 65 } 66 67 public function test_search_enabled() { 68 69 $this->resetAfterTest(); 70 71 // Disabled by default. 72 $this->assertFalse(\core_search\manager::is_global_search_enabled()); 73 74 set_config('enableglobalsearch', true); 75 $this->assertTrue(\core_search\manager::is_global_search_enabled()); 76 77 set_config('enableglobalsearch', false); 78 $this->assertFalse(\core_search\manager::is_global_search_enabled()); 79 } 80 81 public function test_course_search_url() { 82 83 $this->resetAfterTest(); 84 85 // URL is course/search.php by default. 86 $this->assertEquals(new moodle_url("/course/search.php"), \core_search\manager::get_course_search_url()); 87 88 set_config('enableglobalsearch', true); 89 $this->assertEquals(new moodle_url("/search/index.php"), \core_search\manager::get_course_search_url()); 90 91 set_config('enableglobalsearch', false); 92 $this->assertEquals(new moodle_url("/course/search.php"), \core_search\manager::get_course_search_url()); 93 } 94 95 public function test_search_areas() { 96 global $CFG; 97 98 $this->resetAfterTest(); 99 100 set_config('enableglobalsearch', true); 101 102 $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita'); 103 104 $searcharea = \core_search\manager::get_search_area($this->forumpostareaid); 105 $this->assertInstanceOf('\core_search\base', $searcharea); 106 107 $this->assertFalse(\core_search\manager::get_search_area($fakeareaid)); 108 109 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list()); 110 $this->assertArrayNotHasKey($fakeareaid, \core_search\manager::get_search_areas_list()); 111 112 // Enabled by default once global search is enabled. 113 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true)); 114 115 list($componentname, $varname) = $searcharea->get_config_var_name(); 116 set_config($varname . '_enabled', 0, $componentname); 117 \core_search\manager::clear_static(); 118 119 $this->assertArrayNotHasKey('mod_forum', \core_search\manager::get_search_areas_list(true)); 120 121 set_config($varname . '_enabled', 1, $componentname); 122 123 // Although the result is wrong, we want to check that \core_search\manager::get_search_areas_list returns cached results. 124 $this->assertArrayNotHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true)); 125 126 // Now we check the real result. 127 \core_search\manager::clear_static(); 128 $this->assertArrayHasKey($this->forumpostareaid, \core_search\manager::get_search_areas_list(true)); 129 } 130 131 public function test_search_config() { 132 133 $this->resetAfterTest(); 134 135 $search = testable_core_search::instance(); 136 137 // We should test both plugin types and core subsystems. No core subsystems available yet. 138 $searcharea = $search->get_search_area($this->forumpostareaid); 139 140 list($componentname, $varname) = $searcharea->get_config_var_name(); 141 142 // Just with a couple of vars should be enough. 143 $start = time() - 100; 144 $end = time(); 145 set_config($varname . '_indexingstart', $start, $componentname); 146 set_config($varname . '_indexingend', $end, $componentname); 147 148 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea)); 149 $this->assertEquals($start, $configs[$this->forumpostareaid]->indexingstart); 150 $this->assertEquals($end, $configs[$this->forumpostareaid]->indexingend); 151 $this->assertEquals(false, $configs[$this->forumpostareaid]->partial); 152 153 try { 154 $fakeareaid = \core_search\manager::generate_areaid('mod_unexisting', 'chihuaquita'); 155 $search->reset_config($fakeareaid); 156 $this->fail('An exception should be triggered if the provided search area does not exist.'); 157 } catch (moodle_exception $ex) { 158 $this->assertContains($fakeareaid . ' search area is not available.', $ex->getMessage()); 159 } 160 161 // We clean it all but enabled components. 162 $search->reset_config($this->forumpostareaid); 163 $config = $searcharea->get_config(); 164 $this->assertEquals(1, $config[$varname . '_enabled']); 165 $this->assertEquals(0, $config[$varname . '_indexingstart']); 166 $this->assertEquals(0, $config[$varname . '_indexingend']); 167 $this->assertEquals(0, $config[$varname . '_lastindexrun']); 168 $this->assertEquals(0, $config[$varname . '_partial']); 169 // No caching. 170 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea)); 171 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart); 172 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend); 173 174 set_config($varname . '_indexingstart', $start, $componentname); 175 set_config($varname . '_indexingend', $end, $componentname); 176 177 // All components config should be reset. 178 $search->reset_config(); 179 $this->assertEquals(0, get_config($componentname, $varname . '_indexingstart')); 180 $this->assertEquals(0, get_config($componentname, $varname . '_indexingend')); 181 $this->assertEquals(0, get_config($componentname, $varname . '_lastindexrun')); 182 // No caching. 183 $configs = $search->get_areas_config(array($this->forumpostareaid => $searcharea)); 184 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingstart); 185 $this->assertEquals(0, $configs[$this->forumpostareaid]->indexingend); 186 } 187 188 /** 189 * Tests the get_last_indexing_duration method in the base area class. 190 */ 191 public function test_get_last_indexing_duration() { 192 $this->resetAfterTest(); 193 194 $search = testable_core_search::instance(); 195 196 $searcharea = $search->get_search_area($this->forumpostareaid); 197 198 // When never indexed, the duration is false. 199 $this->assertSame(false, $searcharea->get_last_indexing_duration()); 200 201 // Set the start/end times. 202 list($componentname, $varname) = $searcharea->get_config_var_name(); 203 $start = time() - 100; 204 $end = time(); 205 set_config($varname . '_indexingstart', $start, $componentname); 206 set_config($varname . '_indexingend', $end, $componentname); 207 208 // The duration should now be 100. 209 $this->assertSame(100, $searcharea->get_last_indexing_duration()); 210 } 211 212 /** 213 * Tests that partial indexing works correctly. 214 */ 215 public function test_partial_indexing() { 216 global $USER; 217 218 $this->resetAfterTest(); 219 $this->setAdminUser(); 220 221 // Create a course and a forum. 222 $generator = $this->getDataGenerator(); 223 $course = $generator->create_course(); 224 $forum = $generator->create_module('forum', ['course' => $course->id]); 225 226 // Index everything up to current. Ensure the course is older than current second so it 227 // definitely doesn't get indexed again next time. 228 $this->waitForSecond(); 229 $search = testable_core_search::instance(); 230 $search->index(false, 0); 231 232 $searcharea = $search->get_search_area($this->forumpostareaid); 233 list($componentname, $varname) = $searcharea->get_config_var_name(); 234 $this->assertFalse(get_config($componentname, $varname . '_partial')); 235 236 // Add 3 discussions to the forum. 237 $now = time(); 238 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 239 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now, 240 'name' => 'Frog']); 241 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 242 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 1, 243 'name' => 'Toad']); 244 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 245 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2, 246 'name' => 'Zombie']); 247 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 248 'forum' => $forum->id, 'userid' => $USER->id, 'timemodified' => $now + 2, 249 'name' => 'Werewolf']); 250 time_sleep_until($now + 3); 251 252 // Clear the count of added documents. 253 $search->get_engine()->get_and_clear_added_documents(); 254 255 // Make the search engine delay while indexing each document. 256 $search->get_engine()->set_add_delay(1.2); 257 258 // Use fake time, starting from now. 259 testable_core_search::fake_current_time(time()); 260 261 // Index with a limit of 2 seconds - it should index 2 of the documents (after the second 262 // one, it will have taken 2.4 seconds so it will stop). 263 $search->index(false, 2); 264 $added = $search->get_engine()->get_and_clear_added_documents(); 265 $this->assertCount(2, $added); 266 $this->assertEquals('Frog', $added[0]->get('title')); 267 $this->assertEquals('Toad', $added[1]->get('title')); 268 $this->assertEquals(1, get_config($componentname, $varname . '_partial')); 269 // Whilst 2.4 seconds of "time" have elapsed, the indexing duration is 270 // measured in seconds, so should be 2. 271 $this->assertEquals(2, $searcharea->get_last_indexing_duration()); 272 273 // Add a label. 274 $generator->create_module('label', ['course' => $course->id, 'intro' => 'Vampire']); 275 276 // Wait to next second (so as to not reindex the label more than once, as it will now 277 // be timed before the indexing run). 278 $this->waitForSecond(); 279 testable_core_search::fake_current_time(time()); 280 281 // Next index with 1 second limit should do the label and not the forum - the logic is, 282 // if it spent ages indexing an area last time, do that one last on next run. 283 $search->index(false, 1); 284 $added = $search->get_engine()->get_and_clear_added_documents(); 285 $this->assertCount(1, $added); 286 $this->assertEquals('Vampire', $added[0]->get('title')); 287 288 // Index again with a 3 second limit - it will redo last post for safety (because of other 289 // things possibly having the same time second), and then do the remaining one. (Note: 290 // because it always does more than one second worth of items, it would actually index 2 291 // posts even if the limit were less than 2, we are testing it does 3 posts to make sure 292 // the time limiting is actually working with the specified time.) 293 $search->index(false, 3); 294 $added = $search->get_engine()->get_and_clear_added_documents(); 295 $this->assertCount(3, $added); 296 $this->assertEquals('Toad', $added[0]->get('title')); 297 $remainingtitles = [$added[1]->get('title'), $added[2]->get('title')]; 298 sort($remainingtitles); 299 $this->assertEquals(['Werewolf', 'Zombie'], $remainingtitles); 300 $this->assertFalse(get_config($componentname, $varname . '_partial')); 301 302 // Index again - there should be nothing to index this time. 303 $search->index(false, 2); 304 $added = $search->get_engine()->get_and_clear_added_documents(); 305 $this->assertCount(0, $added); 306 $this->assertFalse(get_config($componentname, $varname . '_partial')); 307 } 308 309 /** 310 * Tests the progress display while indexing. 311 * 312 * This tests the different logic about displaying progress for slow/fast and 313 * complete/incomplete processing. 314 */ 315 public function test_index_progress() { 316 $this->resetAfterTest(); 317 $generator = $this->getDataGenerator(); 318 319 // Set up the fake search area. 320 $search = testable_core_search::instance(); 321 $area = new \core_mocksearch\search\mock_search_area(); 322 $search->add_search_area('whatever', $area); 323 $searchgenerator = $generator->get_plugin_generator('core_search'); 324 $searchgenerator->setUp(); 325 326 // Add records with specific time modified values. 327 $time = strtotime('2017-11-01 01:00'); 328 for ($i = 0; $i < 8; $i ++) { 329 $searchgenerator->create_record((object)['timemodified' => $time]); 330 $time += 60; 331 } 332 333 // Simulate slow progress on indexing and initial query. 334 $now = strtotime('2017-11-11 01:00'); 335 \testable_core_search::fake_current_time($now); 336 $area->set_indexing_delay(10.123); 337 $search->get_engine()->set_add_delay(15.789); 338 339 // Run search indexing and check output. 340 $progress = new progress_trace_buffer(new text_progress_trace(), false); 341 $search->index(false, 75, $progress); 342 $out = $progress->get_buffer(); 343 $progress->reset_buffer(); 344 345 // Check for the standard text. 346 $this->assertContains('Processing area: Mock search area', $out); 347 $this->assertContains('Stopping indexing due to time limit', $out); 348 349 // Check for initial query performance indication. 350 $this->assertContains('Initial query took 10.1 seconds.', $out); 351 352 // Check for the two (approximately) every-30-seconds messages. 353 $this->assertContains('01:00:41: Done to 1/11/17, 01:01', $out); 354 $this->assertContains('01:01:13: Done to 1/11/17, 01:03', $out); 355 356 // Check for the 'not complete' indicator showing when it was done until. 357 $this->assertContains('Processed 5 records containing 5 documents, in 89.1 seconds ' . 358 '(not complete; done to 1/11/17, 01:04)', $out); 359 360 // Make the initial query delay less than 5 seconds, so it won't appear. Make the documents 361 // quicker, so that the 30-second delay won't be needed. 362 $area->set_indexing_delay(4.9); 363 $search->get_engine()->set_add_delay(1); 364 365 // Run search indexing (still partial) and check output. 366 $progress = new progress_trace_buffer(new text_progress_trace(), false); 367 $search->index(false, 5, $progress); 368 $out = $progress->get_buffer(); 369 $progress->reset_buffer(); 370 371 $this->assertContains('Processing area: Mock search area', $out); 372 $this->assertContains('Stopping indexing due to time limit', $out); 373 $this->assertNotContains('Initial query took', $out); 374 $this->assertNotContains(': Done to', $out); 375 $this->assertContains('Processed 2 records containing 2 documents, in 6.9 seconds ' . 376 '(not complete; done to 1/11/17, 01:05).', $out); 377 378 // Run the remaining items to complete it. 379 $progress = new progress_trace_buffer(new text_progress_trace(), false); 380 $search->index(false, 100, $progress); 381 $out = $progress->get_buffer(); 382 $progress->reset_buffer(); 383 384 $this->assertContains('Processing area: Mock search area', $out); 385 $this->assertNotContains('Stopping indexing due to time limit', $out); 386 $this->assertNotContains('Initial query took', $out); 387 $this->assertNotContains(': Done to', $out); 388 $this->assertContains('Processed 3 records containing 3 documents, in 7.9 seconds.', $out); 389 390 $searchgenerator->tearDown(); 391 } 392 393 /** 394 * Tests that documents with modified time in the future are NOT indexed (as this would cause 395 * a problem by preventing it from indexing other documents modified between now and the future 396 * date). 397 */ 398 public function test_future_documents() { 399 $this->resetAfterTest(); 400 401 // Create a course and a forum. 402 $generator = $this->getDataGenerator(); 403 $course = $generator->create_course(); 404 $forum = $generator->create_module('forum', ['course' => $course->id]); 405 406 // Index everything up to current. Ensure the course is older than current second so it 407 // definitely doesn't get indexed again next time. 408 $this->waitForSecond(); 409 $search = testable_core_search::instance(); 410 $search->index(false, 0); 411 $search->get_engine()->get_and_clear_added_documents(); 412 413 // Add 2 discussions to the forum, one of which happend just now, but the other is 414 // incorrectly set to the future. 415 $now = time(); 416 $userid = get_admin()->id; 417 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 418 'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now, 419 'name' => 'Frog']); 420 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 421 'forum' => $forum->id, 'userid' => $userid, 'timemodified' => $now + 100, 422 'name' => 'Toad']); 423 424 // Wait for a second so we're not actually on the same second as the forum post (there's a 425 // 1 second overlap between indexing; it would get indexed in both checks below otherwise). 426 $this->waitForSecond(); 427 428 // Index. 429 $search->index(false); 430 $added = $search->get_engine()->get_and_clear_added_documents(); 431 $this->assertCount(1, $added); 432 $this->assertEquals('Frog', $added[0]->get('title')); 433 434 // Check latest time - it should be the same as $now, not the + 100. 435 $searcharea = $search->get_search_area($this->forumpostareaid); 436 list($componentname, $varname) = $searcharea->get_config_var_name(); 437 $this->assertEquals($now, get_config($componentname, $varname . '_lastindexrun')); 438 439 // Index again - there should be nothing to index this time. 440 $search->index(false); 441 $added = $search->get_engine()->get_and_clear_added_documents(); 442 $this->assertCount(0, $added); 443 } 444 445 /** 446 * Tests that indexing a specified context works correctly. 447 */ 448 public function test_context_indexing() { 449 global $USER; 450 451 $this->resetAfterTest(); 452 $this->setAdminUser(); 453 454 // Create a course and two forums and a page. 455 $generator = $this->getDataGenerator(); 456 $course = $generator->create_course(); 457 $now = time(); 458 $forum1 = $generator->create_module('forum', ['course' => $course->id]); 459 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 460 'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now, 461 'name' => 'Frog']); 462 $this->waitForSecond(); 463 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 464 'forum' => $forum1->id, 'userid' => $USER->id, 'timemodified' => $now + 2, 465 'name' => 'Zombie']); 466 $forum2 = $generator->create_module('forum', ['course' => $course->id]); 467 $this->waitForSecond(); 468 $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 469 'forum' => $forum2->id, 'userid' => $USER->id, 'timemodified' => $now + 1, 470 'name' => 'Toad']); 471 $generator->create_module('page', ['course' => $course->id]); 472 $generator->create_module('forum', ['course' => $course->id]); 473 474 // Index forum 1 only. 475 $search = testable_core_search::instance(); 476 $buffer = new progress_trace_buffer(new text_progress_trace(), false); 477 $result = $search->index_context(\context_module::instance($forum1->cmid), '', 0, $buffer); 478 $this->assertTrue($result->complete); 479 $log = $buffer->get_buffer(); 480 $buffer->reset_buffer(); 481 482 // Confirm that output only processed 1 forum activity and 2 posts. 483 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 1 ")); 484 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 ")); 485 486 // Confirm that some areas for different types of context were skipped. 487 $this->assertNotFalse(strpos($log, "area: Users\n Skipping")); 488 $this->assertNotFalse(strpos($log, "area: Courses\n Skipping")); 489 490 // Confirm that another module area had no results. 491 $this->assertNotFalse(strpos($log, "area: Page\n No documents")); 492 493 // Index whole course. 494 $result = $search->index_context(\context_course::instance($course->id), '', 0, $buffer); 495 $this->assertTrue($result->complete); 496 $log = $buffer->get_buffer(); 497 $buffer->reset_buffer(); 498 499 // Confirm that output processed 3 forum activities and 3 posts. 500 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 3 ")); 501 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 3 ")); 502 503 // The course area was also included this time. 504 $this->assertNotFalse(strpos($log, "area: Courses\n Processed 1 ")); 505 506 // Confirm that another module area had results too. 507 $this->assertNotFalse(strpos($log, "area: Page\n Processed 1 ")); 508 509 // Index whole course, but only forum posts. 510 $result = $search->index_context(\context_course::instance($course->id), 'mod_forum-post', 511 0, $buffer); 512 $this->assertTrue($result->complete); 513 $log = $buffer->get_buffer(); 514 $buffer->reset_buffer(); 515 516 // Confirm that output processed 3 posts but not forum activities. 517 $this->assertFalse(strpos($log, "area: Forum - activity information")); 518 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 3 ")); 519 520 // Set time limit and retry index of whole course, taking 3 tries to complete it. 521 $search->get_engine()->set_add_delay(0.4); 522 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer); 523 $log = $buffer->get_buffer(); 524 $buffer->reset_buffer(); 525 $this->assertFalse($result->complete); 526 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 2 ")); 527 528 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer, 529 $result->startfromarea, $result->startfromtime); 530 $log = $buffer->get_buffer(); 531 $buffer->reset_buffer(); 532 $this->assertNotFalse(strpos($log, "area: Forum - activity information\n Processed 2 ")); 533 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 ")); 534 $this->assertFalse($result->complete); 535 536 $result = $search->index_context(\context_course::instance($course->id), '', 1, $buffer, 537 $result->startfromarea, $result->startfromtime); 538 $log = $buffer->get_buffer(); 539 $buffer->reset_buffer(); 540 $this->assertNotFalse(strpos($log, "area: Forum - posts\n Processed 2 ")); 541 $this->assertTrue($result->complete); 542 } 543 544 /** 545 * Adding this test here as get_areas_user_accesses process is the same, results just depend on the context level. 546 * 547 * @return void 548 */ 549 public function test_search_user_accesses() { 550 global $DB; 551 552 $this->resetAfterTest(); 553 554 $frontpage = $DB->get_record('course', array('id' => SITEID)); 555 $frontpagectx = context_course::instance($frontpage->id); 556 $course1 = $this->getDataGenerator()->create_course(); 557 $course1ctx = context_course::instance($course1->id); 558 $course2 = $this->getDataGenerator()->create_course(); 559 $course2ctx = context_course::instance($course2->id); 560 $course3 = $this->getDataGenerator()->create_course(); 561 $course3ctx = context_course::instance($course3->id); 562 $teacher = $this->getDataGenerator()->create_user(); 563 $teacherctx = context_user::instance($teacher->id); 564 $student = $this->getDataGenerator()->create_user(); 565 $studentctx = context_user::instance($student->id); 566 $noaccess = $this->getDataGenerator()->create_user(); 567 $noaccessctx = context_user::instance($noaccess->id); 568 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, 'teacher'); 569 $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student'); 570 571 $frontpageforum = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id)); 572 $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); 573 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); 574 $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id)); 575 $frontpageforumcontext = context_module::instance($frontpageforum->cmid); 576 $context1 = context_module::instance($forum1->cmid); 577 $context2 = context_module::instance($forum2->cmid); 578 $context3 = context_module::instance($forum3->cmid); 579 $forum4 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id)); 580 $context4 = context_module::instance($forum4->cmid); 581 582 $search = testable_core_search::instance(); 583 $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area'); 584 $search->add_core_search_areas(); 585 $search->add_search_area($mockareaid, new core_mocksearch\search\mock_search_area()); 586 587 $this->setAdminUser(); 588 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses()); 589 590 $sitectx = \context_course::instance(SITEID); 591 592 // Can access the frontpage ones. 593 $this->setUser($noaccess); 594 $contexts = $search->get_areas_user_accesses()->usercontexts; 595 $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id), $contexts[$this->forumpostareaid]); 596 $this->assertEquals(array($sitectx->id => $sitectx->id), $contexts[$this->coursesareaid]); 597 $mockctxs = array($noaccessctx->id => $noaccessctx->id, $frontpagectx->id => $frontpagectx->id); 598 $this->assertEquals($mockctxs, $contexts[$mockareaid]); 599 600 $this->setUser($teacher); 601 $contexts = $search->get_areas_user_accesses()->usercontexts; 602 $frontpageandcourse1 = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id, 603 $context2->id => $context2->id); 604 $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]); 605 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id), 606 $contexts[$this->coursesareaid]); 607 $mockctxs = array($teacherctx->id => $teacherctx->id, 608 $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id); 609 $this->assertEquals($mockctxs, $contexts[$mockareaid]); 610 611 $this->setUser($student); 612 $contexts = $search->get_areas_user_accesses()->usercontexts; 613 $this->assertEquals($frontpageandcourse1, $contexts[$this->forumpostareaid]); 614 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id), 615 $contexts[$this->coursesareaid]); 616 $mockctxs = array($studentctx->id => $studentctx->id, 617 $frontpagectx->id => $frontpagectx->id, $course1ctx->id => $course1ctx->id); 618 $this->assertEquals($mockctxs, $contexts[$mockareaid]); 619 620 // Hide the activity. 621 set_coursemodule_visible($forum2->cmid, 0); 622 $contexts = $search->get_areas_user_accesses()->usercontexts; 623 $this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id), 624 $contexts[$this->forumpostareaid]); 625 626 // Now test course limited searches. 627 set_coursemodule_visible($forum2->cmid, 1); 628 $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student'); 629 $contexts = $search->get_areas_user_accesses()->usercontexts; 630 $allcontexts = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id, 631 $context2->id => $context2->id, $context3->id => $context3->id); 632 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]); 633 $this->assertEquals(array($sitectx->id => $sitectx->id, $course1ctx->id => $course1ctx->id, 634 $course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]); 635 636 $contexts = $search->get_areas_user_accesses(array($course1->id, $course2->id))->usercontexts; 637 $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id, $context3->id => $context3->id); 638 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]); 639 $this->assertEquals(array($course1ctx->id => $course1ctx->id, 640 $course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]); 641 642 $contexts = $search->get_areas_user_accesses(array($course2->id))->usercontexts; 643 $allcontexts = array($context3->id => $context3->id); 644 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]); 645 $this->assertEquals(array($course2ctx->id => $course2ctx->id), $contexts[$this->coursesareaid]); 646 647 $contexts = $search->get_areas_user_accesses(array($course1->id))->usercontexts; 648 $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id); 649 $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]); 650 $this->assertEquals(array($course1ctx->id => $course1ctx->id), $contexts[$this->coursesareaid]); 651 652 // Test context limited search with no course limit. 653 $contexts = $search->get_areas_user_accesses(false, 654 [$frontpageforumcontext->id, $course2ctx->id])->usercontexts; 655 $this->assertEquals([$frontpageforumcontext->id => $frontpageforumcontext->id], 656 $contexts[$this->forumpostareaid]); 657 $this->assertEquals([$course2ctx->id => $course2ctx->id], 658 $contexts[$this->coursesareaid]); 659 660 // Test context limited search with course limit. 661 $contexts = $search->get_areas_user_accesses([$course1->id, $course2->id], 662 [$frontpageforumcontext->id, $course2ctx->id])->usercontexts; 663 $this->assertArrayNotHasKey($this->forumpostareaid, $contexts); 664 $this->assertEquals([$course2ctx->id => $course2ctx->id], 665 $contexts[$this->coursesareaid]); 666 667 // Single context and course. 668 $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts; 669 $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]); 670 $this->assertArrayNotHasKey($this->coursesareaid, $contexts); 671 672 // Enable "Include all visible courses" feature. 673 set_config('searchincludeallcourses', 1); 674 $contexts = $search->get_areas_user_accesses()->usercontexts; 675 $expected = [ 676 $sitectx->id => $sitectx->id, 677 $course1ctx->id => $course1ctx->id, 678 $course2ctx->id => $course2ctx->id, 679 $course3ctx->id => $course3ctx->id 680 ]; 681 // Check that a student has assess to all courses data when "searchincludeallcourses" is enabled. 682 $this->assertEquals($expected, $contexts[$this->coursesareaid]); 683 // But at the same time doesn't have access to activities in the courses that the student can't access. 684 $this->assertFalse(key_exists($context4->id, $contexts[$this->forumpostareaid])); 685 686 // For admins, this is still limited only if we specify the things, so it should be same. 687 $this->setAdminUser(); 688 $contexts = $search->get_areas_user_accesses([$course1->id], [$context1->id])->usercontexts; 689 $this->assertEquals([$context1->id => $context1->id], $contexts[$this->forumpostareaid]); 690 $this->assertArrayNotHasKey($this->coursesareaid, $contexts); 691 } 692 693 /** 694 * Tests the block support in get_search_user_accesses. 695 * 696 * @return void 697 */ 698 public function test_search_user_accesses_blocks() { 699 global $DB; 700 701 $this->resetAfterTest(); 702 $this->setAdminUser(); 703 704 // Create course and add HTML block. 705 $generator = $this->getDataGenerator(); 706 $course1 = $generator->create_course(); 707 $context1 = \context_course::instance($course1->id); 708 $page = new \moodle_page(); 709 $page->set_context($context1); 710 $page->set_course($course1); 711 $page->set_pagelayout('standard'); 712 $page->set_pagetype('course-view'); 713 $page->blocks->load_blocks(); 714 $page->blocks->add_block_at_end_of_default_region('html'); 715 716 // Create another course with HTML blocks only in some weird page or a module page (not 717 // yet supported, so both these blocks will be ignored). 718 $course2 = $generator->create_course(); 719 $context2 = \context_course::instance($course2->id); 720 $page = new \moodle_page(); 721 $page->set_context($context2); 722 $page->set_course($course2); 723 $page->set_pagelayout('standard'); 724 $page->set_pagetype('bogus-page'); 725 $page->blocks->load_blocks(); 726 $page->blocks->add_block_at_end_of_default_region('html'); 727 728 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id)); 729 $forumcontext = context_module::instance($forum->cmid); 730 $page = new \moodle_page(); 731 $page->set_context($forumcontext); 732 $page->set_course($course2); 733 $page->set_pagelayout('standard'); 734 $page->set_pagetype('mod-forum-view'); 735 $page->blocks->load_blocks(); 736 $page->blocks->add_block_at_end_of_default_region('html'); 737 738 // The third course has 2 HTML blocks. 739 $course3 = $generator->create_course(); 740 $context3 = \context_course::instance($course3->id); 741 $page = new \moodle_page(); 742 $page->set_context($context3); 743 $page->set_course($course3); 744 $page->set_pagelayout('standard'); 745 $page->set_pagetype('course-view'); 746 $page->blocks->load_blocks(); 747 $page->blocks->add_block_at_end_of_default_region('html'); 748 $page->blocks->add_block_at_end_of_default_region('html'); 749 750 // Student 1 belongs to all 3 courses. 751 $student1 = $generator->create_user(); 752 $generator->enrol_user($student1->id, $course1->id, 'student'); 753 $generator->enrol_user($student1->id, $course2->id, 'student'); 754 $generator->enrol_user($student1->id, $course3->id, 'student'); 755 756 // Student 2 belongs only to course 2. 757 $student2 = $generator->create_user(); 758 $generator->enrol_user($student2->id, $course2->id, 'student'); 759 760 // And the third student is only in course 3. 761 $student3 = $generator->create_user(); 762 $generator->enrol_user($student3->id, $course3->id, 'student'); 763 764 $search = testable_core_search::instance(); 765 $search->add_core_search_areas(); 766 767 // Admin gets 'true' result to function regardless of blocks. 768 $this->setAdminUser(); 769 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses()); 770 771 // Student 1 gets all 3 block contexts. 772 $this->setUser($student1); 773 $contexts = $search->get_areas_user_accesses()->usercontexts; 774 $this->assertArrayHasKey('block_html-content', $contexts); 775 $this->assertCount(3, $contexts['block_html-content']); 776 777 // Student 2 does not get any blocks. 778 $this->setUser($student2); 779 $contexts = $search->get_areas_user_accesses()->usercontexts; 780 $this->assertArrayNotHasKey('block_html-content', $contexts); 781 782 // Student 3 gets only two of them. 783 $this->setUser($student3); 784 $contexts = $search->get_areas_user_accesses()->usercontexts; 785 $this->assertArrayHasKey('block_html-content', $contexts); 786 $this->assertCount(2, $contexts['block_html-content']); 787 788 // A course limited search for student 1 is the same as the student 3 search. 789 $this->setUser($student1); 790 $limitedcontexts = $search->get_areas_user_accesses([$course3->id])->usercontexts; 791 $this->assertEquals($contexts['block_html-content'], $limitedcontexts['block_html-content']); 792 793 // Get block context ids for the blocks that appear. 794 $blockcontextids = $DB->get_fieldset_sql(' 795 SELECT x.id 796 FROM {block_instances} bi 797 JOIN {context} x ON x.instanceid = bi.id AND x.contextlevel = ? 798 WHERE (parentcontextid = ? OR parentcontextid = ?) 799 AND blockname = ? 800 ORDER BY bi.id', [CONTEXT_BLOCK, $context1->id, $context3->id, 'html']); 801 802 // Context limited search (no course). 803 $contexts = $search->get_areas_user_accesses(false, 804 [$blockcontextids[0], $blockcontextids[2]])->usercontexts; 805 $this->assertCount(2, $contexts['block_html-content']); 806 807 // Context limited search (with course 3). 808 $contexts = $search->get_areas_user_accesses([$course2->id, $course3->id], 809 [$blockcontextids[0], $blockcontextids[2]])->usercontexts; 810 $this->assertCount(1, $contexts['block_html-content']); 811 } 812 813 /** 814 * Tests retrieval of users search areas when limiting to a course the user is not enrolled in 815 */ 816 public function test_search_users_accesses_limit_non_enrolled_course() { 817 global $DB; 818 819 $this->resetAfterTest(); 820 821 $user = $this->getDataGenerator()->create_user(); 822 $this->setUser($user); 823 824 $search = testable_core_search::instance(); 825 $search->add_core_search_areas(); 826 827 $course = $this->getDataGenerator()->create_course(); 828 $context = context_course::instance($course->id); 829 830 // Limit courses to search to only those the user is enrolled in. 831 set_config('searchallavailablecourses', 0); 832 833 $usercontexts = $search->get_areas_user_accesses([$course->id])->usercontexts; 834 $this->assertNotEmpty($usercontexts); 835 $this->assertArrayNotHasKey('core_course-course', $usercontexts); 836 837 // This config ensures the search will also include courses the user can view. 838 set_config('searchallavailablecourses', 1); 839 840 // Allow "Authenticated user" role to view the course without being enrolled in it. 841 $userrole = $DB->get_record('role', ['shortname' => 'user'], '*', MUST_EXIST); 842 role_change_permission($userrole->id, $context, 'moodle/course:view', CAP_ALLOW); 843 844 $usercontexts = $search->get_areas_user_accesses([$course->id])->usercontexts; 845 $this->assertNotEmpty($usercontexts); 846 $this->assertArrayHasKey('core_course-course', $usercontexts); 847 $this->assertEquals($context->id, reset($usercontexts['core_course-course'])); 848 } 849 850 /** 851 * Test get_areas_user_accesses with regard to the 'all available courses' config option. 852 * 853 * @return void 854 */ 855 public function test_search_user_accesses_allavailable() { 856 global $DB, $CFG; 857 858 $this->resetAfterTest(); 859 860 // Front page, including a forum. 861 $frontpage = $DB->get_record('course', array('id' => SITEID)); 862 $forumfront = $this->getDataGenerator()->create_module('forum', array('course' => $frontpage->id)); 863 $forumfrontctx = context_module::instance($forumfront->cmid); 864 865 // Course 1 does not allow guest access. 866 $course1 = $this->getDataGenerator()->create_course((object)array( 867 'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED, 868 'enrol_guest_password_0' => '')); 869 $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); 870 $forum1ctx = context_module::instance($forum1->cmid); 871 872 // Course 2 does not allow guest but is accessible by all users. 873 $course2 = $this->getDataGenerator()->create_course((object)array( 874 'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED, 875 'enrol_guest_password_0' => '')); 876 $course2ctx = context_course::instance($course2->id); 877 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id)); 878 $forum2ctx = context_module::instance($forum2->cmid); 879 assign_capability('moodle/course:view', CAP_ALLOW, $CFG->defaultuserroleid, $course2ctx->id); 880 881 // Course 3 allows guest access without password. 882 $course3 = $this->getDataGenerator()->create_course((object)array( 883 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, 884 'enrol_guest_password_0' => '')); 885 $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id)); 886 $forum3ctx = context_module::instance($forum3->cmid); 887 888 // Student user is enrolled in course 1. 889 $student = $this->getDataGenerator()->create_user(); 890 $this->getDataGenerator()->enrol_user($student->id, $course1->id, 'student'); 891 892 // No access user is just a user with no permissions. 893 $noaccess = $this->getDataGenerator()->create_user(); 894 895 // First test without the all available option. 896 $search = testable_core_search::instance(); 897 898 // Admin user can access everything. 899 $this->setAdminUser(); 900 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses()); 901 902 // No-access user can access only the front page forum. 903 $this->setUser($noaccess); 904 $contexts = $search->get_areas_user_accesses()->usercontexts; 905 $this->assertEquals([$forumfrontctx->id], array_keys($contexts[$this->forumpostareaid])); 906 907 // Student can access the front page forum plus the enrolled one. 908 $this->setUser($student); 909 $contexts = $search->get_areas_user_accesses()->usercontexts; 910 $this->assertEquals([$forum1ctx->id, $forumfrontctx->id], 911 array_keys($contexts[$this->forumpostareaid])); 912 913 // Now turn on the all available option. 914 set_config('searchallavailablecourses', 1); 915 916 // Admin user can access everything. 917 $this->setAdminUser(); 918 $this->assertEquals((object)['everything' => true], $search->get_areas_user_accesses()); 919 920 // No-access user can access the front page forum and course 2, 3. 921 $this->setUser($noaccess); 922 $contexts = $search->get_areas_user_accesses()->usercontexts; 923 $this->assertEquals([$forum2ctx->id, $forum3ctx->id, $forumfrontctx->id], 924 array_keys($contexts[$this->forumpostareaid])); 925 926 // Student can access the front page forum plus the enrolled one plus courses 2, 3. 927 $this->setUser($student); 928 $contexts = $search->get_areas_user_accesses()->usercontexts; 929 $this->assertEquals([$forum1ctx->id, $forum2ctx->id, $forum3ctx->id, $forumfrontctx->id], 930 array_keys($contexts[$this->forumpostareaid])); 931 } 932 933 /** 934 * Tests group-related aspects of the get_areas_user_accesses function. 935 */ 936 public function test_search_user_accesses_groups() { 937 global $DB; 938 939 $this->resetAfterTest(); 940 $this->setAdminUser(); 941 942 // Create 2 courses each with 2 groups and 2 forums (separate/visible groups). 943 $generator = $this->getDataGenerator(); 944 $course1 = $generator->create_course(); 945 $course2 = $generator->create_course(); 946 $group1 = $generator->create_group(['courseid' => $course1->id]); 947 $group2 = $generator->create_group(['courseid' => $course1->id]); 948 $group3 = $generator->create_group(['courseid' => $course2->id]); 949 $group4 = $generator->create_group(['courseid' => $course2->id]); 950 $forum1s = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => SEPARATEGROUPS]); 951 $id1s = context_module::instance($forum1s->cmid)->id; 952 $forum1v = $generator->create_module('forum', ['course' => $course1->id, 'groupmode' => VISIBLEGROUPS]); 953 $id1v = context_module::instance($forum1v->cmid)->id; 954 $forum2s = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => SEPARATEGROUPS]); 955 $id2s = context_module::instance($forum2s->cmid)->id; 956 $forum2n = $generator->create_module('forum', ['course' => $course2->id, 'groupmode' => NOGROUPS]); 957 $id2n = context_module::instance($forum2n->cmid)->id; 958 959 // Get search instance. 960 $search = testable_core_search::instance(); 961 $search->add_core_search_areas(); 962 963 // User 1 is a manager in one course and a student in the other one. They belong to 964 // all of the groups 1, 2, 3, and 4. 965 $user1 = $generator->create_user(); 966 $generator->enrol_user($user1->id, $course1->id, 'manager'); 967 $generator->enrol_user($user1->id, $course2->id, 'student'); 968 groups_add_member($group1, $user1); 969 groups_add_member($group2, $user1); 970 groups_add_member($group3, $user1); 971 groups_add_member($group4, $user1); 972 973 $this->setUser($user1); 974 $accessinfo = $search->get_areas_user_accesses(); 975 $contexts = $accessinfo->usercontexts; 976 977 // Double-check all the forum contexts. 978 $postcontexts = $contexts['mod_forum-post']; 979 sort($postcontexts); 980 $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts); 981 982 // Only the context in the second course (no accessallgroups) is restricted. 983 $restrictedcontexts = $accessinfo->separategroupscontexts; 984 sort($restrictedcontexts); 985 $this->assertEquals([$id2s], $restrictedcontexts); 986 987 // Only the groups from the second course (no accessallgroups) are included. 988 $groupids = $accessinfo->usergroups; 989 sort($groupids); 990 $this->assertEquals([$group3->id, $group4->id], $groupids); 991 992 // User 2 is a student in each course and belongs to groups 2 and 4. 993 $user2 = $generator->create_user(); 994 $generator->enrol_user($user2->id, $course1->id, 'student'); 995 $generator->enrol_user($user2->id, $course2->id, 'student'); 996 groups_add_member($group2, $user2); 997 groups_add_member($group4, $user2); 998 999 $this->setUser($user2); 1000 $accessinfo = $search->get_areas_user_accesses(); 1001 $contexts = $accessinfo->usercontexts; 1002 1003 // Double-check all the forum contexts. 1004 $postcontexts = $contexts['mod_forum-post']; 1005 sort($postcontexts); 1006 $this->assertEquals([$id1s, $id1v, $id2s, $id2n], $postcontexts); 1007 1008 // Both separate groups forums are restricted. 1009 $restrictedcontexts = $accessinfo->separategroupscontexts; 1010 sort($restrictedcontexts); 1011 $this->assertEquals([$id1s, $id2s], $restrictedcontexts); 1012 1013 // Groups from both courses are included. 1014 $groupids = $accessinfo->usergroups; 1015 sort($groupids); 1016 $this->assertEquals([$group2->id, $group4->id], $groupids); 1017 1018 // User 3 is a manager at system level. 1019 $user3 = $generator->create_user(); 1020 role_assign($DB->get_field('role', 'id', ['shortname' => 'manager'], MUST_EXIST), $user3->id, 1021 \context_system::instance()); 1022 1023 $this->setUser($user3); 1024 $accessinfo = $search->get_areas_user_accesses(); 1025 1026 // Nothing is restricted and no groups are relevant. 1027 $this->assertEquals([], $accessinfo->separategroupscontexts); 1028 $this->assertEquals([], $accessinfo->usergroups); 1029 } 1030 1031 /** 1032 * test_is_search_area 1033 * 1034 * @return void 1035 */ 1036 public function test_is_search_area() { 1037 1038 $this->assertFalse(testable_core_search::is_search_area('\asd\asd')); 1039 $this->assertFalse(testable_core_search::is_search_area('\mod_forum\search\posta')); 1040 $this->assertFalse(testable_core_search::is_search_area('\core_search\base_mod')); 1041 $this->assertTrue(testable_core_search::is_search_area('\mod_forum\search\post')); 1042 $this->assertTrue(testable_core_search::is_search_area('\\mod_forum\\search\\post')); 1043 $this->assertTrue(testable_core_search::is_search_area('mod_forum\\search\\post')); 1044 } 1045 1046 /** 1047 * Tests the request_index function used for reindexing certain contexts. This only tests 1048 * adding things to the request list, it doesn't test that they are actually indexed by the 1049 * scheduled task. 1050 */ 1051 public function test_request_index() { 1052 global $DB; 1053 1054 $this->resetAfterTest(); 1055 1056 $course1 = $this->getDataGenerator()->create_course(); 1057 $course1ctx = context_course::instance($course1->id); 1058 $course2 = $this->getDataGenerator()->create_course(); 1059 $course2ctx = context_course::instance($course2->id); 1060 $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]); 1061 $forum1ctx = context_module::instance($forum1->cmid); 1062 $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]); 1063 $forum2ctx = context_module::instance($forum2->cmid); 1064 1065 // Initially no requests. 1066 $this->assertEquals(0, $DB->count_records('search_index_requests')); 1067 1068 // Request update for course 1, all areas. 1069 \core_search\manager::request_index($course1ctx); 1070 1071 // Check all details of entry. 1072 $results = array_values($DB->get_records('search_index_requests')); 1073 $this->assertCount(1, $results); 1074 $this->assertEquals($course1ctx->id, $results[0]->contextid); 1075 $this->assertEquals('', $results[0]->searcharea); 1076 $now = time(); 1077 $this->assertLessThanOrEqual($now, $results[0]->timerequested); 1078 $this->assertGreaterThan($now - 10, $results[0]->timerequested); 1079 $this->assertEquals('', $results[0]->partialarea); 1080 $this->assertEquals(0, $results[0]->partialtime); 1081 1082 // Request forum 1, all areas; not added as covered by course 1. 1083 \core_search\manager::request_index($forum1ctx); 1084 $this->assertEquals(1, $DB->count_records('search_index_requests')); 1085 1086 // Request forum 1, specific area; not added as covered by course 1 all areas. 1087 \core_search\manager::request_index($forum1ctx, 'forum-post'); 1088 $this->assertEquals(1, $DB->count_records('search_index_requests')); 1089 1090 // Request course 1 again, specific area; not added as covered by all areas. 1091 \core_search\manager::request_index($course1ctx, 'forum-post'); 1092 $this->assertEquals(1, $DB->count_records('search_index_requests')); 1093 1094 // Request course 1 again, all areas; not needed as covered already. 1095 \core_search\manager::request_index($course1ctx); 1096 $this->assertEquals(1, $DB->count_records('search_index_requests')); 1097 1098 // Request course 2, specific area. 1099 \core_search\manager::request_index($course2ctx, 'label-activity'); 1100 // Note: I'm ordering by ID for convenience - this is dangerous in real code (see MDL-43447) 1101 // but in a unit test it shouldn't matter as nobody is using clustered databases for unit 1102 // test. 1103 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1104 $this->assertCount(2, $results); 1105 $this->assertEquals($course1ctx->id, $results[0]->contextid); 1106 $this->assertEquals($course2ctx->id, $results[1]->contextid); 1107 $this->assertEquals('label-activity', $results[1]->searcharea); 1108 1109 // Request forum 2, same specific area; not added. 1110 \core_search\manager::request_index($forum2ctx, 'label-activity'); 1111 $this->assertEquals(2, $DB->count_records('search_index_requests')); 1112 1113 // Request forum 2, different specific area; added. 1114 \core_search\manager::request_index($forum2ctx, 'forum-post'); 1115 $this->assertEquals(3, $DB->count_records('search_index_requests')); 1116 1117 // Request forum 2, all areas; also added. (Note: This could obviously remove the previous 1118 // one, but for simplicity, I didn't make it do that; also it could perhaps cause problems 1119 // if we had already begun processing the previous entry.) 1120 \core_search\manager::request_index($forum2ctx); 1121 $this->assertEquals(4, $DB->count_records('search_index_requests')); 1122 1123 // Clear queue and do tests relating to priority. 1124 $DB->delete_records('search_index_requests'); 1125 1126 // Request forum 1, specific area, priority 100. 1127 \core_search\manager::request_index($forum1ctx, 'forum-post', 100); 1128 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1129 $this->assertCount(1, $results); 1130 $this->assertEquals(100, $results[0]->indexpriority); 1131 1132 // Request forum 1, same area, lower priority; no change. 1133 \core_search\manager::request_index($forum1ctx, 'forum-post', 99); 1134 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1135 $this->assertCount(1, $results); 1136 $this->assertEquals(100, $results[0]->indexpriority); 1137 1138 // Request forum 1, same area, higher priority; priority stored changes. 1139 \core_search\manager::request_index($forum1ctx, 'forum-post', 101); 1140 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1141 $this->assertCount(1, $results); 1142 $this->assertEquals(101, $results[0]->indexpriority); 1143 1144 // Request forum 1, all areas, lower priority; adds second entry. 1145 \core_search\manager::request_index($forum1ctx, '', 100); 1146 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1147 $this->assertCount(2, $results); 1148 $this->assertEquals(100, $results[1]->indexpriority); 1149 1150 // Request course 1, all areas, lower priority; adds third entry. 1151 \core_search\manager::request_index($course1ctx, '', 99); 1152 $results = array_values($DB->get_records('search_index_requests', null, 'id')); 1153 $this->assertCount(3, $results); 1154 $this->assertEquals(99, $results[2]->indexpriority); 1155 } 1156 1157 /** 1158 * Tests the process_index_requests function. 1159 */ 1160 public function test_process_index_requests() { 1161 global $DB; 1162 1163 $this->resetAfterTest(); 1164 1165 $search = testable_core_search::instance(); 1166 1167 // When there are no index requests, nothing gets logged. 1168 $progress = new progress_trace_buffer(new text_progress_trace(), false); 1169 $search->process_index_requests(0.0, $progress); 1170 $out = $progress->get_buffer(); 1171 $progress->reset_buffer(); 1172 $this->assertEquals('', $out); 1173 1174 // Set up the course with 3 forums. 1175 $generator = $this->getDataGenerator(); 1176 $course = $generator->create_course(['fullname' => 'TCourse']); 1177 $forum1 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum1']); 1178 $forum2 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum2']); 1179 $forum3 = $generator->create_module('forum', ['course' => $course->id, 'name' => 'TForum3']); 1180 1181 // Hack the forums so they have different creation times. 1182 $now = time(); 1183 $DB->set_field('forum', 'timemodified', $now - 30, ['id' => $forum1->id]); 1184 $DB->set_field('forum', 'timemodified', $now - 20, ['id' => $forum2->id]); 1185 $DB->set_field('forum', 'timemodified', $now - 10, ['id' => $forum3->id]); 1186 $forum2time = $now - 20; 1187 1188 // Make 2 index requests. 1189 testable_core_search::fake_current_time($now - 3); 1190 $search::request_index(context_course::instance($course->id), 'mod_label-activity'); 1191 testable_core_search::fake_current_time($now - 2); 1192 $search::request_index(context_module::instance($forum1->cmid)); 1193 1194 // Run with no time limit. 1195 $search->process_index_requests(0.0, $progress); 1196 $out = $progress->get_buffer(); 1197 $progress->reset_buffer(); 1198 1199 // Check that it's done both areas. 1200 $this->assertContains( 1201 'Indexing requested context: Course: TCourse (search area: mod_label-activity)', 1202 $out); 1203 $this->assertContains( 1204 'Completed requested context: Course: TCourse (search area: mod_label-activity)', 1205 $out); 1206 $this->assertContains('Indexing requested context: Forum: TForum1', $out); 1207 $this->assertContains('Completed requested context: Forum: TForum1', $out); 1208 1209 // Check the requests database table is now empty. 1210 $this->assertEquals(0, $DB->count_records('search_index_requests')); 1211 1212 // Request indexing the course a couple of times. 1213 testable_core_search::fake_current_time($now - 3); 1214 $search::request_index(context_course::instance($course->id), 'mod_forum-activity'); 1215 testable_core_search::fake_current_time($now - 2); 1216 $search::request_index(context_course::instance($course->id), 'mod_forum-post'); 1217 1218 // Do the processing again with a time limit and indexing delay. The time limit is too 1219 // small; because of the way the logic works, this means it will index 2 activities. 1220 $search->get_engine()->set_add_delay(0.2); 1221 $search->process_index_requests(0.1, $progress); 1222 $out = $progress->get_buffer(); 1223 $progress->reset_buffer(); 1224 1225 // Confirm the right wrapper information was logged. 1226 $this->assertContains( 1227 'Indexing requested context: Course: TCourse (search area: mod_forum-activity)', 1228 $out); 1229 $this->assertContains('Stopping indexing due to time limit', $out); 1230 $this->assertContains( 1231 'Ending requested context: Course: TCourse (search area: mod_forum-activity)', 1232 $out); 1233 1234 // Check the database table has been updated with progress. 1235 $records = array_values($DB->get_records('search_index_requests', null, 'searcharea')); 1236 $this->assertEquals('mod_forum-activity', $records[0]->partialarea); 1237 $this->assertEquals($forum2time, $records[0]->partialtime); 1238 1239 // Run again and confirm it now finishes. 1240 $search->process_index_requests(2.0, $progress); 1241 $out = $progress->get_buffer(); 1242 $progress->reset_buffer(); 1243 $this->assertContains( 1244 'Completed requested context: Course: TCourse (search area: mod_forum-activity)', 1245 $out); 1246 $this->assertContains( 1247 'Completed requested context: Course: TCourse (search area: mod_forum-post)', 1248 $out); 1249 1250 // Confirm table is now empty. 1251 $this->assertEquals(0, $DB->count_records('search_index_requests')); 1252 1253 // Make 2 requests - first one is low priority. 1254 testable_core_search::fake_current_time($now - 3); 1255 $search::request_index(context_module::instance($forum1->cmid), 'mod_forum-activity', 1256 \core_search\manager::INDEX_PRIORITY_REINDEXING); 1257 testable_core_search::fake_current_time($now - 2); 1258 $search::request_index(context_module::instance($forum2->cmid), 'mod_forum-activity'); 1259 1260 // Process with short time limit and confirm it does the second one first. 1261 $search->process_index_requests(0.1, $progress); 1262 $out = $progress->get_buffer(); 1263 $progress->reset_buffer(); 1264 $this->assertContains( 1265 'Completed requested context: Forum: TForum2 (search area: mod_forum-activity)', 1266 $out); 1267 $search->process_index_requests(0.1, $progress); 1268 $out = $progress->get_buffer(); 1269 $progress->reset_buffer(); 1270 $this->assertContains( 1271 'Completed requested context: Forum: TForum1 (search area: mod_forum-activity)', 1272 $out); 1273 1274 // Make a request for a course context... 1275 $course = $generator->create_course(); 1276 $context = context_course::instance($course->id); 1277 $search::request_index($context); 1278 1279 // ...but then delete it (note: delete_course spews output, so we throw it away). 1280 ob_start(); 1281 delete_course($course); 1282 ob_end_clean(); 1283 1284 // Process requests - it should only note the deleted context. 1285 $search->process_index_requests(10, $progress); 1286 $out = $progress->get_buffer(); 1287 $progress->reset_buffer(); 1288 $this->assertContains('Skipped deleted context: ' . $context->id, $out); 1289 1290 // Confirm request table is now empty. 1291 $this->assertEquals(0, $DB->count_records('search_index_requests')); 1292 } 1293 1294 /** 1295 * Test search area categories. 1296 */ 1297 public function test_get_search_area_categories() { 1298 $categories = \core_search\manager::get_search_area_categories(); 1299 1300 $this->assertTrue(is_array($categories)); 1301 $this->assertTrue(count($categories) >= 4); // We always should have 4 core categories. 1302 $this->assertArrayHasKey('core-all', $categories); 1303 $this->assertArrayHasKey('core-course-content', $categories); 1304 $this->assertArrayHasKey('core-courses', $categories); 1305 $this->assertArrayHasKey('core-users', $categories); 1306 1307 foreach ($categories as $category) { 1308 $this->assertInstanceOf('\core_search\area_category', $category); 1309 } 1310 } 1311 1312 /** 1313 * Test that we can find out if search area categories functionality is enabled. 1314 */ 1315 public function test_is_search_area_categories_enabled() { 1316 $this->resetAfterTest(); 1317 1318 $this->assertFalse(\core_search\manager::is_search_area_categories_enabled()); 1319 set_config('searchenablecategories', 1); 1320 $this->assertTrue(\core_search\manager::is_search_area_categories_enabled()); 1321 set_config('searchenablecategories', 0); 1322 $this->assertFalse(\core_search\manager::is_search_area_categories_enabled()); 1323 } 1324 1325 /** 1326 * Test that we can find out if hiding all results category is enabled. 1327 */ 1328 public function test_should_hide_all_results_category() { 1329 $this->resetAfterTest(); 1330 1331 $this->assertEquals(0, \core_search\manager::should_hide_all_results_category()); 1332 set_config('searchhideallcategory', 1); 1333 $this->assertEquals(1, \core_search\manager::should_hide_all_results_category()); 1334 set_config('searchhideallcategory', 0); 1335 $this->assertEquals(0, \core_search\manager::should_hide_all_results_category()); 1336 } 1337 1338 /** 1339 * Test that we can get default search category name. 1340 */ 1341 public function test_get_default_area_category_name() { 1342 $this->resetAfterTest(); 1343 1344 $expected = 'core-all'; 1345 $this->assertEquals($expected, \core_search\manager::get_default_area_category_name()); 1346 1347 set_config('searchhideallcategory', 1); 1348 $expected = 'core-course-content'; 1349 $this->assertEquals($expected, \core_search\manager::get_default_area_category_name()); 1350 1351 set_config('searchhideallcategory', 0); 1352 $expected = 'core-all'; 1353 $this->assertEquals($expected, \core_search\manager::get_default_area_category_name()); 1354 } 1355 1356 /** 1357 * Test that we can get correct search area category by its name. 1358 */ 1359 public function test_get_search_area_category_by_name() { 1360 $this->resetAfterTest(); 1361 1362 $testcategory = \core_search\manager::get_search_area_category_by_name('test_random_name'); 1363 $this->assertEquals('core-all', $testcategory->get_name()); 1364 1365 $testcategory = \core_search\manager::get_search_area_category_by_name('core-courses'); 1366 $this->assertEquals('core-courses', $testcategory->get_name()); 1367 1368 set_config('searchhideallcategory', 1); 1369 $testcategory = \core_search\manager::get_search_area_category_by_name('test_random_name'); 1370 $this->assertEquals('core-course-content', $testcategory->get_name()); 1371 } 1372 1373 /** 1374 * Test that we can check that "Include all visible courses" feature is enabled. 1375 */ 1376 public function test_include_all_courses_enabled() { 1377 $this->resetAfterTest(); 1378 $this->assertFalse(\core_search\manager::include_all_courses()); 1379 set_config('searchincludeallcourses', 1); 1380 $this->assertTrue(\core_search\manager::include_all_courses()); 1381 } 1382 1383 /** 1384 * Test that we can correctly build a list of courses for a course filter for the search results. 1385 */ 1386 public function test_build_limitcourseids() { 1387 global $USER; 1388 1389 $this->resetAfterTest(); 1390 $this->setAdminUser(); 1391 1392 $course1 = $this->getDataGenerator()->create_course(); 1393 $course2 = $this->getDataGenerator()->create_course(); 1394 $course3 = $this->getDataGenerator()->create_course(); 1395 $course4 = $this->getDataGenerator()->create_course(); 1396 1397 $this->getDataGenerator()->enrol_user($USER->id, $course1->id); 1398 $this->getDataGenerator()->enrol_user($USER->id, $course3->id); 1399 1400 $search = testable_core_search::instance(); 1401 1402 $formdata = new stdClass(); 1403 $formdata->courseids = []; 1404 $formdata->mycoursesonly = false; 1405 $limitcourseids = $search->build_limitcourseids($formdata); 1406 $this->assertEquals(false, $limitcourseids); 1407 1408 $formdata->courseids = []; 1409 $formdata->mycoursesonly = true; 1410 $limitcourseids = $search->build_limitcourseids($formdata); 1411 $this->assertEquals([$course1->id, $course3->id], $limitcourseids); 1412 1413 $formdata->courseids = [$course1->id, $course2->id, $course4->id]; 1414 $formdata->mycoursesonly = false; 1415 $limitcourseids = $search->build_limitcourseids($formdata); 1416 $this->assertEquals([$course1->id, $course2->id, $course4->id], $limitcourseids); 1417 1418 $formdata->courseids = [$course1->id, $course2->id, $course4->id]; 1419 $formdata->mycoursesonly = true; 1420 $limitcourseids = $search->build_limitcourseids($formdata); 1421 $this->assertEquals([$course1->id], $limitcourseids); 1422 } 1423 1424 /** 1425 * Test data for test_parse_areaid test fucntion. 1426 * 1427 * @return array 1428 */ 1429 public function parse_search_area_id_data_provider() { 1430 return [ 1431 ['mod_book-chapter', ['mod_book', 'search_chapter']], 1432 ['mod_customcert-activity', ['mod_customcert', 'search_activity']], 1433 ['core_course-mycourse', ['core_search', 'core_course_mycourse']], 1434 ]; 1435 } 1436 1437 /** 1438 * Test that manager class can parse area id correctly. 1439 * @dataProvider parse_search_area_id_data_provider 1440 * 1441 * @param string $areaid Area id to parse. 1442 * @param array $expected Expected result of parsing. 1443 */ 1444 public function test_parse_search_area_id($areaid, $expected) { 1445 $this->assertEquals($expected, \core_search\manager::parse_areaid($areaid)); 1446 } 1447 1448 /** 1449 * Test that manager class will throw an exception when parsing an invalid area id. 1450 */ 1451 public function test_parse_invalid_search_area_id() { 1452 $this->expectException('coding_exception'); 1453 $this->expectExceptionMessage('Trying to parse invalid search area id invalid_area'); 1454 \core_search\manager::parse_areaid('invalid_area'); 1455 } 1456 1457 /** 1458 * Test getting a coding exception when trying to lean up existing search area. 1459 */ 1460 public function test_cleaning_up_existing_search_area() { 1461 $expectedmessage = "Area mod_assign-activity exists. Please use appropriate search area class to manipulate the data."; 1462 1463 $this->expectException('coding_exception'); 1464 $this->expectExceptionMessage($expectedmessage); 1465 1466 \core_search\manager::clean_up_non_existing_area('mod_assign-activity'); 1467 } 1468 1469 /** 1470 * Test clean up of non existing search area. 1471 */ 1472 public function test_clean_up_non_existing_search_area() { 1473 global $DB; 1474 1475 $this->resetAfterTest(); 1476 1477 $areaid = 'core_course-mycourse'; 1478 $plugin = 'core_search'; 1479 1480 // Get all settings to DB and make sure they are there. 1481 foreach (\core_search\base::get_settingnames() as $settingname) { 1482 $record = new stdClass(); 1483 $record->plugin = $plugin; 1484 $record->name = 'core_course_mycourse'. $settingname; 1485 $record->value = 'test'; 1486 1487 $DB->insert_record('config_plugins', $record); 1488 $this->assertTrue($DB->record_exists('config_plugins', ['plugin' => $plugin, 'name' => $record->name])); 1489 } 1490 1491 // Clean up the search area. 1492 \core_search\manager::clean_up_non_existing_area($areaid); 1493 1494 // Check that records are not in DB after we ran clean up. 1495 foreach (\core_search\base::get_settingnames() as $settingname) { 1496 $plugin = 'core_search'; 1497 $name = 'core_course_mycourse'. $settingname; 1498 $this->assertFalse($DB->record_exists('config_plugins', ['plugin' => $plugin, 'name' => $name])); 1499 } 1500 } 1501 1502 /** 1503 * Tests the context_deleted, course_deleting_start, and course_deleting_finish methods. 1504 */ 1505 public function test_context_deletion() { 1506 $this->resetAfterTest(); 1507 1508 // Create one course with 4 activities, and another with one. 1509 $generator = $this->getDataGenerator(); 1510 $course1 = $generator->create_course(); 1511 $page1 = $generator->create_module('page', ['course' => $course1]); 1512 $context1 = \context_module::instance($page1->cmid); 1513 $page2 = $generator->create_module('page', ['course' => $course1]); 1514 $page3 = $generator->create_module('page', ['course' => $course1]); 1515 $context3 = \context_module::instance($page3->cmid); 1516 $page4 = $generator->create_module('page', ['course' => $course1]); 1517 $course2 = $generator->create_course(); 1518 $page5 = $generator->create_module('page', ['course' => $course2]); 1519 $context5 = \context_module::instance($page5->cmid); 1520 1521 // Also create a user. 1522 $user = $generator->create_user(); 1523 $usercontext = \context_user::instance($user->id); 1524 1525 $search = testable_core_search::instance(); 1526 1527 // Delete two of the pages individually. 1528 course_delete_module($page1->cmid); 1529 course_delete_module($page3->cmid); 1530 1531 // Delete the course with another two. 1532 delete_course($course1->id, false); 1533 1534 // Delete the user. 1535 delete_user($user); 1536 1537 // Delete the page from the other course. 1538 course_delete_module($page5->cmid); 1539 1540 // It should have deleted the contexts and the course, but not the contexts in the course. 1541 $expected = [ 1542 ['context', $context1->id], 1543 ['context', $context3->id], 1544 ['course', $course1->id], 1545 ['context', $usercontext->id], 1546 ['context', $context5->id] 1547 ]; 1548 $this->assertEquals($expected, $search->get_engine()->get_and_clear_deletes()); 1549 } 1550 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body