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]

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