Fix whitespace.
[moodle.git] / question / type / shortanswer / questiontype.php
CommitLineData
516cf3eb 1<?php // $Id$
2
3///////////////////
4/// SHORTANSWER ///
5///////////////////
6
7/// QUESTION TYPE CLASS //////////////////
8
9///
10/// This class contains some special features in order to make the
11/// question type embeddable within a multianswer (cloze) question
12///
22f2f418 13require_once("$CFG->dirroot/question/type/questiontype.php");
516cf3eb 14
af3830ee 15class question_shortanswer_qtype extends default_questiontype {
516cf3eb 16
17 function name() {
18 return 'shortanswer';
19 }
20
21 function get_question_options(&$question) {
22 // Get additional information from database
23 // and attach it to the question object
32a189d6 24 if (!$question->options = get_record('question_shortanswer', 'question', $question->id)) {
516cf3eb 25 notify('Error: Missing question options!');
26 return false;
27 }
28
dc1f00de 29 if (!$question->options->answers = get_records('question_answers', 'question',
516cf3eb 30 $question->id, 'id ASC')) {
31 notify('Error: Missing question answers!');
32 return false;
33 }
34 return true;
35 }
36
37 function save_question_options($question) {
dc1f00de 38 if (!$oldanswers = get_records("question_answers", "question", $question->id, "id ASC")) {
516cf3eb 39 $oldanswers = array();
40 }
41
42 $answers = array();
43 $maxfraction = -1;
44
45 // Insert all the new answers
46 foreach ($question->answer as $key => $dataanswer) {
47 if ($dataanswer != "") {
48 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
49 $answer = $oldanswer;
50 $answer->answer = trim($dataanswer);
51 $answer->fraction = $question->fraction[$key];
52 $answer->feedback = $question->feedback[$key];
dc1f00de 53 if (!update_record("question_answers", $answer)) {
516cf3eb 54 $result->error = "Could not update quiz answer! (id=$answer->id)";
55 return $result;
56 }
57 } else { // This is a completely new answer
58 unset($answer);
59 $answer->answer = trim($dataanswer);
60 $answer->question = $question->id;
61 $answer->fraction = $question->fraction[$key];
62 $answer->feedback = $question->feedback[$key];
dc1f00de 63 if (!$answer->id = insert_record("question_answers", $answer)) {
516cf3eb 64 $result->error = "Could not insert quiz answer!";
65 return $result;
66 }
67 }
68 $answers[] = $answer->id;
69 if ($question->fraction[$key] > $maxfraction) {
70 $maxfraction = $question->fraction[$key];
71 }
72 }
73 }
74
32a189d6 75 if ($options = get_record("question_shortanswer", "question", $question->id)) {
516cf3eb 76 $options->answers = implode(",",$answers);
77 $options->usecase = $question->usecase;
32a189d6 78 if (!update_record("question_shortanswer", $options)) {
516cf3eb 79 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
80 return $result;
81 }
82 } else {
83 unset($options);
84 $options->question = $question->id;
85 $options->answers = implode(",",$answers);
86 $options->usecase = $question->usecase;
32a189d6 87 if (!insert_record("question_shortanswer", $options)) {
516cf3eb 88 $result->error = "Could not insert quiz shortanswer options!";
89 return $result;
90 }
91 }
92
93 // delete old answer records
94 if (!empty($oldanswers)) {
95 foreach($oldanswers as $oa) {
dc1f00de 96 delete_records('question_answers', 'id', $oa->id);
516cf3eb 97 }
98 }
99
100 /// Perform sanity checks on fractional grades
101 if ($maxfraction != 1) {
102 $maxfraction = $maxfraction * 100;
103 $result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
104 return $result;
105 } else {
106 return true;
107 }
108 }
109
110 /**
111 * Deletes question from the question-type specific tables
112 *
113 * @return boolean Success/Failure
114 * @param object $question The question being deleted
115 */
90c3f310 116 function delete_question($questionid) {
117 delete_records("question_shortanswer", "question", $questionid);
516cf3eb 118 return true;
119 }
120
121 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
122 global $CFG;
dfa47f96 123 /// This implementation is also used by question type 'numerical'
516cf3eb 124 $correctanswers = $this->get_correct_responses($question, $state);
125 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
7347c60b 126 $formatoptions->noclean = true;
127 $formatoptions->para = false;
516cf3eb 128 $nameprefix = $question->name_prefix;
129
130 /// Print question text and media
131
132 $questiontext = format_text($question->questiontext,
133 $question->questiontextformat,
7347c60b 134 $formatoptions, $cmoptions->course);
4f48fb42 135 $image = get_question_image($question, $cmoptions->course);
516cf3eb 136
137 /// Print input controls
138
139 if (isset($state->responses[''])) {
140 $value = ' value="'.s($state->responses['']).'" ';
141 } else {
142 $value = ' value="" ';
143 }
144 $inputname = ' name="'.$nameprefix.'" ';
145
146 $feedback = '';
147 if ($options->feedback) {
1a1293ed 148 foreach($question->options->answers as $answer) {
149 if($this->test_response($question, $state, $answer)) {
150 if ($answer->feedback) {
151 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
152 }
516cf3eb 153 break;
154 }
155 }
156 }
157
158 $correctanswer = '';
159 if ($options->readonly && $options->correct_responses) {
160 $delimiter = '';
161 if ($correctanswers) {
162 foreach ($correctanswers as $ca) {
163 $correctanswer .= $delimiter.$ca;
164 $delimiter = ', ';
165 }
166 }
167 }
aaae75b0 168 include("$CFG->dirroot/question/type/shortanswer/display.html");
516cf3eb 169 }
170
171 // ULPGC ecastro
172 function check_response(&$question, &$state) {
173 $answers = &$question->options->answers;
174 $testedstate = clone($state);
175 $teststate = clone($state);
176 foreach($answers as $aid => $answer) {
177 $teststate->responses[''] = trim($answer->answer);
178 if($this->compare_responses($question, $testedstate, $teststate)) {
179 return $aid;
180 }
181 }
182 return false;
183 }
184
185 function grade_responses(&$question, &$state, $cmoptions) {
1a1293ed 186
187 $teststate = clone($state);
516cf3eb 188 $state->raw_grade = 0;
1a1293ed 189 // Compare the response with every teacher answer in turn
190 // and return the first one that matches.
191 foreach($question->options->answers as $answer) {
192 // Now we use a bit of a hack: we put the answer into the response
193 // of a teststate so that we can use the function compare_responses()
516cf3eb 194 $teststate->responses[''] = trim($answer->answer);
1a1293ed 195 if($this->compare_responses($question, $state, $teststate)) {
516cf3eb 196 $state->raw_grade = $answer->fraction;
197 break;
198 }
199 }
200
201 // Make sure we don't assign negative or too high marks
202 $state->raw_grade = min(max((float) $state->raw_grade,
203 0.0), 1.0) * $question->maxgrade;
204 $state->penalty = $question->penalty * $question->maxgrade;
205
f30bbcaf 206 // mark the state as graded
207 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
208
516cf3eb 209 return true;
210 }
211
212 function compare_responses(&$question, &$state, &$teststate) {
1a1293ed 213 // In this questiontype this function is not only used to compare responses
214 // between two different states but it is also used by grade_responses() and
215 // by test_responses() to compare responses with answers.
516cf3eb 216 if (isset($state->responses[''])) {
217 $response0 = trim(stripslashes($state->responses['']));
218 } else {
219 $response0 = '';
220 }
221
222 if (isset($teststate->responses[''])) {
223 $response1 = trim(stripslashes($teststate->responses['']));
224 } else {
225 $response1 = '';
226 }
227
228 if (!$question->options->usecase) { // Don't compare case
229 $response0 = strtolower($response0);
230 $response1 = strtolower($response1);
231 }
232
233 /// These are things to protect in the strings when wildcards are used
234 $search = array('\\', '+', '(', ')', '[', ']', '-');
235 $replace = array('\\\\', '\+', '\(', '\)', '\[', '\]', '\-');
236
237 if (strpos(' '.$response1, '*')) {
238 $response1 = str_replace('\*','@@@@@@',$response1);
239 $response1 = str_replace('*','.*',$response1);
240 $response1 = str_replace($search, $replace, $response1);
241 $response1 = str_replace('@@@@@@', '\*',$response1);
242
243 if (ereg('^'.$response1.'$', $response0)) {
244 return true;
245 }
246
247 } else if ($response1 == $response0) {
248 return true;
249 }
250
251 return false;
252 }
1a1293ed 253
254 function test_response(&$question, &$state, &$answer) {
255 $teststate = clone($state);
256 $teststate->responses[''] = trim($answer->answer);
257 if($this->compare_responses($question, $state, $teststate)) {
258 return true;
259 }
260 return false;
261 }
262
263 /// BACKUP FUNCTIONS ////////////////////////////
c5d94c41 264
265 /*
266 * Backup the data in the question
267 *
268 * This is used in question/backuplib.php
269 */
270 function backup($bf,$preferences,$question,$level=6) {
271
272 $status = true;
273
274 $shortanswers = get_records("question_shortanswer","question",$question,"id");
275 //If there are shortanswers
276 if ($shortanswers) {
277 //Iterate over each shortanswer
278 foreach ($shortanswers as $shortanswer) {
279 $status = fwrite ($bf,start_tag("SHORTANSWER",$level,true));
280 //Print shortanswer contents
281 fwrite ($bf,full_tag("ANSWERS",$level+1,false,$shortanswer->answers));
282 fwrite ($bf,full_tag("USECASE",$level+1,false,$shortanswer->usecase));
283 $status = fwrite ($bf,end_tag("SHORTANSWER",$level,true));
284 }
285 //Now print question_answers
286 $status = question_backup_answers($bf,$preferences,$question);
287 }
288 return $status;
289 }
516cf3eb 290
315559d3 291/// RESTORE FUNCTIONS /////////////////
292
293 /*
294 * Restores the data in the question
295 *
296 * This is used in question/restorelib.php
297 */
298 function restore($old_question_id,$new_question_id,$info,$restore) {
299
300 $status = true;
301
302 //Get the shortanswers array
303 $shortanswers = $info['#']['SHORTANSWER'];
304
305 //Iterate over shortanswers
306 for($i = 0; $i < sizeof($shortanswers); $i++) {
307 $sho_info = $shortanswers[$i];
308
309 //Now, build the question_shortanswer record structure
310 $shortanswer->question = $new_question_id;
311 $shortanswer->answers = backup_todb($sho_info['#']['ANSWERS']['0']['#']);
312 $shortanswer->usecase = backup_todb($sho_info['#']['USECASE']['0']['#']);
313
314 //We have to recode the answers field (a list of answers id)
315 //Extracts answer id from sequence
316 $answers_field = "";
317 $in_first = true;
318 $tok = strtok($shortanswer->answers,",");
319 while ($tok) {
320 //Get the answer from backup_ids
321 $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
322 if ($answer) {
323 if ($in_first) {
324 $answers_field .= $answer->new_id;
325 $in_first = false;
326 } else {
327 $answers_field .= ",".$answer->new_id;
328 }
329 }
330 //check for next
331 $tok = strtok(",");
332 }
333 //We have the answers field recoded to its new ids
334 $shortanswer->answers = $answers_field;
335
336 //The structure is equal to the db, so insert the question_shortanswer
337 $newid = insert_record ("question_shortanswer",$shortanswer);
338
339 //Do some output
340 if (($i+1) % 50 == 0) {
341 if (!defined('RESTORE_SILENTLY')) {
342 echo ".";
343 if (($i+1) % 1000 == 0) {
344 echo "<br />";
345 }
346 }
347 backup_flush(300);
348 }
349
350 if (!$newid) {
351 $status = false;
352 }
353 }
354
355 return $status;
356 }
357
516cf3eb 358}
359//// END OF CLASS ////
360
361//////////////////////////////////////////////////////////////////////////
362//// INITIATION - Without this line the question type is not in use... ///
363//////////////////////////////////////////////////////////////////////////
dfa47f96 364$QTYPES['shortanswer']= new question_shortanswer_qtype();
ccccf04f 365// The following adds the questiontype to the menu of types shown to teachers
dfa47f96 366$QTYPE_MENU['shortanswer'] = get_string("shortanswer", "quiz");
516cf3eb 367
368?>