Search moodle.org's
Developer Documentation

See Release Notes

  • 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 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  /**
  18   * Glossary lib tests.
  19   *
  20   * @package    mod_glossary
  21   * @copyright  2015 Frédéric Massart - FMCorz.net
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace mod_glossary;
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/mod/glossary/lib.php');
  30  require_once($CFG->dirroot . '/mod/glossary/locallib.php');
  31  
  32  /**
  33   * Glossary lib testcase.
  34   *
  35   * @package    mod_glossary
  36   * @copyright  2015 Frédéric Massart - FMCorz.net
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class lib_test extends \advanced_testcase {
  40  
  41      public function test_glossary_view() {
  42          global $CFG;
  43          $origcompletion = $CFG->enablecompletion;
  44          $CFG->enablecompletion = true;
  45          $this->resetAfterTest(true);
  46  
  47          // Generate all the things.
  48          $c1 = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  49          $g1 = $this->getDataGenerator()->create_module('glossary', array(
  50              'course' => $c1->id,
  51              'completion' => COMPLETION_TRACKING_AUTOMATIC,
  52              'completionview' => 1
  53          ));
  54          $g2 = $this->getDataGenerator()->create_module('glossary', array(
  55              'course' => $c1->id,
  56              'completion' => COMPLETION_TRACKING_AUTOMATIC,
  57              'completionview' => 1
  58          ));
  59          $u1 = $this->getDataGenerator()->create_user();
  60          $this->getDataGenerator()->enrol_user($u1->id, $c1->id);
  61          $modinfo = \course_modinfo::instance($c1->id);
  62          $cm1 = $modinfo->get_cm($g1->cmid);
  63          $cm2 = $modinfo->get_cm($g2->cmid);
  64          $ctx1 = $cm1->context;
  65          $completion = new \completion_info($c1);
  66  
  67          $this->setUser($u1);
  68  
  69          // Confirm what we've set up.
  70          $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed);
  71          $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate);
  72          $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed);
  73          $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate);
  74  
  75          // Simulate the view call.
  76          $sink = $this->redirectEvents();
  77          glossary_view($g1, $c1, $cm1, $ctx1, 'letter');
  78          $events = $sink->get_events();
  79  
  80          // Assertions.
  81          $this->assertCount(3, $events);
  82          $this->assertEquals('\core\event\course_module_completion_updated', $events[0]->eventname);
  83          $this->assertEquals('\core\event\course_module_completion_updated', $events[1]->eventname);
  84          $this->assertEquals('\mod_glossary\event\course_module_viewed', $events[2]->eventname);
  85          $this->assertEquals($g1->id, $events[2]->objectid);
  86          $this->assertEquals('letter', $events[2]->other['mode']);
  87          $this->assertEquals(COMPLETION_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed);
  88          $this->assertEquals(COMPLETION_COMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate);
  89          $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed);
  90          $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate);
  91  
  92          // Tear down.
  93          $sink->close();
  94          $CFG->enablecompletion = $origcompletion;
  95      }
  96  
  97      public function test_glossary_entry_view() {
  98          $this->resetAfterTest(true);
  99  
 100          // Generate all the things.
 101          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 102          $c1 = $this->getDataGenerator()->create_course();
 103          $g1 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id));
 104          $e1 = $gg->create_content($g1);
 105          $u1 = $this->getDataGenerator()->create_user();
 106          $ctx = \context_module::instance($g1->cmid);
 107          $this->getDataGenerator()->enrol_user($u1->id, $c1->id);
 108  
 109          // Assertions.
 110          $sink = $this->redirectEvents();
 111          glossary_entry_view($e1, $ctx);
 112          $events = $sink->get_events();
 113          $this->assertCount(1, $events);
 114          $this->assertEquals('\mod_glossary\event\entry_viewed', $events[0]->eventname);
 115          $this->assertEquals($e1->id, $events[0]->objectid);
 116          $sink->close();
 117      }
 118  
 119      public function test_glossary_core_calendar_provide_event_action() {
 120          $this->resetAfterTest();
 121          $this->setAdminUser();
 122  
 123          // Create the activity.
 124          $course = $this->getDataGenerator()->create_course();
 125          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 126  
 127          // Create a calendar event.
 128          $event = $this->create_action_event($course->id, $glossary->id,
 129              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 130  
 131          // Create an action factory.
 132          $factory = new \core_calendar\action_factory();
 133  
 134          // Decorate action event.
 135          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
 136  
 137          // Confirm the event was decorated.
 138          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 139          $this->assertEquals(get_string('view'), $actionevent->get_name());
 140          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 141          $this->assertEquals(1, $actionevent->get_item_count());
 142          $this->assertTrue($actionevent->is_actionable());
 143      }
 144  
 145      public function test_glossary_core_calendar_provide_event_action_as_non_user() {
 146          global $CFG;
 147  
 148          $this->resetAfterTest();
 149          $this->setAdminUser();
 150  
 151          // Create the activity.
 152          $course = $this->getDataGenerator()->create_course();
 153          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 154  
 155          // Create a calendar event.
 156          $event = $this->create_action_event($course->id, $glossary->id,
 157                  \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 158  
 159          // Now log out.
 160          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 161          $this->setUser();
 162  
 163          // Create an action factory.
 164          $factory = new \core_calendar\action_factory();
 165  
 166          // Decorate action event for the student.
 167          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
 168  
 169          // Confirm the event is not shown at all.
 170          $this->assertNull($actionevent);
 171      }
 172  
 173      public function test_glossary_core_calendar_provide_event_action_for_user() {
 174          global $CFG;
 175  
 176          $this->resetAfterTest();
 177          $this->setAdminUser();
 178  
 179          // Create a course.
 180          $course = $this->getDataGenerator()->create_course();
 181  
 182          // Create a student.
 183          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 184  
 185          // Create the activity.
 186          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 187  
 188          // Create a calendar event.
 189          $event = $this->create_action_event($course->id, $glossary->id,
 190                  \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 191  
 192          // Now log out.
 193          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 194          $this->setUser();
 195  
 196          // Create an action factory.
 197          $factory = new \core_calendar\action_factory();
 198  
 199          // Decorate action event for the student.
 200          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
 201  
 202          // Confirm the event was decorated.
 203          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 204          $this->assertEquals(get_string('view'), $actionevent->get_name());
 205          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 206          $this->assertEquals(1, $actionevent->get_item_count());
 207          $this->assertTrue($actionevent->is_actionable());
 208      }
 209  
 210      public function test_glossary_core_calendar_provide_event_action_in_hidden_section() {
 211          $this->resetAfterTest();
 212          $this->setAdminUser();
 213  
 214          // Create a course.
 215          $course = $this->getDataGenerator()->create_course();
 216  
 217          // Create a student.
 218          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 219  
 220          // Create the activity.
 221          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 222  
 223          // Create a calendar event.
 224          $event = $this->create_action_event($course->id, $glossary->id,
 225                  \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 226  
 227          // Set sections 0 as hidden.
 228          set_section_visible($course->id, 0, 0);
 229  
 230          // Create an action factory.
 231          $factory = new \core_calendar\action_factory();
 232  
 233          // Decorate action event for the student.
 234          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
 235  
 236          // Confirm the event is not shown at all.
 237          $this->assertNull($actionevent);
 238      }
 239  
 240      public function test_glossary_core_calendar_provide_event_action_already_completed() {
 241          global $CFG;
 242  
 243          $this->resetAfterTest();
 244          $this->setAdminUser();
 245  
 246          $CFG->enablecompletion = 1;
 247  
 248          // Create the activity.
 249          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 250          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id),
 251              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 252  
 253          // Get some additional data.
 254          $cm = get_coursemodule_from_instance('glossary', $glossary->id);
 255  
 256          // Create a calendar event.
 257          $event = $this->create_action_event($course->id, $glossary->id,
 258              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 259  
 260          // Mark the activity as completed.
 261          $completion = new \completion_info($course);
 262          $completion->set_module_viewed($cm);
 263  
 264          // Create an action factory.
 265          $factory = new \core_calendar\action_factory();
 266  
 267          // Decorate action event.
 268          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
 269  
 270          // Ensure result was null.
 271          $this->assertNull($actionevent);
 272      }
 273  
 274      public function test_glossary_core_calendar_provide_event_action_already_completed_for_user() {
 275          global $CFG;
 276  
 277          $this->resetAfterTest();
 278          $this->setAdminUser();
 279  
 280          $CFG->enablecompletion = 1;
 281  
 282          // Create a course.
 283          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 284  
 285          // Create a student.
 286          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 287  
 288          // Create the activity.
 289          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id),
 290                  array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 291  
 292          // Get some additional data.
 293          $cm = get_coursemodule_from_instance('glossary', $glossary->id);
 294  
 295          // Create a calendar event.
 296          $event = $this->create_action_event($course->id, $glossary->id,
 297                  \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 298  
 299          // Mark the activity as completed for the user.
 300          $completion = new \completion_info($course);
 301          $completion->set_module_viewed($cm, $student->id);
 302  
 303          // Create an action factory.
 304          $factory = new \core_calendar\action_factory();
 305  
 306          // Decorate action event.
 307          $actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
 308  
 309          // Ensure result was null.
 310          $this->assertNull($actionevent);
 311      }
 312  
 313      /**
 314       * Creates an action event.
 315       *
 316       * @param int $courseid The course id.
 317       * @param int $instanceid The instance id.
 318       * @param string $eventtype The event type.
 319       * @return bool|calendar_event
 320       */
 321      private function create_action_event($courseid, $instanceid, $eventtype) {
 322          $event = new \stdClass();
 323          $event->name = 'Calendar event';
 324          $event->modulename  = 'glossary';
 325          $event->courseid = $courseid;
 326          $event->instance = $instanceid;
 327          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 328          $event->eventtype = $eventtype;
 329          $event->timestart = time();
 330  
 331          return \calendar_event::create($event);
 332      }
 333  
 334      /**
 335       * Test the callback responsible for returning the completion rule descriptions.
 336       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 337       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 338       */
 339      public function test_mod_glossary_completion_get_active_rule_descriptions() {
 340          $this->resetAfterTest();
 341          $this->setAdminUser();
 342  
 343          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 344          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
 345          $glossary1 = $this->getDataGenerator()->create_module('glossary', [
 346              'course' => $course->id,
 347              'completion' => 2,
 348              'completionentries' => 3
 349          ]);
 350          $glossary2 = $this->getDataGenerator()->create_module('glossary', [
 351              'course' => $course->id,
 352              'completion' => 2,
 353              'completionentries' => 0
 354          ]);
 355          $cm1 = \cm_info::create(get_coursemodule_from_instance('glossary', $glossary1->id));
 356          $cm2 = \cm_info::create(get_coursemodule_from_instance('glossary', $glossary2->id));
 357  
 358          // Data for the stdClass input type.
 359          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 360          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 361          $moddefaults = new \stdClass();
 362          $moddefaults->customdata = ['customcompletionrules' => ['completionentries' => 3]];
 363          $moddefaults->completion = 2;
 364  
 365          $activeruledescriptions = [get_string('completionentriesdesc', 'glossary', $glossary1->completionentries)];
 366          $this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 367          $this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($cm2), []);
 368          $this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 369          $this->assertEquals(mod_glossary_get_completion_active_rule_descriptions(new \stdClass()), []);
 370      }
 371  
 372      public function test_mod_glossary_get_tagged_entries() {
 373          global $DB;
 374  
 375          $this->resetAfterTest();
 376          $this->setAdminUser();
 377  
 378          // Setup test data.
 379          $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 380          $course3 = $this->getDataGenerator()->create_course();
 381          $course2 = $this->getDataGenerator()->create_course();
 382          $course1 = $this->getDataGenerator()->create_course();
 383  
 384          // Create and enrol a student.
 385          $student = self::getDataGenerator()->create_user();
 386          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 387          $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
 388          $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
 389  
 390          // Create glossaries and entries.
 391          $glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1->id));
 392          $glossary2 = $this->getDataGenerator()->create_module('glossary', array('course' => $course2->id));
 393          $glossary3 = $this->getDataGenerator()->create_module('glossary', array('course' => $course3->id));
 394          $entry11 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats', 'Dogs')));
 395          $entry12 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats', 'mice')));
 396          $entry13 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats')));
 397          $entry14 = $glossarygenerator->create_content($glossary1);
 398          $entry15 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats')));
 399          $entry16 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats'), 'approved' => false));
 400          $entry17 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats'), 'approved' => false, 'userid' => $student->id));
 401          $entry21 = $glossarygenerator->create_content($glossary2, array('tags' => array('Cats')));
 402          $entry22 = $glossarygenerator->create_content($glossary2, array('tags' => array('Cats', 'Dogs')));
 403          $entry23 = $glossarygenerator->create_content($glossary2, array('tags' => array('mice', 'Cats')));
 404          $entry31 = $glossarygenerator->create_content($glossary3, array('tags' => array('mice', 'Cats')));
 405  
 406          $tag = \core_tag_tag::get_by_name(0, 'Cats');
 407  
 408          // Admin can see everything.
 409          // Get first page of tagged entries (first 5 entries).
 410          $res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
 411              /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */0);
 412          $this->assertMatchesRegularExpression('/'.$entry11->concept.'</', $res->content);
 413          $this->assertMatchesRegularExpression('/'.$entry12->concept.'</', $res->content);
 414          $this->assertMatchesRegularExpression('/'.$entry13->concept.'</', $res->content);
 415          $this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'</', $res->content);
 416          $this->assertMatchesRegularExpression('/'.$entry15->concept.'</', $res->content);
 417          $this->assertMatchesRegularExpression('/'.$entry16->concept.'</', $res->content);
 418          $this->assertDoesNotMatchRegularExpression('/'.$entry17->concept.'</', $res->content);
 419          $this->assertDoesNotMatchRegularExpression('/'.$entry21->concept.'</', $res->content);
 420          $this->assertDoesNotMatchRegularExpression('/'.$entry22->concept.'</', $res->content);
 421          $this->assertDoesNotMatchRegularExpression('/'.$entry23->concept.'</', $res->content);
 422          $this->assertDoesNotMatchRegularExpression('/'.$entry31->concept.'</', $res->content);
 423          $this->assertEmpty($res->prevpageurl);
 424          $this->assertNotEmpty($res->nextpageurl);
 425          // Get second page of tagged entries (second 5 entries).
 426          $res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
 427              /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */1);
 428          $this->assertDoesNotMatchRegularExpression('/'.$entry11->concept.'</', $res->content);
 429          $this->assertDoesNotMatchRegularExpression('/'.$entry12->concept.'</', $res->content);
 430          $this->assertDoesNotMatchRegularExpression('/'.$entry13->concept.'</', $res->content);
 431          $this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'</', $res->content);
 432          $this->assertDoesNotMatchRegularExpression('/'.$entry15->concept.'</', $res->content);
 433          $this->assertDoesNotMatchRegularExpression('/'.$entry16->concept.'</', $res->content);
 434          $this->assertMatchesRegularExpression('/'.$entry17->concept.'</', $res->content);
 435          $this->assertMatchesRegularExpression('/'.$entry21->concept.'</', $res->content);
 436          $this->assertMatchesRegularExpression('/'.$entry22->concept.'</', $res->content);
 437          $this->assertMatchesRegularExpression('/'.$entry23->concept.'</', $res->content);
 438          $this->assertMatchesRegularExpression('/'.$entry31->concept.'</', $res->content);
 439          $this->assertNotEmpty($res->prevpageurl);
 440          $this->assertEmpty($res->nextpageurl);
 441  
 442          $this->setUser($student);
 443          \core_tag_index_builder::reset_caches();
 444  
 445          // User can not see entries in course 3 because he is not enrolled.
 446          $res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
 447              /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */1);
 448          $this->assertMatchesRegularExpression('/'.$entry22->concept.'/', $res->content);
 449          $this->assertMatchesRegularExpression('/'.$entry23->concept.'/', $res->content);
 450          $this->assertDoesNotMatchRegularExpression('/'.$entry31->concept.'/', $res->content);
 451  
 452          // User can search glossary entries inside a course.
 453          $coursecontext = \context_course::instance($course1->id);
 454          $res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
 455              /*$fromctx = */0, /*$ctx = */$coursecontext->id, /*$rec = */1, /*$entry = */0);
 456          $this->assertMatchesRegularExpression('/'.$entry11->concept.'/', $res->content);
 457          $this->assertMatchesRegularExpression('/'.$entry12->concept.'/', $res->content);
 458          $this->assertMatchesRegularExpression('/'.$entry13->concept.'/', $res->content);
 459          $this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'/', $res->content);
 460          $this->assertMatchesRegularExpression('/'.$entry15->concept.'/', $res->content);
 461          $this->assertDoesNotMatchRegularExpression('/'.$entry21->concept.'/', $res->content);
 462          $this->assertDoesNotMatchRegularExpression('/'.$entry22->concept.'/', $res->content);
 463          $this->assertDoesNotMatchRegularExpression('/'.$entry23->concept.'/', $res->content);
 464          $this->assertEmpty($res->nextpageurl);
 465  
 466          // User cannot see unapproved entries unless he is an author.
 467          $this->assertDoesNotMatchRegularExpression('/'.$entry16->concept.'/', $res->content);
 468          $this->assertMatchesRegularExpression('/'.$entry17->concept.'/', $res->content);
 469      }
 470  
 471      public function test_glossary_get_entries_search() {
 472          $this->resetAfterTest();
 473          $this->setAdminUser();
 474          // Turn on glossary autolinking (usedynalink).
 475          set_config('glossary_linkentries', 1);
 476          $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 477          $course = $this->getDataGenerator()->create_course();
 478          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 479          // Note this entry is not case sensitive by default (casesensitive = 0).
 480          $entry = $glossarygenerator->create_content($glossary);
 481          // Check that a search for the concept return the entry.
 482          $concept = $entry->concept;
 483          $search = glossary_get_entries_search($concept, $course->id);
 484          $this->assertCount(1, $search);
 485          $foundentry = array_shift($search);
 486          $this->assertEquals($foundentry->concept, $entry->concept);
 487          // Now try the same search but with a lowercase term.
 488          $concept = strtolower($entry->concept);
 489          $search = glossary_get_entries_search($concept, $course->id);
 490          $this->assertCount(1, $search);
 491          $foundentry = array_shift($search);
 492          $this->assertEquals($foundentry->concept, $entry->concept);
 493  
 494          // Make an entry that is case sensitive (casesensitive = 1).
 495          set_config('glossary_casesensitive', 1);
 496          $entry = $glossarygenerator->create_content($glossary);
 497          $concept = $entry->concept;
 498          $search = glossary_get_entries_search($concept, $course->id);
 499          $this->assertCount(1, $search);
 500          $foundentry = array_shift($search);
 501          $this->assertEquals($foundentry->concept, $entry->concept);
 502          // Now try the same search but with a lowercase term.
 503          $concept = strtolower($entry->concept);
 504          $search = glossary_get_entries_search($concept, $course->id);
 505          $this->assertCount(0, $search);
 506      }
 507  
 508      public function test_mod_glossary_can_delete_entry_users() {
 509          $this->resetAfterTest();
 510  
 511          // Create required data.
 512          $course = $this->getDataGenerator()->create_course();
 513          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 514          $anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
 515          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 516          $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
 517  
 518          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 519          $this->setUser($student);
 520          $entry = $gg->create_content($glossary);
 521          $context = \context_module::instance($glossary->cmid);
 522  
 523          // Test student can delete.
 524          $this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
 525  
 526          // Test teacher can delete.
 527          $this->setUser($teacher);
 528          $this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
 529  
 530          // Test admin can delete.
 531          $this->setAdminUser();
 532          $this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
 533  
 534          // Test a different student is not able to delete.
 535          $this->setUser($anotherstudent);
 536          $this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
 537  
 538          // Test exception.
 539          $this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
 540          mod_glossary_can_delete_entry($entry, $glossary, $context, false);
 541      }
 542  
 543      public function test_mod_glossary_can_delete_entry_edit_period() {
 544          global $CFG;
 545          $this->resetAfterTest();
 546  
 547          // Create required data.
 548          $course = $this->getDataGenerator()->create_course();
 549          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 550          $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id, 'editalways' => 1]);
 551  
 552          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 553          $this->setUser($student);
 554          $entry = $gg->create_content($glossary);
 555          $context = \context_module::instance($glossary->cmid);
 556  
 557          // Test student can always delete when edit always is set to 1.
 558          $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
 559          $this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
 560  
 561          // Test student cannot delete old entries when edit always is set to 0.
 562          $glossary->editalways = 0;
 563          $this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
 564  
 565          // Test student can delete recent entries when edit always is set to 0.
 566          $entry->timecreated = time();
 567          $this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
 568  
 569          // Check exception.
 570          $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
 571          $this->expectExceptionMessage(get_string('errdeltimeexpired', 'glossary'));
 572          mod_glossary_can_delete_entry($entry, $glossary, $context, false);
 573      }
 574  
 575      public function test_mod_glossary_delete_entry() {
 576          global $DB, $CFG;
 577          $this->resetAfterTest();
 578          require_once($CFG->dirroot . '/rating/lib.php');
 579  
 580          // Create required data.
 581          $course = $this->getDataGenerator()->create_course();
 582          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 583          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 584  
 585          $record = new \stdClass();
 586          $record->course = $course->id;
 587          $record->assessed = RATING_AGGREGATE_AVERAGE;
 588          $scale = $this->getDataGenerator()->create_scale(['scale' => 'A,B,C,D']);
 589          $record->scale = "-$scale->id";
 590          $glossary = $this->getDataGenerator()->create_module('glossary', $record);
 591          $context = \context_module::instance($glossary->cmid);
 592          $cm = get_coursemodule_from_instance('glossary', $glossary->id);
 593  
 594          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 595          $this->setUser($student1);
 596  
 597          // Create entry with tags and rating.
 598          $entry = $gg->create_content(
 599              $glossary,
 600              ['approved' => 1, 'userid' => $student1->id, 'tags' => ['Cats', 'Dogs']],
 601              ['alias1', 'alias2']
 602          );
 603  
 604          // Rate the entry as user2.
 605          $rating1 = new \stdClass();
 606          $rating1->contextid = $context->id;
 607          $rating1->component = 'mod_glossary';
 608          $rating1->ratingarea = 'entry';
 609          $rating1->itemid = $entry->id;
 610          $rating1->rating = 1; // 1 is A.
 611          $rating1->scaleid = "-$scale->id";
 612          $rating1->userid = $student2->id;
 613          $rating1->timecreated = time();
 614          $rating1->timemodified = time();
 615          $rating1->id = $DB->insert_record('rating', $rating1);
 616  
 617          $sink = $this->redirectEvents();
 618          mod_glossary_delete_entry(fullclone($entry), $glossary, $cm, $context, $course);
 619          $events = $sink->get_events();
 620          $event = array_pop($events);
 621  
 622          // Check events.
 623          $this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
 624          $this->assertEquals($entry->id, $event->objectid);
 625          $sink->close();
 626  
 627          // No entry, no alias, no ratings, no tags.
 628          $this->assertEquals(0, $DB->count_records('glossary_entries', ['id' => $entry->id]));
 629          $this->assertEquals(0, $DB->count_records('glossary_alias', ['entryid' => $entry->id]));
 630          $this->assertEquals(0, $DB->count_records('rating', ['component' => 'mod_glossary', 'itemid' => $entry->id]));
 631          $this->assertEmpty(\core_tag_tag::get_by_name(0, 'Cats'));
 632      }
 633  
 634      public function test_mod_glossary_delete_entry_imported() {
 635          global $DB;
 636          $this->resetAfterTest();
 637  
 638          // Create required data.
 639          $course = $this->getDataGenerator()->create_course();
 640          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 641  
 642          $glossary1 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
 643          $glossary2 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
 644  
 645          $context = \context_module::instance($glossary2->cmid);
 646          $cm = get_coursemodule_from_instance('glossary', $glossary2->id);
 647  
 648          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 649          $this->setUser($student);
 650  
 651          $entry1 = $gg->create_content($glossary1);
 652          $entry2 = $gg->create_content(
 653              $glossary2,
 654              ['approved' => 1, 'userid' => $student->id, 'sourceglossaryid' => $glossary1->id, 'tags' => ['Cats', 'Dogs']]
 655          );
 656  
 657          $sink = $this->redirectEvents();
 658          mod_glossary_delete_entry(fullclone($entry2), $glossary2, $cm, $context, $course);
 659          $events = $sink->get_events();
 660          $event = array_pop($events);
 661  
 662          // Check events.
 663          $this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
 664          $this->assertEquals($entry2->id, $event->objectid);
 665          $sink->close();
 666  
 667          // Check source.
 668          $this->assertEquals(0, $DB->get_field('glossary_entries', 'sourceglossaryid', ['id' => $entry2->id]));
 669          $this->assertEquals($glossary1->id, $DB->get_field('glossary_entries', 'glossaryid', ['id' => $entry2->id]));
 670  
 671          // Tags.
 672          $this->assertEmpty(\core_tag_tag::get_by_name(0, 'Cats'));
 673      }
 674  
 675      public function test_mod_glossary_can_update_entry_users() {
 676          $this->resetAfterTest();
 677  
 678          // Create required data.
 679          $course = $this->getDataGenerator()->create_course();
 680          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 681          $anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
 682          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 683          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
 684  
 685          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 686          $this->setUser($student);
 687          $entry = $gg->create_content($glossary);
 688          $context = \context_module::instance($glossary->cmid);
 689          $cm = get_coursemodule_from_instance('glossary', $glossary->id);
 690  
 691          // Test student can update.
 692          $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 693  
 694          // Test teacher can update.
 695          $this->setUser($teacher);
 696          $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 697  
 698          // Test admin can update.
 699          $this->setAdminUser();
 700          $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 701  
 702          // Test a different student is not able to update.
 703          $this->setUser($anotherstudent);
 704          $this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 705  
 706          // Test exception.
 707          $this->expectExceptionMessage(get_string('errcannoteditothers', 'glossary'));
 708          mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
 709      }
 710  
 711      public function test_mod_glossary_can_update_entry_edit_period() {
 712          global $CFG;
 713          $this->resetAfterTest();
 714  
 715          // Create required data.
 716          $course = $this->getDataGenerator()->create_course();
 717          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 718          $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id, 'editalways' => 1));
 719  
 720          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 721          $this->setUser($student);
 722          $entry = $gg->create_content($glossary);
 723          $context = \context_module::instance($glossary->cmid);
 724          $cm = get_coursemodule_from_instance('glossary', $glossary->id);
 725  
 726          // Test student can always update when edit always is set to 1.
 727          $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
 728          $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 729  
 730          // Test student cannot update old entries when edit always is set to 0.
 731          $glossary->editalways = 0;
 732          $this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 733  
 734          // Test student can update recent entries when edit always is set to 0.
 735          $entry->timecreated = time();
 736          $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
 737  
 738          // Check exception.
 739          $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
 740          $this->expectExceptionMessage(get_string('erredittimeexpired', 'glossary'));
 741          mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
 742      }
 743  
 744      public function test_prepare_entry_for_edition() {
 745          global $USER;
 746          $this->resetAfterTest(true);
 747  
 748          $course = $this->getDataGenerator()->create_course();
 749          $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
 750          $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 751  
 752          $this->setAdminUser();
 753          $aliases = ['alias1', 'alias2'];
 754          $entry = $gg->create_content(
 755              $glossary,
 756              ['approved' => 1, 'userid' => $USER->id],
 757              $aliases
 758          );
 759  
 760          $cat1 = $gg->create_category($glossary, [], [$entry]);
 761          $gg->create_category($glossary);
 762  
 763          $entry = mod_glossary_prepare_entry_for_edition($entry);
 764          $this->assertCount(1, $entry->categories);
 765          $this->assertEquals($cat1->id, $entry->categories[0]);
 766          $returnedaliases = array_values(explode("\n", trim($entry->aliases)));
 767          sort($returnedaliases);
 768          $this->assertEquals($aliases, $returnedaliases);
 769      }
 770  }