Fixed bug in make_mod_upload_directory() vecause of new lang packs
[moodle.git] / question / questiontypes / match / questiontype.php
CommitLineData
516cf3eb 1<?php // $Id$
2
3/////////////
4/// MATCH ///
5/////////////
6
7/// QUESTION TYPE CLASS //////////////////
af3830ee 8class question_match_qtype extends default_questiontype {
516cf3eb 9
10 function name() {
11 return 'match';
12 }
13
14 function get_question_options(&$question) {
32a189d6 15 $question->options = get_record('question_match', 'question', $question->id);
16 $question->options->subquestions = get_records("question_match_sub", "question", $question->id, "id ASC" );
516cf3eb 17 return true;
18 }
19
20 function save_question_options($question) {
21
32a189d6 22 if (!$oldsubquestions = get_records("question_match_sub", "question", $question->id, "id ASC")) {
516cf3eb 23 $oldsubquestions = array();
24 }
25
26 // following hack to check at least three answers exist
27 $answercount = 0;
28 foreach ($question->subquestions as $key=>$questiontext) {
29 $answertext = $question->subanswers[$key];
30 if (!empty($questiontext) or !empty($answertext)) {
31 $answercount++;
32 }
33 }
34 $answercount += count($oldsubquestions);
35 if ($answercount < 3) { // check there are at lest 3 answers for matching type questions
36 $result->notice = get_string("notenoughanswers", "quiz", "3");
37 return $result;
38 }
39
40 // $subquestions will be an array with subquestion ids
41 $subquestions = array();
42
43 // Insert all the new question+answer pairs
44 foreach ($question->subquestions as $key => $questiontext) {
45 $answertext = $question->subanswers[$key];
46 if (!empty($questiontext) or !empty($answertext)) {
47 if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
48 $subquestion->questiontext = $questiontext;
49 $subquestion->answertext = $answertext;
32a189d6 50 if (!update_record("question_match_sub", $subquestion)) {
516cf3eb 51 $result->error = "Could not insert quiz match subquestion! (id=$subquestion->id)";
52 return $result;
53 }
54 } else {
55 unset($subquestion);
56 // Determine a unique random code
57 $subquestion->code = rand(1,999999999);
18bd0d68 58 while (record_exists('question_match_sub', 'code', $subquestion->code, 'question', $question->id)) {
516cf3eb 59 $subquestion->code = rand();
60 }
61 $subquestion->question = $question->id;
62 $subquestion->questiontext = $questiontext;
63 $subquestion->answertext = $answertext;
32a189d6 64 if (!$subquestion->id = insert_record("question_match_sub", $subquestion)) {
516cf3eb 65 $result->error = "Could not insert quiz match subquestion!";
66 return $result;
67 }
68 }
69 $subquestions[] = $subquestion->id;
70 }
71 }
72
73 // delete old subquestions records
74 if (!empty($oldsubquestions)) {
75 foreach($oldsubquestions as $os) {
32a189d6 76 delete_records('question_match_sub', 'id', $os->id);
516cf3eb 77 }
78 }
79
80 if (count($subquestions) < 3) {
81 $result->noticeyesno = get_string("notenoughsubquestions", "quiz");
82 return $result;
83 }
84
32a189d6 85 if ($options = get_record("question_match", "question", $question->id)) {
516cf3eb 86 $options->subquestions = implode(",",$subquestions);
87 $options->shuffleanswers = $question->shuffleanswers;
32a189d6 88 if (!update_record("question_match", $options)) {
516cf3eb 89 $result->error = "Could not update quiz match options! (id=$options->id)";
90 return $result;
91 }
92 } else {
93 unset($options);
94 $options->question = $question->id;
95 $options->subquestions = implode(",",$subquestions);
96 $options->shuffleanswers = $question->shuffleanswers;
32a189d6 97 if (!insert_record("question_match", $options)) {
516cf3eb 98 $result->error = "Could not insert quiz match options!";
99 return $result;
100 }
101 }
102 return true;
103 }
104
105 /**
106 * Deletes question from the question-type specific tables
107 *
108 * @return boolean Success/Failure
109 * @param integer $question->id
110 */
90c3f310 111 function delete_question($questionid) {
112 delete_records("question_match", "question", $questionid);
113 delete_records("question_match_sub", "question", $questionid);
516cf3eb 114 return true;
115 }
116
117 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
32a189d6 118 if (!$state->options->subquestions = get_records('question_match_sub',
516cf3eb 119 'question', $question->id)) {
120 notify('Error: Missing subquestions!');
121 return false;
122 }
123
124 foreach ($state->options->subquestions as $key => $subquestion) {
125 // This seems rather over complicated, but it is useful for the
126 // randomsamatch questiontype, which can then inherit the print
127 // and grading functions. This way it is possible to define multiple
128 // answers per question, each with different marks and feedback.
129 $answer = new stdClass();
130 $answer->id = $subquestion->code;
131 $answer->answer = $subquestion->answertext;
132 $answer->fraction = 1.0;
133 $state->options->subquestions[$key]->options
134 ->answers[$subquestion->code] = clone($answer);
135
136 $state->responses[$key] = '';
137 }
138
139 // Shuffle the answers if required
140 if ($cmoptions->shuffleanswers and $question->options->shuffleanswers) {
141 $state->options->subquestions = swapshuffle_assoc($state->options->subquestions);
142 }
143
144 return true;
145 }
146
147 function restore_session_and_responses(&$question, &$state) {
148 // The serialized format for matching questions is a comma separated
149 // list of question answer pairs (e.g. 1-1,2-3,3-2), where the ids of
32a189d6 150 // both refer to the id in the table question_match_sub.
516cf3eb 151 $responses = explode(',', $state->responses['']);
152 $responses = array_map(create_function('$val',
153 'return explode("-", $val);'), $responses);
154
32a189d6 155 if (!$questions = get_records('question_match_sub',
516cf3eb 156 'question', $question->id)) {
157 notify('Error: Missing subquestions!');
158 return false;
159 }
160
161 // Restore the previous responses and place the questions into the state options
162 $state->responses = array();
163 $state->options->subquestions = array();
164 foreach ($responses as $response) {
165 $state->responses[$response[0]] = $response[1];
166 $state->options->subquestions[$response[0]] = $questions[$response[0]];
167 }
168
169 foreach ($state->options->subquestions as $key => $subquestion) {
170 // This seems rather over complicated, but it is useful for the
171 // randomsamatch questiontype, which can then inherit the print
172 // and grading functions. This way it is possible to define multiple
173 // answers per question, each with different marks and feedback.
174 $answer = new stdClass();
175 $answer->id = $subquestion->code;
176 $answer->answer = $subquestion->answertext;
177 $answer->fraction = 1.0;
178 $state->options->subquestions[$key]->options
179 ->answers[$subquestion->code] = clone($answer);
180 }
181
182 return true;
183 }
184
185 function save_session_and_responses(&$question, &$state) {
186 // Serialize responses
187 $responses = array();
188 foreach ($state->options->subquestions as $key => $subquestion) {
189 $responses[] = $key.'-'.($state->responses[$key] ? $state->responses[$key] : 0);
190 }
191 $responses = implode(',', $responses);
192
193 // Set the legacy answer field
4f48fb42 194 if (!set_field('question_states', 'answer', $responses, 'id',
516cf3eb 195 $state->id)) {
196 return false;
197 }
198 return true;
199 }
200
201 function get_correct_responses(&$question, &$state) {
202 $responses = array();
203 foreach ($state->options->subquestions as $sub) {
204 foreach ($sub->options->answers as $answer) {
205 if (1 == $answer->fraction) {
206 $responses[$sub->id] = $answer->id;
207 }
208 }
209 }
210 return empty($responses) ? null : $responses;
211 }
212
213 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
37a12367 214 global $CFG;
516cf3eb 215 $subquestions = $state->options->subquestions;
216 $correctanswers = $this->get_correct_responses($question, $state);
217 $nameprefix = $question->name_prefix;
218 $answers = array();
219 $responses = &$state->responses;
220
7347c60b 221 $formatoptions->noclean = true;
222 $formatoptions->para = false;
516cf3eb 223
224 foreach ($subquestions as $subquestion) {
225 foreach ($subquestion->options->answers as $ans) {
226 $answers[$ans->id] = $ans->answer;
227 }
228 }
229
230 // Shuffle the answers
231 $answers = draw_rand_array($answers, count($answers));
232
37a12367 233 // Print formulation
234 $questiontext = format_text($question->questiontext,
235 $question->questiontextformat,
7347c60b 236 $formatoptions, $cmoptions->course);
37a12367 237 $image = get_question_image($question, $cmoptions->course);
516cf3eb 238
239 ///// Print the input controls //////
240 echo '<table border="0" cellpadding="10" align="right">';
241 foreach ($subquestions as $key => $subquestion) {
37a12367 242
516cf3eb 243 /// Subquestion text:
37a12367 244 $a->text = format_text($subquestion->questiontext,
7347c60b 245 $question->questiontextformat, $formatoptions, $cmoptions->course);
516cf3eb 246
247 /// Drop-down list:
248 $menuname = $nameprefix.$subquestion->id;
249 $response = isset($state->responses[$subquestion->id])
250 ? $state->responses[$subquestion->id] : '0';
251 if ($options->readonly
252 and $options->correct_responses
253 and isset($correctanswers[$subquestion->id])
254 and ($correctanswers[$subquestion->id] == $response)) {
37a12367 255 $a->class = ' class="highlight" ';
516cf3eb 256 } else {
37a12367 257 $a->class = '';
516cf3eb 258 }
37a12367 259
260 $a->control = choose_from_menu($answers, $menuname, $response, 'choose', '', 0,
261 true, $options->readonly);
516cf3eb 262
263 // Neither the editing interface or the database allow to provide
264 // fedback for this question type.
265 // However (as was pointed out in bug bug 3294) the randomsamatch
266 // type which reuses this method can have feedback defined for
267 // the wrapped shortanswer questions.
268 //if ($options->feedback
269 // && !empty($subquestion->options->answers[$responses[$key]]->feedback)) {
270 // quiz_print_comment($subquestion->options->answers[$responses[$key]]->feedback);
271 //}
37a12367 272
273 $anss[] = clone($a);
516cf3eb 274 }
37a12367 275 include("$CFG->dirroot/question/questiontypes/match/display.html");
516cf3eb 276 }
277
278 function grade_responses(&$question, &$state, $cmoptions) {
279 $subquestions = &$state->options->subquestions;
280 $responses = &$state->responses;
281
282 $sumgrade = 0;
283 foreach ($subquestions as $key => $sub) {
284 if (isset($sub->options->answers[$responses[$key]])) {
285 $sumgrade += $sub->options->answers[$responses[$key]]->fraction;
286 }
287 }
288
289 $state->raw_grade = $sumgrade/count($subquestions);
290 if (empty($state->raw_grade)) {
291 $state->raw_grade = 0;
292 }
293
294 // Make sure we don't assign negative or too high marks
295 $state->raw_grade = min(max((float) $state->raw_grade,
296 0.0), 1.0) * $question->maxgrade;
297 $state->penalty = $question->penalty * $question->maxgrade;
298
f30bbcaf 299 // mark the state as graded
300 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
301
516cf3eb 302 return true;
303 }
304
305 // ULPGC ecastro for stats report
306 function get_all_responses($question, $state) {
307 unset($answers);
308 if (is_array($question->options->subquestions)) {
309 foreach ($question->options->subquestions as $aid=>$answer) {
310 unset ($r);
311 $r->answer = $answer->questiontext." : ".$answer->answertext;
312 $r->credit = 1;
313 $answers[$aid] = $r;
314 }
315 } else {
316 $answers[]="error"; // just for debugging, eliminate
317 }
318 $result->id = $question->id;
319 $result->responses = $answers;
320 return $result;
321 }
322
323 // ULPGC ecastro
324 function get_actual_response($question, $state) {
325 unset($results);
326 if (isset($state->responses)) {
327 foreach($state->responses as $left=>$right){
328 $lpair = $question->options->subquestions[$left]->questiontext;
329 $rpair = $question->options->subquestions[$right]->answertext;
330 $results[$left] = $lpair." : ".$rpair;
331 }
332 return $results;
333 } else {
334 return null;
335 }
336 }
337
338
339}
340//// END OF CLASS ////
341
342//////////////////////////////////////////////////////////////////////////
343//// INITIATION - Without this line the question type is not in use... ///
344//////////////////////////////////////////////////////////////////////////
ccccf04f 345// define("MATCH", "5"); // already defined in questionlib.php
32a189d6 346$QTYPES[MATCH]= new question_match_qtype();
ccccf04f 347// The following adds the questiontype to the menu of types shown to teachers
348$QTYPE_MENU[MATCH] = get_string("match", "quiz");
516cf3eb 349
350?>