Merge branch 'MDL-70422' of https://github.com/paulholden/moodle
[moodle.git] / mod / quiz / tests / generator / lib.php
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/>.
17 defined('MOODLE_INTERNAL') || die();
19 /**
20  * Quiz module test data generator class
21  *
22  * @package mod_quiz
23  * @copyright 2012 The Open University
24  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
26 class mod_quiz_generator extends testing_module_generator {
28     public function create_instance($record = null, array $options = null) {
29         global $CFG;
31         require_once($CFG->dirroot.'/mod/quiz/locallib.php');
32         $record = (object)(array)$record;
34         $defaultquizsettings = array(
35             'timeopen'               => 0,
36             'timeclose'              => 0,
37             'preferredbehaviour'     => 'deferredfeedback',
38             'attempts'               => 0,
39             'attemptonlast'          => 0,
40             'grademethod'            => QUIZ_GRADEHIGHEST,
41             'decimalpoints'          => 2,
42             'questiondecimalpoints'  => -1,
43             'attemptduring'          => 1,
44             'correctnessduring'      => 1,
45             'marksduring'            => 1,
46             'specificfeedbackduring' => 1,
47             'generalfeedbackduring'  => 1,
48             'rightanswerduring'      => 1,
49             'overallfeedbackduring'  => 0,
50             'attemptimmediately'          => 1,
51             'correctnessimmediately'      => 1,
52             'marksimmediately'            => 1,
53             'specificfeedbackimmediately' => 1,
54             'generalfeedbackimmediately'  => 1,
55             'rightanswerimmediately'      => 1,
56             'overallfeedbackimmediately'  => 1,
57             'attemptopen'            => 1,
58             'correctnessopen'        => 1,
59             'marksopen'              => 1,
60             'specificfeedbackopen'   => 1,
61             'generalfeedbackopen'    => 1,
62             'rightansweropen'        => 1,
63             'overallfeedbackopen'    => 1,
64             'attemptclosed'          => 1,
65             'correctnessclosed'      => 1,
66             'marksclosed'            => 1,
67             'specificfeedbackclosed' => 1,
68             'generalfeedbackclosed'  => 1,
69             'rightanswerclosed'      => 1,
70             'overallfeedbackclosed'  => 1,
71             'questionsperpage'       => 1,
72             'shuffleanswers'         => 1,
73             'sumgrades'              => 0,
74             'grade'                  => 100,
75             'timecreated'            => time(),
76             'timemodified'           => time(),
77             'timelimit'              => 0,
78             'overduehandling'        => 'autoabandon',
79             'graceperiod'            => 86400,
80             'quizpassword'           => '',
81             'subnet'                 => '',
82             'browsersecurity'        => '',
83             'delay1'                 => 0,
84             'delay2'                 => 0,
85             'showuserpicture'        => 0,
86             'showblocks'             => 0,
87             'navmethod'              => QUIZ_NAVMETHOD_FREE,
88         );
90         foreach ($defaultquizsettings as $name => $value) {
91             if (!isset($record->{$name})) {
92                 $record->{$name} = $value;
93             }
94         }
96         return parent::create_instance($record, (array)$options);
97     }
99     /**
100      * Create a quiz attempt for a particular user at a particular course.
101      *
102      * Currently this method can only create a first attempt for each
103      * user at each quiz. TODO remove this limitation.
104      *
105      * @param int $quizid the quiz id (from the mdl_quit table, not cmid).
106      * @param int $userid the user id.
107      * @param array $forcedrandomquestions slot => questionid. Optional,
108      *      used with random questions, to control which one is 'randomly' selected in that slot.
109      * @param array $forcedvariants slot => variantno. Optional. Optional,
110      *      used with question where get_num_variants is > 1, to control which
111      *      variants is 'randomly' selected.
112      * @return stdClass the new attempt.
113      */
114     public function create_attempt($quizid, $userid, array $forcedrandomquestions = [],
115             array $forcedvariants = []) {
116         // Build quiz object and load questions.
117         $quizobj = quiz::create($quizid, $userid);
119         if (quiz_get_user_attempts($quizid, $userid, 'all', true)) {
120             throw new coding_exception('mod_quiz_generator is currently limited to only ' .
121                     'be able to create one attempt for each user. (This should be fixed.)');
122         }
124         return quiz_prepare_and_start_new_attempt($quizobj, 1, null, false,
125                 $forcedrandomquestions, $forcedvariants);
126     }
128     /**
129      * Submit responses to a quiz attempt.
130      *
131      * To be realistic, you should ensure that $USER is set to the user whose attempt
132      * it is before calling this.
133      *
134      * @param int $attemptid the id of the attempt which is being
135      * @param array $responses array responses to submit. See description on
136      *      {@link core_question_generator::get_simulated_post_data_for_questions_in_usage()}.
137      * @param bool $checkbutton if simulate a click on the check button for each question, else simulate save.
138      *      This should only be used with behaviours that have a check button.
139      * @param bool $finishattempt if true, the attempt will be submitted.
140      */
141     public function submit_responses($attemptid, array $responses, $checkbutton, $finishattempt) {
142         $questiongenerator = $this->datagenerator->get_plugin_generator('core_question');
144         $attemptobj = quiz_attempt::create($attemptid);
146         $postdata = $questiongenerator->get_simulated_post_data_for_questions_in_usage(
147                 $attemptobj->get_question_usage(), $responses, $checkbutton);
149         $attemptobj->process_submitted_actions(time(), false, $postdata);
151         // Bit if a hack for interactive behaviour.
152         // TODO handle this in a more plugin-friendly way.
153         if ($checkbutton) {
154             $postdata = [];
155             foreach ($responses as $slot => $notused) {
156                 $qa = $attemptobj->get_question_attempt($slot);
157                 if ($qa->get_behaviour() instanceof qbehaviour_interactive && $qa->get_behaviour()->is_try_again_state()) {
158                     $postdata[$qa->get_control_field_name('sequencecheck')] = (string)$qa->get_sequence_check_count();
159                     $postdata[$qa->get_flag_field_name()] = (string)(int)$qa->is_flagged();
160                     $postdata[$qa->get_behaviour_field_name('tryagain')] = 1;
161                 }
162             }
164             if ($postdata) {
165                 $attemptobj->process_submitted_actions(time(), false, $postdata);
166             }
167         }
169         if ($finishattempt) {
170             $attemptobj->process_finish(time(), false);
171         }
172     }
174     /**
175      * Create a quiz override (either user or group).
176      *
177      * @param array $data must specify quizid, and one of userid or groupid.
178      */
179     public function create_override(array $data): void {
180         global $DB;
182         if (!isset($data['quiz'])) {
183             throw new coding_exception('Must specify quiz (id) when creating a quiz override.');
184         }
186         if (!isset($data['userid']) && !isset($data['groupid'])) {
187             throw new coding_exception('Must specify one of userid or groupid when creating a quiz override.');
188         }
190         if (isset($data['userid']) && isset($data['groupid'])) {
191             throw new coding_exception('Cannot specify both userid and groupid when creating a quiz override.');
192         }
194         $DB->insert_record('quiz_overrides', (object) $data);
195     }