Added string for editorshortcutkeys.
[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',
9e8dba79 30 $question->id, 'id ASC')) {
31 notify('Error: Missing question answers!');
32 return false;
516cf3eb 33 }
34 return true;
35 }
36
37 function save_question_options($question) {
5a14d563 38 $result = new stdClass;
39
a4d79b0e 40 if (!$oldanswers = get_records('question_answers', 'question', $question->id, 'id ASC')) {
516cf3eb 41 $oldanswers = array();
42 }
43
44 $answers = array();
45 $maxfraction = -1;
46
47 // Insert all the new answers
48 foreach ($question->answer as $key => $dataanswer) {
49 if ($dataanswer != "") {
50 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
51 $answer = $oldanswer;
52 $answer->answer = trim($dataanswer);
53 $answer->fraction = $question->fraction[$key];
54 $answer->feedback = $question->feedback[$key];
dc1f00de 55 if (!update_record("question_answers", $answer)) {
516cf3eb 56 $result->error = "Could not update quiz answer! (id=$answer->id)";
57 return $result;
58 }
59 } else { // This is a completely new answer
5a14d563 60 $answer = new stdClass;
516cf3eb 61 $answer->answer = trim($dataanswer);
62 $answer->question = $question->id;
63 $answer->fraction = $question->fraction[$key];
64 $answer->feedback = $question->feedback[$key];
dc1f00de 65 if (!$answer->id = insert_record("question_answers", $answer)) {
516cf3eb 66 $result->error = "Could not insert quiz answer!";
67 return $result;
68 }
69 }
70 $answers[] = $answer->id;
71 if ($question->fraction[$key] > $maxfraction) {
72 $maxfraction = $question->fraction[$key];
73 }
74 }
75 }
76
32a189d6 77 if ($options = get_record("question_shortanswer", "question", $question->id)) {
516cf3eb 78 $options->answers = implode(",",$answers);
79 $options->usecase = $question->usecase;
32a189d6 80 if (!update_record("question_shortanswer", $options)) {
516cf3eb 81 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
82 return $result;
83 }
84 } else {
85 unset($options);
86 $options->question = $question->id;
87 $options->answers = implode(",",$answers);
88 $options->usecase = $question->usecase;
32a189d6 89 if (!insert_record("question_shortanswer", $options)) {
516cf3eb 90 $result->error = "Could not insert quiz shortanswer options!";
91 return $result;
92 }
93 }
94
95 // delete old answer records
96 if (!empty($oldanswers)) {
97 foreach($oldanswers as $oa) {
dc1f00de 98 delete_records('question_answers', 'id', $oa->id);
516cf3eb 99 }
100 }
101
102 /// Perform sanity checks on fractional grades
103 if ($maxfraction != 1) {
104 $maxfraction = $maxfraction * 100;
105 $result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
106 return $result;
107 } else {
108 return true;
109 }
110 }
111
112 /**
113 * Deletes question from the question-type specific tables
114 *
115 * @return boolean Success/Failure
116 * @param object $question The question being deleted
117 */
90c3f310 118 function delete_question($questionid) {
119 delete_records("question_shortanswer", "question", $questionid);
516cf3eb 120 return true;
121 }
122
123 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
124 global $CFG;
dfa47f96 125 /// This implementation is also used by question type 'numerical'
516cf3eb 126 $correctanswers = $this->get_correct_responses($question, $state);
127 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
5a14d563 128 $formatoptions = new stdClass;
7347c60b 129 $formatoptions->noclean = true;
130 $formatoptions->para = false;
516cf3eb 131 $nameprefix = $question->name_prefix;
132
133 /// Print question text and media
134
135 $questiontext = format_text($question->questiontext,
136 $question->questiontextformat,
7347c60b 137 $formatoptions, $cmoptions->course);
4f48fb42 138 $image = get_question_image($question, $cmoptions->course);
516cf3eb 139
140 /// Print input controls
141
142 if (isset($state->responses[''])) {
9e8dba79 143 $value = ' value="'.s($state->responses[''], true).'" ';
516cf3eb 144 } else {
145 $value = ' value="" ';
146 }
147 $inputname = ' name="'.$nameprefix.'" ';
148
149 $feedback = '';
150 if ($options->feedback) {
1a1293ed 151 foreach($question->options->answers as $answer) {
152 if($this->test_response($question, $state, $answer)) {
153 if ($answer->feedback) {
154 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
155 }
516cf3eb 156 break;
157 }
158 }
159 }
160
161 $correctanswer = '';
162 if ($options->readonly && $options->correct_responses) {
163 $delimiter = '';
164 if ($correctanswers) {
165 foreach ($correctanswers as $ca) {
166 $correctanswer .= $delimiter.$ca;
167 $delimiter = ', ';
168 }
169 }
170 }
aaae75b0 171 include("$CFG->dirroot/question/type/shortanswer/display.html");
516cf3eb 172 }
173
174 // ULPGC ecastro
175 function check_response(&$question, &$state) {
176 $answers = &$question->options->answers;
177 $testedstate = clone($state);
178 $teststate = clone($state);
179 foreach($answers as $aid => $answer) {
180 $teststate->responses[''] = trim($answer->answer);
181 if($this->compare_responses($question, $testedstate, $teststate)) {
182 return $aid;
183 }
184 }
185 return false;
186 }
187
5a14d563 188 function compare_responses($question, $state, $teststate) {
189 if (isset($state->responses['']) && isset($teststate->responses[''])) {
190 if ($question->options->usecase) {
c82f76d0 191 return strcmp($state->responses[''], $teststate->responses['']) == 0;
5a14d563 192 } else {
485349dd 193 $textlib = textlib_get_instance();
194 return strcmp($textlib->strtolower($state->responses['']),
195 $textlib->strtolower($teststate->responses[''])) == 0;
516cf3eb 196 }
197 }
5a14d563 198 return false;
516cf3eb 199 }
200
5a14d563 201 function test_response(&$question, $state, $answer) {
9e8dba79 202 return $this->compare_string_with_wildcard(stripslashes_safe($state->responses['']),
5a14d563 203 $answer->answer, !$question->options->usecase);
516cf3eb 204 }
1a1293ed 205
5a14d563 206 function compare_string_with_wildcard($string, $pattern, $ignorecase) {
207 // Break the string on non-escaped asterisks.
208 $bits = preg_split('/(?<!\\\\)\*/', $pattern);
209 // Escape regexp special characters in the bits.
210 $bits = array_map('preg_quote', $bits);
211 // Put it back together to make the regexp.
212 $regexp = '|^' . implode('.*', $bits) . '$|u';
213
214 // Make the match insensitive if requested to.
215 if ($ignorecase) {
216 $regexp .= 'i';
217 }
218
219 return preg_match($regexp, trim($string));
1a1293ed 220 }
221
222 /// BACKUP FUNCTIONS ////////////////////////////
c5d94c41 223
224 /*
225 * Backup the data in the question
226 *
227 * This is used in question/backuplib.php
228 */
229 function backup($bf,$preferences,$question,$level=6) {
230
231 $status = true;
232
a4d79b0e 233 $shortanswers = get_records('question_shortanswer', 'question', $question, 'id ASC');
c5d94c41 234 //If there are shortanswers
235 if ($shortanswers) {
236 //Iterate over each shortanswer
237 foreach ($shortanswers as $shortanswer) {
238 $status = fwrite ($bf,start_tag("SHORTANSWER",$level,true));
239 //Print shortanswer contents
240 fwrite ($bf,full_tag("ANSWERS",$level+1,false,$shortanswer->answers));
241 fwrite ($bf,full_tag("USECASE",$level+1,false,$shortanswer->usecase));
242 $status = fwrite ($bf,end_tag("SHORTANSWER",$level,true));
243 }
244 //Now print question_answers
245 $status = question_backup_answers($bf,$preferences,$question);
246 }
247 return $status;
248 }
516cf3eb 249
315559d3 250/// RESTORE FUNCTIONS /////////////////
251
252 /*
253 * Restores the data in the question
254 *
255 * This is used in question/restorelib.php
256 */
257 function restore($old_question_id,$new_question_id,$info,$restore) {
258
259 $status = true;
260
261 //Get the shortanswers array
262 $shortanswers = $info['#']['SHORTANSWER'];
263
264 //Iterate over shortanswers
265 for($i = 0; $i < sizeof($shortanswers); $i++) {
266 $sho_info = $shortanswers[$i];
267
268 //Now, build the question_shortanswer record structure
5a14d563 269 $shortanswer = new stdClass;
315559d3 270 $shortanswer->question = $new_question_id;
271 $shortanswer->answers = backup_todb($sho_info['#']['ANSWERS']['0']['#']);
272 $shortanswer->usecase = backup_todb($sho_info['#']['USECASE']['0']['#']);
273
274 //We have to recode the answers field (a list of answers id)
275 //Extracts answer id from sequence
276 $answers_field = "";
277 $in_first = true;
278 $tok = strtok($shortanswer->answers,",");
279 while ($tok) {
280 //Get the answer from backup_ids
281 $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
282 if ($answer) {
283 if ($in_first) {
284 $answers_field .= $answer->new_id;
285 $in_first = false;
286 } else {
287 $answers_field .= ",".$answer->new_id;
288 }
289 }
290 //check for next
291 $tok = strtok(",");
292 }
293 //We have the answers field recoded to its new ids
294 $shortanswer->answers = $answers_field;
295
296 //The structure is equal to the db, so insert the question_shortanswer
297 $newid = insert_record ("question_shortanswer",$shortanswer);
298
299 //Do some output
300 if (($i+1) % 50 == 0) {
301 if (!defined('RESTORE_SILENTLY')) {
302 echo ".";
303 if (($i+1) % 1000 == 0) {
304 echo "<br />";
305 }
306 }
307 backup_flush(300);
308 }
309
310 if (!$newid) {
311 $status = false;
312 }
313 }
314
315 return $status;
316 }
317
516cf3eb 318}
319//// END OF CLASS ////
320
321//////////////////////////////////////////////////////////////////////////
322//// INITIATION - Without this line the question type is not in use... ///
323//////////////////////////////////////////////////////////////////////////
a2156789 324question_register_questiontype(new question_shortanswer_qtype());
516cf3eb 325?>