Fix bug 4952
[moodle.git] / question / questiontypes / 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///
13
af3830ee 14class question_shortanswer_qtype extends default_questiontype {
516cf3eb 15
16 function name() {
17 return 'shortanswer';
18 }
19
20 function get_question_options(&$question) {
21 // Get additional information from database
22 // and attach it to the question object
32a189d6 23 if (!$question->options = get_record('question_shortanswer', 'question', $question->id)) {
516cf3eb 24 notify('Error: Missing question options!');
25 return false;
26 }
27
dc1f00de 28 if (!$question->options->answers = get_records('question_answers', 'question',
516cf3eb 29 $question->id, 'id ASC')) {
30 notify('Error: Missing question answers!');
31 return false;
32 }
33 return true;
34 }
35
36 function save_question_options($question) {
dc1f00de 37 if (!$oldanswers = get_records("question_answers", "question", $question->id, "id ASC")) {
516cf3eb 38 $oldanswers = array();
39 }
40
41 $answers = array();
42 $maxfraction = -1;
43
44 // Insert all the new answers
45 foreach ($question->answer as $key => $dataanswer) {
46 if ($dataanswer != "") {
47 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
48 $answer = $oldanswer;
49 $answer->answer = trim($dataanswer);
50 $answer->fraction = $question->fraction[$key];
51 $answer->feedback = $question->feedback[$key];
dc1f00de 52 if (!update_record("question_answers", $answer)) {
516cf3eb 53 $result->error = "Could not update quiz answer! (id=$answer->id)";
54 return $result;
55 }
56 } else { // This is a completely new answer
57 unset($answer);
58 $answer->answer = trim($dataanswer);
59 $answer->question = $question->id;
60 $answer->fraction = $question->fraction[$key];
61 $answer->feedback = $question->feedback[$key];
dc1f00de 62 if (!$answer->id = insert_record("question_answers", $answer)) {
516cf3eb 63 $result->error = "Could not insert quiz answer!";
64 return $result;
65 }
66 }
67 $answers[] = $answer->id;
68 if ($question->fraction[$key] > $maxfraction) {
69 $maxfraction = $question->fraction[$key];
70 }
71 }
72 }
73
32a189d6 74 if ($options = get_record("question_shortanswer", "question", $question->id)) {
516cf3eb 75 $options->answers = implode(",",$answers);
76 $options->usecase = $question->usecase;
32a189d6 77 if (!update_record("question_shortanswer", $options)) {
516cf3eb 78 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
79 return $result;
80 }
81 } else {
82 unset($options);
83 $options->question = $question->id;
84 $options->answers = implode(",",$answers);
85 $options->usecase = $question->usecase;
32a189d6 86 if (!insert_record("question_shortanswer", $options)) {
516cf3eb 87 $result->error = "Could not insert quiz shortanswer options!";
88 return $result;
89 }
90 }
91
92 // delete old answer records
93 if (!empty($oldanswers)) {
94 foreach($oldanswers as $oa) {
dc1f00de 95 delete_records('question_answers', 'id', $oa->id);
516cf3eb 96 }
97 }
98
99 /// Perform sanity checks on fractional grades
100 if ($maxfraction != 1) {
101 $maxfraction = $maxfraction * 100;
102 $result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
103 return $result;
104 } else {
105 return true;
106 }
107 }
108
109 /**
110 * Deletes question from the question-type specific tables
111 *
112 * @return boolean Success/Failure
113 * @param object $question The question being deleted
114 */
90c3f310 115 function delete_question($questionid) {
116 delete_records("question_shortanswer", "question", $questionid);
516cf3eb 117 return true;
118 }
119
120 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
121 global $CFG;
122 /// This implementation is also used by question type NUMERICAL
123 $answers = &$question->options->answers;
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) {
516cf3eb 148 foreach($answers as $answer) {
37a12367 149 if($answer->feedback and $this->test_response($question, $state, $answer)) {
7347c60b 150 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
516cf3eb 151 break;
152 }
153 }
154 }
155
156 $correctanswer = '';
157 if ($options->readonly && $options->correct_responses) {
158 $delimiter = '';
159 if ($correctanswers) {
160 foreach ($correctanswers as $ca) {
161 $correctanswer .= $delimiter.$ca;
162 $delimiter = ', ';
163 }
164 }
165 }
e586cfb4 166 include("$CFG->dirroot/question/questiontypes/shortanswer/display.html");
516cf3eb 167 }
168
169 // ULPGC ecastro
170 function check_response(&$question, &$state) {
171 $answers = &$question->options->answers;
172 $testedstate = clone($state);
173 $teststate = clone($state);
174 foreach($answers as $aid => $answer) {
175 $teststate->responses[''] = trim($answer->answer);
176 if($this->compare_responses($question, $testedstate, $teststate)) {
177 return $aid;
178 }
179 }
180 return false;
181 }
182
183 function grade_responses(&$question, &$state, $cmoptions) {
184 $answers = &$question->options->answers;
185 $testedstate = clone($state);
186 $teststate = clone($state);
187 $state->raw_grade = 0;
188
189 foreach($answers as $answer) {
190 $teststate->responses[''] = trim($answer->answer);
191 if($this->compare_responses($question, $testedstate, $teststate)) {
192 $state->raw_grade = $answer->fraction;
193 break;
194 }
195 }
196
197 // Make sure we don't assign negative or too high marks
198 $state->raw_grade = min(max((float) $state->raw_grade,
199 0.0), 1.0) * $question->maxgrade;
200 $state->penalty = $question->penalty * $question->maxgrade;
201
f30bbcaf 202 // mark the state as graded
203 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
204
516cf3eb 205 return true;
206 }
207
208 function compare_responses(&$question, &$state, &$teststate) {
209 if (isset($state->responses[''])) {
210 $response0 = trim(stripslashes($state->responses['']));
211 } else {
212 $response0 = '';
213 }
214
215 if (isset($teststate->responses[''])) {
216 $response1 = trim(stripslashes($teststate->responses['']));
217 } else {
218 $response1 = '';
219 }
220
221 if (!$question->options->usecase) { // Don't compare case
222 $response0 = strtolower($response0);
223 $response1 = strtolower($response1);
224 }
225
226 /// These are things to protect in the strings when wildcards are used
227 $search = array('\\', '+', '(', ')', '[', ']', '-');
228 $replace = array('\\\\', '\+', '\(', '\)', '\[', '\]', '\-');
229
230 if (strpos(' '.$response1, '*')) {
231 $response1 = str_replace('\*','@@@@@@',$response1);
232 $response1 = str_replace('*','.*',$response1);
233 $response1 = str_replace($search, $replace, $response1);
234 $response1 = str_replace('@@@@@@', '\*',$response1);
235
236 if (ereg('^'.$response1.'$', $response0)) {
237 return true;
238 }
239
240 } else if ($response1 == $response0) {
241 return true;
242 }
243
244 return false;
245 }
c5d94c41 246
247/// BACKUP FUNCTIONS ////////////////////////////
248
249 /*
250 * Backup the data in the question
251 *
252 * This is used in question/backuplib.php
253 */
254 function backup($bf,$preferences,$question,$level=6) {
255
256 $status = true;
257
258 $shortanswers = get_records("question_shortanswer","question",$question,"id");
259 //If there are shortanswers
260 if ($shortanswers) {
261 //Iterate over each shortanswer
262 foreach ($shortanswers as $shortanswer) {
263 $status = fwrite ($bf,start_tag("SHORTANSWER",$level,true));
264 //Print shortanswer contents
265 fwrite ($bf,full_tag("ANSWERS",$level+1,false,$shortanswer->answers));
266 fwrite ($bf,full_tag("USECASE",$level+1,false,$shortanswer->usecase));
267 $status = fwrite ($bf,end_tag("SHORTANSWER",$level,true));
268 }
269 //Now print question_answers
270 $status = question_backup_answers($bf,$preferences,$question);
271 }
272 return $status;
273 }
516cf3eb 274
275}
276//// END OF CLASS ////
277
278//////////////////////////////////////////////////////////////////////////
279//// INITIATION - Without this line the question type is not in use... ///
280//////////////////////////////////////////////////////////////////////////
ccccf04f 281// define("SHORTANSWER", "1"); // already defined in questionlib.php
32a189d6 282$QTYPES[SHORTANSWER]= new question_shortanswer_qtype();
ccccf04f 283// The following adds the questiontype to the menu of types shown to teachers
284$QTYPE_MENU[SHORTANSWER] = get_string("shortanswer", "quiz");
516cf3eb 285
286?>