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