Moving quiz-independent question scripts to their new location. In a following commit...
[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
14class quiz_shortanswer_qtype extends quiz_default_questiontype {
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
23 if (!$question->options = get_record('quiz_shortanswer', 'question', $question->id)) {
24 notify('Error: Missing question options!');
25 return false;
26 }
27
28 if (!$question->options->answers = get_records('quiz_answers', 'question',
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) {
37 if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
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];
52 if (!update_record("quiz_answers", $answer)) {
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];
62 if (!$answer->id = insert_record("quiz_answers", $answer)) {
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
74 if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
75 $options->answers = implode(",",$answers);
76 $options->usecase = $question->usecase;
77 if (!update_record("quiz_shortanswer", $options)) {
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;
86 if (!insert_record("quiz_shortanswer", $options)) {
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) {
95 delete_records('quiz_answers', 'id', $oa->id);
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 */
115 function delete_question($question) {
116 delete_records("quiz_shortanswer", "question", $question->id);
117 //TODO: delete also the states from quiz_rqp_states
118 return true;
119 }
120
121 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
122 global $CFG;
123 /// This implementation is also used by question type NUMERICAL
124 $answers = &$question->options->answers;
125 $correctanswers = $this->get_correct_responses($question, $state);
126 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
127 $nameprefix = $question->name_prefix;
128
129 /// Print question text and media
130
131 $questiontext = format_text($question->questiontext,
132 $question->questiontextformat,
133 NULL, $cmoptions->course);
134 $image = quiz_get_image($question, $cmoptions->course);
135
136 /// Print input controls
137
138 if (isset($state->responses[''])) {
139 $value = ' value="'.s($state->responses['']).'" ';
140 } else {
141 $value = ' value="" ';
142 }
143 $inputname = ' name="'.$nameprefix.'" ';
144
145 $feedback = '';
146 if ($options->feedback) {
147 $testedstate = clone($state);
148 $teststate = clone($state);
149 foreach($answers as $answer) {
150 $teststate->responses[''] = trim($answer->answer);
151 if($answer->feedback and $this->compare_responses($question, $testedstate, $teststate)) {
152 $feedback = format_text($answer->feedback, true, false);
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 }
168 include("$CFG->dirroot/mod/quiz/questiontypes/shortanswer/display.html");
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) {
186 $answers = &$question->options->answers;
187 $testedstate = clone($state);
188 $teststate = clone($state);
189 $state->raw_grade = 0;
190
191 foreach($answers as $answer) {
192 $teststate->responses[''] = trim($answer->answer);
193 if($this->compare_responses($question, $testedstate, $teststate)) {
194 $state->raw_grade = $answer->fraction;
195 break;
196 }
197 }
198
199 // Make sure we don't assign negative or too high marks
200 $state->raw_grade = min(max((float) $state->raw_grade,
201 0.0), 1.0) * $question->maxgrade;
202 $state->penalty = $question->penalty * $question->maxgrade;
203
204 return true;
205 }
206
207 function compare_responses(&$question, &$state, &$teststate) {
208 if (isset($state->responses[''])) {
209 $response0 = trim(stripslashes($state->responses['']));
210 } else {
211 $response0 = '';
212 }
213
214 if (isset($teststate->responses[''])) {
215 $response1 = trim(stripslashes($teststate->responses['']));
216 } else {
217 $response1 = '';
218 }
219
220 if (!$question->options->usecase) { // Don't compare case
221 $response0 = strtolower($response0);
222 $response1 = strtolower($response1);
223 }
224
225 /// These are things to protect in the strings when wildcards are used
226 $search = array('\\', '+', '(', ')', '[', ']', '-');
227 $replace = array('\\\\', '\+', '\(', '\)', '\[', '\]', '\-');
228
229 if (strpos(' '.$response1, '*')) {
230 $response1 = str_replace('\*','@@@@@@',$response1);
231 $response1 = str_replace('*','.*',$response1);
232 $response1 = str_replace($search, $replace, $response1);
233 $response1 = str_replace('@@@@@@', '\*',$response1);
234
235 if (ereg('^'.$response1.'$', $response0)) {
236 return true;
237 }
238
239 } else if ($response1 == $response0) {
240 return true;
241 }
242
243 return false;
244 }
245
246}
247//// END OF CLASS ////
248
249//////////////////////////////////////////////////////////////////////////
250//// INITIATION - Without this line the question type is not in use... ///
251//////////////////////////////////////////////////////////////////////////
252$QUIZ_QTYPES[SHORTANSWER]= new quiz_shortanswer_qtype();
253
254?>