Fix for bug 3288.
[moodle.git] / mod / quiz / questiontypes / random / questiontype.php
CommitLineData
d1290cec 1<?php // $Id$
8966a111 2
3//////////////
4/// RANDOM ///
5//////////////
6
7/// QUESTION TYPE CLASS //////////////////
8class quiz_random_qtype extends quiz_default_questiontype {
9
10 var $possiblerandomqtypes = array(SHORTANSWER,
11 NUMERICAL,
12 MULTICHOICE,
13 MATCH,
14 // RANDOMSAMATCH,// Can cause unexpected outcomes
15 TRUEFALSE,
16 MULTIANSWER);
17
18 // Carries questions available as randoms sorted by category
19 // This array is used when needed only
20 var $catrandoms = array();
21
22 function name() {
23 return 'random';
24 }
25
ee1fb969 26 function get_question_options(&$question) {
27 // Don't do anything here, because the random question has no options.
28 // Everything is handled by the create- or restore_session_and_responses
29 // functions.
8966a111 30 return true;
31 }
32
ee1fb969 33 function save_question_options($question) {
34 // No options, but we use the parent field to hide random questions.
35 // To avoid problems we set the parent field to the question id.
36 return (set_field('quiz_questions', 'parent', $question->id, 'id',
37 $question->id) ? true : false);
38 }
39
40 function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
41 // Choose a random question from the category:
42 // We need to make sure that no question is used more than once in the
43 // quiz. Therfore the following need to be excluded:
44 // 1. All questions that are explicitly assigned to the quiz
45 // 2. All random questions
46 // 3. All questions that are already chosen by an other random question
47 if (!isset($quiz->questionsinuse)) {
48 $quiz->questionsinuse = $quiz->questions;
49 }
50
51 if (!isset($this->catrandoms[$question->category])) {
52 // Need to fetch random questions from category $question->category"
53 // (Note: $this refers to the questiontype, not the question.)
54 global $CFG;
55 $possiblerandomqtypes = "'"
56 . implode("','", $this->possiblerandomqtypes) . "'";
57 if ($question->questiontext == "1") {
58 // recurse into subcategories
59 $categorylist = quiz_categorylist($question->category);
60 } else {
61 $categorylist = $question->category;
8966a111 62 }
ee1fb969 63 $this->catrandoms[$question->category] = get_records_sql
64 ("SELECT * FROM {$CFG->prefix}quiz_questions
65 WHERE category IN ($categorylist)
66 AND parent = '0'
67 AND id NOT IN ($quiz->questionsinuse)
68 AND qtype IN ($possiblerandomqtypes)");
69 $this->catrandoms[$question->category] =
70 draw_rand_array($this->catrandoms[$question->category],
71 count($this->catrandoms[$question->category])); // from bug 1889
8966a111 72 }
ee1fb969 73
74 while ($wrappedquestion =
75 array_pop($this->catrandoms[$question->category])) {
76 if (!ereg("(^|,)$wrappedquestion->id(,|$)", $quiz->questionsinuse)) {
77 /// $randomquestion is not in use and will therefore be used
78 /// as the randomquestion here...
79
80 global $QUIZ_QTYPES;
81 $QUIZ_QTYPES[$wrappedquestion->qtype]
82 ->get_question_options($wrappedquestion);
57b77d43 83
84 // Backup the original state of the random question
85 // And change the $state to match the wrapped question. This
86 // is sensible, because so the wrapped question's state gets
87 // put through all the generic processing.
88 $state->options->state = clone($state);
89 $state->question = $wrappedquestion->id;
ee1fb969 90 $QUIZ_QTYPES[$wrappedquestion->qtype]
57b77d43 91 ->create_session_and_responses($wrappedquestion,
92 $state, $quiz, $attempt);
ee1fb969 93 $wrappedquestion->name_prefix = $question->name_prefix;
94 $wrappedquestion->maxgrade = $question->maxgrade;
95 $quiz->questionsinuse .= ",$wrappedquestion->id";
ee1fb969 96 $state->options->question = &$wrappedquestion;
97 return true;
98 }
99 }
100 notify(get_string('toomanyrandom', 'quiz', $question->category));
8966a111 101 return false;
102 }
103
ee1fb969 104 function restore_session_and_responses(&$question, &$state) {
105 global $QUIZ_QTYPES;
57b77d43 106 if(!$randomstate = get_record('quiz_states', 'question',
16d85f57 107 $question->id, 'attempt', $state->attempt)) {
ee1fb969 108 return false;
109 }
ee1fb969 110
57b77d43 111 if (!$wrappedquestion = get_record('quiz_questions', 'id',
112 $randomstate->answer)) {
ee1fb969 113 return false;
114 }
57b77d43 115 $state->question = $wrappedquestion->id;
ee1fb969 116
117 if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
118 ->get_question_options($wrappedquestion)) {
119 return false;
120 }
121
356d8295 122 // We need to set responses[''] to whatever was saved in the most recent
123 // state of the wrapped question.
124 if(!$wrappedstates = get_records_select('quiz_states',
125 "question = $wrappedquestion->id AND attempt = $state->attempt",
126 'seq_number DESC')) {
127 return false;
128 }
129 $wrappedstates = array_values($wrappedstates);
130 $state->responses = array('' => $wrappedstates[0]->answer);
131
ee1fb969 132 if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
133 ->restore_session_and_responses($wrappedquestion, $state)) {
134 return false;
135 }
136 $wrappedquestion->name_prefix = $question->name_prefix;
137 $wrappedquestion->maxgrade = $question->maxgrade;
ee1fb969 138 $state->options->question = &$wrappedquestion;
57b77d43 139 $state->options->state = &$randomstate;
ee1fb969 140 return true;
141 }
142
143 function save_session_and_responses(&$question, &$state) {
144 global $QUIZ_QTYPES;
145 $wrappedquestion = &$state->options->question;
57b77d43 146 $randomstate = &$state->options->state;
147
148 // We need to save the randomstate manually, because we can only process
149 // one response record automatically
150 if (empty($randomstate->id)) {
151 $randomstate->answer = $wrappedquestion->id;
152 $randomstate->id = insert_record('quiz_states', $randomstate);
153 }
ee1fb969 154
155 // Trick the wrapped question into pretending to be the random one.
156 $realqid = $wrappedquestion->id;
157 $wrappedquestion->id = $question->id;
158 $QUIZ_QTYPES[$wrappedquestion->qtype]
159 ->save_session_and_responses($wrappedquestion, $state);
160
ee1fb969 161 // Restore the real id
162 $wrappedquestion->id = $realqid;
163 return true;
164 }
165
166 function get_correct_responses(&$question, &$state) {
167 global $QUIZ_QTYPES;
168 $wrappedquestion = &$state->options->question;
169 return $QUIZ_QTYPES[$wrappedquestion->qtype]
170 ->get_correct_responses($wrappedquestion, $state);
171 }
172
0fc59399 173 // ULPGC ecastro
174 function get_all_responses($question, $state){
175 global $QUIZ_QTYPES;
176 $wrappedquestion = &$state->options->question;
177 return $QUIZ_QTYPES[$wrappedquestion->qtype]
57b77d43 178 ->get_all_responses($wrappedquestion, $state);
0fc59399 179 }
180
181 // ULPGC ecastro
182 function get_actual_response($question, $state){
183 global $QUIZ_QTYPES;
184 $wrappedquestion = &$state->options->question;
185 return $QUIZ_QTYPES[$wrappedquestion->qtype]
57b77d43 186 ->get_actual_response($wrappedquestion, $state);
0fc59399 187 }
188
189
190 function print_question(&$question, &$state, &$number, $quiz, $options) {
ee1fb969 191 global $QUIZ_QTYPES;
192 $wrappedquestion = &$state->options->question;
193 $QUIZ_QTYPES[$wrappedquestion->qtype]
194 ->print_question($wrappedquestion, $state, $number, $quiz, $options);
195 }
196/*
197 function print_question_grading_details(&$question, &$state, $quiz,
198 $options) {
199 global $QUIZ_QTYPES;
200 $wrappedquestion = &$state->options->question;
201 $QUIZ_QTYPES[$wrappedquestion->qtype]
202 ->print_question_grading_details($wrappedquestion, $state, $quiz,
203 $options);
204 }
205
206 function print_question_formulation_and_controls(&$question, &$state, $quiz,
207 $options) {
208 global $QUIZ_QTYPES;
209 $wrappedquestion = &$state->options->question;
210 $QUIZ_QTYPES[$wrappedquestion->qtype]
211 ->print_question_formulation_and_controls($wrappedquestion, $state,
212 $quiz, $options);
213 }
214
215 function print_question_submit_buttons(&$question, &$state, $quiz,
216 $options) {
217 global $QUIZ_QTYPES;
218 $wrappedquestion = &$state->options->question;
219 $QUIZ_QTYPES[$wrappedquestion->qtype]
220 ->print_question_submit_buttons($wrappedquestion, $state, $quiz,
221 $options);
222 }
223*/
224 function grade_responses(&$question, &$state, $quiz) {
225 global $QUIZ_QTYPES;
226 $wrappedquestion = &$state->options->question;
227 return $QUIZ_QTYPES[$wrappedquestion->qtype]
228 ->grade_responses($wrappedquestion, $state, $quiz);
229 }
230
231 function get_texsource(&$question, &$state, $quiz, $type) {
232 global $QUIZ_QTYPES;
233 $wrappedquestion = &$state->options->question;
234 return $QUIZ_QTYPES[$wrappedquestion->qtype]
235 ->get_texsource($wrappedquestion, $state, $quiz, $type);
236 }
237
238 function compare_responses(&$question, $state, $teststate) {
239 global $QUIZ_QTYPES;
240 $wrappedquestion = &$state->options->question;
241 return $QUIZ_QTYPES[$wrappedquestion->qtype]
242 ->compare_responses($wrappedquestion, $state, $teststate);
243 }
244
245 function print_replacement_options($question, $course, $quizid='0') {
246 global $QUIZ_QTYPES;
247 $wrappedquestion = &$state->options->question;
248 return $QUIZ_QTYPES[$wrappedquestion->qtype]
249 ->print_replacement_options($wrappedquestion, $state, $quizid);
250 }
251
252 function print_question_form_end($question, $submitscript='') {
253 global $QUIZ_QTYPES;
254 $wrappedquestion = &$state->options->question;
255 return $QUIZ_QTYPES[$wrappedquestion->qtype]
256 ->print_question_form_end($wrappedquestion, $state, $quizid);
257 }
258/*
8966a111 259 function convert_to_response_answer_field($questionresponse) {
8966a111 260 global $QUIZ_QTYPES;
261
262 foreach ($questionresponse as $key => $response) {
263 if (ereg('[^0-9][0-9]+random$', $key)) {
264 unset($questionresponse[$key]);
265 $randomquestion = get_record('quiz_questions',
266 'id', $response);
267 return "random$response-"
268 .$QUIZ_QTYPES[$randomquestion->qtype]
269 ->convert_to_response_answer_field($questionresponse);
270 }
271 }
272 return '';
273 }
274
ee1fb969 275
276
8966a111 277 function create_response($question, $nameprefix, $questionsinuse) {
278 // It's for question types like RANDOMSAMATCH and RANDOM that
279 // the true power of the pattern with this function comes to the surface.
c03d7832 280
8966a111 281
ee1fb969 282 }
8966a111 283
ee1fb969 284
285*/
286 /*
287 function print_question_formulation_and_controls($question,
288 $quiz, $readonly, $answers, $correctanswers, $nameprefix) {
289 global $QUIZ_QTYPES;
290
291 // Get the wrapped question...
292 if ($actualquestion = $this->get_wrapped_question($question,
293 $nameprefix)) {
294 echo '<input type="hidden" name="' . $nameprefix
295 . '" value="' . $actualquestion->id . '" />';
296 return $QUIZ_QTYPES[$actualquestion->qtype]
297 ->print_question_formulation_and_controls($actualquestion,
298 $quiz, $readonly, $answers, $correctanswers,
299 quiz_qtype_nameprefix($actualquestion, $nameprefix));
300 } else {
301 echo '<p>' . get_string('random', 'quiz') . '</p>';
8966a111 302 }
ee1fb969 303 }
8966a111 304
8966a111 305
ee1fb969 306 function get_wrapped_question($question, $nameprefix) {
307 if (!empty($question->response[$nameprefix])
308 and $actualquestion = get_record('quiz_questions',
309 'id', $question->response[$nameprefix])) {
310 $actualquestion->response = $question->response;
311 unset($actualquestion->response[$nameprefix]);
312 $actualquestion->maxgrade = $question->maxgrade;
313 return $actualquestion;
314 } else {
315 return false;
8966a111 316 }
8966a111 317 }
318
ee1fb969 319 function grade_response($question, $nameprefix) {
320 global $QUIZ_QTYPES;
321
322 // Get the wrapped question...
323 if ($actualquestion = $this->get_wrapped_question($question,
324 $nameprefix)) {
325 return $QUIZ_QTYPES[$actualquestion->qtype]->grade_response(
326 $actualquestion,
327 quiz_qtype_nameprefix($actualquestion, $nameprefix));
328 } else {
329 $result->grade = 0.0;
330 $result->answers = array();
331 $result->correctanswers = array();
332 return $result;
333 }
334 }
335
336*/
337
8966a111 338 function extract_response($rawresponse, $nameprefix) {
339 global $QUIZ_QTYPES;
c03d7832 340
341 /// The raw response records for random questions come in two flavours:
342 /// ---- 1 ----
343 /// For responses stored by Moodle version 1.5 and later the answer
344 /// field has the pattern random#-* where the # part is the numeric
345 /// question id of the actual question shown in the quiz attempt
346 /// and * represents the student response to that actual question.
347 /// ---- 2 ----
348 /// For responses stored by older Moodle versions - the answer field is
349 /// simply the question id of the actual question. The student response
350 /// to the actual question is stored in a separate response record.
351 /// -----------------------
352 /// This means that prior to Moodle version 1.5, random questions needed
353 /// two response records for storing the response to a single question.
354 /// From version 1.5 and later the question type random works like all
355 /// the other question types in that it now only needs one response
356 /// record per question.
357 /// Because updating the old response records to fit the new response
358 /// record format could need hours of CPU time and the equivalent
359 /// amount of down time for the Moodle site and because a response
360 /// storage with two response formats for random question only effect
361 /// this function, where the response record is translated, this
362 /// function is now able to handle both types of response record.
363
364
ee1fb969 365 // Pick random question id from the answer field in a way that
366 /// works for both formats:
c03d7832 367 if (!ereg('^(random)?([0-9]+)(-(.*))?$', $rawresponse->answer, $answerregs)) {
368 error("The answer value '$rawresponse->answer' for the response with "
369 ."id=$rawresponse->id to the random question "
370 ."$rawresponse->question is malformated."
371 ." - No response can be extracted!");
372 }
373 $randomquestionid = $answerregs[2];
374
8966a111 375 if ($randomquestion = get_record('quiz_questions',
c03d7832 376 'id', $randomquestionid)) {
377
378 if ($answerregs[1] && $answerregs[3]) {
ee1fb969 379 // The raw response is formatted according to
380 // Moodle version 1.5 or later
c03d7832 381 $randomresponse = $rawresponse;
382 $randomresponse->question = $randomquestionid;
383 $randomresponse->answer = $answerregs[4];
384
385 } else if ($randomresponse = get_record
8966a111 386 ('quiz_responses', 'question', $rawresponse->answer,
387 'attempt', $rawresponse->attempt)) {
ee1fb969 388 // The response was stored by an older version of Moodle
389 // :-)
390
c03d7832 391 } else {
392 notify("Error: Cannot find response to random question $randomquestionid");
393 unset($randomresponse);
394 }
ee1fb969 395
c03d7832 396 if (isset($randomresponse)) {
8966a111 397 /// The prefered case:
ee1fb969 398 /// There is a random question and a response field, from
c03d7832 399 /// which the response array can be extracted:
8966a111 400
8966a111 401
402 } else {
8966a111 403
404 /// Instead: workaround by creating a new response:
405 $response = $QUIZ_QTYPES[$randomquestion->qtype]
406 ->create_response($randomquestion,
407 quiz_qtype_nameprefix($randomquestion, $nameprefix),
c03d7832 408 "$rawresponse->question,$randomquestionid");
8966a111 409 // (That last argument is instead of $questionsinuse.
410 // It is not correct but it would be very messy to
411 // determine the correct value, while very few
412 // question types actually use it and they who do have
413 // good chances to execute properly anyway.)
414 }
c03d7832 415 $response[$nameprefix] = $randomquestionid;
ee1fb969 416 //return $response;
417 return '';
8966a111 418 } else {
419 notify("Error: Unable to find random question $rawresponse->question");
420 /// No new random question is picked as this is probably
421 /// not what the moodle user has in mind anyway
422 return array();
423 }
424 }
8966a111 425}
426//// END OF CLASS ////
427
428//////////////////////////////////////////////////////////////////////////
429//// INITIATION - Without this line the question type is not in use... ///
430//////////////////////////////////////////////////////////////////////////
431$QUIZ_QTYPES[RANDOM]= new quiz_random_qtype();
432
433?>