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 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace mod_quiz;
  18  
  19  use mod_quiz_overdue_attempt_updater;
  20  use question_engine;
  21  use quiz;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot.'/group/lib.php');
  27  
  28  /**
  29   * Unit tests for quiz attempt overdue handling
  30   *
  31   * @package    mod_quiz
  32   * @category   test
  33   * @copyright  2012 Matt Petro
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class attempts_test extends \advanced_testcase {
  37  
  38      /**
  39       * Test the functions quiz_update_open_attempts(), get_list_of_overdue_attempts() and
  40       * update_overdue_attempts().
  41       */
  42      public function test_bulk_update_functions() {
  43          global $DB,$CFG;
  44  
  45          require_once($CFG->dirroot.'/mod/quiz/cronlib.php');
  46  
  47          $this->resetAfterTest();
  48  
  49          $this->setAdminUser();
  50  
  51          // Setup course, user and groups
  52  
  53          $course = $this->getDataGenerator()->create_course();
  54          $user1 = $this->getDataGenerator()->create_user();
  55          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
  56          $this->assertNotEmpty($studentrole);
  57          $this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
  58          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
  59          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
  60          $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
  61          $this->assertTrue(groups_add_member($group1, $user1));
  62          $this->assertTrue(groups_add_member($group2, $user1));
  63  
  64          $usertimes = [];
  65  
  66          /** @var mod_quiz_generator $quizgenerator */
  67          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
  68  
  69          // Basic quiz settings
  70  
  71          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
  72          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
  73                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
  74          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test1A', 'time1000state' => 'finished'];
  75  
  76          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 1800]);
  77          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
  78                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
  79          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 1800, 'message' => 'Test1B', 'time1000state' => 'inprogress'];
  80  
  81          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
  82          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
  83                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
  84          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 0, 'message' => 'Test1C', 'time1000state' => 'inprogress'];
  85  
  86          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
  87          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
  88                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
  89          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test1D', 'time1000state' => 'finished'];
  90  
  91          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 0]);
  92          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
  93                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
  94          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test1E', 'time1000state' => 'inprogress'];
  95  
  96          // Group overrides
  97  
  98          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
  99          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 100                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 101          $DB->insert_record('quiz_overrides',
 102                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
 103          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 0, 'message' => 'Test2A', 'time1000state' => 'inprogress'];
 104  
 105          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
 106          $DB->insert_record('quiz_overrides',
 107                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1100, 'timelimit' => null]);
 108          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 109                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 110          $usertimes[$attemptid] = ['timeclose' => 1100, 'timelimit' => 0, 'message' => 'Test2B', 'time1000state' => 'inprogress'];
 111  
 112          $quiz = $quizgenerator->create_instance(
 113                  ['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600, 'overduehandling' => 'autoabandon']);
 114          $DB->insert_record('quiz_overrides',
 115                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
 116          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 117                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 118          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 700, 'message' => 'Test2C', 'time1000state' => 'abandoned'];
 119  
 120          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
 121          $DB->insert_record('quiz_overrides',
 122                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 500]);
 123          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 124                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 125          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 500, 'message' => 'Test2D', 'time1000state' => 'finished'];
 126  
 127          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
 128          $DB->insert_record('quiz_overrides',
 129                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 0]);
 130          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 131                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 132          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test2E', 'time1000state' => 'inprogress'];
 133  
 134          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 135          $DB->insert_record('quiz_overrides',
 136                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
 137          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 138                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 139          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2F', 'time1000state' => 'finished'];
 140          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 141                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 142          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2G', 'time1000state' => 'inprogress'];
 143  
 144          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 145          $DB->insert_record('quiz_overrides',
 146                  ['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1300, 'timelimit' => 500]); // User not in group.
 147          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 148                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 149          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2H', 'time1000state' => 'finished'];
 150          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 151                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 152          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2I', 'time1000state' => 'inprogress'];
 153  
 154          // Multiple group overrides
 155  
 156          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 157          $DB->insert_record('quiz_overrides',
 158                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 501]);
 159          $DB->insert_record('quiz_overrides',
 160                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 500]);
 161          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 162                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 163          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3A', 'time1000state' => 'finished'];
 164          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 165                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 166          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3B', 'time1000state' => 'inprogress'];
 167  
 168          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 169          $DB->insert_record('quiz_overrides',
 170                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
 171          $DB->insert_record('quiz_overrides',
 172                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
 173          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 174                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 175          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3C', 'time1000state' => 'finished'];
 176          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 177                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 178          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3D', 'time1000state' => 'inprogress'];
 179  
 180          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
 181                  'overduehandling' => 'autoabandon']);
 182          $DB->insert_record('quiz_overrides',
 183                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
 184          $DB->insert_record('quiz_overrides',
 185                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
 186          $DB->insert_record('quiz_overrides',
 187                  ['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1500, 'timelimit' => 1000]); // User not in group.
 188          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 189                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 190          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3E', 'time1000state' => 'abandoned'];
 191          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 192                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 193          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3F', 'time1000state' => 'inprogress'];
 194  
 195          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 196          $DB->insert_record('quiz_overrides',
 197                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
 198          $DB->insert_record('quiz_overrides',
 199                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => null, 'timelimit' => 501]);
 200          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 201                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 202          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3G', 'time1000state' => 'finished'];
 203          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 204                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 205          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3H', 'time1000state' => 'inprogress'];
 206  
 207          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 208          $DB->insert_record('quiz_overrides',
 209                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
 210          $DB->insert_record('quiz_overrides',
 211                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => null]);
 212          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 213                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 214          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3I', 'time1000state' => 'finished'];
 215          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 216                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 217          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3J', 'time1000state' => 'inprogress'];
 218  
 219          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 220          $DB->insert_record('quiz_overrides',
 221                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
 222          $DB->insert_record('quiz_overrides',
 223                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 0]);
 224          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 225                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 226          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3K', 'time1000state' => 'inprogress'];
 227          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 228                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 229          $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3L', 'time1000state' => 'inprogress'];
 230  
 231          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 232          $DB->insert_record('quiz_overrides',
 233                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
 234          $DB->insert_record('quiz_overrides',
 235                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 0, 'timelimit' => 501]);
 236          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 237                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 238          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3M', 'time1000state' => 'finished'];
 239          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 240                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 241          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3N', 'time1000state' => 'inprogress'];
 242  
 243          // User overrides
 244  
 245          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 246          $DB->insert_record('quiz_overrides',
 247                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 248          $DB->insert_record('quiz_overrides',
 249                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 601]);
 250          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 251                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 252          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4A', 'time1000state' => 'finished'];
 253          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 254                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 255          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4B', 'time1000state' => 'inprogress'];
 256  
 257          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 258          $DB->insert_record('quiz_overrides',
 259                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 260          $DB->insert_record('quiz_overrides',
 261                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 0, 'timelimit' => 601]);
 262          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 263                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 264          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4C', 'time1000state' => 'finished'];
 265          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 266                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 267          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4D', 'time1000state' => 'inprogress'];
 268  
 269          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 270          $DB->insert_record('quiz_overrides',
 271                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 272          $DB->insert_record('quiz_overrides',
 273                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 0]);
 274          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 275                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 276          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4E', 'time1000state' => 'inprogress'];
 277          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 278                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 279          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4F', 'time1000state' => 'inprogress'];
 280  
 281          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
 282                  'overduehandling' => 'autoabandon']);
 283          $DB->insert_record('quiz_overrides',
 284                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 285          $DB->insert_record('quiz_overrides',
 286                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
 287          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 288                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 289          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4G', 'time1000state' => 'abandoned'];
 290          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 291                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 292          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4H', 'time1000state' => 'inprogress'];
 293  
 294          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 295          $DB->insert_record('quiz_overrides',
 296                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
 297          $DB->insert_record('quiz_overrides',
 298                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
 299          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 300                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 301          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4I', 'time1000state' => 'finished'];
 302          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 303                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 304          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4J', 'time1000state' => 'inprogress'];
 305  
 306          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 307          $DB->insert_record('quiz_overrides',
 308                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 309          $DB->insert_record('quiz_overrides',
 310                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
 311          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 312                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 313          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4K', 'time1000state' => 'finished'];
 314          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 315                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 316          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4L', 'time1000state' => 'inprogress'];
 317  
 318          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 319          $DB->insert_record('quiz_overrides',
 320                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
 321          $DB->insert_record('quiz_overrides',
 322                  ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
 323          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 324                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 325          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4M', 'time1000state' => 'finished'];
 326          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 327                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 328          $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4N', 'time1000state' => 'inprogress'];
 329  
 330          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
 331          $DB->insert_record('quiz_overrides',
 332                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
 333          $DB->insert_record('quiz_overrides',
 334                  ['quiz' => $quiz->id, 'userid' => 0, 'timeclose' => 1201, 'timelimit' => 601]); // Not user.
 335          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 336                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 337          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4O', 'time1000state' => 'finished'];
 338          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 339                  'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
 340          $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4P', 'time1000state' => 'inprogress'];
 341  
 342          // Attempt state overdue
 343  
 344          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
 345                  'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
 346          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
 347                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 348          $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test5A', 'time1000state' => 'overdue'];
 349  
 350          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600,
 351                  'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
 352          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
 353                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 354          $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test5B', 'time1000state' => 'overdue'];
 355  
 356          // Compute expected end time for each attempt.
 357          foreach ($usertimes as $attemptid => $times) {
 358              $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
 359  
 360              if ($times['timeclose'] > 0 && $times['timelimit'] > 0) {
 361                  $usertimes[$attemptid]['timedue'] = min($times['timeclose'], $attempt->timestart + $times['timelimit']);
 362              } else if ($times['timeclose'] > 0) {
 363                  $usertimes[$attemptid]['timedue'] = $times['timeclose'];
 364              } else if ($times['timelimit'] > 0) {
 365                  $usertimes[$attemptid]['timedue'] = $attempt->timestart + $times['timelimit'];
 366              }
 367          }
 368  
 369          //
 370          // Test quiz_update_open_attempts().
 371          //
 372  
 373          quiz_update_open_attempts(['courseid' => $course->id]);
 374          foreach ($usertimes as $attemptid => $times) {
 375              $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
 376  
 377              if ($attempt->state == 'overdue') {
 378                  $graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
 379              } else {
 380                  $graceperiod = 0;
 381              }
 382              if (isset($times['timedue'])) {
 383                  $this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
 384              } else {
 385                  $this->assertNull($attempt->timecheckstate, $times['message']);
 386              }
 387          }
 388  
 389          //
 390          // Test get_list_of_overdue_attempts().
 391          //
 392  
 393          $overduehander = new mod_quiz_overdue_attempt_updater();
 394  
 395          $attempts = $overduehander->get_list_of_overdue_attempts(100000); // way in the future
 396          $count = 0;
 397          foreach ($attempts as $attempt) {
 398              $this->assertTrue(isset($usertimes[$attempt->id]));
 399              $times = $usertimes[$attempt->id];
 400              $this->assertEquals($times['timeclose'], $attempt->usertimeclose, $times['message']);
 401              $this->assertEquals($times['timelimit'], $attempt->usertimelimit, $times['message']);
 402              $count++;
 403  
 404          }
 405          $attempts->close();
 406          $this->assertEquals($DB->count_records_select('quiz_attempts', 'timecheckstate IS NOT NULL'), $count);
 407  
 408          $attempts = $overduehander->get_list_of_overdue_attempts(0); // before all attempts
 409          $count = 0;
 410          foreach ($attempts as $attempt) {
 411              $count++;
 412          }
 413          $attempts->close();
 414          $this->assertEquals(0, $count);
 415  
 416          //
 417          // Test update_overdue_attempts().
 418          //
 419  
 420          [$count, $quizcount] = $overduehander->update_overdue_attempts(1000, 940);
 421  
 422          $attempts = $DB->get_records('quiz_attempts', null, 'quiz, userid, attempt',
 423                  'id, quiz, userid, attempt, state, timestart, timefinish, timecheckstate');
 424          foreach ($attempts as $attempt) {
 425              $this->assertTrue(isset($usertimes[$attempt->id]));
 426              $times = $usertimes[$attempt->id];
 427              $this->assertEquals($times['time1000state'], $attempt->state, $times['message']);
 428              switch ($times['time1000state']) {
 429                  case 'finished':
 430                      $this->assertEquals($times['timedue'], $attempt->timefinish, $times['message']);
 431                      $this->assertNull($attempt->timecheckstate, $times['message']);
 432                      break;
 433  
 434                  case 'overdue':
 435                      $this->assertEquals(0, $attempt->timefinish, $times['message']);
 436                      $graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
 437                      $this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
 438                      break;
 439  
 440                  case 'abandoned':
 441                      $this->assertEquals(0, $attempt->timefinish, $times['message']);
 442                      $this->assertNull($attempt->timecheckstate, $times['message']);
 443                      break;
 444              }
 445          }
 446  
 447          $this->assertEquals(19, $count);
 448          $this->assertEquals(19, $quizcount);
 449      }
 450  
 451      /**
 452       * Make any old question usage for a quiz.
 453       *
 454       * The attempts used in test_bulk_update_functions must have some
 455       * question usage to store in uniqueid, but they don't have to be
 456       * very realistic.
 457       *
 458       * @param \stdClass $quiz
 459       * @return int question usage id.
 460       */
 461      protected function usage_id(\stdClass $quiz): int {
 462          $quba = question_engine::make_questions_usage_by_activity('mod_quiz',
 463                  \context_module::instance($quiz->cmid));
 464          $quba->set_preferred_behaviour('deferredfeedback');
 465          question_engine::save_questions_usage_by_activity($quba);
 466          return $quba->get_id();
 467      }
 468  
 469      /**
 470       * Test the group event handlers
 471       */
 472      public function test_group_event_handlers() {
 473          global $DB;
 474  
 475          $this->resetAfterTest();
 476  
 477          $this->setAdminUser();
 478  
 479          // Setup course, user and groups
 480  
 481          $course = $this->getDataGenerator()->create_course();
 482          $user1 = $this->getDataGenerator()->create_user();
 483          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 484          $this->assertNotEmpty($studentrole);
 485          $this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
 486          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 487          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 488          $this->assertTrue(groups_add_member($group1, $user1));
 489          $this->assertTrue(groups_add_member($group2, $user1));
 490  
 491          /** @var mod_quiz_generator $quizgenerator */
 492          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 493  
 494          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
 495  
 496          // add a group1 override
 497          $DB->insert_record('quiz_overrides',
 498                  ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
 499  
 500          // add an attempt
 501          $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
 502                  'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
 503  
 504          // update timecheckstate
 505          quiz_update_open_attempts(['quizid' => $quiz->id]);
 506          $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 507  
 508          // remove from group
 509          $this->assertTrue(groups_remove_member($group1, $user1));
 510          $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 511  
 512          // add back to group
 513          $this->assertTrue(groups_add_member($group1, $user1));
 514          $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 515  
 516          // delete group
 517          groups_delete_group($group1);
 518          $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 519          $this->assertEquals(0, $DB->count_records('quiz_overrides', ['quiz' => $quiz->id]));
 520  
 521          // add a group2 override
 522          $DB->insert_record('quiz_overrides',
 523                  ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1400, 'timelimit' => null]);
 524          quiz_update_open_attempts(['quizid' => $quiz->id]);
 525          $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 526  
 527          // delete user1 from all groups
 528          groups_delete_group_members($course->id, $user1->id);
 529          $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 530  
 531          // add back to group2
 532          $this->assertTrue(groups_add_member($group2, $user1));
 533          $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 534  
 535          // delete everyone from all groups
 536          groups_delete_group_members($course->id);
 537          $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
 538      }
 539  
 540      /**
 541       * Test the functions quiz_create_attempt_handling_errors
 542       */
 543      public function test_quiz_create_attempt_handling_errors() {
 544          $this->resetAfterTest(true);
 545          $this->setAdminUser();
 546  
 547          // Make a quiz.
 548          $course = $this->getDataGenerator()->create_course();
 549          $user1 = $this->getDataGenerator()->create_user();
 550          $student = $this->getDataGenerator()->create_user();
 551          $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
 552          /** @var mod_quiz_generator $quizgenerator */
 553          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 554          /** @var core_question_generator $questiongenerator */
 555          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 556          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0, 'grade' => 100.0,
 557              'sumgrades' => 2]);
 558          // Create questions.
 559          $cat = $questiongenerator->create_question_category();
 560          $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
 561          $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
 562          // Add them to the quiz.
 563          quiz_add_quiz_question($saq->id, $quiz);
 564          quiz_add_quiz_question($numq->id, $quiz);
 565          $quizobj = quiz::create($quiz->id, $user1->id);
 566          $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 567          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 568          $timenow = time();
 569          // Create an attempt.
 570          $attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user1->id);
 571          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 572          quiz_attempt_save_started($quizobj, $quba, $attempt);
 573          $result = quiz_create_attempt_handling_errors($attempt->id, $quiz->cmid);
 574          $this->assertEquals($result->get_attemptid(), $attempt->id);
 575          try {
 576              $result = quiz_create_attempt_handling_errors($attempt->id, 9999);
 577              $this->fail('Exception expected due to invalid course module id.');
 578          } catch (\moodle_exception $e) {
 579              $this->assertEquals('invalidcoursemodule', $e->errorcode);
 580          }
 581          try {
 582              quiz_create_attempt_handling_errors(9999, $result->get_cmid());
 583              $this->fail('Exception expected due to quiz content change.');
 584          } catch (\moodle_exception $e) {
 585              $this->assertEquals('attempterrorcontentchange', $e->errorcode);
 586          }
 587          try {
 588              quiz_create_attempt_handling_errors(9999);
 589              $this->fail('Exception expected due to invalid quiz attempt id.');
 590          } catch (\moodle_exception $e) {
 591              $this->assertEquals('attempterrorinvalid', $e->errorcode);
 592          }
 593          // Set up as normal user without permission to view preview.
 594          $this->setUser($student->id);
 595          try {
 596              quiz_create_attempt_handling_errors(9999, $result->get_cmid());
 597              $this->fail('Exception expected due to quiz content change for user without permission.');
 598          } catch (\moodle_exception $e) {
 599              $this->assertEquals('attempterrorcontentchangeforuser', $e->errorcode);
 600          }
 601          try {
 602              quiz_create_attempt_handling_errors($attempt->id, 9999);
 603              $this->fail('Exception expected due to invalid course module id for user without permission.');
 604          } catch (\moodle_exception $e) {
 605              $this->assertEquals('invalidcoursemodule', $e->errorcode);
 606          }
 607      }
 608  }