Commit | Line | Data |
---|---|---|
aeb15530 | 1 | <?php |
fe6ce234 DC |
2 | |
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
1fe641f7 | 18 | /** |
1fe641f7 | 19 | * @author Martin Dougiamas and many others. Tim Hunt. |
20 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License | |
1976496e | 21 | * @package questionbank |
22 | * @subpackage questiontypes | |
fe6ce234 | 23 | */ |
516cf3eb | 24 | |
aaae75b0 | 25 | require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php"); |
516cf3eb | 26 | |
1fe641f7 | 27 | /** |
28 | * NUMERICAL QUESTION TYPE CLASS | |
29 | * | |
30 | * This class contains some special features in order to make the | |
31 | * question type embeddable within a multianswer (cloze) question | |
32 | * | |
33 | * This question type behaves like shortanswer in most cases. | |
34 | * Therefore, it extends the shortanswer question type... | |
1976496e | 35 | * @package questionbank |
36 | * @subpackage questiontypes | |
1fe641f7 | 37 | */ |
fe6ce234 | 38 | |
32a189d6 | 39 | class question_numerical_qtype extends question_shortanswer_qtype { |
516cf3eb | 40 | |
c5da9906 | 41 | public $virtualqtype = false; |
516cf3eb | 42 | function name() { |
43 | return 'numerical'; | |
44 | } | |
aeb15530 | 45 | |
869309b8 | 46 | function has_wildcards_in_responses() { |
47 | return true; | |
48 | } | |
49 | ||
50 | function requires_qtypes() { | |
51 | return array('shortanswer'); | |
52 | } | |
516cf3eb | 53 | |
54 | function get_question_options(&$question) { | |
55 | // Get the question answers and their respective tolerances | |
32a189d6 | 56 | // Note: question_numerical is an extension of the answer table rather than |
4f48fb42 | 57 | // the question table as is usually the case for qtype |
516cf3eb | 58 | // specific tables. |
fef8f84e | 59 | global $CFG, $DB, $OUTPUT; |
f34488b2 | 60 | if (!$question->options->answers = $DB->get_records_sql( |
516cf3eb | 61 | "SELECT a.*, n.tolerance " . |
f34488b2 | 62 | "FROM {question_answers} a, " . |
63 | " {question_numerical} n " . | |
64 | "WHERE a.question = ? " . | |
026bec73 | 65 | " AND a.id = n.answer " . |
f34488b2 | 66 | "ORDER BY a.id ASC", array($question->id))) { |
fef8f84e | 67 | echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!'); |
516cf3eb | 68 | return false; |
69 | } | |
70 | $this->get_numerical_units($question); | |
cf146692 | 71 | //get_numerical_options() need to know if there are units |
fe6ce234 | 72 | // to set correctly default values |
cf146692 | 73 | $this->get_numerical_options($question); |
516cf3eb | 74 | |
5a14d563 | 75 | // If units are defined we strip off the default unit from the answer, if |
516cf3eb | 76 | // it is present. (Required for compatibility with the old code and DB). |
77 | if ($defaultunit = $this->get_default_numerical_unit($question)) { | |
78 | foreach($question->options->answers as $key => $val) { | |
79 | $answer = trim($val->answer); | |
80 | $length = strlen($defaultunit->unit); | |
1fe641f7 | 81 | if ($length && substr($answer, -$length) == $defaultunit->unit) { |
516cf3eb | 82 | $question->options->answers[$key]->answer = |
1fe641f7 | 83 | substr($answer, 0, strlen($answer)-$length); |
516cf3eb | 84 | } |
85 | } | |
86 | } | |
c5da9906 | 87 | |
88 | return true; | |
89 | } | |
92b36005 PP |
90 | function get_numerical_units(&$question) { |
91 | global $DB; | |
92 | if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) { | |
93 | $units = array_values($units); | |
94 | } else { | |
95 | $units = array(); | |
96 | } | |
97 | foreach ($units as $key => $unit) { | |
98 | $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_NUMBER); | |
99 | } | |
100 | $question->options->units = $units; | |
101 | return true; | |
102 | } | |
103 | ||
104 | function get_default_numerical_unit(&$question) { | |
105 | if (isset($question->options->units[0])) { | |
106 | foreach ($question->options->units as $unit) { | |
107 | if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) { | |
108 | return $unit; | |
109 | } | |
110 | } | |
111 | } | |
112 | return false; | |
113 | } | |
114 | ||
c5da9906 | 115 | function get_numerical_options(&$question) { |
116 | global $DB; | |
cf146692 | 117 | if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) { |
f1ee48c8 | 118 | $question->options->unitgradingtype = 0; // total grade |
92b36005 | 119 | $question->options->unitpenalty = 0.1; // default for old questions |
fe6ce234 | 120 | // the default |
cf146692 PP |
121 | if ($defaultunit = $this->get_default_numerical_unit($question)) { |
122 | // so units can be graded | |
04e91671 | 123 | $question->options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ; |
cf146692 PP |
124 | }else { |
125 | // only numerical will be graded | |
04e91671 | 126 | $question->options->showunits = NUMERICALQUESTIONUNITNODISPLAY ; |
cf146692 PP |
127 | } |
128 | $question->options->unitsleft = 0 ; | |
fe6ce234 DC |
129 | $question->options->instructions = ''; |
130 | $question->options->instructionsformat = editors_get_preferred_format(); | |
c5da9906 | 131 | } else { |
cf146692 PP |
132 | $question->options->unitgradingtype = $options->unitgradingtype; |
133 | $question->options->unitpenalty = $options->unitpenalty; | |
fe6ce234 DC |
134 | $question->options->showunits = $options->showunits; |
135 | $question->options->unitsleft = $options->unitsleft; | |
136 | $question->options->instructions = $options->instructions; | |
137 | $question->options->instructionsformat = $options->instructionsformat; | |
c5da9906 | 138 | } |
aeb15530 | 139 | |
516cf3eb | 140 | return true; |
141 | } | |
fe6ce234 | 142 | |
516cf3eb | 143 | |
1fe641f7 | 144 | /** |
145 | * Save the units and the answers associated with this question. | |
146 | */ | |
516cf3eb | 147 | function save_question_options($question) { |
f34488b2 | 148 | global $DB; |
fe6ce234 DC |
149 | $context = $question->context; |
150 | ||
516cf3eb | 151 | // Get old versions of the objects |
f34488b2 | 152 | if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { |
516cf3eb | 153 | $oldanswers = array(); |
154 | } | |
155 | ||
f34488b2 | 156 | if (!$oldoptions = $DB->get_records('question_numerical', array('question' => $question->id), 'answer ASC')) { |
516cf3eb | 157 | $oldoptions = array(); |
158 | } | |
159 | ||
1fe641f7 | 160 | // Save the units. |
516cf3eb | 161 | $result = $this->save_numerical_units($question); |
162 | if (isset($result->error)) { | |
163 | return $result; | |
164 | } else { | |
165 | $units = &$result->units; | |
166 | } | |
167 | ||
168 | // Insert all the new answers | |
169 | foreach ($question->answer as $key => $dataanswer) { | |
94a6d656 | 170 | // Check for, and ingore, completely blank answer from the form. |
171 | if (trim($dataanswer) == '' && $question->fraction[$key] == 0 && | |
fe6ce234 | 172 | html_is_blank($question->feedback[$key]['text'])) { |
94a6d656 | 173 | continue; |
174 | } | |
516cf3eb | 175 | |
94a6d656 | 176 | $answer = new stdClass; |
177 | $answer->question = $question->id; | |
fac1189d | 178 | if (trim($dataanswer) === '*') { |
94a6d656 | 179 | $answer->answer = '*'; |
180 | } else { | |
92b36005 | 181 | $answer->answer = $this->apply_unit($dataanswer, $units); |
94a6d656 | 182 | if ($answer->answer === false) { |
183 | $result->notice = get_string('invalidnumericanswer', 'quiz'); | |
516cf3eb | 184 | } |
94a6d656 | 185 | } |
186 | $answer->fraction = $question->fraction[$key]; | |
fe6ce234 DC |
187 | |
188 | $feedbacktext = trim($question->feedback[$key]['text']); | |
189 | $draftid = $question->feedback[$key]['itemid']; | |
190 | ||
191 | ||
192 | $answer->feedbackformat = $question->feedback[$key]['format']; | |
516cf3eb | 193 | |
94a6d656 | 194 | if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it |
fe6ce234 DC |
195 | $feedbacktext = file_save_draft_area_files($draftid, $context->id, 'question', 'answerfeedback', $oldanswer->id, self::$fileoptions, $feedbacktext); |
196 | $answer->feedback = $feedbacktext; | |
94a6d656 | 197 | $answer->id = $oldanswer->id; |
0bcf8b6f | 198 | $DB->update_record("question_answers", $answer); |
94a6d656 | 199 | } else { // This is a completely new answer |
fe6ce234 | 200 | $answer->feedback = $feedbacktext; |
0bcf8b6f | 201 | $answer->id = $DB->insert_record("question_answers", $answer); |
fe6ce234 DC |
202 | $feedbacktext = file_save_draft_area_files($draftid, $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $feedbacktext); |
203 | $DB->set_field('question_answers', 'feedback', $feedbacktext, array('id'=>$answer->id)); | |
94a6d656 | 204 | } |
f34488b2 | 205 | |
94a6d656 | 206 | // Set up the options object |
207 | if (!$options = array_shift($oldoptions)) { | |
208 | $options = new stdClass; | |
209 | } | |
210 | $options->question = $question->id; | |
211 | $options->answer = $answer->id; | |
212 | if (trim($question->tolerance[$key]) == '') { | |
213 | $options->tolerance = ''; | |
214 | } else { | |
92b36005 | 215 | $options->tolerance = $this->apply_unit($question->tolerance[$key], $units); |
94a6d656 | 216 | if ($options->tolerance === false) { |
217 | $result->notice = get_string('invalidnumerictolerance', 'quiz'); | |
218 | } | |
219 | } | |
220 | ||
221 | // Save options | |
222 | if (isset($options->id)) { // reusing existing record | |
0bcf8b6f | 223 | $DB->update_record('question_numerical', $options); |
94a6d656 | 224 | } else { // new options |
0bcf8b6f | 225 | $DB->insert_record('question_numerical', $options); |
1fe641f7 | 226 | } |
227 | } | |
228 | // delete old answer records | |
229 | if (!empty($oldanswers)) { | |
230 | foreach($oldanswers as $oa) { | |
f34488b2 | 231 | $DB->delete_records('question_answers', array('id' => $oa->id)); |
1fe641f7 | 232 | } |
233 | } | |
516cf3eb | 234 | |
1fe641f7 | 235 | // delete old answer records |
236 | if (!empty($oldoptions)) { | |
237 | foreach($oldoptions as $oo) { | |
f34488b2 | 238 | $DB->delete_records('question_numerical', array('id' => $oo->id)); |
516cf3eb | 239 | } |
240 | } | |
c5da9906 | 241 | $result = $this->save_numerical_options($question); |
242 | if (isset($result->error)) { | |
243 | return $result; | |
244 | } | |
1fe641f7 | 245 | // Report any problems. |
246 | if (!empty($result->notice)) { | |
247 | return $result; | |
248 | } | |
1fe641f7 | 249 | return true; |
516cf3eb | 250 | } |
251 | ||
92b36005 PP |
252 | /** |
253 | * The numerical options control the display and the grading of the unit | |
254 | * part of the numerical question and related types (calculateds) | |
255 | * Questions previous to 2,0 do not have this table as multianswer questions | |
256 | * in all versions including 2,0. The default values are set to give the same grade | |
257 | * as old question. | |
258 | * | |
259 | */ | |
c5da9906 | 260 | function save_numerical_options(&$question) { |
261 | global $DB; | |
92b36005 | 262 | // echo"<p> ".$question->id."question<pre>";print_r($question) ;echo"</pre></p>"; |
fe6ce234 | 263 | |
c5da9906 | 264 | $result = new stdClass; |
265 | // numerical options | |
aeb15530 | 266 | $update = true ; |
fe6ce234 | 267 | $options = $DB->get_record('question_numerical_options', array('question' => $question->id)); |
c5da9906 | 268 | if (!$options) { |
269 | $update = false; | |
270 | $options = new stdClass; | |
271 | $options->question = $question->id; | |
272 | } | |
92b36005 PP |
273 | if(isset($question->options->unitgradingtype)){ |
274 | $options->unitgradingtype = $question->options->unitgradingtype; | |
c5da9906 | 275 | }else { |
276 | $options->unitgradingtype = 0 ; | |
277 | } | |
278 | if(isset($question->unitpenalty)){ | |
279 | $options->unitpenalty = $question->unitpenalty; | |
92b36005 PP |
280 | }else { //so this is either an old question or a close question type |
281 | $options->unitpenalty = 1 ; | |
c5da9906 | 282 | } |
cf146692 PP |
283 | // if we came from the form then 'unitrole' exists |
284 | if(isset($question->unitrole)){ | |
92b36005 PP |
285 | switch ($question->unitrole){ |
286 | case '0' : $options->showunits = NUMERICALQUESTIONUNITNODISPLAY ; | |
287 | break ; | |
288 | case '1' : $options->showunits = NUMERICALQUESTIONUNITTEXTDISPLAY ; | |
289 | break ; | |
290 | case '2' : $options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ; | |
291 | $options->unitgradingtype = 0 ; | |
292 | break ; | |
293 | case '3' : $options->showunits = $question->multichoicedisplay ; | |
294 | $options->unitgradingtype = $question->unitgradingtypes ; | |
295 | break ; | |
cf146692 | 296 | } |
92b36005 | 297 | } else { |
cf146692 PP |
298 | if(isset($question->showunits)){ |
299 | $options->showunits = $question->showunits; | |
300 | }else { | |
301 | if ($defaultunit = $this->get_default_numerical_unit($question)) { | |
92b36005 | 302 | // so units can be used |
04e91671 | 303 | $options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ; |
cf146692 PP |
304 | }else { |
305 | // only numerical will be graded | |
04e91671 | 306 | $options->showunits = NUMERICALQUESTIONUNITNODISPLAY ; |
cf146692 PP |
307 | } |
308 | } | |
c5da9906 | 309 | } |
310 | if(isset($question->unitsleft)){ | |
311 | $options->unitsleft = $question->unitsleft; | |
312 | }else { | |
313 | $options->unitsleft = 0 ; | |
314 | } | |
fe6ce234 | 315 | $options->instructionsformat = $question->instructions['format']; |
c5da9906 | 316 | if(isset($question->instructions)){ |
fe6ce234 | 317 | $options->instructions = trim($question->instructions['text']); |
c5da9906 | 318 | }else { |
319 | $options->instructions = '' ; | |
320 | } | |
fe6ce234 DC |
321 | $component = 'qtype_' . $question->qtype; |
322 | $options->instructions = file_save_draft_area_files($question->instructions['itemid'], | |
323 | $question->context->id, // context | |
324 | $component, // component | |
325 | 'instruction', // filearea | |
326 | $question->id, // itemid | |
327 | self::$fileoptions, // options | |
328 | $question->instructions['text'] // text | |
329 | ); | |
c5da9906 | 330 | if ($update) { |
fe6ce234 | 331 | $DB->update_record("question_numerical_options", $options); |
c5da9906 | 332 | } else { |
fe6ce234 | 333 | $id = $DB->insert_record("question_numerical_options", $options); |
c5da9906 | 334 | } |
fe6ce234 | 335 | |
c5da9906 | 336 | return $result; |
337 | } | |
338 | ||
516cf3eb | 339 | function save_numerical_units($question) { |
f34488b2 | 340 | global $DB; |
ed0ba6da | 341 | $result = new stdClass; |
516cf3eb | 342 | |
ed0ba6da | 343 | // Delete the units previously saved for this question. |
f34488b2 | 344 | $DB->delete_records('question_numerical_units', array('question' => $question->id)); |
ed0ba6da | 345 | |
26b26662 | 346 | // Nothing to do. |
347 | if (!isset($question->multiplier)) { | |
348 | $result->units = array(); | |
349 | return $result; | |
350 | } | |
351 | ||
ed0ba6da | 352 | // Save the new units. |
516cf3eb | 353 | $units = array(); |
cf146692 | 354 | $unitalreadyinsert = array(); |
ed0ba6da | 355 | foreach ($question->multiplier as $i => $multiplier) { |
516cf3eb | 356 | // Discard any unit which doesn't specify the unit or the multiplier |
de08803f PS |
357 | if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])&& !array_key_exists($question->unit[$i],$unitalreadyinsert)) { |
358 | $unitalreadyinsert[$question->unit[$i]] = 1 ; | |
ed0ba6da | 359 | $units[$i] = new stdClass; |
516cf3eb | 360 | $units[$i]->question = $question->id; |
92b36005 | 361 | $units[$i]->multiplier = $this->apply_unit($question->multiplier[$i], array()); |
516cf3eb | 362 | $units[$i]->unit = $question->unit[$i]; |
979425b5 | 363 | $DB->insert_record('question_numerical_units', $units[$i]); |
516cf3eb | 364 | } |
365 | } | |
366 | unset($question->multiplier, $question->unit); | |
367 | ||
516cf3eb | 368 | $result->units = &$units; |
369 | return $result; | |
370 | } | |
371 | ||
c5da9906 | 372 | function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { |
373 | $state->responses = array(); | |
374 | $state->responses['answer'] = ''; | |
375 | $state->responses['unit'] = ''; | |
fe6ce234 | 376 | |
c5da9906 | 377 | return true; |
378 | } | |
379 | function restore_session_and_responses(&$question, &$state) { | |
380 | if(false === strpos($state->responses[''], '|||||')){ | |
aeb15530 | 381 | $state->responses['answer']= $state->responses['']; |
c5da9906 | 382 | $state->responses['unit'] = ''; |
1c5299bf | 383 | $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] ); |
c5da9906 | 384 | }else { |
385 | $responses = explode('|||||', $state->responses['']); | |
386 | $state->responses['answer']= $responses[0]; | |
387 | $state->responses['unit'] = $responses[1]; | |
388 | } | |
c5da9906 | 389 | |
c5da9906 | 390 | return true; |
391 | } | |
392 | ||
393 | function find_unit_index(&$question,$value){ | |
394 | $length = 0; | |
395 | $goodkey = 0 ; | |
396 | foreach ($question->options->units as $key => $unit){ | |
397 | if($unit->unit ==$value ) { | |
398 | return $key ; | |
399 | } | |
aeb15530 | 400 | } |
c5da9906 | 401 | return 0 ; |
402 | } | |
403 | ||
404 | function split_old_answer($rawresponse, $units, &$answer ,&$unit ) { | |
405 | $answer = $rawresponse ; | |
406 | // remove spaces and normalise decimal places. | |
407 | $search = array(' ', ','); | |
408 | $replace = array('', '.'); | |
409 | $rawresponse = str_replace($search, $replace, trim($rawresponse)); | |
410 | if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~', | |
411 | $rawresponse, $responseparts)) { | |
fe6ce234 | 412 | if(isset($responseparts[5]) ){ |
49580a59 PP |
413 | $unit = $responseparts[5] ; |
414 | } | |
fe6ce234 | 415 | if(isset($responseparts[1]) ){ |
49580a59 PP |
416 | $answer = $responseparts[1] ; |
417 | } | |
c5da9906 | 418 | } |
419 | return ; | |
420 | } | |
421 | ||
422 | ||
423 | function save_session_and_responses(&$question, &$state) { | |
424 | global $DB; | |
c5da9906 | 425 | |
426 | $responses = ''; | |
427 | if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){ | |
428 | $responses = $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit; | |
429 | }else if(isset($state->responses['unit'])){ | |
430 | $responses = $state->responses['answer'].'|||||'.$state->responses['unit'] ; | |
431 | }else { | |
432 | $responses = $state->responses['answer'].'|||||'; | |
433 | } | |
434 | // Set the legacy answer field | |
f685e830 | 435 | $DB->set_field('question_states', 'answer', $responses, array('id' => $state->id)); |
c5da9906 | 436 | return true; |
437 | } | |
438 | ||
04e91671 | 439 | /** |
1fe641f7 | 440 | * Deletes question from the question-type specific tables |
441 | * | |
442 | * @return boolean Success/Failure | |
443 | * @param object $question The question being deleted | |
444 | */ | |
90c3f310 | 445 | function delete_question($questionid) { |
f34488b2 | 446 | global $DB; |
447 | $DB->delete_records("question_numerical", array("question" => $questionid)); | |
c5da9906 | 448 | $DB->delete_records("question_numerical_options", array("question" => $questionid)); |
f34488b2 | 449 | $DB->delete_records("question_numerical_units", array("question" => $questionid)); |
516cf3eb | 450 | return true; |
451 | } | |
f1ee48c8 PP |
452 | /** |
453 | * This function has been reinserted in numerical/questiontype.php to simplify | |
454 | * the separate rendering of number and unit | |
455 | */ | |
456 | function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { | |
92b36005 PP |
457 | global $CFG, $OUTPUT; |
458 | //echo"<p> ".$question->id."question->options<pre>";print_r($question->options) ;echo"</pre></p>"; | |
459 | //echo"<p> ".$question->id."state<pre>";print_r($state) ;echo"</pre></p>"; | |
460 | ||
fe6ce234 | 461 | $context = $this->get_context_by_category_id($question->category); |
f1ee48c8 PP |
462 | $readonly = empty($options->readonly) ? '' : 'readonly="readonly"'; |
463 | $formatoptions = new stdClass; | |
464 | $formatoptions->noclean = true; | |
465 | $formatoptions->para = false; | |
466 | $nameprefix = $question->name_prefix; | |
fe6ce234 DC |
467 | $component = 'qtype_' . $question->qtype; |
468 | // rewrite instructions text | |
469 | $question->options->instructions = quiz_rewrite_question_urls($question->options->instructions, 'pluginfile.php', $context->id, $component, 'instruction', array($state->attempt, $state->question), $question->id); | |
f1ee48c8 PP |
470 | |
471 | /// Print question text and media | |
472 | ||
473 | $questiontext = format_text($question->questiontext, | |
474 | $question->questiontextformat, | |
475 | $formatoptions, $cmoptions->course); | |
f1ee48c8 PP |
476 | |
477 | /// Print input controls | |
478 | // as the entry is controlled the question type here is numerical | |
479 | // In all cases there is a text input for the number | |
fe6ce234 | 480 | // If $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY |
f1ee48c8 PP |
481 | // there is an additional text input for the unit |
482 | // If $question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY" | |
483 | // radio elements display the defined unit | |
484 | // The code allows the input number elememt to be displayed | |
485 | // before i.e. at left or after at rigth of the unit variants. | |
fe6ce234 | 486 | $nameanswer = "name=\"".$question->name_prefix."answer\""; |
f1ee48c8 | 487 | $nameunit = "name=\"".$question->name_prefix."unit\""; |
92b36005 | 488 | // put old answer data in $state->responses['answer'] and $state->responses['unit'] |
46a8f8ee | 489 | if (isset($state->responses['']) && $state->responses[''] != '' && !isset($state->responses['answer'])){ |
b79c7c28 PP |
490 | $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] ); |
491 | } | |
92b36005 | 492 | // prepare the values of the input elements to be dispalyed answer i.e. number and unit |
f1ee48c8 PP |
493 | if (isset($state->responses['answer']) && $state->responses['answer']!='') { |
494 | $valueanswer = ' value="'.s($state->responses['answer']).'" '; | |
495 | } else { | |
496 | $valueanswer = ' value="" '; | |
497 | } | |
498 | if (isset($state->responses['unit']) && $state->responses['unit']!='') { | |
499 | $valueunit = ' value="'.s($state->responses['unit']).'" '; | |
fe6ce234 | 500 | } else { |
f1ee48c8 PP |
501 | $valueunit = ' value="" '; |
502 | if ($question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ){ | |
503 | $valueunit = ' value="'.s($question->options->units[0]->unit).'" '; | |
504 | } | |
505 | } | |
506 | ||
507 | $feedback = ''; | |
508 | $class = ''; | |
1285531b | 509 | $classunit = '' ; |
eb1284a2 | 510 | $classunitvalue = '' ; |
f1ee48c8 PP |
511 | $feedbackimg = ''; |
512 | $feedbackimgunit = '' ; | |
513 | $answerasterisk = false ; | |
1285531b | 514 | $response = '' ; |
eb1284a2 | 515 | $valid_numerical_unit = false ; |
92b36005 PP |
516 | $valid_numerical_unit_index = -1 ; |
517 | $unit_in_numerical_answer = false ; | |
eb1284a2 | 518 | $rawgrade = 0 ; |
f1ee48c8 PP |
519 | if ($options->feedback) { |
520 | $class = question_get_feedback_class(0); | |
1285531b | 521 | $classunit = question_get_feedback_class(0); |
f1ee48c8 PP |
522 | $feedbackimg = question_get_feedback_image(0); |
523 | $feedbackimgunit = question_get_feedback_image(0); | |
524 | $classunitvalue = 0 ; | |
92b36005 PP |
525 | $valid_numerical_unit_index = -1 ; |
526 | // if there is unit in answer and unitgradingtype = 0 | |
527 | // the grade is 0 | |
f1ee48c8 PP |
528 | //this is OK for the first answer with a good response |
529 | // having to test for * so response as long as not empty | |
92b36005 PP |
530 | // $response = $this->extract_numerical_response($state->responses['answer']); |
531 | // test for a greater than 0 grade | |
f1ee48c8 | 532 | foreach($question->options->answers as $answer) { |
92b36005 PP |
533 | if ($this->test_response($question, $state, $answer)) { |
534 | // Answer was correct or partially correct. | |
535 | if ( $answer->answer === '*'){ | |
536 | $answerasterisk = true ; | |
537 | } | |
538 | // in all cases | |
eb1284a2 PP |
539 | $class = question_get_feedback_class($answer->fraction); |
540 | $feedbackimg = question_get_feedback_image($answer->fraction); | |
92b36005 PP |
541 | if ($question->options->unitgradingtype == 0 || ($question->options->unitgradingtype == 0 && $answer->answer === '*')){ |
542 | // if * then unit has the $answer->fraction value | |
543 | // if $question->options->unitgradingtype == 0 everything has been checked | |
544 | // if $question->options->showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY | |
545 | // then number - unit combination has been used to test response | |
546 | // so the unit should have same color | |
547 | $classunit = question_get_feedback_class($answer->fraction); | |
548 | $feedbackimgunit = question_get_feedback_image($answer->fraction); | |
549 | $rawgrade = $answer->fraction ; | |
550 | ||
551 | ||
552 | }else { | |
553 | // so we need to apply unit grading i.e. to check if the number-unit combination | |
554 | // was the rigth one | |
555 | // on NUMERICALQUESTIONUNITTEXTINPUTDISPLAY we need only to ckeck if applyunit will test OK | |
556 | // with the $state->responses['unit'] value which cannot be empty | |
557 | // if $state->responses['unit'] | |
558 | // if apply-unit is true with a specific unit as long as the unit as been written either in the | |
559 | // we need the numerical response and test it with the available units | |
560 | // if the unit used is good then it should be set OK | |
561 | // however the unit could have been put in the number element in this case | |
562 | // the unit penalty should be apllied. | |
563 | // testing apply_unit with no units will get us a false response if there is any text in it | |
564 | // testing apply_unit with a given unit will get a good value if the number is good with this unit | |
565 | // apply unit will return the numerical if | |
566 | // we need to know which conditions let to a good numerical value that were done in the | |
567 | // | |
568 | // echo"<p> unit grading > 0 asterisk <pre>";print_r($answer) ;echo"</pre></p>"; | |
569 | $valid_numerical_unit = false ; | |
570 | $validunit = false ; | |
571 | $rawgrade = $answer->fraction ; | |
572 | $valid_numerical_unit_index = -1 ; | |
573 | $invalid_unit_in_numerical_answer = false ; | |
574 | if ( $answerasterisk ) { | |
575 | $classunit = question_get_feedback_class($answer->fraction); | |
576 | $feedbackimgunit = question_get_feedback_image($answer->fraction); | |
577 | $valid_numerical_unit = true ;//everything is true with * | |
578 | //echo"<p> answer asterisk <pre>";print_r($answer) ;echo"</pre></p>"; | |
579 | } else { | |
580 | //echo"<p> else after answer asterisk <pre>";print_r($answer) ;echo"</pre></p>"; | |
581 | // if( isset($state->responses['unit']) && $state->responses['unit'] != '' ){// unit should be written in the unit input or checked in multichoice | |
582 | // we need to see if something was written in the answer field that was not in the number | |
583 | // although we cannot actually detect units put before the number which will cause bad numerical. | |
584 | // use extract response | |
585 | $response = $this->extract_numerical_response($state->responses['answer']); | |
586 | if(isset($response->unit ) && $response->unit != ''){ | |
587 | $unit_in_numerical_answer = true ; | |
fe6ce234 | 588 | }else { |
92b36005 | 589 | $unit_in_numerical_answer = false ; |
eb1284a2 | 590 | } |
92b36005 PP |
591 | |
592 | // the we let the testing to the two cases either | |
593 | // NUMERICALQUESTIONUNITTEXTINPUTDISPLAY or | |
594 | // NUMERICALQUESTIONUNITMULTICHOICEDISPLAY | |
595 | if( !isset($state->responses['unit']) || $state->responses['unit'] == '' ){ | |
596 | // unit should be written in the unit input or checked in multichoice | |
597 | $valid_numerical_unit = false ; | |
598 | $classunit = question_get_feedback_class(0); | |
599 | $feedbackimgunit = question_get_feedback_image(0); | |
600 | $empty_unit = true ; | |
601 | } else { | |
602 | // echo"<p> some unit answer <pre>";print_r($answer) ;echo"</pre></p>"; | |
603 | // echo"<p> some unit answer <pre>";print_r($answer) ;echo"</pre></p>"; | |
604 | $empty_unit = false ; | |
605 | $valid_numerical_unit = false ; | |
606 | ||
607 | foreach ($question->options->units as $key => $unit) { | |
608 | if ($unit->unit == $state->responses['unit']){ | |
609 | // $response = $this->apply_unit($state->responses['answer'].$unit->unit, array($question->options->units[$key])) ; | |
610 | // echo "<p> avant false valid_numerical_unit_index $valid_numerical_unit_index ".$state->responses['answer']."</p>"; | |
611 | $invalid_unit_found = 0 ; | |
612 | if ($response->number !== false) { | |
613 | //echo "<p> avanr get valid_numerical_unit_index $valid_numerical_unit_index </p>"; | |
614 | // $this->get_tolerance_interval($answer); | |
615 | $testresponse = $response->number /$unit->multiplier ; | |
616 | if($answer->min <= $testresponse && $testresponse <= $answer->max){ | |
617 | //echo "<p> apres min max valid_numerical_unit_index $valid_numerical_unit_index </p>"; | |
618 | $classunit = question_get_feedback_class($answer->fraction) ; //question_get_feedback_class(1); | |
619 | $feedbackimgunit = question_get_feedback_image($rawgrade); | |
620 | $valid_numerical_unit = true ; | |
621 | $validunit = true ; | |
622 | $valid_numerical_unit_index = $key ; | |
623 | break ; | |
624 | } | |
625 | } | |
626 | } // else | |
627 | ||
628 | // } | |
629 | } | |
630 | //echo "<p> apres la boucle valid_numerical_unit $valid_numerical_unit valid_numerical_unit_index $valid_numerical_unit_index </p>"; | |
631 | ||
632 | } | |
633 | } | |
f1ee48c8 | 634 | } |
92b36005 | 635 | // echo "<p> dans valid_numerical_unit_index $valid_numerical_unit_index </p>"; |
f1ee48c8 | 636 | if ($answer->feedback) { |
92b36005 PP |
637 | $answer->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id); |
638 | $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); | |
f1ee48c8 | 639 | } |
92b36005 | 640 | |
fe6ce234 DC |
641 | break; |
642 | } | |
f1ee48c8 | 643 | } |
92b36005 PP |
644 | |
645 | ||
646 | } | |
647 | // echo "<p> rawgrade $rawgrade classunit $classunit valid_numerical_unit_index $valid_numerical_unit_index ".$feedbackimgunit."</p>"; | |
eb1284a2 PP |
648 | $state->options->raw_unitpenalty = 0 ; |
649 | $raw_unitpenalty = 0 ; | |
fe6ce234 | 650 | if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY || |
eb1284a2 PP |
651 | $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) { |
652 | $classunitvalue = 1 ; | |
653 | } | |
fe6ce234 | 654 | |
92b36005 | 655 | if(! $answerasterisk && $question->options->unitgradingtype != 0 && (! $valid_numerical_unit || $unit_in_numerical_answer)){ |
eb1284a2 PP |
656 | if($question->options->unitgradingtype == 1){ |
657 | $raw_unitpenalty = $question->options->unitpenalty * $rawgrade ; | |
658 | }else { | |
659 | $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade; | |
660 | } | |
fe6ce234 | 661 | $state->options->raw_unitpenalty = $raw_unitpenalty ; |
eb1284a2 PP |
662 | } |
663 | ||
f1ee48c8 PP |
664 | /// Removed correct answer, to be displayed later MDL-7496 |
665 | include("$CFG->dirroot/question/type/numerical/display.html"); | |
666 | } | |
aeb15530 | 667 | |
516cf3eb | 668 | |
5a14d563 | 669 | function compare_responses(&$question, $state, $teststate) { |
aa384ade | 670 | |
92b36005 | 671 | if ($question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY && isset($question->options->units) && isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']] )){ |
c5da9906 | 672 | $state->responses['unit']=$question->options->units[$state->responses['unit']]->unit; |
673 | }; | |
674 | ||
aeb15530 | 675 | |
c5da9906 | 676 | $responses = ''; |
677 | $testresponses = ''; | |
678 | if (isset($state->responses['answer'])){ | |
679 | $responses = $state->responses['answer']; | |
680 | } | |
681 | if (isset($state->responses['unit'])){ | |
682 | $responses .= $state->responses['unit']; | |
683 | } | |
684 | if (isset($teststate->responses['answer'])){ | |
685 | $testresponses = $teststate->responses['answer']; | |
686 | } | |
687 | if (isset($teststate->responses['unit'])){ | |
688 | $testresponses .= $teststate->responses['unit']; | |
689 | } | |
8b831fbb | 690 | |
f0b6151c | 691 | if ( isset($responses) && isset($testresponses )) { |
c5da9906 | 692 | |
693 | return $responses == $testresponses ; | |
5a14d563 | 694 | } |
695 | return false; | |
516cf3eb | 696 | } |
697 | ||
1fe641f7 | 698 | /** |
699 | * Checks whether a response matches a given answer, taking the tolerance | |
f1ee48c8 PP |
700 | * and but NOT the unit into account. Returns a true for if a response matches the |
701 | * answer or in one of the unit , false if it doesn't. | |
702 | * the total grading will see if the unit match. | |
fe6ce234 | 703 | * if unit != -1 then the test is done only on this unit |
1fe641f7 | 704 | */ |
f1ee48c8 | 705 | function test_response(&$question, &$state, $answer ) { |
55894a42 | 706 | // Deal with the match anything answer. |
92b36005 PP |
707 | // echo"<p> test_response answer <pre>";print_r($answer) ;echo"</pre></p>"; |
708 | // echo"<p>test_response state <pre>";print_r($state) ;echo"</pre></p>"; | |
fac1189d | 709 | if ($answer->answer === '*') { |
55894a42 | 710 | return true; |
516cf3eb | 711 | } |
92b36005 PP |
712 | // using old grading process if $question->unitgradingtype == 0 |
713 | // and adding unit1 for the new option NUMERICALQUESTIONUNITTEXTDISPLAY | |
714 | if ($question->options->unitgradingtype == 0 ){ | |
715 | // values coming form old question stored in attempts | |
716 | if (!isset($state->responses['answer']) && isset($state->responses[''])){ | |
717 | $state->responses['answer'] = $state->responses['']; | |
f1ee48c8 | 718 | } |
92b36005 PP |
719 | $answertotest = $state->responses['answer']; |
720 | // values coming from NUMERICALQUESTIONUNITTEXTINPUTDISPLAY | |
721 | // or NUMERICALQUESTIONUNITTEXTDISPLAY as unit hidden HTML element | |
8f609a37 PP |
722 | |
723 | if($question->options->showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ){ | |
724 | // | |
725 | $testresponse = $this->extract_numerical_response($state->responses['answer']); | |
726 | if($testresponse->unit != '' || $testresponse->number === false){ | |
727 | return false; | |
728 | } | |
729 | $answertotest = $testresponse->number ; | |
730 | } | |
92b36005 | 731 | if(isset($state->responses['unit'])) { |
8f609a37 | 732 | $answertotest .= $state->responses['unit'] ; |
92b36005 PP |
733 | } |
734 | // if ($question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY && isset($question->options->units[0])){ | |
735 | // $answertotest .= $question->options->units[0]->unit ; | |
736 | // } | |
737 | // test OK if only numerical or numerical with known unit names with the unit mltiplier applied | |
738 | $response = $this->apply_unit($answertotest, $question->options->units); | |
739 | // echo"<p> dans response apres apply <pre>";print_r($response) ;echo"</pre></p>"; | |
740 | ||
741 | if ($response === false) { | |
742 | return false; // The student did not type a number. | |
743 | } | |
744 | ||
745 | // The student did type a number, so check it with tolerances. | |
746 | $this->get_tolerance_interval($answer); | |
747 | // echo"<p> test_response apres get tolerance interval answer <pre>";print_r($answer) ;echo"</pre></p>"; | |
748 | return ($answer->min <= $response && $response <= $answer->max); | |
749 | }else { // $question->options->unitgradingtype > 0 | |
750 | /* testing with unitgradingtype $question->options->unitgradingtype > 0 | |
751 | * if the response is at least patially true | |
752 | * if the numerical value agree in the interval | |
753 | * if so the only non valid case will be a bad unit and a unity penalty. | |
754 | ||
755 | To be able to test (old) questions that do not have an unit | |
756 | * input element the test is done using the $state->responses[''] | |
757 | * which contains the response which is analyzed by extract_numerical_response() | |
758 | * If the data comes from the numerical or calculated display | |
759 | * the $state->responses['unit'] comes from either | |
760 | * a multichoice radio element NUMERICALQUESTIONUNITMULTICHOICEDISPLAY | |
761 | * where the $state->responses['unit'] value is the key => unit object | |
762 | * in the the $question->options->units array | |
763 | * or an input text element NUMERICALQUESTIONUNITTEXTINPUTDISPLAY | |
764 | * which contains the student response | |
765 | * for NUMERICALQUESTIONUNITTEXTDISPLAY and NUMERICALQUESTIONUNITNODISPLAY | |
766 | * | |
767 | */ | |
768 | ||
769 | $response = $this->extract_numerical_response($state->responses['answer']); | |
770 | ||
771 | // echo"<p> response <pre>";print_r($response) ;echo"</pre></p>"; | |
772 | // echo"<p> response <pre>";print_r($response) ;echo"</pre></p>"; | |
773 | ||
774 | if ($response->number === false ) { | |
775 | return false; // The student did not type a number. | |
776 | } | |
777 | ||
778 | // The student did type a number, so check it with tolerances. | |
779 | $this->get_tolerance_interval($answer); | |
780 | if ($answer->min <= $response->number && $response->number <= $answer->max){ | |
781 | // echo"<p> response true <pre>";print_r($response) ;echo"</pre></p>"; | |
782 | return true; | |
783 | } | |
784 | // testing for other units | |
785 | if ( isset($question->options->units) && count($question->options->units) > 0) { | |
786 | foreach($question->options->units as $key =>$unit){ | |
787 | $testresponse = $response->number /$unit->multiplier ; | |
788 | if($answer->min <= $testresponse && $testresponse<= $answer->max) { | |
789 | return true; | |
790 | } | |
791 | } | |
792 | } | |
793 | return false; | |
f1ee48c8 PP |
794 | } |
795 | return false; | |
516cf3eb | 796 | } |
797 | ||
04e91671 | 798 | /** |
fe6ce234 | 799 | * Performs response processing and grading |
04e91671 | 800 | * The function was redefined for handling correctly the two parts |
fe6ce234 DC |
801 | * number and unit of numerical or calculated questions |
802 | * The code handles also the case when there no unit defined by the user or | |
803 | * when used in a multianswer (Cloze) question. | |
04e91671 PP |
804 | * This function performs response processing and grading and updates |
805 | * the state accordingly. | |
806 | * @return boolean Indicates success or failure. | |
807 | * @param object $question The question to be graded. Question type | |
808 | * specific information is included. | |
809 | * @param object $state The state of the question to grade. The current | |
810 | * responses are in ->responses. The last graded state | |
811 | * is in ->last_graded (hence the most recently graded | |
812 | * responses are in ->last_graded->responses). The | |
813 | * question type specific information is also | |
814 | * included. The ->raw_grade and ->penalty fields | |
815 | * must be updated. The method is able to | |
816 | * close the question session (preventing any further | |
817 | * attempts at this question) by setting | |
818 | * $state->event to QUESTION_EVENTCLOSEANDGRADE | |
819 | * @param object $cmoptions | |
820 | */ | |
f1ee48c8 | 821 | function grade_responses(&$question, &$state, $cmoptions) { |
92b36005 PP |
822 | // echo"<p>grade question->options<pre>";print_r($question->options) ;echo"</pre></p>"; |
823 | // echo"<p>grade state response<pre>";print_r($state->responses) ;echo"</pre></p>"; | |
824 | /* if (!isset($state->responses['answer']) && isset($state->responses[''])){ | |
f1ee48c8 | 825 | $state->responses['answer'] = $state->responses['']; |
92b36005 PP |
826 | }*/ |
827 | if ( isset($state->responses['']) && $state->responses[''] != '' && !isset($state->responses['answer'])){ | |
828 | $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] ); | |
f1ee48c8 | 829 | } |
fe6ce234 | 830 | |
c5da9906 | 831 | //to apply the unit penalty we need to analyse the response in a more complex way |
832 | //the apply_unit() function analysis could be used to obtain the infos | |
aeb15530 | 833 | // however it is used to detect good or bad numbers but also |
92b36005 | 834 | // gives false if there is a unit |
c5da9906 | 835 | $state->raw_grade = 0; |
eb1284a2 | 836 | $valid_numerical_unit = false ; |
f1ee48c8 PP |
837 | $break = 0 ; |
838 | $unittested = ''; | |
839 | $hasunits = 0 ; | |
92b36005 | 840 | // $response = $this->extract_numerical_response($state->responses['answer']); |
f1ee48c8 | 841 | $answerasterisk = false ; |
fe6ce234 DC |
842 | |
843 | $break = 0 ; | |
c5da9906 | 844 | foreach($question->options->answers as $answer) { |
92b36005 PP |
845 | if ($this->test_response($question, $state, $answer)) { |
846 | // Answer was correct or partially correct. | |
847 | $state->raw_grade = $answer->fraction ; | |
848 | if ($question->options->unitgradingtype == 0 || $answer->answer === '*'){ | |
849 | // if * then unit has the $answer->fraction value | |
850 | // if $question->options->unitgradingtype == 0 everything has been checked | |
851 | // if $question->options->showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY | |
852 | // then number - unit combination has been used to test response | |
853 | // so the unit should have same color | |
854 | ||
855 | }else { | |
856 | // so we need to apply unit grading i.e. to check if the number-unit combination | |
857 | // was the rigth one | |
858 | $valid_numerical_unit = false ; | |
859 | $class = question_get_feedback_class($answer->fraction); | |
860 | $feedbackimg = question_get_feedback_image($answer->fraction); | |
861 | if(isset($state->responses['unit']) && $state->responses['unit'] != '' ){ | |
862 | foreach ($question->options->units as $key => $unit) { | |
863 | if ($unit->unit == $state->responses['unit']){ | |
864 | ||
865 | $response = $this->apply_unit($state->responses['answer'].$state->responses['unit'], array($question->options->units[$key])) ; | |
866 | if ($response !== false) { | |
867 | $this->get_tolerance_interval($answer); | |
868 | if($answer->min <= $response && $response <= $answer->max){ | |
869 | $valid_numerical_unit = true ; | |
870 | } | |
871 | } | |
872 | break ; | |
873 | } | |
874 | } | |
fe6ce234 | 875 | } |
f1ee48c8 | 876 | } |
92b36005 PP |
877 | break ; |
878 | } | |
0a49ee5c | 879 | } |
c5da9906 | 880 | // apply unit penalty |
eb1284a2 | 881 | $raw_unitpenalty = 0 ; |
92b36005 | 882 | if($question->options->unitgradingtype != 0 && !empty($question->options->unitpenalty)&& $valid_numerical_unit != true ){ |
c5da9906 | 883 | if($question->options->unitgradingtype == 1){ |
eb1284a2 | 884 | $raw_unitpenalty = $question->options->unitpenalty * $state->raw_grade ; |
c5da9906 | 885 | }else { |
eb1284a2 | 886 | $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade; |
c5da9906 | 887 | } |
eb1284a2 | 888 | $state->raw_grade -= $raw_unitpenalty ; |
c5da9906 | 889 | } |
92b36005 | 890 | |
c5da9906 | 891 | // Make sure we don't assign negative or too high marks. |
c5da9906 | 892 | $state->raw_grade = min(max((float) $state->raw_grade, |
893 | 0.0), 1.0) * $question->maxgrade; | |
92b36005 | 894 | |
c5da9906 | 895 | // Update the penalty. |
896 | $state->penalty = $question->penalty * $question->maxgrade; | |
897 | ||
898 | // mark the state as graded | |
899 | $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE; | |
900 | ||
901 | return true; | |
902 | } | |
aeb15530 PS |
903 | |
904 | ||
516cf3eb | 905 | function get_correct_responses(&$question, &$state) { |
906 | $correct = parent::get_correct_responses($question, $state); | |
c85aea03 | 907 | $unit = $this->get_default_numerical_unit($question); |
908 | if (isset($correct['']) && $correct[''] != '*' && $unit) { | |
516cf3eb | 909 | $correct[''] .= ' '.$unit->unit; |
aeb15530 | 910 | } |
516cf3eb | 911 | return $correct; |
912 | } | |
913 | ||
914 | // ULPGC ecastro | |
915 | function get_all_responses(&$question, &$state) { | |
1fe641f7 | 916 | $result = new stdClass; |
917 | $answers = array(); | |
516cf3eb | 918 | $unit = $this->get_default_numerical_unit($question); |
919 | if (is_array($question->options->answers)) { | |
920 | foreach ($question->options->answers as $aid=>$answer) { | |
1fe641f7 | 921 | $r = new stdClass; |
516cf3eb | 922 | $r->answer = $answer->answer; |
923 | $r->credit = $answer->fraction; | |
924 | $this->get_tolerance_interval($answer); | |
55894a42 | 925 | if ($r->answer != '*' && $unit) { |
926 | $r->answer .= ' ' . $unit->unit; | |
516cf3eb | 927 | } |
928 | if ($answer->max != $answer->min) { | |
929 | $max = "$answer->max"; //format_float($answer->max, 2); | |
930 | $min = "$answer->min"; //format_float($answer->max, 2); | |
931 | $r->answer .= ' ('.$min.'..'.$max.')'; | |
932 | } | |
933 | $answers[$aid] = $r; | |
934 | } | |
516cf3eb | 935 | } |
936 | $result->id = $question->id; | |
937 | $result->responses = $answers; | |
938 | return $result; | |
939 | } | |
c5da9906 | 940 | function get_actual_response($question, $state) { |
aeb15530 | 941 | if (!empty($state->responses) && !empty($state->responses[''])) { |
c5da9906 | 942 | if(false === strpos($state->responses[''], '|||||')){ |
943 | $responses[] = $state->responses['']; | |
944 | }else { | |
945 | $resp = explode('|||||', $state->responses['']); | |
aeb15530 | 946 | $responses[] = $resp[0].$resp[1]; |
c5da9906 | 947 | } |
948 | } else { | |
949 | $responses[] = ''; | |
950 | } | |
aeb15530 | 951 | |
c5da9906 | 952 | return $responses; |
953 | } | |
954 | ||
516cf3eb | 955 | |
956 | function get_tolerance_interval(&$answer) { | |
957 | // No tolerance | |
958 | if (empty($answer->tolerance)) { | |
1fe641f7 | 959 | $answer->tolerance = 0; |
516cf3eb | 960 | } |
961 | ||
962 | // Calculate the interval of correct responses (min/max) | |
963 | if (!isset($answer->tolerancetype)) { | |
964 | $answer->tolerancetype = 2; // nominal | |
965 | } | |
966 | ||
223ad0b9 | 967 | // We need to add a tiny fraction depending on the set precision to make the |
516cf3eb | 968 | // comparison work correctly. Otherwise seemingly equal values can yield |
969 | // false. (fixes bug #3225) | |
223ad0b9 | 970 | $tolerance = (float)$answer->tolerance + ("1.0e-".ini_get('precision')); |
516cf3eb | 971 | switch ($answer->tolerancetype) { |
972 | case '1': case 'relative': | |
973 | /// Recalculate the tolerance and fall through | |
974 | /// to the nominal case: | |
975 | $tolerance = $answer->answer * $tolerance; | |
dcd4192a | 976 | // Do not fall through to the nominal case because the tiny fraction is a factor of the answer |
977 | $tolerance = abs($tolerance); // important - otherwise min and max are swapped | |
978 | $max = $answer->answer + $tolerance; | |
979 | $min = $answer->answer - $tolerance; | |
980 | break; | |
516cf3eb | 981 | case '2': case 'nominal': |
982 | $tolerance = abs($tolerance); // important - otherwise min and max are swapped | |
dcd4192a | 983 | // $answer->tolerance 0 or something else |
984 | if ((float)$answer->tolerance == 0.0 && abs((float)$answer->answer) <= $tolerance ){ | |
f34488b2 | 985 | $tolerance = (float) ("1.0e-".ini_get('precision')) * abs((float)$answer->answer) ; //tiny fraction |
dcd4192a | 986 | } else if ((float)$answer->tolerance != 0.0 && abs((float)$answer->tolerance) < abs((float)$answer->answer) && abs((float)$answer->answer) <= $tolerance){ |
f34488b2 | 987 | $tolerance = (1+("1.0e-".ini_get('precision')) )* abs((float) $answer->tolerance) ;//tiny fraction |
988 | } | |
989 | ||
516cf3eb | 990 | $max = $answer->answer + $tolerance; |
991 | $min = $answer->answer - $tolerance; | |
992 | break; | |
ed0ba6da | 993 | case '3': case 'geometric': |
516cf3eb | 994 | $quotient = 1 + abs($tolerance); |
995 | $max = $answer->answer * $quotient; | |
996 | $min = $answer->answer / $quotient; | |
997 | break; | |
998 | default: | |
0b4f4187 | 999 | print_error('unknowntolerance', 'question', '', $answer->tolerancetype); |
516cf3eb | 1000 | } |
1001 | ||
1002 | $answer->min = $min; | |
1003 | $answer->max = $max; | |
1004 | return true; | |
1005 | } | |
1006 | ||
1007 | /** | |
1fe641f7 | 1008 | * Checks if the $rawresponse has a unit and applys it if appropriate. |
1009 | * | |
1010 | * @param string $rawresponse The response string to be converted to a float. | |
1011 | * @param array $units An array with the defined units, where the | |
1012 | * unit is the key and the multiplier the value. | |
1013 | * @return float The rawresponse with the unit taken into | |
1014 | * account as a float. | |
1015 | */ | |
f1ee48c8 | 1016 | function extract_numerical_response($rawresponse) { |
92b36005 | 1017 | $extractedresponse = new stdClass() ; |
f1ee48c8 PP |
1018 | $rawresponse = trim($rawresponse) ; |
1019 | $search = array(' ', ','); | |
1020 | // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , | |
1021 | if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { | |
1022 | $replace = array('', ''); | |
fe6ce234 | 1023 | }else { // remove spaces and normalise , to a . . |
f1ee48c8 PP |
1024 | $replace = array('', '.'); |
1025 | } | |
1026 | $rawresponse = str_replace($search, $replace, $rawresponse); | |
1027 | ||
1028 | if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~', | |
1029 | $rawresponse, $responseparts)) { | |
92b36005 PP |
1030 | //return (float)$responseparts[1] ; |
1031 | $extractedresponse->number = (float)$responseparts[1] ; | |
1032 | }else { | |
1033 | $extractedresponse->number = false ; | |
1034 | } | |
1035 | if (!empty($responseparts[5])) { | |
1036 | $extractedresponse->unit = $responseparts[5] ; | |
1037 | }else { | |
1038 | $extractedresponse->unit = ''; | |
f1ee48c8 | 1039 | } |
92b36005 | 1040 | |
f1ee48c8 | 1041 | // Invalid number. Must be wrong. |
92b36005 | 1042 | return clone($extractedresponse) ; |
f1ee48c8 | 1043 | } |
eb1284a2 | 1044 | /** |
f1ee48c8 PP |
1045 | * Checks if the $rawresponse has a unit and applys it if appropriate. |
1046 | * | |
1047 | * @param string $rawresponse The response string to be converted to a float. | |
1048 | * @param array $units An array with the defined units, where the | |
1049 | * unit is the key and the multiplier the value. | |
1050 | * @return float The rawresponse with the unit taken into | |
1051 | * account as a float. | |
1052 | */ | |
92b36005 PP |
1053 | function apply_unit($rawresponse, $units) { |
1054 | // echo"<p> rawresponse $rawresponse <pre>";print_r($units) ;echo"</pre></p>"; | |
1055 | ||
516cf3eb | 1056 | // Make units more useful |
1057 | $tmpunits = array(); | |
1058 | foreach ($units as $unit) { | |
1059 | $tmpunits[$unit->unit] = $unit->multiplier; | |
1060 | } | |
1fe641f7 | 1061 | // remove spaces and normalise decimal places. |
be1bb80e | 1062 | $rawresponse = trim($rawresponse) ; |
516cf3eb | 1063 | $search = array(' ', ','); |
be1bb80e PP |
1064 | // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , |
1065 | if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { | |
1066 | $replace = array('', ''); | |
fe6ce234 | 1067 | }else { // remove spaces and normalise , to a . . |
be1bb80e PP |
1068 | $replace = array('', '.'); |
1069 | } | |
1070 | $rawresponse = str_replace($search, $replace, $rawresponse); | |
f34488b2 | 1071 | |
f1ee48c8 | 1072 | |
1fe641f7 | 1073 | // Apply any unit that is present. |
f1ee48c8 | 1074 | if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$', |
1fe641f7 | 1075 | $rawresponse, $responseparts)) { |
92b36005 | 1076 | // echo"<p> responseparts <pre>";print_r($responseparts) ;echo"</pre></p>"; |
f34488b2 | 1077 | |
1fe641f7 | 1078 | if (!empty($responseparts[5])) { |
f34488b2 | 1079 | |
1fe641f7 | 1080 | if (isset($tmpunits[$responseparts[5]])) { |
1081 | // Valid number with unit. | |
1082 | return (float)$responseparts[1] / $tmpunits[$responseparts[5]]; | |
c5da9906 | 1083 | } else { |
f1ee48c8 PP |
1084 | // Valid number with invalid unit. Must be wrong. |
1085 | return false; | |
c5da9906 | 1086 | } |
1087 | ||
1088 | } else { | |
1089 | // Valid number without unit. | |
1090 | return (float)$responseparts[1]; | |
1091 | } | |
1092 | } | |
1093 | // Invalid number. Must be wrong. | |
1094 | return false; | |
1095 | } | |
f1ee48c8 | 1096 | |
04e91671 | 1097 | /** |
fe6ce234 DC |
1098 | * function used in function definition_inner() |
1099 | * of edit_..._form.php for | |
1100 | * numerical, calculated, calculatedsimple | |
1101 | */ | |
cf146692 | 1102 | function add_units_options(&$mform, &$that){ |
cf146692 | 1103 | // Units are graded |
92b36005 PP |
1104 | $mform->addElement('header', 'unithandling', get_string('unitshandling', 'qtype_numerical')); |
1105 | $mform->addElement('radio', 'unitrole', get_string('unitnotused', 'qtype_numerical'), get_string('onlynumerical', 'qtype_numerical'),0); | |
1106 | // $mform->addElement('header', 'unithandling1', get_string('unitnotgraded', 'qtype_numerical')); | |
1107 | $mform->addElement('radio', 'unitrole', get_string('unitdisplay', 'qtype_numerical'), get_string('oneunitshown', 'qtype_numerical'),1); | |
1108 | $mform->addElement('radio', 'unitrole', get_string('unitsused', 'qtype_numerical'), get_string('manynumerical', 'qtype_numerical'),2); | |
1109 | /* $showunits1grp = array(); | |
1110 | $showunits1grp[] = & $mform->createElement('radio', 'showunits1', '', get_string('no', 'moodle'),3); | |
1111 | $showunits1grp[] = & $mform->createElement('radio', 'showunits1', '', get_string('yes', 'moodle'),2);*/ | |
1112 | // $mform->addGroup($showunits1grp, 'showunits1grp', get_string('unitdisplay', 'qtype_numerical'),' ' , false); | |
1113 | $mform->addElement('static', 'separator2', '', '<HR/>'); | |
1114 | $mform->addElement('radio', 'unitrole', get_string('unitgraded1', 'qtype_numerical'), get_string('unitgraded', 'qtype_numerical'),3); | |
cf146692 PP |
1115 | $penaltygrp = array(); |
1116 | $penaltygrp[] =& $mform->createElement('text', 'unitpenalty', get_string('unitpenalty', 'qtype_numerical') , | |
1117 | array('size' => 6)); | |
1118 | $unitgradingtypes = array('1' => get_string('decfractionofquestiongrade', 'qtype_numerical'), '2' => get_string('decfractionofresponsegrade', 'qtype_numerical')); | |
92b36005 | 1119 | $penaltygrp[] =& $mform->createElement('select', 'unitgradingtypes', '' , $unitgradingtypes ); |
cf146692 | 1120 | $mform->addGroup($penaltygrp, 'penaltygrp', get_string('unitpenalty', 'qtype_numerical'),' ' , false); |
92b36005 PP |
1121 | $multichoicedisplaygrp = array(); |
1122 | $multichoicedisplaygrp[] =& $mform->createElement('radio', 'multichoicedisplay', get_string('unitedit', 'qtype_numerical'), get_string('editableunittext', 'qtype_numerical'),0); | |
1123 | $multichoicedisplaygrp[] =& $mform->createElement('radio', 'multichoicedisplay', get_string('selectunits', 'qtype_numerical') , get_string('unitchoice', 'qtype_numerical'),1); | |
1124 | $mform->addGroup($multichoicedisplaygrp, 'multichoicedisplaygrp', get_string('studentunitanswer', 'qtype_numerical'),' OR ' , false); | |
1125 | ||
1126 | ||
1127 | ||
1128 | $unitslefts = array('0' => get_string('rightexample', 'qtype_numerical'),'1' => get_string('leftexample', 'qtype_numerical')); | |
1129 | $mform->addElement('select', 'unitsleft', get_string('unitposition', 'qtype_numerical') , $unitslefts ); | |
1130 | ||
1131 | $mform->addElement('static', 'separator2', '<HR/>', '<HR/>'); | |
1132 | ||
1133 | ||
fe6ce234 | 1134 | $mform->addElement('editor', 'instructions', get_string('instructions', 'qtype_numerical'), null, $that->editoroptions); |
92b36005 | 1135 | // $mform->addElement('static', 'separator1', '<HR/>', '<HR/>'); |
cf146692 | 1136 | // Units are not graded |
cf146692 | 1137 | $showunits1grp = array(); |
cf146692 | 1138 | $mform->addElement('static', 'separator2', '<HR/>', '<HR/>'); |
aeb15530 | 1139 | |
c5da9906 | 1140 | $mform->setType('unitpenalty', PARAM_NUMBER); |
c5da9906 | 1141 | $mform->setDefault('unitpenalty', 0.1); |
92b36005 PP |
1142 | $mform->setDefault('unitgradingtypes', 1); |
1143 | $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical'); // TODO help did not exist before MDL-21695 | |
1144 | // $mform->setDefault('multichoicedisplay', 1); | |
1145 | // $mform->setDefault('showunits1', 3); | |
c5da9906 | 1146 | $mform->setDefault('unitsleft', 0); |
c5da9906 | 1147 | $mform->setType('instructions', PARAM_RAW); |
92b36005 | 1148 | // $mform->addHelpButton('instructions', 'unituses', 'qtype_numerical'); |
5fcefc97 | 1149 | $mform->addHelpButton('instructions', 'numericalinstructions', 'qtype_numerical'); |
92b36005 | 1150 | $mform->disabledIf('penaltygrp', 'unitrole','eq','0'); |
5fe5e8ac | 1151 | $mform->disabledIf('penaltygrp', 'unitrole','eq','1'); |
92b36005 PP |
1152 | $mform->disabledIf('penaltygrp', 'unitrole','eq','2'); |
1153 | // $mform->disabledIf('unitgradingtype', 'unitrole','eq','1'); | |
1154 | // $mform->disabledIf('instructions', 'unitrole','eq','1'); | |
1155 | $mform->disabledIf('unitsleft', 'unitrole','eq','0'); | |
1156 | // $mform->disabledIf('showunits1','unitrole','eq','0'); | |
1157 | $mform->disabledIf('multichoicedisplay','unitrole','eq','0'); | |
1158 | $mform->disabledIf('multichoicedisplay','unitrole','eq','1'); | |
1159 | $mform->disabledIf('multichoicedisplay','unitrole','eq','2'); | |
fe6ce234 DC |
1160 | |
1161 | ||
c5da9906 | 1162 | } |
5fcefc97 DM |
1163 | |
1164 | /** | |
1165 | * function used in in function definition_inner() | |
1166 | * of edit_..._form.php for | |
1167 | * numerical, calculated, calculatedsimple | |
1168 | */ | |
fe6ce234 | 1169 | function add_units_elements(& $mform,& $that) { |
cf146692 PP |
1170 | $repeated = array(); |
1171 | $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}')); | |
1172 | ||
1173 | $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz')); | |
1174 | $mform->setType('unit', PARAM_NOTAGS); | |
1175 | ||
1176 | $repeated[] =& $mform->createElement('text', 'multiplier', get_string('multiplier', 'quiz')); | |
1177 | $mform->setType('multiplier', PARAM_NUMBER); | |
1178 | ||
04e91671 | 1179 | if (isset($that->question->options)){ |
cf146692 PP |
1180 | $countunits = count($that->question->options->units); |
1181 | } else { | |
1182 | $countunits = 0; | |
1183 | } | |
1184 | if ($that->question->formoptions->repeatelements){ | |
1185 | $repeatsatstart = $countunits + 1; | |
1186 | } else { | |
1187 | $repeatsatstart = $countunits; | |
1188 | } | |
1189 | $that->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_calculated', '{no}')); | |
1190 | ||
1191 | if ($mform->elementExists('multiplier[0]')){ | |
1192 | $firstunit =& $mform->getElement('multiplier[0]'); | |
1193 | $firstunit->freeze(); | |
1194 | $firstunit->setValue('1.0'); | |
1195 | $firstunit->setPersistantFreeze(true); | |
5fcefc97 | 1196 | $mform->addHelpButton('multiplier[0]', 'numericalmultiplier', 'qtype_numerical'); |
cf146692 PP |
1197 | } |
1198 | } | |
5fcefc97 DM |
1199 | |
1200 | /** | |
fe6ce234 DC |
1201 | * function used in in function data_preprocessing() of edit_numerical_form.php for |
1202 | * numerical, calculated, calculatedsimple | |
1203 | */ | |
1204 | function set_numerical_unit_data($mform, &$question, &$default_values){ | |
1205 | ||
1206 | list($categoryid) = explode(',', $question->category); | |
1207 | $context = $this->get_context_by_category_id($categoryid); | |
f1ee48c8 PP |
1208 | |
1209 | if (isset($question->options)){ | |
92b36005 PP |
1210 | $default_values['unitgradingtypes'] = 1 ; |
1211 | if ($question->options->unitgradingtype == 2 ) { | |
1212 | $default_values['unitgradingtypes'] = 1 ; | |
1213 | } | |
1214 | if ($question->options->unitgradingtype == 0 ) { | |
1215 | $default_values['unitgradingtypes'] = 0 ; | |
1216 | } | |
f1ee48c8 PP |
1217 | $default_values['unitpenalty'] = $question->options->unitpenalty ; |
1218 | switch ($question->options->showunits){ | |
92b36005 PP |
1219 | case 0 :// NUMERICALQUESTIONUNITTEXTINPUTDISPLAY |
1220 | if($question->options->unitgradingtype == 0 ){ | |
1221 | $default_values['unitrole'] = 2 ; | |
1222 | $default_values['multichoicedisplay'] = 0 ; | |
1223 | }else { // 1 or 2 | |
1224 | $default_values['unitrole'] = 3 ; | |
1225 | $default_values['multichoicedisplay'] = 0 ; | |
1226 | $default_values['unitgradingtypes'] = $question->options->unitgradingtype ; | |
1227 | } | |
1228 | break; | |
1229 | case 1 : // NUMERICALQUESTIONUNITMULTICHOICEDISPLAY | |
1230 | $default_values['unitrole'] = 3 ; | |
1231 | $default_values['multichoicedisplay'] = $question->options->unitgradingtype ; | |
1232 | $default_values['unitgradingtypes'] = $question->options->unitgradingtype ; | |
f1ee48c8 | 1233 | break; |
92b36005 | 1234 | case 2 : // NUMERICALQUESTIONUNITTEXTDISPLAY |
f1ee48c8 | 1235 | $default_values['unitrole'] = 1 ; |
92b36005 PP |
1236 | case 3 : // NUMERICALQUESTIONUNITNODISPLAY |
1237 | $default_values['unitrole'] = 0 ; | |
1238 | // $default_values['showunits1'] = $question->options->showunits ; | |
f1ee48c8 | 1239 | break; |
fe6ce234 | 1240 | } |
f1ee48c8 | 1241 | $default_values['unitsleft'] = $question->options->unitsleft ; |
92b36005 | 1242 | // $question->unitrole = $default_values['unitrole'] ; |
fe6ce234 DC |
1243 | |
1244 | // processing files | |
1245 | $component = 'qtype_' . $question->qtype; | |
1246 | $draftid = file_get_submitted_draft_itemid('instructions'); | |
1247 | $default_values['instructions'] = array(); | |
1248 | $default_values['instructions']['format'] = $question->options->instructionsformat; | |
1249 | $default_values['instructions']['text'] = file_prepare_draft_area( | |
1250 | $draftid, // draftid | |
1251 | $context->id, // context | |
1252 | $component, // component | |
1253 | 'instruction', // filarea | |
1254 | !empty($question->id)?(int)$question->id:null, // itemid | |
1255 | $mform->fileoptions, // options | |
1256 | $question->options->instructions // text | |
1257 | ); | |
1258 | $default_values['instructions']['itemid'] = $draftid; | |
1259 | ||
1260 | if (isset($question->options->units)) { | |
f1ee48c8 PP |
1261 | $units = array_values($question->options->units); |
1262 | if (!empty($units)) { | |
1263 | foreach ($units as $key => $unit){ | |
1264 | $default_values['unit['.$key.']'] = $unit->unit; | |
1265 | $default_values['multiplier['.$key.']'] = $unit->multiplier; | |
1266 | } | |
1267 | } | |
1268 | } | |
1269 | } | |
1270 | } | |
1271 | ||
fe6ce234 DC |
1272 | /** |
1273 | * function use in in function validation() | |
1274 | * of edit_..._form.php for | |
1275 | * numerical, calculated, calculatedsimple | |
1276 | */ | |
cf146692 PP |
1277 | |
1278 | function validate_numerical_options(& $data, & $errors){ | |
1279 | $units = $data['unit']; | |
92b36005 PP |
1280 | switch ($data['unitrole']){ |
1281 | case '0' : $showunits = NUMERICALQUESTIONUNITNODISPLAY ; | |
1282 | break ; | |
1283 | case '1' : $showunits = NUMERICALQUESTIONUNITTEXTDISPLAY ; | |
1284 | break ; | |
1285 | case '2' : $showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ; | |
1286 | break ; | |
1287 | case '3' : $showunits = $data['multichoicedisplay'] ; | |
1288 | break ; | |
1289 | } | |
fe6ce234 DC |
1290 | |
1291 | if (($showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY) || | |
1292 | ($showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY ) || | |
04e91671 | 1293 | ($showunits == NUMERICALQUESTIONUNITTEXTDISPLAY )){ |
cf146692 PP |
1294 | if (trim($units[0]) == ''){ |
1295 | $errors['unit[0]'] = 'You must set a valid unit name' ; | |
1296 | } | |
1297 | } | |
04e91671 | 1298 | if ($showunits == NUMERICALQUESTIONUNITNODISPLAY ){ |
cf146692 PP |
1299 | if (count($units)) { |
1300 | foreach ($units as $key => $unit){ | |
1301 | if ($units[$key] != ''){ | |
1302 | $errors["unit[$key]"] = 'You must erase this unit name' ; | |
1303 | } | |
1304 | } | |
1305 | } | |
1306 | } | |
fe6ce234 DC |
1307 | |
1308 | ||
cf146692 PP |
1309 | // Check double units. |
1310 | $alreadyseenunits = array(); | |
1311 | if (isset($data['unit'])) { | |
1312 | foreach ($data['unit'] as $key => $unit) { | |
1313 | $trimmedunit = trim($unit); | |
1314 | if ($trimmedunit!='' && in_array($trimmedunit, $alreadyseenunits)) { | |
1315 | $errors["unit[$key]"] = get_string('errorrepeatedunit', 'qtype_numerical'); | |
1316 | if (trim($data['multiplier'][$key]) == '') { | |
1317 | $errors["multiplier[$key]"] = get_string('errornomultiplier', 'qtype_numerical'); | |
1318 | } | |
1319 | } elseif($trimmedunit!='') { | |
1320 | $alreadyseenunits[] = $trimmedunit; | |
1321 | } | |
1322 | } | |
1323 | } | |
1324 | $units = $data['unit']; | |
1325 | if (count($units)) { | |
1326 | foreach ($units as $key => $unit){ | |
1327 | if (is_numeric($unit)){ | |
1328 | $errors['unit['.$key.']'] = get_string('mustnotbenumeric', 'qtype_calculated'); | |
1329 | } | |
1330 | $trimmedunit = trim($unit); | |
1331 | $trimmedmultiplier = trim($data['multiplier'][$key]); | |
1332 | if (!empty($trimmedunit)){ | |
1333 | if (empty($trimmedmultiplier)){ | |
1334 | $errors['multiplier['.$key.']'] = get_string('youmustenteramultiplierhere', 'qtype_calculated'); | |
1335 | } | |
1336 | if (!is_numeric($trimmedmultiplier)){ | |
1337 | $errors['multiplier['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated'); | |
1338 | } | |
1339 | ||
1340 | } | |
fe6ce234 | 1341 | } |
cf146692 PP |
1342 | } |
1343 | ||
1344 | } | |
92b36005 | 1345 | |
aeb15530 | 1346 | |
04e91671 | 1347 | function valid_unit($rawresponse, $units) { |
c5da9906 | 1348 | // Make units more useful |
1349 | $tmpunits = array(); | |
1350 | foreach ($units as $unit) { | |
1351 | $tmpunits[$unit->unit] = $unit->multiplier; | |
1352 | } | |
1353 | // remove spaces and normalise decimal places. | |
1354 | $search = array(' ', ','); | |
1355 | $replace = array('', '.'); | |
1356 | $rawresponse = str_replace($search, $replace, trim($rawresponse)); | |
1357 | ||
1358 | // Apply any unit that is present. | |
1359 | if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~', | |
1360 | $rawresponse, $responseparts)) { | |
1361 | ||
1362 | if (!empty($responseparts[5])) { | |
1363 | ||
1364 | if (isset($tmpunits[$responseparts[5]])) { | |
1365 | // Valid number with unit. | |
1366 | return true ; //(float)$responseparts[1] / $tmpunits[$responseparts[5]]; | |
1fe641f7 | 1367 | } else { |
1368 | // Valid number with invalid unit. Must be wrong. | |
1369 | return false; | |
1370 | } | |
1371 | ||
516cf3eb | 1372 | } else { |
1fe641f7 | 1373 | // Valid number without unit. |
c5da9906 | 1374 | return false ; //(float)$responseparts[1]; |
516cf3eb | 1375 | } |
1376 | } | |
1fe641f7 | 1377 | // Invalid number. Must be wrong. |
1378 | return false; | |
516cf3eb | 1379 | } |
f34488b2 | 1380 | |
b9bd6da4 | 1381 | /** |
1382 | * Runs all the code required to set up and save an essay question for testing purposes. | |
1383 | * Alternate DB table prefix may be used to facilitate data deletion. | |
1384 | */ | |
1385 | function generate_test($name, $courseid = null) { | |
1386 | global $DB; | |
1387 | list($form, $question) = default_questiontype::generate_test($name, $courseid); | |
1388 | $question->category = $form->category; | |
1389 | ||
1390 | $form->questiontext = "What is 674 * 36?"; | |
1391 | $form->generalfeedback = "Thank you"; | |
1392 | $form->penalty = 0.1; | |
1393 | $form->defaultgrade = 1; | |
1394 | $form->noanswers = 3; | |
1395 | $form->answer = array('24264', '24264', '1'); | |
1396 | $form->tolerance = array(10, 100, 0); | |
1397 | $form->fraction = array(1, 0.5, 0); | |
1398 | $form->nounits = 2; | |
1399 | $form->unit = array(0 => null, 1 => null); | |
1400 | $form->multiplier = array(1, 0); | |
1401 | $form->feedback = array('Very good', 'Close, but not quite there', 'Well at least you tried....'); | |
1402 | ||
1403 | if ($courseid) { | |
1404 | $course = $DB->get_record('course', array('id' => $courseid)); | |
1405 | } | |
1406 | ||
1407 | return $this->save_question($question, $form, $course); | |
1408 | } | |
fe6ce234 DC |
1409 | /** |
1410 | * When move the category of questions, the belonging files should be moved as well | |
1411 | * @param object $question, question information | |
1412 | * @param object $newcategory, target category information | |
1413 | */ | |
1414 | function move_files($question, $newcategory) { | |
1415 | global $DB; | |
1416 | parent::move_files($question, $newcategory); | |
1417 | ||
1418 | $fs = get_file_storage(); | |
1419 | // process files in answer | |
1420 | if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { | |
1421 | $oldanswers = array(); | |
1422 | } | |
1423 | $component = 'question'; | |
1424 | $filearea = 'answerfeedback'; | |
1425 | foreach ($oldanswers as $answer) { | |
1426 | $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); | |
1427 | foreach ($files as $storedfile) { | |
1428 | if (!$storedfile->is_directory()) { | |
7f389342 | 1429 | $newfile = new stdClass(); |
fe6ce234 DC |
1430 | $newfile->contextid = (int)$newcategory->contextid; |
1431 | $fs->create_file_from_storedfile($newfile, $storedfile); | |
1432 | $storedfile->delete(); | |
1433 | } | |
1434 | } | |
1435 | } | |
1436 | $component = 'qtype_numerical'; | |
1437 | $filearea = 'instruction'; | |
1438 | $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); | |
1439 | foreach ($files as $storedfile) { | |
1440 | if (!$storedfile->is_directory()) { | |
7f389342 | 1441 | $newfile = new stdClass(); |
fe6ce234 DC |
1442 | $newfile->contextid = (int)$newcategory->contextid; |
1443 | $fs->create_file_from_storedfile($newfile, $storedfile); | |
1444 | $storedfile->delete(); | |
1445 | } | |
1446 | } | |
1447 | } | |
1448 | ||
1449 | function check_file_access($question, $state, $options, $contextid, $component, | |
1450 | $filearea, $args) { | |
1451 | $itemid = reset($args); | |
1452 | if ($component == 'question' && $filearea == 'answerfeedback') { | |
1453 | $result = $options->feedback && array_key_exists($itemid, $question->options->answers); | |
1454 | if (!$result) { | |
1455 | return false; | |
1456 | } | |
1457 | foreach($question->options->answers as $answer) { | |
1458 | if ($this->test_response($question, $state, $answer)) { | |
1459 | return true; | |
1460 | } | |
1461 | } | |
1462 | return false; | |
1463 | } else if ($filearea == 'instruction') { | |
1464 | if ($itemid != $question->id) { | |
1465 | return false; | |
1466 | } else { | |
1467 | return true; | |
1468 | } | |
1469 | } else { | |
1470 | return parent::check_file_access($question, $state, $options, $contextid, $component, | |
1471 | $filearea, $args); | |
1472 | } | |
1473 | } | |
516cf3eb | 1474 | } |
516cf3eb | 1475 | |
1fe641f7 | 1476 | // INITIATION - Without this line the question type is not in use. |
a2156789 | 1477 | question_register_questiontype(new question_numerical_qtype()); |
04e91671 PP |
1478 | if ( ! defined ("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY")) { |
1479 | define("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY", 0); | |
1480 | } | |
1481 | if ( ! defined ("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY")) { | |
1482 | define("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY", 1); | |
1483 | } | |
1484 | if ( ! defined ("NUMERICALQUESTIONUNITTEXTDISPLAY")) { | |
1485 | define("NUMERICALQUESTIONUNITTEXTDISPLAY", 2); | |
1486 | } | |
1487 | if ( ! defined ("NUMERICALQUESTIONUNITNODISPLAY")) { | |
1488 | define("NUMERICALQUESTIONUNITNODISPLAY", 3); | |
1489 | } |