Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [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  }