MDL-14724 Students cannot see discussions in forums made by teachers if timedposts...
[moodle.git] / question / type / random / questiontype.php
CommitLineData
516cf3eb 1<?php // $Id$
1976496e 2/**
8b192edb 3 * Class for the random question type.
4 *
5 * The random question type does not have any options. When the question is
6 * attempted, it picks a question at random from the category it is in (and
7 * optionally its subcategories). For details see create_session_and_responses.
8 * Then all other method calls as delegated to that other question.
9 *
1976496e 10 * @package questionbank
11 * @subpackage questiontypes
53a4d39f 12 */
7518b645 13class random_qtype extends default_questiontype {
516cf3eb 14
8b192edb 15 // Caches questions available as randoms sorted by category
16 // This is a 2-d array. The first key is question category, and the
17 // second is whether to include subcategories.
516cf3eb 18 var $catrandoms = array();
19
20 function name() {
21 return 'random';
22 }
23
a2156789 24 function menu_name() {
8b192edb 25 // Don't include this question type in the 'add new question' menu.
a2156789 26 return false;
27 }
28
29 function is_usable_by_random() {
30 return false;
31 }
32
516cf3eb 33 function get_question_options(&$question) {
34 // Don't do anything here, because the random question has no options.
35 // Everything is handled by the create- or restore_session_and_responses
36 // functions.
37 return true;
38 }
39
f59dba84 40 /**
41 * Random questions always get a question name that is Random (cateogryname).
42 * This function is a centralised place to calculate that, given the category.
43 */
44 function question_name($category) {
45 return get_string('random', 'quiz') .' ('. $category->name .')';
46 }
47
48 function save_question($question, $form, $course) {
49 // If the category is changing, set things up as default_questiontype::save_question expects.
50 list($formcategory, $unused) = explode(',', $form->category);
51 if (isset($question->id) && $formcategory != $question->category) {
52 $form->categorymoveto = $form->category;
53 }
54 $form->name = '';
55 $question = parent::save_question($question, $form, $course);
56 if (!$category = get_record('question_categories', 'id', $question->category)) {
5a2a5331 57 print_error('Could retrieve question category');
f59dba84 58 }
59 $question->name = $this->question_name($category);
60 if (!set_field('question', 'name', $question->name, 'id', $question->id)) {
5a2a5331 61 print_error('Could not update random question name');
f59dba84 62 }
63 return $question;
64 }
65
516cf3eb 66 function save_question_options($question) {
8b192edb 67 // No options, but we set the parent field to the question's own id.
68 // Setting the parent field has the effect of hiding this question in
69 // various places.
4f48fb42 70 return (set_field('question', 'parent', $question->id, 'id',
516cf3eb 71 $question->id) ? true : false);
72 }
73
74 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
a2156789 75 global $QTYPE_EXCLUDE_FROM_RANDOM;
516cf3eb 76 // Choose a random question from the category:
77 // We need to make sure that no question is used more than once in the
78 // quiz. Therfore the following need to be excluded:
79 // 1. All questions that are explicitly assigned to the quiz
80 // 2. All random questions
81 // 3. All questions that are already chosen by an other random question
5dba6590 82 // 4. Deleted questions
516cf3eb 83 if (!isset($cmoptions->questionsinuse)) {
84 $cmoptions->questionsinuse = $attempt->layout;
85 }
86
87 if (!isset($this->catrandoms[$question->category][$question->questiontext])) {
88 // Need to fetch random questions from category $question->category"
89 // (Note: $this refers to the questiontype, not the question.)
90 global $CFG;
516cf3eb 91 if ($question->questiontext == "1") {
92 // recurse into subcategories
dc1f00de 93 $categorylist = question_categorylist($question->category);
516cf3eb 94 } else {
95 $categorylist = $question->category;
96 }
5dba6590 97 if ($catrandoms = get_records_select('question',
98 "category IN ($categorylist)
516cf3eb 99 AND parent = '0'
5dba6590 100 AND hidden = '0'
516cf3eb 101 AND id NOT IN ($cmoptions->questionsinuse)
5dba6590 102 AND qtype NOT IN ($QTYPE_EXCLUDE_FROM_RANDOM)", '', 'id')) {
516cf3eb 103 $this->catrandoms[$question->category][$question->questiontext] =
5dba6590 104 draw_rand_array($catrandoms, count($catrandoms));
516cf3eb 105 } else {
106 $this->catrandoms[$question->category][$question->questiontext] = array();
107 }
108 }
109
110 while ($wrappedquestion =
111 array_pop($this->catrandoms[$question->category][$question->questiontext])) {
112 if (!ereg("(^|,)$wrappedquestion->id(,|$)", $cmoptions->questionsinuse)) {
113 /// $randomquestion is not in use and will therefore be used
114 /// as the randomquestion here...
4f48fb42 115 $wrappedquestion = get_record('question', 'id', $wrappedquestion->id);
f02c6f01 116 global $QTYPES;
117 $QTYPES[$wrappedquestion->qtype]
516cf3eb 118 ->get_question_options($wrappedquestion);
f02c6f01 119 $QTYPES[$wrappedquestion->qtype]
516cf3eb 120 ->create_session_and_responses($wrappedquestion,
121 $state, $cmoptions, $attempt);
122 $wrappedquestion->name_prefix = $question->name_prefix;
123 $wrappedquestion->maxgrade = $question->maxgrade;
124 $cmoptions->questionsinuse .= ",$wrappedquestion->id";
125 $state->options->question = &$wrappedquestion;
126 return true;
127 }
128 }
129 $question->questiontext = '<span class="notifyproblem">'.
130 get_string('toomanyrandom', 'quiz'). '</span>';
dfa47f96 131 $question->qtype = 'description';
516cf3eb 132 $state->responses = array('' => '');
133 return true;
134 }
135
136 function restore_session_and_responses(&$question, &$state) {
137 /// The raw response records for random questions come in two flavours:
138 /// ---- 1 ----
139 /// For responses stored by Moodle version 1.5 and later the answer
140 /// field has the pattern random#-* where the # part is the numeric
141 /// question id of the actual question shown in the quiz attempt
142 /// and * represents the student response to that actual question.
143 /// ---- 2 ----
144 /// For responses stored by older Moodle versions - the answer field is
145 /// simply the question id of the actual question. The student response
146 /// to the actual question is stored in a separate response record.
147 /// -----------------------
148 /// This means that prior to Moodle version 1.5, random questions needed
149 /// two response records for storing the response to a single question.
150 /// From version 1.5 and later the question type random works like all
151 /// the other question types in that it now only needs one response
152 /// record per question.
f02c6f01 153 global $QTYPES;
516cf3eb 154 if (!ereg('^random([0-9]+)-(.*)$', $state->responses[''], $answerregs)) {
155 if (empty($state->responses[''])) {
156 // This is the case if there weren't enough questions available in the category.
157 $question->questiontext = '<span class="notifyproblem">'.
158 get_string('toomanyrandom', 'quiz'). '</span>';
dfa47f96 159 $question->qtype = 'description';
516cf3eb 160 return true;
161 }
162 // this must be an old-style state which stores only the id for the wrapped question
4f48fb42 163 if (!$wrappedquestion = get_record('question', 'id', $state->responses[''])) {
516cf3eb 164 notify("Can not find wrapped question {$state->responses['']}");
165 }
166 // In the old model the actual response was stored in a separate entry in
167 // the state table and fortunately there was only a single state per question
4f48fb42 168 if (!$state->responses[''] = get_field('question_states', 'answer', 'attempt', $state->attempt, 'question', $wrappedquestion->id)) {
516cf3eb 169 notify("Wrapped state missing");
170 }
171 } else {
4f48fb42 172 if (!$wrappedquestion = get_record('question', 'id', $answerregs[1])) {
4572d78f 173 // The teacher must have deleted this question by mistake
174 // Convert it into a description type question with an explanation to the student
175 $wrappedquestion = clone($question);
176 $wrappedquestion->id = $answerregs[1];
177 $wrappedquestion->questiontext = get_string('questiondeleted', 'quiz');
f57c6242 178 $wrappedquestion->qtype = 'missingtype';
516cf3eb 179 }
180 $state->responses[''] = (false === $answerregs[2]) ? '' : $answerregs[2];
181 }
182
f02c6f01 183 if (!$QTYPES[$wrappedquestion->qtype]
516cf3eb 184 ->get_question_options($wrappedquestion)) {
185 return false;
186 }
187
f02c6f01 188 if (!$QTYPES[$wrappedquestion->qtype]
516cf3eb 189 ->restore_session_and_responses($wrappedquestion, $state)) {
190 return false;
191 }
192 $wrappedquestion->name_prefix = $question->name_prefix;
193 $wrappedquestion->maxgrade = $question->maxgrade;
194 $state->options->question = &$wrappedquestion;
195 return true;
196 }
197
198 function save_session_and_responses(&$question, &$state) {
f02c6f01 199 global $QTYPES;
516cf3eb 200 $wrappedquestion = &$state->options->question;
201
202 // Trick the wrapped question into pretending to be the random one.
203 $realqid = $wrappedquestion->id;
204 $wrappedquestion->id = $question->id;
f02c6f01 205 $QTYPES[$wrappedquestion->qtype]
516cf3eb 206 ->save_session_and_responses($wrappedquestion, $state);
207
208 // Read what the wrapped question has just set the answer field to
209 // (if anything)
4f48fb42 210 $response = get_field('question_states', 'answer', 'id', $state->id);
516cf3eb 211 if(false === $response) {
212 return false;
213 }
214
215 // Prefix the answer field...
216 $response = "random$realqid-$response";
217
218 // ... and save it again.
4f48fb42 219 if (!set_field('question_states', 'answer', addslashes($response), 'id', $state->id)) {
516cf3eb 220 return false;
221 }
222
223 // Restore the real id
224 $wrappedquestion->id = $realqid;
225 return true;
226 }
227
228 function get_correct_responses(&$question, &$state) {
f02c6f01 229 global $QTYPES;
516cf3eb 230 $wrappedquestion = &$state->options->question;
f02c6f01 231 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 232 ->get_correct_responses($wrappedquestion, $state);
233 }
234
235 // ULPGC ecastro
236 function get_all_responses(&$question, &$state){
f02c6f01 237 global $QTYPES;
516cf3eb 238 $wrappedquestion = &$state->options->question;
f02c6f01 239 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 240 ->get_all_responses($wrappedquestion, $state);
241 }
242
243 // ULPGC ecastro
244 function get_actual_response(&$question, &$state){
f02c6f01 245 global $QTYPES;
516cf3eb 246 $wrappedquestion = &$state->options->question;
f02c6f01 247 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 248 ->get_actual_response($wrappedquestion, $state);
249 }
250
99ba746d 251 function get_html_head_contributions(&$question, &$state) {
252 global $QTYPES;
253 $wrappedquestion = &$state->options->question;
254 return $QTYPES[$wrappedquestion->qtype]
255 ->get_html_head_contributions($wrappedquestion, $state);
256 }
516cf3eb 257
258 function print_question(&$question, &$state, &$number, $cmoptions, $options) {
f02c6f01 259 global $QTYPES;
516cf3eb 260 $wrappedquestion = &$state->options->question;
8bb3fac1 261 $wrappedquestion->randomquestionid = $question->id;
f02c6f01 262 $QTYPES[$wrappedquestion->qtype]
516cf3eb 263 ->print_question($wrappedquestion, $state, $number, $cmoptions, $options);
264 }
265
266 function grade_responses(&$question, &$state, $cmoptions) {
f02c6f01 267 global $QTYPES;
516cf3eb 268 $wrappedquestion = &$state->options->question;
f02c6f01 269 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 270 ->grade_responses($wrappedquestion, $state, $cmoptions);
271 }
272
273 function get_texsource(&$question, &$state, $cmoptions, $type) {
f02c6f01 274 global $QTYPES;
516cf3eb 275 $wrappedquestion = &$state->options->question;
f02c6f01 276 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 277 ->get_texsource($wrappedquestion, $state, $cmoptions, $type);
278 }
279
280 function compare_responses(&$question, $state, $teststate) {
f02c6f01 281 global $QTYPES;
bb080d20 282 $wrappedquestion = &$teststate->options->question;
f02c6f01 283 return $QTYPES[$wrappedquestion->qtype]
516cf3eb 284 ->compare_responses($wrappedquestion, $state, $teststate);
285 }
286
287}
288//// END OF CLASS ////
289
290//////////////////////////////////////////////////////////////////////////
291//// INITIATION - Without this line the question type is not in use... ///
292//////////////////////////////////////////////////////////////////////////
a2156789 293question_register_questiontype(new random_qtype());
516cf3eb 294
295?>