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