Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Unit tests for (some of) mod/quiz/locallib.php.
  19   *
  20   * @package    mod_quiz
  21   * @category   test
  22   * @copyright  2008 Tim Hunt
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  namespace mod_quiz;
  26  
  27  use mod_quiz\output\renderer;
  28  use mod_quiz\question\display_options;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  global $CFG;
  33  require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  34  
  35  
  36  /**
  37   * Unit tests for (some of) mod/quiz/locallib.php.
  38   *
  39   * @copyright  2008 Tim Hunt
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class locallib_test extends \advanced_testcase {
  43  
  44      public function test_quiz_rescale_grade() {
  45          $quiz = new \stdClass();
  46          $quiz->decimalpoints = 2;
  47          $quiz->questiondecimalpoints = 3;
  48          $quiz->grade = 10;
  49          $quiz->sumgrades = 10;
  50          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, false), 0.12345678);
  51          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, true), format_float(0.12, 2));
  52          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, 'question'),
  53              format_float(0.123, 3));
  54          $quiz->sumgrades = 5;
  55          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, false), 0.24691356);
  56          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, true), format_float(0.25, 2));
  57          $this->assertEquals(quiz_rescale_grade(0.12345678, $quiz, 'question'),
  58              format_float(0.247, 3));
  59      }
  60  
  61      public function quiz_attempt_state_data_provider() {
  62          return [
  63              [quiz_attempt::IN_PROGRESS, null, null, display_options::DURING],
  64              [quiz_attempt::FINISHED, -90, null, display_options::IMMEDIATELY_AFTER],
  65              [quiz_attempt::FINISHED, -7200, null, display_options::LATER_WHILE_OPEN],
  66              [quiz_attempt::FINISHED, -7200, 3600, display_options::LATER_WHILE_OPEN],
  67              [quiz_attempt::FINISHED, -30, 30, display_options::IMMEDIATELY_AFTER],
  68              [quiz_attempt::FINISHED, -90, -30, display_options::AFTER_CLOSE],
  69              [quiz_attempt::FINISHED, -7200, -3600, display_options::AFTER_CLOSE],
  70              [quiz_attempt::FINISHED, -90, -3600, display_options::AFTER_CLOSE],
  71              [quiz_attempt::ABANDONED, -10000000, null, display_options::LATER_WHILE_OPEN],
  72              [quiz_attempt::ABANDONED, -7200, 3600, display_options::LATER_WHILE_OPEN],
  73              [quiz_attempt::ABANDONED, -7200, -3600, display_options::AFTER_CLOSE],
  74          ];
  75      }
  76  
  77      /**
  78       * @dataProvider quiz_attempt_state_data_provider
  79       *
  80       * @param string $attemptstate as in the quiz_attempts.state DB column.
  81       * @param int|null $relativetimefinish time relative to now when the attempt finished, or null for 0.
  82       * @param int|null $relativetimeclose time relative to now when the quiz closes, or null for 0.
  83       * @param int $expectedstate expected result. One of the display_options constants.
  84       * @covers ::quiz_attempt_state
  85       */
  86      public function test_quiz_attempt_state(string $attemptstate,
  87              ?int $relativetimefinish, ?int $relativetimeclose, int $expectedstate) {
  88  
  89          $attempt = new \stdClass();
  90          $attempt->state = $attemptstate;
  91          if ($relativetimefinish === null) {
  92              $attempt->timefinish = 0;
  93          } else {
  94              $attempt->timefinish = time() + $relativetimefinish;
  95          }
  96  
  97          $quiz = new \stdClass();
  98          if ($relativetimeclose === null) {
  99              $quiz->timeclose = 0;
 100          } else {
 101              $quiz->timeclose = time() + $relativetimeclose;
 102          }
 103  
 104          $this->assertEquals($expectedstate, quiz_attempt_state($quiz, $attempt));
 105      }
 106  
 107      /**
 108       * @covers ::quiz_question_tostring
 109       */
 110      public function test_quiz_question_tostring() {
 111          $question = new \stdClass();
 112          $question->qtype = 'multichoice';
 113          $question->name = 'The question name';
 114          $question->questiontext = '<p>What sort of <b>inequality</b> is x &lt; y<img alt="?" src="..."></p>';
 115          $question->questiontextformat = FORMAT_HTML;
 116  
 117          $summary = quiz_question_tostring($question);
 118          $this->assertEquals('<span class="questionname">The question name</span> ' .
 119                  '<span class="questiontext">What sort of INEQUALITY is x &lt; y[?]' . "\n" . '</span>', $summary);
 120      }
 121  
 122      /**
 123       * @covers ::quiz_question_tostring
 124       */
 125      public function test_quiz_question_tostring_does_not_filter() {
 126          $question = new \stdClass();
 127          $question->qtype = 'multichoice';
 128          $question->name = 'The question name';
 129          $question->questiontext = '<p>No emoticons here :-)</p>';
 130          $question->questiontextformat = FORMAT_HTML;
 131  
 132          $summary = quiz_question_tostring($question);
 133          $this->assertEquals('<span class="questionname">The question name</span> ' .
 134                  '<span class="questiontext">No emoticons here :-)' . "\n</span>", $summary);
 135      }
 136  
 137      /**
 138       * Test quiz_view
 139       * @return void
 140       */
 141      public function test_quiz_view() {
 142          global $CFG;
 143  
 144          $CFG->enablecompletion = 1;
 145          $this->resetAfterTest();
 146  
 147          $this->setAdminUser();
 148          // Setup test data.
 149          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 150          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id],
 151                                                              ['completion' => 2, 'completionview' => 1]);
 152          $context = \context_module::instance($quiz->cmid);
 153          $cm = get_coursemodule_from_instance('quiz', $quiz->id);
 154  
 155          // Trigger and capture the event.
 156          $sink = $this->redirectEvents();
 157  
 158          quiz_view($quiz, $course, $cm, $context);
 159  
 160          $events = $sink->get_events();
 161          // 2 additional events thanks to completion.
 162          $this->assertCount(3, $events);
 163          $event = array_shift($events);
 164  
 165          // Checking that the event contains the expected values.
 166          $this->assertInstanceOf('\mod_quiz\event\course_module_viewed', $event);
 167          $this->assertEquals($context, $event->get_context());
 168          $moodleurl = new \moodle_url('/mod/quiz/view.php', ['id' => $cm->id]);
 169          $this->assertEquals($moodleurl, $event->get_url());
 170          $this->assertEventContextNotUsed($event);
 171          $this->assertNotEmpty($event->get_name());
 172          // Check completion status.
 173          $completion = new \completion_info($course);
 174          $completiondata = $completion->get_data($cm);
 175          $this->assertEquals(1, $completiondata->completionstate);
 176      }
 177  
 178      /**
 179       * Return false when there are not overrides for this quiz instance.
 180       */
 181      public function test_quiz_is_overriden_calendar_event_no_override() {
 182          global $CFG, $DB;
 183  
 184          $this->resetAfterTest();
 185          $this->setAdminUser();
 186  
 187          $generator = $this->getDataGenerator();
 188          $user = $generator->create_user();
 189          $course = $generator->create_course();
 190          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 191          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 192  
 193          $event = new \calendar_event((object)[
 194              'modulename' => 'quiz',
 195              'instance' => $quiz->id,
 196              'userid' => $user->id
 197          ]);
 198  
 199          $this->assertFalse(quiz_is_overriden_calendar_event($event));
 200      }
 201  
 202      /**
 203       * Return false if the given event isn't an quiz module event.
 204       */
 205      public function test_quiz_is_overriden_calendar_event_no_module_event() {
 206          global $CFG, $DB;
 207  
 208          $this->resetAfterTest();
 209          $this->setAdminUser();
 210  
 211          $generator = $this->getDataGenerator();
 212          $user = $generator->create_user();
 213          $course = $generator->create_course();
 214          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 215          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 216  
 217          $event = new \calendar_event((object)[
 218              'userid' => $user->id
 219          ]);
 220  
 221          $this->assertFalse(quiz_is_overriden_calendar_event($event));
 222      }
 223  
 224      /**
 225       * Return false if there is overrides for this use but they belong to another quiz
 226       * instance.
 227       */
 228      public function test_quiz_is_overriden_calendar_event_different_quiz_instance() {
 229          global $CFG, $DB;
 230  
 231          $this->resetAfterTest();
 232          $this->setAdminUser();
 233  
 234          $generator = $this->getDataGenerator();
 235          $user = $generator->create_user();
 236          $course = $generator->create_course();
 237          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 238          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 239          $quiz2 = $quizgenerator->create_instance(['course' => $course->id]);
 240  
 241          $event = new \calendar_event((object) [
 242              'modulename' => 'quiz',
 243              'instance' => $quiz->id,
 244              'userid' => $user->id
 245          ]);
 246  
 247          $record = (object) [
 248              'quiz' => $quiz2->id,
 249              'userid' => $user->id
 250          ];
 251  
 252          $DB->insert_record('quiz_overrides', $record);
 253  
 254          $this->assertFalse(quiz_is_overriden_calendar_event($event));
 255      }
 256  
 257      /**
 258       * Return true if there is a user override for this event and quiz instance.
 259       */
 260      public function test_quiz_is_overriden_calendar_event_user_override() {
 261          global $CFG, $DB;
 262  
 263          $this->resetAfterTest();
 264          $this->setAdminUser();
 265  
 266          $generator = $this->getDataGenerator();
 267          $user = $generator->create_user();
 268          $course = $generator->create_course();
 269          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 270          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 271  
 272          $event = new \calendar_event((object) [
 273              'modulename' => 'quiz',
 274              'instance' => $quiz->id,
 275              'userid' => $user->id
 276          ]);
 277  
 278          $record = (object) [
 279              'quiz' => $quiz->id,
 280              'userid' => $user->id
 281          ];
 282  
 283          $DB->insert_record('quiz_overrides', $record);
 284  
 285          $this->assertTrue(quiz_is_overriden_calendar_event($event));
 286      }
 287  
 288      /**
 289       * Return true if there is a group override for the event and quiz instance.
 290       */
 291      public function test_quiz_is_overriden_calendar_event_group_override() {
 292          global $CFG, $DB;
 293  
 294          $this->resetAfterTest();
 295          $this->setAdminUser();
 296  
 297          $generator = $this->getDataGenerator();
 298          $user = $generator->create_user();
 299          $course = $generator->create_course();
 300          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 301          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 302          $group = $this->getDataGenerator()->create_group(['courseid' => $quiz->course]);
 303          $groupid = $group->id;
 304          $userid = $user->id;
 305  
 306          $event = new \calendar_event((object) [
 307              'modulename' => 'quiz',
 308              'instance' => $quiz->id,
 309              'groupid' => $groupid
 310          ]);
 311  
 312          $record = (object) [
 313              'quiz' => $quiz->id,
 314              'groupid' => $groupid
 315          ];
 316  
 317          $DB->insert_record('quiz_overrides', $record);
 318  
 319          $this->assertTrue(quiz_is_overriden_calendar_event($event));
 320      }
 321  
 322      /**
 323       * Test test_quiz_get_user_timeclose().
 324       */
 325      public function test_quiz_get_user_timeclose() {
 326          global $DB;
 327  
 328          $this->resetAfterTest();
 329          $this->setAdminUser();
 330  
 331          $basetimestamp = time(); // The timestamp we will base the enddates on.
 332  
 333          // Create generator, course and quizzes.
 334          $student1 = $this->getDataGenerator()->create_user();
 335          $student2 = $this->getDataGenerator()->create_user();
 336          $student3 = $this->getDataGenerator()->create_user();
 337          $teacher = $this->getDataGenerator()->create_user();
 338          $course = $this->getDataGenerator()->create_course();
 339          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 340  
 341          // Both quizzes close in two hours.
 342          $quiz1 = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => $basetimestamp + 7200]);
 343          $quiz2 = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => $basetimestamp + 7200]);
 344          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 345          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 346  
 347          $student1id = $student1->id;
 348          $student2id = $student2->id;
 349          $student3id = $student3->id;
 350          $teacherid = $teacher->id;
 351  
 352          // Users enrolments.
 353          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 354          $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
 355          $this->getDataGenerator()->enrol_user($student1id, $course->id, $studentrole->id, 'manual');
 356          $this->getDataGenerator()->enrol_user($student2id, $course->id, $studentrole->id, 'manual');
 357          $this->getDataGenerator()->enrol_user($student3id, $course->id, $studentrole->id, 'manual');
 358          $this->getDataGenerator()->enrol_user($teacherid, $course->id, $teacherrole->id, 'manual');
 359  
 360          // Create groups.
 361          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 362          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 363          $group1id = $group1->id;
 364          $group2id = $group2->id;
 365          $this->getDataGenerator()->create_group_member(['userid' => $student1id, 'groupid' => $group1id]);
 366          $this->getDataGenerator()->create_group_member(['userid' => $student2id, 'groupid' => $group2id]);
 367  
 368          // Group 1 gets an group override for quiz 1 to close in three hours.
 369          $record1 = (object) [
 370              'quiz' => $quiz1->id,
 371              'groupid' => $group1id,
 372              'timeclose' => $basetimestamp + 10800 // In three hours.
 373          ];
 374          $DB->insert_record('quiz_overrides', $record1);
 375  
 376          // Let's test quiz 1 closes in three hours for user student 1 since member of group 1.
 377          // Quiz 2 closes in two hours.
 378          $this->setUser($student1id);
 379          $params = new \stdClass();
 380  
 381          $comparearray = [];
 382          $object = new \stdClass();
 383          $object->id = $quiz1->id;
 384          $object->usertimeclose = $basetimestamp + 10800; // The overriden timeclose for quiz 1.
 385  
 386          $comparearray[$quiz1->id] = $object;
 387  
 388          $object = new \stdClass();
 389          $object->id = $quiz2->id;
 390          $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2.
 391  
 392          $comparearray[$quiz2->id] = $object;
 393  
 394          $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id));
 395  
 396          // Let's test quiz 1 closes in two hours (the original value) for user student 3 since member of no group.
 397          $this->setUser($student3id);
 398          $params = new \stdClass();
 399  
 400          $comparearray = [];
 401          $object = new \stdClass();
 402          $object->id = $quiz1->id;
 403          $object->usertimeclose = $basetimestamp + 7200; // The original timeclose for quiz 1.
 404  
 405          $comparearray[$quiz1->id] = $object;
 406  
 407          $object = new \stdClass();
 408          $object->id = $quiz2->id;
 409          $object->usertimeclose = $basetimestamp + 7200; // The original timeclose for quiz 2.
 410  
 411          $comparearray[$quiz2->id] = $object;
 412  
 413          $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id));
 414  
 415          // User 2 gets an user override for quiz 1 to close in four hours.
 416          $record2 = (object) [
 417              'quiz' => $quiz1->id,
 418              'userid' => $student2id,
 419              'timeclose' => $basetimestamp + 14400 // In four hours.
 420          ];
 421          $DB->insert_record('quiz_overrides', $record2);
 422  
 423          // Let's test quiz 1 closes in four hours for user student 2 since personally overriden.
 424          // Quiz 2 closes in two hours.
 425          $this->setUser($student2id);
 426  
 427          $comparearray = [];
 428          $object = new \stdClass();
 429          $object->id = $quiz1->id;
 430          $object->usertimeclose = $basetimestamp + 14400; // The overriden timeclose for quiz 1.
 431  
 432          $comparearray[$quiz1->id] = $object;
 433  
 434          $object = new \stdClass();
 435          $object->id = $quiz2->id;
 436          $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2.
 437  
 438          $comparearray[$quiz2->id] = $object;
 439  
 440          $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id));
 441  
 442          // Let's test a teacher sees the original times.
 443          // Quiz 1 and quiz 2 close in two hours.
 444          $this->setUser($teacherid);
 445  
 446          $comparearray = [];
 447          $object = new \stdClass();
 448          $object->id = $quiz1->id;
 449          $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 1.
 450  
 451          $comparearray[$quiz1->id] = $object;
 452  
 453          $object = new \stdClass();
 454          $object->id = $quiz2->id;
 455          $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2.
 456  
 457          $comparearray[$quiz2->id] = $object;
 458  
 459          $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id));
 460      }
 461  
 462      /**
 463       * This function creates a quiz with some standard (non-random) and some random questions.
 464       * The standard questions are created first and then random questions follow them.
 465       * So in a quiz with 3 standard question and 2 random question, the first random question is at slot 4.
 466       *
 467       * @param int $qnum Number of standard questions that should be created in the quiz.
 468       * @param int $randomqnum Number of random questions that should be created in the quiz.
 469       * @param array $questiontags Tags to be used for random questions.
 470       *      This is an array in the following format:
 471       *      [
 472       *          0 => ['foo', 'bar'],
 473       *          1 => ['baz', 'qux']
 474       *      ]
 475       * @param string[] $unusedtags Some additional tags to be created.
 476       * @return array An array of 2 elements: $quiz and $tagobjects.
 477       *      $tagobjects is an associative array of all created tag objects with its key being tag names.
 478       */
 479      private function setup_quiz_and_tags($qnum, $randomqnum, $questiontags = [], $unusedtags = []) {
 480          global $SITE;
 481  
 482          $tagobjects = [];
 483  
 484          // Get all the tags that need to be created.
 485          $alltags = [];
 486          foreach ($questiontags as $questiontag) {
 487              $alltags = array_merge($alltags, $questiontag);
 488          }
 489          $alltags = array_merge($alltags, $unusedtags);
 490          $alltags = array_unique($alltags);
 491  
 492          // Create tags.
 493          foreach ($alltags as $tagname) {
 494              $tagrecord = [
 495                  'isstandard' => 1,
 496                  'flag' => 0,
 497                  'rawname' => $tagname,
 498                  'description' => $tagname . ' desc'
 499              ];
 500              $tagobjects[$tagname] = $this->getDataGenerator()->create_tag($tagrecord);
 501          }
 502  
 503          // Create a quiz.
 504          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 505          $quiz = $quizgenerator->create_instance(['course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0]);
 506  
 507          // Create a question category in the system context.
 508          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 509          $cat = $questiongenerator->create_question_category();
 510  
 511          // Setup standard questions.
 512          for ($i = 0; $i < $qnum; $i++) {
 513              $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
 514              quiz_add_quiz_question($question->id, $quiz);
 515          }
 516          // Setup random questions.
 517          for ($i = 0; $i < $randomqnum; $i++) {
 518              // Just create a standard question first, so there would be enough questions to pick a random question from.
 519              $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
 520              $tagids = [];
 521              if (!empty($questiontags[$i])) {
 522                  foreach ($questiontags[$i] as $tagname) {
 523                      $tagids[] = $tagobjects[$tagname]->id;
 524                  }
 525              }
 526              quiz_add_random_questions($quiz, 0, $cat->id, 1, false, $tagids);
 527          }
 528  
 529          return [$quiz, $tagobjects];
 530      }
 531  
 532      public function test_quiz_override_summary() {
 533          global $DB, $PAGE;
 534          $this->resetAfterTest();
 535          $generator = $this->getDataGenerator();
 536          /** @var mod_quiz_generator $quizgenerator */
 537          $quizgenerator = $generator->get_plugin_generator('mod_quiz');
 538          /** @var renderer $renderer */
 539          $renderer = $PAGE->get_renderer('mod_quiz');
 540  
 541          // Course with quiz and a group - plus some others, to verify they don't get counted.
 542          $course = $generator->create_course();
 543          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'groupmode' => SEPARATEGROUPS]);
 544          $cm = get_coursemodule_from_id('quiz', $quiz->cmid, $course->id);
 545          $group = $generator->create_group(['courseid' => $course->id]);
 546          $othergroup = $generator->create_group(['courseid' => $course->id]);
 547          $otherquiz = $quizgenerator->create_instance(['course' => $course->id]);
 548  
 549          // Initial test (as admin) with no data.
 550          $this->setAdminUser();
 551          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'allgroups'],
 552                  quiz_override_summary($quiz, $cm));
 553          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
 554                  quiz_override_summary($quiz, $cm, $group->id));
 555  
 556          // Editing teacher.
 557          $teacher = $generator->create_user();
 558          $generator->enrol_user($teacher->id, $course->id, 'editingteacher');
 559  
 560          // Non-editing teacher.
 561          $tutor = $generator->create_user();
 562          $generator->enrol_user($tutor->id, $course->id, 'teacher');
 563          $generator->create_group_member(['userid' => $tutor->id, 'groupid' => $group->id]);
 564  
 565          // Three students.
 566          $student1 = $generator->create_user();
 567          $generator->enrol_user($student1->id, $course->id, 'student');
 568          $generator->create_group_member(['userid' => $student1->id, 'groupid' => $group->id]);
 569  
 570          $student2 = $generator->create_user();
 571          $generator->enrol_user($student2->id, $course->id, 'student');
 572          $generator->create_group_member(['userid' => $student2->id, 'groupid' => $othergroup->id]);
 573  
 574          $student3 = $generator->create_user();
 575          $generator->enrol_user($student3->id, $course->id, 'student');
 576  
 577          // Initial test now users exist, but before overrides.
 578          // Test as teacher.
 579          $this->setUser($teacher);
 580          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'allgroups'],
 581                  quiz_override_summary($quiz, $cm));
 582          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
 583                  quiz_override_summary($quiz, $cm, $group->id));
 584  
 585          // Test as tutor.
 586          $this->setUser($tutor);
 587          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'somegroups'],
 588                  quiz_override_summary($quiz, $cm));
 589          $this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
 590                  quiz_override_summary($quiz, $cm, $group->id));
 591          $this->assertEquals('', $renderer->quiz_override_summary_links($quiz, $cm));
 592  
 593          // Quiz setting overrides for students 1 and 3.
 594          $quizgenerator->create_override(['quiz' => $quiz->id, 'userid' => $student1->id, 'attempts' => 2]);
 595          $quizgenerator->create_override(['quiz' => $quiz->id, 'userid' => $student3->id, 'attempts' => 2]);
 596          $quizgenerator->create_override(['quiz' => $quiz->id, 'groupid' => $group->id, 'attempts' => 3]);
 597          $quizgenerator->create_override(['quiz' => $quiz->id, 'groupid' => $othergroup->id, 'attempts' => 3]);
 598          $quizgenerator->create_override(['quiz' => $otherquiz->id, 'userid' => $student2->id, 'attempts' => 2]);
 599  
 600          // Test as teacher.
 601          $this->setUser($teacher);
 602          $this->assertEquals(['group' => 2, 'user' => 2, 'mode' => 'allgroups'],
 603                  quiz_override_summary($quiz, $cm));
 604          $this->assertEquals('Settings overrides exist (Groups: 2, Users: 2)',
 605                  // Links checked by Behat, so strip them for these tests.
 606                  html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
 607          $this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'onegroup'],
 608                  quiz_override_summary($quiz, $cm, $group->id));
 609          $this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for this group',
 610                  html_to_text($renderer->quiz_override_summary_links($quiz, $cm, $group->id), 0, false));
 611  
 612          // Test as tutor.
 613          $this->setUser($tutor);
 614          $this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'somegroups'],
 615                  quiz_override_summary($quiz, $cm));
 616          $this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for your groups',
 617                  html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
 618          $this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'onegroup'],
 619                  quiz_override_summary($quiz, $cm, $group->id));
 620          $this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for this group',
 621                  html_to_text($renderer->quiz_override_summary_links($quiz, $cm, $group->id), 0, false));
 622  
 623          // Now set the quiz to be group mode: no groups, and re-test as tutor.
 624          // In this case, the tutor should see all groups.
 625          $DB->set_field('course_modules', 'groupmode', NOGROUPS, ['id' => $cm->id]);
 626          $cm = get_coursemodule_from_id('quiz', $quiz->cmid, $course->id);
 627  
 628          $this->assertEquals(['group' => 2, 'user' => 2, 'mode' => 'allgroups'],
 629                  quiz_override_summary($quiz, $cm));
 630          $this->assertEquals('Settings overrides exist (Groups: 2, Users: 2)',
 631                  html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
 632      }
 633  
 634      /**
 635       *  Test quiz_send_confirmation function.
 636       */
 637      public function test_quiz_send_confirmation() {
 638          global $CFG, $DB;
 639  
 640          $this->resetAfterTest();
 641          $this->setAdminUser();
 642          $this->preventResetByRollback();
 643  
 644          $course = $this->getDataGenerator()->create_course();
 645          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 646          $quiz = $quizgenerator->create_instance(['course' => $course->id]);
 647          $cm = get_coursemodule_from_instance('quiz', $quiz->id);
 648  
 649          $recipient = $this->getDataGenerator()->create_user(['email' => 'student@example.com']);
 650  
 651          // Allow recipent to receive email confirm submission.
 652          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 653          assign_capability('mod/quiz:emailconfirmsubmission', CAP_ALLOW, $studentrole->id,
 654              \context_course::instance($course->id), true);
 655          $this->getDataGenerator()->enrol_user($recipient->id, $course->id, $studentrole->id, 'manual');
 656  
 657          $timenow = time();
 658          $data = new \stdClass();
 659          // Course info.
 660          $data->courseid        = $course->id;
 661          $data->coursename      = $course->fullname;
 662          // Quiz info.
 663          $data->quizname        = $quiz->name;
 664          $data->quizurl         = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
 665          $data->quizid          = $quiz->id;
 666          $data->quizcmid        = $quiz->cmid;
 667          $data->attemptid       = 1;
 668          $data->submissiontime = userdate($timenow);
 669  
 670          $sink = $this->redirectEmails();
 671          quiz_send_confirmation($recipient, $data, true);
 672          $messages = $sink->get_messages();
 673          $message = reset($messages);
 674          $this->assertStringContainsString("Thank you for submitting your answers" ,
 675              quoted_printable_decode($message->body));
 676          $sink->close();
 677  
 678          $sink = $this->redirectEmails();
 679          quiz_send_confirmation($recipient, $data, false);
 680          $messages = $sink->get_messages();
 681          $message = reset($messages);
 682          $this->assertStringContainsString("Your answers were submitted automatically" ,
 683              quoted_printable_decode($message->body));
 684          $sink->close();
 685      }
 686  }