MDL-22164 backup - finished cleaning of questions backup code
[moodle.git] / question / type / numerical / questiontype.php
CommitLineData
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 25require_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 39class 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 }
90 function get_numerical_options(&$question) {
91 global $DB;
cf146692 92 if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) {
f1ee48c8 93 $question->options->unitgradingtype = 0; // total grade
cf146692 94 $question->options->unitpenalty = 0;
fe6ce234 95 // the default
cf146692
PP
96 if ($defaultunit = $this->get_default_numerical_unit($question)) {
97 // so units can be graded
04e91671 98 $question->options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ;
f1ee48c8 99 $question->options->unitpenalty = 1;
cf146692
PP
100 }else {
101 // only numerical will be graded
04e91671 102 $question->options->showunits = NUMERICALQUESTIONUNITNODISPLAY ;
cf146692
PP
103 }
104 $question->options->unitsleft = 0 ;
fe6ce234
DC
105 $question->options->instructions = '';
106 $question->options->instructionsformat = editors_get_preferred_format();
c5da9906 107 } else {
cf146692
PP
108 $question->options->unitgradingtype = $options->unitgradingtype;
109 $question->options->unitpenalty = $options->unitpenalty;
fe6ce234
DC
110 $question->options->showunits = $options->showunits;
111 $question->options->unitsleft = $options->unitsleft;
112 $question->options->instructions = $options->instructions;
113 $question->options->instructionsformat = $options->instructionsformat;
c5da9906 114 }
aeb15530 115
516cf3eb 116 return true;
117 }
fe6ce234 118
516cf3eb 119 function get_numerical_units(&$question) {
f34488b2 120 global $DB;
121 if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) {
3a513ba4 122 $units = array_values($units);
516cf3eb 123 } else {
b4d7d27c 124 $units = array();
516cf3eb 125 }
b4d7d27c 126 foreach ($units as $key => $unit) {
127 $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_NUMBER);
128 }
129 $question->options->units = $units;
516cf3eb 130 return true;
131 }
132
133 function get_default_numerical_unit(&$question) {
ed0ba6da 134 if (isset($question->options->units[0])) {
135 foreach ($question->options->units as $unit) {
136 if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) {
137 return $unit;
516cf3eb 138 }
139 }
140 }
ed0ba6da 141 return false;
516cf3eb 142 }
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 {
f1ee48c8 181 $answer->answer = $this->apply_unit_old($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 {
f1ee48c8 215 $options->tolerance = $this->apply_unit_old($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
c5da9906 252 function save_numerical_options(&$question) {
253 global $DB;
fe6ce234 254
c5da9906 255 $result = new stdClass;
256 // numerical options
aeb15530 257 $update = true ;
fe6ce234 258 $options = $DB->get_record('question_numerical_options', array('question' => $question->id));
c5da9906 259 if (!$options) {
260 $update = false;
261 $options = new stdClass;
262 $options->question = $question->id;
263 }
264 if(isset($question->unitgradingtype)){
265 $options->unitgradingtype = $question->unitgradingtype;
266 }else {
267 $options->unitgradingtype = 0 ;
268 }
269 if(isset($question->unitpenalty)){
270 $options->unitpenalty = $question->unitpenalty;
271 }else {
272 $options->unitpenalty = 0 ;
273 }
cf146692
PP
274 // if we came from the form then 'unitrole' exists
275 if(isset($question->unitrole)){
276 if ($question->unitrole == 0 ){
277 $options->showunits = $question->showunits0;
278 }else {
279 $options->showunits = $question->showunits1;
280 }
fe6ce234 281 }else {
cf146692
PP
282 if(isset($question->showunits)){
283 $options->showunits = $question->showunits;
284 }else {
285 if ($defaultunit = $this->get_default_numerical_unit($question)) {
286 // so units can be graded
04e91671 287 $options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ;
cf146692
PP
288 }else {
289 // only numerical will be graded
04e91671 290 $options->showunits = NUMERICALQUESTIONUNITNODISPLAY ;
cf146692
PP
291 }
292 }
c5da9906 293 }
294 if(isset($question->unitsleft)){
295 $options->unitsleft = $question->unitsleft;
296 }else {
297 $options->unitsleft = 0 ;
298 }
fe6ce234 299 $options->instructionsformat = $question->instructions['format'];
c5da9906 300 if(isset($question->instructions)){
fe6ce234 301 $options->instructions = trim($question->instructions['text']);
c5da9906 302 }else {
303 $options->instructions = '' ;
304 }
fe6ce234
DC
305 $component = 'qtype_' . $question->qtype;
306 $options->instructions = file_save_draft_area_files($question->instructions['itemid'],
307 $question->context->id, // context
308 $component, // component
309 'instruction', // filearea
310 $question->id, // itemid
311 self::$fileoptions, // options
312 $question->instructions['text'] // text
313 );
c5da9906 314 if ($update) {
fe6ce234 315 $DB->update_record("question_numerical_options", $options);
c5da9906 316 } else {
fe6ce234 317 $id = $DB->insert_record("question_numerical_options", $options);
c5da9906 318 }
fe6ce234 319
c5da9906 320 return $result;
321 }
322
516cf3eb 323 function save_numerical_units($question) {
f34488b2 324 global $DB;
ed0ba6da 325 $result = new stdClass;
516cf3eb 326
ed0ba6da 327 // Delete the units previously saved for this question.
f34488b2 328 $DB->delete_records('question_numerical_units', array('question' => $question->id));
ed0ba6da 329
26b26662 330 // Nothing to do.
331 if (!isset($question->multiplier)) {
332 $result->units = array();
333 return $result;
334 }
335
ed0ba6da 336 // Save the new units.
516cf3eb 337 $units = array();
cf146692 338 $unitalreadyinsert = array();
ed0ba6da 339 foreach ($question->multiplier as $i => $multiplier) {
516cf3eb 340 // Discard any unit which doesn't specify the unit or the multiplier
de08803f
PS
341 if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])&& !array_key_exists($question->unit[$i],$unitalreadyinsert)) {
342 $unitalreadyinsert[$question->unit[$i]] = 1 ;
ed0ba6da 343 $units[$i] = new stdClass;
516cf3eb 344 $units[$i]->question = $question->id;
f1ee48c8 345 $units[$i]->multiplier = $this->apply_unit_old($question->multiplier[$i], array());
516cf3eb 346 $units[$i]->unit = $question->unit[$i];
979425b5 347 $DB->insert_record('question_numerical_units', $units[$i]);
516cf3eb 348 }
349 }
350 unset($question->multiplier, $question->unit);
351
516cf3eb 352 $result->units = &$units;
353 return $result;
354 }
355
c5da9906 356 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
357 $state->responses = array();
358 $state->responses['answer'] = '';
359 $state->responses['unit'] = '';
fe6ce234 360
c5da9906 361 return true;
362 }
363 function restore_session_and_responses(&$question, &$state) {
364 if(false === strpos($state->responses[''], '|||||')){
aeb15530 365 $state->responses['answer']= $state->responses[''];
c5da9906 366 $state->responses['unit'] = '';
1c5299bf 367 $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
c5da9906 368 }else {
369 $responses = explode('|||||', $state->responses['']);
370 $state->responses['answer']= $responses[0];
371 $state->responses['unit'] = $responses[1];
372 }
c5da9906 373
c5da9906 374 return true;
375 }
376
377 function find_unit_index(&$question,$value){
378 $length = 0;
379 $goodkey = 0 ;
380 foreach ($question->options->units as $key => $unit){
381 if($unit->unit ==$value ) {
382 return $key ;
383 }
aeb15530 384 }
c5da9906 385 return 0 ;
386 }
387
388 function split_old_answer($rawresponse, $units, &$answer ,&$unit ) {
389 $answer = $rawresponse ;
390 // remove spaces and normalise decimal places.
391 $search = array(' ', ',');
392 $replace = array('', '.');
393 $rawresponse = str_replace($search, $replace, trim($rawresponse));
394 if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
395 $rawresponse, $responseparts)) {
fe6ce234 396 if(isset($responseparts[5]) ){
49580a59
PP
397 $unit = $responseparts[5] ;
398 }
fe6ce234 399 if(isset($responseparts[1]) ){
49580a59
PP
400 $answer = $responseparts[1] ;
401 }
c5da9906 402 }
403 return ;
404 }
405
406
407 function save_session_and_responses(&$question, &$state) {
408 global $DB;
c5da9906 409
410 $responses = '';
411 if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
412 $responses = $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit;
413 }else if(isset($state->responses['unit'])){
414 $responses = $state->responses['answer'].'|||||'.$state->responses['unit'] ;
415 }else {
416 $responses = $state->responses['answer'].'|||||';
417 }
418 // Set the legacy answer field
f685e830 419 $DB->set_field('question_states', 'answer', $responses, array('id' => $state->id));
c5da9906 420 return true;
421 }
422
04e91671 423 /**
1fe641f7 424 * Deletes question from the question-type specific tables
425 *
426 * @return boolean Success/Failure
427 * @param object $question The question being deleted
428 */
90c3f310 429 function delete_question($questionid) {
f34488b2 430 global $DB;
431 $DB->delete_records("question_numerical", array("question" => $questionid));
c5da9906 432 $DB->delete_records("question_numerical_options", array("question" => $questionid));
f34488b2 433 $DB->delete_records("question_numerical_units", array("question" => $questionid));
516cf3eb 434 return true;
435 }
f1ee48c8
PP
436 /**
437 * This function has been reinserted in numerical/questiontype.php to simplify
438 * the separate rendering of number and unit
439 */
440 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
441 global $CFG;
fe6ce234 442 $context = $this->get_context_by_category_id($question->category);
f1ee48c8
PP
443 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
444 $formatoptions = new stdClass;
445 $formatoptions->noclean = true;
446 $formatoptions->para = false;
447 $nameprefix = $question->name_prefix;
fe6ce234
DC
448 $component = 'qtype_' . $question->qtype;
449 // rewrite instructions text
450 $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
451
452 /// Print question text and media
453
454 $questiontext = format_text($question->questiontext,
455 $question->questiontextformat,
456 $formatoptions, $cmoptions->course);
f1ee48c8
PP
457
458 /// Print input controls
459 // as the entry is controlled the question type here is numerical
460 // In all cases there is a text input for the number
fe6ce234 461 // If $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY
f1ee48c8
PP
462 // there is an additional text input for the unit
463 // If $question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY"
464 // radio elements display the defined unit
465 // The code allows the input number elememt to be displayed
466 // before i.e. at left or after at rigth of the unit variants.
fe6ce234 467 $nameanswer = "name=\"".$question->name_prefix."answer\"";
f1ee48c8 468 $nameunit = "name=\"".$question->name_prefix."unit\"";
46a8f8ee 469 if (isset($state->responses['']) && $state->responses[''] != '' && !isset($state->responses['answer'])){
b79c7c28
PP
470 $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
471 }
f1ee48c8
PP
472 if (isset($state->responses['answer']) && $state->responses['answer']!='') {
473 $valueanswer = ' value="'.s($state->responses['answer']).'" ';
474 } else {
475 $valueanswer = ' value="" ';
476 }
477 if (isset($state->responses['unit']) && $state->responses['unit']!='') {
478 $valueunit = ' value="'.s($state->responses['unit']).'" ';
fe6ce234 479 } else {
f1ee48c8
PP
480 $valueunit = ' value="" ';
481 if ($question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ){
482 $valueunit = ' value="'.s($question->options->units[0]->unit).'" ';
483 }
484 }
485
486 $feedback = '';
487 $class = '';
1285531b 488 $classunit = '' ;
eb1284a2 489 $classunitvalue = '' ;
f1ee48c8
PP
490 $feedbackimg = '';
491 $feedbackimgunit = '' ;
492 $answerasterisk = false ;
1285531b 493 $response = '' ;
eb1284a2
PP
494 $valid_numerical_unit = false ;
495 $rawgrade = 0 ;
f1ee48c8
PP
496 if ($options->feedback) {
497 $class = question_get_feedback_class(0);
1285531b 498 $classunit = question_get_feedback_class(0);
f1ee48c8
PP
499 $feedbackimg = question_get_feedback_image(0);
500 $feedbackimgunit = question_get_feedback_image(0);
501 $classunitvalue = 0 ;
502 //this is OK for the first answer with a good response
503 // having to test for * so response as long as not empty
504 $response = $this->extract_numerical_response($state->responses['answer']);
fe6ce234 505 $break = 0 ;
f1ee48c8 506 foreach($question->options->answers as $answer) {
fe6ce234 507 // if * then everything has the $answer->fraction value
f1ee48c8
PP
508 if ($answer->answer !== '*' ) {
509 $this->get_tolerance_interval($answer);
510 }
fe6ce234
DC
511
512 $answer->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id);
f1ee48c8 513 if ($answer->answer === '*') {
eb1284a2 514 $answerasterisk = true ;
fe6ce234 515 $rawgrade = $answer->fraction ;
eb1284a2
PP
516 $class = question_get_feedback_class($answer->fraction);
517 $feedbackimg = question_get_feedback_image($answer->fraction);
518 $classunitvalue = $class ;
fe6ce234 519 $classunit = question_get_feedback_class($answer->fraction);
eb1284a2
PP
520 $feedbackimgunit = question_get_feedback_image($answer->fraction, $options->feedback);
521 if ($answer->feedback) {
522 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
523 }
fe6ce234
DC
524 if ( isset($question->options->units))
525 {
eb1284a2
PP
526 $valid_numerical_unit = true ;
527 }
528 $break = 1 ;
f1ee48c8
PP
529 } else if ($response !== false && isset($question->options->units) && count($question->options->units) > 0) {
530 $hasunits = 1 ;
fe6ce234 531 foreach($question->options->units as $key => $unit){
eb1284a2 532 // The student did type a number, so check it with tolerances.
fe6ce234 533 $testresponse = $response /$unit->multiplier ;
eb1284a2
PP
534 if($answer->min <= $testresponse && $testresponse <= $answer->max) {
535 $unittested = $unit->unit ;
fe6ce234 536 $rawgrade = $answer->fraction ;
eb1284a2
PP
537 $class = question_get_feedback_class($answer->fraction);
538 $feedbackimg = question_get_feedback_image($answer->fraction);
539 if ($answer->feedback) {
540 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
541 }
fe6ce234 542 if($state->responses['unit'] == $unit->unit){
eb1284a2 543 $classunitvalue = $answer->fraction ;
fe6ce234 544 }else {
eb1284a2
PP
545 $classunitvalue == 0 ;
546 }
fe6ce234 547 $classunit = question_get_feedback_class($classunitvalue);
eb1284a2 548 $feedbackimgunit = question_get_feedback_image($classunitvalue, $options->feedback);
fe6ce234 549 $break = 1 ;
eb1284a2 550 break;
f1ee48c8
PP
551 }
552 }
fe6ce234
DC
553 } else if($response !== false && ($answer->min <= $response && $response <= $answer->max) ) {
554 $rawgrade = $answer->fraction ;
f1ee48c8
PP
555 $class = question_get_feedback_class($answer->fraction);
556 $feedbackimg = question_get_feedback_image($answer->fraction);
557 if ($answer->feedback) {
fe6ce234 558 $feedback = format_text($answer->feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course);
f1ee48c8 559 }
eb1284a2 560 $break = 1 ;
f1ee48c8 561 }
fe6ce234
DC
562 if ($break) {
563 break;
564 }
f1ee48c8
PP
565 }
566 }
eb1284a2
PP
567 $state->options->raw_unitpenalty = 0 ;
568 $raw_unitpenalty = 0 ;
fe6ce234 569 if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY ||
eb1284a2
PP
570 $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) {
571 $classunitvalue = 1 ;
572 }
fe6ce234 573
eb1284a2
PP
574
575 if($classunitvalue == 0){
576 if($question->options->unitgradingtype == 1){
577 $raw_unitpenalty = $question->options->unitpenalty * $rawgrade ;
578 }else {
579 $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade;
580 }
fe6ce234 581 $state->options->raw_unitpenalty = $raw_unitpenalty ;
eb1284a2
PP
582 }
583
f1ee48c8
PP
584 /// Removed correct answer, to be displayed later MDL-7496
585 include("$CFG->dirroot/question/type/numerical/display.html");
586 }
aeb15530 587
516cf3eb 588
5a14d563 589 function compare_responses(&$question, $state, $teststate) {
aa384ade 590
04e91671 591 if ($question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY && isset($question->options->units) && isset($question->options->units[$state->responses['unit']] )){
c5da9906 592 $state->responses['unit']=$question->options->units[$state->responses['unit']]->unit;
593 };
594
aeb15530 595
c5da9906 596 $responses = '';
597 $testresponses = '';
598 if (isset($state->responses['answer'])){
599 $responses = $state->responses['answer'];
600 }
601 if (isset($state->responses['unit'])){
602 $responses .= $state->responses['unit'];
603 }
604 if (isset($teststate->responses['answer'])){
605 $testresponses = $teststate->responses['answer'];
606 }
607 if (isset($teststate->responses['unit'])){
608 $testresponses .= $teststate->responses['unit'];
609 }
8b831fbb 610
f0b6151c 611 if ( isset($responses) && isset($testresponses )) {
c5da9906 612
613 return $responses == $testresponses ;
5a14d563 614 }
615 return false;
516cf3eb 616 }
617
1fe641f7 618 /**
619 * Checks whether a response matches a given answer, taking the tolerance
f1ee48c8
PP
620 * and but NOT the unit into account. Returns a true for if a response matches the
621 * answer or in one of the unit , false if it doesn't.
622 * the total grading will see if the unit match.
fe6ce234 623 * if unit != -1 then the test is done only on this unit
1fe641f7 624 */
f1ee48c8 625 function test_response(&$question, &$state, $answer ) {
55894a42 626 // Deal with the match anything answer.
fac1189d 627 if ($answer->answer === '*') {
55894a42 628 return true;
516cf3eb 629 }
04e91671
PP
630 /* To be able to test (old) questions that do not have an unit
631 * input element the test is done using the $state->responses['']
fe6ce234 632 * which contains the response which is analyzed by extract_numerical_response()
04e91671 633 * If the data comes from the numerical or calculated display
fe6ce234 634 * the $state->responses['unit'] comes from either
04e91671
PP
635 * a multichoice radio element NUMERICALQUESTIONUNITMULTICHOICEDISPLAY
636 * where the $state->responses['unit'] value is the key => unit object
637 * in the the $question->options->units array
638 * or an input text element NUMERICALUNITTEXTINPUTDISPLAY
639 * which contains the student response
640 * for NUMERICALQUESTIONUNITTEXTDISPLAY and NUMERICALQUESTIONUNITNODISPLAY
fe6ce234
DC
641 *
642 */
1fe641f7 643
f1ee48c8
PP
644 if (!isset($state->responses['answer']) && isset($state->responses[''])){
645 $state->responses['answer'] = $state->responses[''];
646 }
eb1284a2 647 $response = $this->extract_numerical_response($state->responses['answer']);
1fe641f7 648 if ($response === false) {
649 return false; // The student did not type a number.
516cf3eb 650 }
eb1284a2 651 // The student did type a number, so check it with tolerances.
1fe641f7 652 $this->get_tolerance_interval($answer);
f1ee48c8 653 if ($answer->min <= $response && $response <= $answer->max){
fe6ce234 654 return true;
f1ee48c8 655 }
fe6ce234 656 // testing for other units
f1ee48c8 657 if ( isset($question->options->units) && count($question->options->units) > 0) {
fe6ce234 658 foreach($question->options->units as $key =>$unit){
f1ee48c8
PP
659 $testresponse = $response /$unit->multiplier ;
660 if($answer->min <= $testresponse && $testresponse<= $answer->max) {
fe6ce234 661 return true;
f1ee48c8
PP
662 }
663 }
664 }
665 return false;
516cf3eb 666 }
667
04e91671 668 /**
fe6ce234 669 * Performs response processing and grading
04e91671 670 * The function was redefined for handling correctly the two parts
fe6ce234
DC
671 * number and unit of numerical or calculated questions
672 * The code handles also the case when there no unit defined by the user or
673 * when used in a multianswer (Cloze) question.
04e91671
PP
674 * This function performs response processing and grading and updates
675 * the state accordingly.
676 * @return boolean Indicates success or failure.
677 * @param object $question The question to be graded. Question type
678 * specific information is included.
679 * @param object $state The state of the question to grade. The current
680 * responses are in ->responses. The last graded state
681 * is in ->last_graded (hence the most recently graded
682 * responses are in ->last_graded->responses). The
683 * question type specific information is also
684 * included. The ->raw_grade and ->penalty fields
685 * must be updated. The method is able to
686 * close the question session (preventing any further
687 * attempts at this question) by setting
688 * $state->event to QUESTION_EVENTCLOSEANDGRADE
689 * @param object $cmoptions
690 */
f1ee48c8
PP
691 function grade_responses(&$question, &$state, $cmoptions) {
692 if (!isset($state->responses['answer']) && isset($state->responses[''])){
693 $state->responses['answer'] = $state->responses[''];
694 }
fe6ce234 695
c5da9906 696 //to apply the unit penalty we need to analyse the response in a more complex way
697 //the apply_unit() function analysis could be used to obtain the infos
aeb15530
PS
698 // however it is used to detect good or bad numbers but also
699 // gives false
c5da9906 700 $state->raw_grade = 0;
eb1284a2 701 $valid_numerical_unit = false ;
f1ee48c8
PP
702 $break = 0 ;
703 $unittested = '';
704 $hasunits = 0 ;
705 $response = $this->extract_numerical_response($state->responses['answer']);
706 $answerasterisk = false ;
fe6ce234
DC
707
708 $break = 0 ;
c5da9906 709 foreach($question->options->answers as $answer) {
f1ee48c8
PP
710 if ($answer->answer !== '*' ) {
711 // The student did type a number, so check it with tolerances.
712 $this->get_tolerance_interval($answer);
713 }
714
fe6ce234 715 // if * then everything is OK even unit
f1ee48c8 716 if ($answer->answer === '*') {
c5da9906 717 $state->raw_grade = $answer->fraction;
fe6ce234 718 if ( isset($question->options->units)){
eb1284a2 719 $valid_numerical_unit = true ;
f1ee48c8 720 }
fe6ce234 721 $answerasterisk = true ;
f1ee48c8 722 $break = 1 ;
1285531b 723 }else if ($response !== false && isset($question->options->units) && count($question->options->units) > 0) {
f1ee48c8 724 $hasunits = 1 ;
fe6ce234
DC
725 foreach($question->options->units as $key => $unit){
726 $testresponse = $response /$unit->multiplier ;
727
f1ee48c8
PP
728 if($answer->min <= $testresponse && $testresponse <= $answer->max) {
729 $state->raw_grade = $answer->fraction;
f1ee48c8 730 $unittested = $unit->unit ;
fe6ce234 731 $break = 1 ;
f1ee48c8 732 break;
fe6ce234 733 }
f1ee48c8
PP
734 }
735 }else if ($response !== false) {
736 if($this->test_response($question, $state, $answer)) {
737 $state->raw_grade = $answer->fraction;
f1ee48c8
PP
738 break;
739 }
c5da9906 740 }
f1ee48c8
PP
741 if ($break) break;
742 } //foreach($question->options
fe6ce234 743
0a49ee5c 744 // in all cases the unit should be tested
fe6ce234 745 if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY ||
aa384ade 746 $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) {
eb1284a2 747 $valid_numerical_unit = true ;
0a49ee5c 748 }else {
eb1284a2 749 // $valid_numerical_unit means that the grading was done with the unit defined
f1ee48c8
PP
750 //
751 if ($hasunits && !$answerasterisk ){
eb1284a2 752 $valid_numerical_unit = ($state->responses['unit'] == $unittested) ;
f1ee48c8 753 } else {
eb1284a2 754 $valid_numerical_unit = true ;
f1ee48c8 755 }
0a49ee5c 756 }
fe6ce234 757
c5da9906 758 // apply unit penalty
eb1284a2
PP
759 $raw_unitpenalty = 0 ;
760 if(!empty($question->options->unitpenalty)&& $valid_numerical_unit != true ){
c5da9906 761 if($question->options->unitgradingtype == 1){
eb1284a2 762 $raw_unitpenalty = $question->options->unitpenalty * $state->raw_grade ;
c5da9906 763 }else {
eb1284a2 764 $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade;
c5da9906 765 }
eb1284a2 766 $state->raw_grade -= $raw_unitpenalty ;
c5da9906 767 }
fe6ce234 768
c5da9906 769 // Make sure we don't assign negative or too high marks.
c5da9906 770 $state->raw_grade = min(max((float) $state->raw_grade,
771 0.0), 1.0) * $question->maxgrade;
772
773 // Update the penalty.
774 $state->penalty = $question->penalty * $question->maxgrade;
775
776 // mark the state as graded
777 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
778
779 return true;
780 }
aeb15530
PS
781
782
516cf3eb 783 function get_correct_responses(&$question, &$state) {
784 $correct = parent::get_correct_responses($question, $state);
c85aea03 785 $unit = $this->get_default_numerical_unit($question);
786 if (isset($correct['']) && $correct[''] != '*' && $unit) {
516cf3eb 787 $correct[''] .= ' '.$unit->unit;
aeb15530 788 }
516cf3eb 789 return $correct;
790 }
791
792 // ULPGC ecastro
793 function get_all_responses(&$question, &$state) {
1fe641f7 794 $result = new stdClass;
795 $answers = array();
516cf3eb 796 $unit = $this->get_default_numerical_unit($question);
797 if (is_array($question->options->answers)) {
798 foreach ($question->options->answers as $aid=>$answer) {
1fe641f7 799 $r = new stdClass;
516cf3eb 800 $r->answer = $answer->answer;
801 $r->credit = $answer->fraction;
802 $this->get_tolerance_interval($answer);
55894a42 803 if ($r->answer != '*' && $unit) {
804 $r->answer .= ' ' . $unit->unit;
516cf3eb 805 }
806 if ($answer->max != $answer->min) {
807 $max = "$answer->max"; //format_float($answer->max, 2);
808 $min = "$answer->min"; //format_float($answer->max, 2);
809 $r->answer .= ' ('.$min.'..'.$max.')';
810 }
811 $answers[$aid] = $r;
812 }
516cf3eb 813 }
814 $result->id = $question->id;
815 $result->responses = $answers;
816 return $result;
817 }
c5da9906 818 function get_actual_response($question, $state) {
aeb15530 819 if (!empty($state->responses) && !empty($state->responses[''])) {
c5da9906 820 if(false === strpos($state->responses[''], '|||||')){
821 $responses[] = $state->responses[''];
822 }else {
823 $resp = explode('|||||', $state->responses['']);
aeb15530 824 $responses[] = $resp[0].$resp[1];
c5da9906 825 }
826 } else {
827 $responses[] = '';
828 }
aeb15530 829
c5da9906 830 return $responses;
831 }
832
516cf3eb 833
834 function get_tolerance_interval(&$answer) {
835 // No tolerance
836 if (empty($answer->tolerance)) {
1fe641f7 837 $answer->tolerance = 0;
516cf3eb 838 }
839
840 // Calculate the interval of correct responses (min/max)
841 if (!isset($answer->tolerancetype)) {
842 $answer->tolerancetype = 2; // nominal
843 }
844
223ad0b9 845 // We need to add a tiny fraction depending on the set precision to make the
516cf3eb 846 // comparison work correctly. Otherwise seemingly equal values can yield
847 // false. (fixes bug #3225)
223ad0b9 848 $tolerance = (float)$answer->tolerance + ("1.0e-".ini_get('precision'));
516cf3eb 849 switch ($answer->tolerancetype) {
850 case '1': case 'relative':
851 /// Recalculate the tolerance and fall through
852 /// to the nominal case:
853 $tolerance = $answer->answer * $tolerance;
dcd4192a 854 // Do not fall through to the nominal case because the tiny fraction is a factor of the answer
855 $tolerance = abs($tolerance); // important - otherwise min and max are swapped
856 $max = $answer->answer + $tolerance;
857 $min = $answer->answer - $tolerance;
858 break;
516cf3eb 859 case '2': case 'nominal':
860 $tolerance = abs($tolerance); // important - otherwise min and max are swapped
dcd4192a 861 // $answer->tolerance 0 or something else
862 if ((float)$answer->tolerance == 0.0 && abs((float)$answer->answer) <= $tolerance ){
f34488b2 863 $tolerance = (float) ("1.0e-".ini_get('precision')) * abs((float)$answer->answer) ; //tiny fraction
dcd4192a 864 } else if ((float)$answer->tolerance != 0.0 && abs((float)$answer->tolerance) < abs((float)$answer->answer) && abs((float)$answer->answer) <= $tolerance){
f34488b2 865 $tolerance = (1+("1.0e-".ini_get('precision')) )* abs((float) $answer->tolerance) ;//tiny fraction
866 }
867
516cf3eb 868 $max = $answer->answer + $tolerance;
869 $min = $answer->answer - $tolerance;
870 break;
ed0ba6da 871 case '3': case 'geometric':
516cf3eb 872 $quotient = 1 + abs($tolerance);
873 $max = $answer->answer * $quotient;
874 $min = $answer->answer / $quotient;
875 break;
876 default:
0b4f4187 877 print_error('unknowntolerance', 'question', '', $answer->tolerancetype);
516cf3eb 878 }
879
880 $answer->min = $min;
881 $answer->max = $max;
882 return true;
883 }
884
885 /**
1fe641f7 886 * Checks if the $rawresponse has a unit and applys it if appropriate.
887 *
888 * @param string $rawresponse The response string to be converted to a float.
889 * @param array $units An array with the defined units, where the
890 * unit is the key and the multiplier the value.
891 * @return float The rawresponse with the unit taken into
892 * account as a float.
893 */
f1ee48c8
PP
894 function extract_numerical_response($rawresponse) {
895 $rawresponse = trim($rawresponse) ;
896 $search = array(' ', ',');
897 // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and ,
898 if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) {
899 $replace = array('', '');
fe6ce234 900 }else { // remove spaces and normalise , to a . .
f1ee48c8
PP
901 $replace = array('', '.');
902 }
903 $rawresponse = str_replace($search, $replace, $rawresponse);
904
905 if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
906 $rawresponse, $responseparts)) {
907 return (float)$responseparts[1] ;
908 }
909 // Invalid number. Must be wrong.
910 return false;
911 }
eb1284a2 912 /**
f1ee48c8
PP
913 * Checks if the $rawresponse has a unit and applys it if appropriate.
914 *
915 * @param string $rawresponse The response string to be converted to a float.
916 * @param array $units An array with the defined units, where the
917 * unit is the key and the multiplier the value.
918 * @return float The rawresponse with the unit taken into
919 * account as a float.
920 */
921 function apply_unit_old($rawresponse, $units) {
516cf3eb 922 // Make units more useful
923 $tmpunits = array();
924 foreach ($units as $unit) {
925 $tmpunits[$unit->unit] = $unit->multiplier;
926 }
1fe641f7 927 // remove spaces and normalise decimal places.
be1bb80e 928 $rawresponse = trim($rawresponse) ;
516cf3eb 929 $search = array(' ', ',');
be1bb80e
PP
930 // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and ,
931 if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) {
932 $replace = array('', '');
fe6ce234 933 }else { // remove spaces and normalise , to a . .
be1bb80e
PP
934 $replace = array('', '.');
935 }
936 $rawresponse = str_replace($search, $replace, $rawresponse);
f34488b2 937
f1ee48c8 938
1fe641f7 939 // Apply any unit that is present.
f1ee48c8 940 if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$',
1fe641f7 941 $rawresponse, $responseparts)) {
f34488b2 942
1fe641f7 943 if (!empty($responseparts[5])) {
f34488b2 944
1fe641f7 945 if (isset($tmpunits[$responseparts[5]])) {
946 // Valid number with unit.
947 return (float)$responseparts[1] / $tmpunits[$responseparts[5]];
c5da9906 948 } else {
f1ee48c8
PP
949 // Valid number with invalid unit. Must be wrong.
950 return false;
c5da9906 951 }
952
953 } else {
954 // Valid number without unit.
955 return (float)$responseparts[1];
956 }
957 }
958 // Invalid number. Must be wrong.
959 return false;
960 }
f1ee48c8 961
04e91671 962 /**
fe6ce234
DC
963 * function used in function definition_inner()
964 * of edit_..._form.php for
965 * numerical, calculated, calculatedsimple
966 */
cf146692
PP
967 function add_units_options(&$mform, &$that){
968 $mform->addElement('header', 'unithandling', get_string('unitshandling', 'qtype_numerical'));
969 // Units are graded
970 $mform->addElement('radio', 'unitrole', get_string('unitgraded1', 'qtype_numerical'), get_string('unitgraded', 'qtype_numerical'),0);
971 $penaltygrp = array();
972 $penaltygrp[] =& $mform->createElement('text', 'unitpenalty', get_string('unitpenalty', 'qtype_numerical') ,
973 array('size' => 6));
974 $unitgradingtypes = array('1' => get_string('decfractionofquestiongrade', 'qtype_numerical'), '2' => get_string('decfractionofresponsegrade', 'qtype_numerical'));
975 $penaltygrp[] =& $mform->createElement('select', 'unitgradingtype', '' , $unitgradingtypes );
976 $mform->addGroup($penaltygrp, 'penaltygrp', get_string('unitpenalty', 'qtype_numerical'),' ' , false);
977 $showunits0grp = array();
978 $showunits0grp[] =& $mform->createElement('radio', 'showunits0', get_string('unitedit', 'qtype_numerical'), get_string('editableunittext', 'qtype_numerical'),0);
979 $showunits0grp[] =& $mform->createElement('radio', 'showunits0', get_string('selectunits', 'qtype_numerical') , get_string('unitchoice', 'qtype_numerical'),1);
980 $mform->addGroup($showunits0grp, 'showunits0grp', get_string('studentunitanswer', 'qtype_numerical'),' OR ' , false);
fe6ce234 981 $mform->addElement('editor', 'instructions', get_string('instructions', 'qtype_numerical'), null, $that->editoroptions);
cf146692
PP
982 $mform->addElement('static', 'separator1', '<HR/>', '<HR/>');
983 // Units are not graded
984 $mform->addElement('radio', 'unitrole', get_string('unitnotgraded', 'qtype_numerical'), get_string('onlynumerical', 'qtype_numerical'),1);
985 $showunits1grp = array();
986 $showunits1grp[] = & $mform->createElement('radio', 'showunits1', '', get_string('no', 'moodle'),3);
987 $showunits1grp[] = & $mform->createElement('radio', 'showunits1', '', get_string('yes', 'moodle'),2);
988 $mform->addGroup($showunits1grp, 'showunits1grp', get_string('unitdisplay', 'qtype_numerical'),' ' , false);
989 $unitslefts = array('0' => get_string('rightexample', 'qtype_numerical'),'1' => get_string('leftexample', 'qtype_numerical'));
990 $mform->addElement('static', 'separator2', '<HR/>', '<HR/>');
991 $mform->addElement('select', 'unitsleft', get_string('unitposition', 'qtype_numerical') , $unitslefts );
8b831fbb 992 $currentgrp1 = array();
aeb15530 993
c5da9906 994 $mform->setType('unitpenalty', PARAM_NUMBER);
c5da9906 995 $mform->setDefault('unitpenalty', 0.1);
c5da9906 996 $mform->setDefault('unitgradingtype', 1);
5fcefc97 997 // $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical'); // TODO help did not exist before MDL-21695
cf146692
PP
998 $mform->setDefault('showunits0', 0);
999 $mform->setDefault('showunits1', 3);
c5da9906 1000 $mform->setDefault('unitsleft', 0);
c5da9906 1001 $mform->setType('instructions', PARAM_RAW);
5fcefc97 1002 $mform->addHelpButton('instructions', 'numericalinstructions', 'qtype_numerical');
f266594c
PP
1003 // $mform->disabledIf('penaltygrp', 'unitrole','eq','1');
1004 // $mform->disabledIf('unitgradingtype', 'unitrole','eq','1');
1005 // $mform->disabledIf('instructions', 'unitrole','eq','1');
1006 // $mform->disabledIf('unitsleft', 'showunits1','eq','3');
1007 // $mform->disabledIf('showunits1','unitrole','eq','0');
1008 // $mform->disabledIf('showunits0','unitrole','eq','1');
fe6ce234
DC
1009
1010
c5da9906 1011 }
5fcefc97
DM
1012
1013 /**
1014 * function used in in function definition_inner()
1015 * of edit_..._form.php for
1016 * numerical, calculated, calculatedsimple
1017 */
fe6ce234 1018 function add_units_elements(& $mform,& $that) {
cf146692
PP
1019 $repeated = array();
1020 $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}'));
1021
1022 $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz'));
1023 $mform->setType('unit', PARAM_NOTAGS);
1024
1025 $repeated[] =& $mform->createElement('text', 'multiplier', get_string('multiplier', 'quiz'));
1026 $mform->setType('multiplier', PARAM_NUMBER);
1027
04e91671 1028 if (isset($that->question->options)){
cf146692
PP
1029 $countunits = count($that->question->options->units);
1030 } else {
1031 $countunits = 0;
1032 }
1033 if ($that->question->formoptions->repeatelements){
1034 $repeatsatstart = $countunits + 1;
1035 } else {
1036 $repeatsatstart = $countunits;
1037 }
1038 $that->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_calculated', '{no}'));
1039
1040 if ($mform->elementExists('multiplier[0]')){
1041 $firstunit =& $mform->getElement('multiplier[0]');
1042 $firstunit->freeze();
1043 $firstunit->setValue('1.0');
1044 $firstunit->setPersistantFreeze(true);
5fcefc97 1045 $mform->addHelpButton('multiplier[0]', 'numericalmultiplier', 'qtype_numerical');
cf146692
PP
1046 }
1047 }
5fcefc97
DM
1048
1049 /**
fe6ce234
DC
1050 * function used in in function data_preprocessing() of edit_numerical_form.php for
1051 * numerical, calculated, calculatedsimple
1052 */
1053 function set_numerical_unit_data($mform, &$question, &$default_values){
1054
1055 list($categoryid) = explode(',', $question->category);
1056 $context = $this->get_context_by_category_id($categoryid);
f1ee48c8
PP
1057
1058 if (isset($question->options)){
1059 $default_values['unitgradingtype'] = $question->options->unitgradingtype ;
1060 $default_values['unitpenalty'] = $question->options->unitpenalty ;
1061 switch ($question->options->showunits){
1062 case 'O' :
fe6ce234 1063 case '1' :
f1ee48c8
PP
1064 $default_values['showunits0'] = $question->options->showunits ;
1065 $default_values['unitrole'] = 0 ;
1066 break;
1067 case '2' :
fe6ce234 1068 case '3' :
f1ee48c8
PP
1069 $default_values['showunits1'] = $question->options->showunits ;
1070 $default_values['unitrole'] = 1 ;
1071 break;
fe6ce234 1072 }
f1ee48c8 1073 $default_values['unitsleft'] = $question->options->unitsleft ;
fe6ce234
DC
1074
1075 // processing files
1076 $component = 'qtype_' . $question->qtype;
1077 $draftid = file_get_submitted_draft_itemid('instructions');
1078 $default_values['instructions'] = array();
1079 $default_values['instructions']['format'] = $question->options->instructionsformat;
1080 $default_values['instructions']['text'] = file_prepare_draft_area(
1081 $draftid, // draftid
1082 $context->id, // context
1083 $component, // component
1084 'instruction', // filarea
1085 !empty($question->id)?(int)$question->id:null, // itemid
1086 $mform->fileoptions, // options
1087 $question->options->instructions // text
1088 );
1089 $default_values['instructions']['itemid'] = $draftid;
1090
1091 if (isset($question->options->units)) {
f1ee48c8
PP
1092 $units = array_values($question->options->units);
1093 if (!empty($units)) {
1094 foreach ($units as $key => $unit){
1095 $default_values['unit['.$key.']'] = $unit->unit;
1096 $default_values['multiplier['.$key.']'] = $unit->multiplier;
1097 }
1098 }
1099 }
1100 }
1101 }
1102
fe6ce234
DC
1103 /**
1104 * function use in in function validation()
1105 * of edit_..._form.php for
1106 * numerical, calculated, calculatedsimple
1107 */
cf146692
PP
1108
1109 function validate_numerical_options(& $data, & $errors){
1110 $units = $data['unit'];
1111 if ($data['unitrole'] == 0 ){
1112 $showunits = $data['showunits0'];
1113 }else {
1114 $showunits = $data['showunits1'];
1115 }
fe6ce234
DC
1116
1117 if (($showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY) ||
1118 ($showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY ) ||
04e91671 1119 ($showunits == NUMERICALQUESTIONUNITTEXTDISPLAY )){
cf146692
PP
1120 if (trim($units[0]) == ''){
1121 $errors['unit[0]'] = 'You must set a valid unit name' ;
1122 }
1123 }
04e91671 1124 if ($showunits == NUMERICALQUESTIONUNITNODISPLAY ){
cf146692
PP
1125 if (count($units)) {
1126 foreach ($units as $key => $unit){
1127 if ($units[$key] != ''){
1128 $errors["unit[$key]"] = 'You must erase this unit name' ;
1129 }
1130 }
1131 }
1132 }
fe6ce234
DC
1133
1134
cf146692
PP
1135 // Check double units.
1136 $alreadyseenunits = array();
1137 if (isset($data['unit'])) {
1138 foreach ($data['unit'] as $key => $unit) {
1139 $trimmedunit = trim($unit);
1140 if ($trimmedunit!='' && in_array($trimmedunit, $alreadyseenunits)) {
1141 $errors["unit[$key]"] = get_string('errorrepeatedunit', 'qtype_numerical');
1142 if (trim($data['multiplier'][$key]) == '') {
1143 $errors["multiplier[$key]"] = get_string('errornomultiplier', 'qtype_numerical');
1144 }
1145 } elseif($trimmedunit!='') {
1146 $alreadyseenunits[] = $trimmedunit;
1147 }
1148 }
1149 }
1150 $units = $data['unit'];
1151 if (count($units)) {
1152 foreach ($units as $key => $unit){
1153 if (is_numeric($unit)){
1154 $errors['unit['.$key.']'] = get_string('mustnotbenumeric', 'qtype_calculated');
1155 }
1156 $trimmedunit = trim($unit);
1157 $trimmedmultiplier = trim($data['multiplier'][$key]);
1158 if (!empty($trimmedunit)){
1159 if (empty($trimmedmultiplier)){
1160 $errors['multiplier['.$key.']'] = get_string('youmustenteramultiplierhere', 'qtype_calculated');
1161 }
1162 if (!is_numeric($trimmedmultiplier)){
1163 $errors['multiplier['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
1164 }
1165
1166 }
fe6ce234 1167 }
cf146692
PP
1168 }
1169
1170 }
c5da9906 1171
aeb15530 1172
04e91671 1173 function valid_unit($rawresponse, $units) {
c5da9906 1174 // Make units more useful
1175 $tmpunits = array();
1176 foreach ($units as $unit) {
1177 $tmpunits[$unit->unit] = $unit->multiplier;
1178 }
1179 // remove spaces and normalise decimal places.
1180 $search = array(' ', ',');
1181 $replace = array('', '.');
1182 $rawresponse = str_replace($search, $replace, trim($rawresponse));
1183
1184 // Apply any unit that is present.
1185 if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
1186 $rawresponse, $responseparts)) {
1187
1188 if (!empty($responseparts[5])) {
1189
1190 if (isset($tmpunits[$responseparts[5]])) {
1191 // Valid number with unit.
1192 return true ; //(float)$responseparts[1] / $tmpunits[$responseparts[5]];
1fe641f7 1193 } else {
1194 // Valid number with invalid unit. Must be wrong.
1195 return false;
1196 }
1197
516cf3eb 1198 } else {
1fe641f7 1199 // Valid number without unit.
c5da9906 1200 return false ; //(float)$responseparts[1];
516cf3eb 1201 }
1202 }
1fe641f7 1203 // Invalid number. Must be wrong.
1204 return false;
516cf3eb 1205 }
f34488b2 1206
1fe641f7 1207 /// RESTORE FUNCTIONS /////////////////
315559d3 1208
1fe641f7 1209 /**
315559d3 1210 * Restores the data in the question
1211 *
1212 * This is used in question/restorelib.php
1213 */
1214 function restore($old_question_id,$new_question_id,$info,$restore) {
9db7dab2 1215 global $DB;
315559d3 1216
1217 $status = true;
1218
1219 //Get the numerical array
27cabbe6 1220 if (isset($info['#']['NUMERICAL'])) {
1221 $numericals = $info['#']['NUMERICAL'];
1222 } else {
1223 $numericals = array();
1224 }
315559d3 1225
1226 //Iterate over numericals
1227 for($i = 0; $i < sizeof($numericals); $i++) {
1228 $num_info = $numericals[$i];
1229
1230 //Now, build the question_numerical record structure
1fe641f7 1231 $numerical = new stdClass;
315559d3 1232 $numerical->question = $new_question_id;
1233 $numerical->answer = backup_todb($num_info['#']['ANSWER']['0']['#']);
1234 $numerical->tolerance = backup_todb($num_info['#']['TOLERANCE']['0']['#']);
1235
55894a42 1236 //We have to recode the answer field
315559d3 1237 $answer = backup_getid($restore->backup_unique_code,"question_answers",$numerical->answer);
1238 if ($answer) {
1239 $numerical->answer = $answer->new_id;
1240 }
1241
1242 //The structure is equal to the db, so insert the question_numerical
9db7dab2 1243 $newid = $DB->insert_record ("question_numerical", $numerical);
315559d3 1244
1245 //Do some output
1246 if (($i+1) % 50 == 0) {
1247 if (!defined('RESTORE_SILENTLY')) {
1248 echo ".";
1249 if (($i+1) % 1000 == 0) {
1250 echo "<br />";
1251 }
1252 }
1253 backup_flush(300);
1254 }
1255
1256 //Now restore numerical_units
1257 $status = question_restore_numerical_units ($old_question_id,$new_question_id,$num_info,$restore);
1258
c5da9906 1259 //Now restore numerical_options
1260 $status = question_restore_numerical_options ($old_question_id,$new_question_id,$num_info,$restore);
1261
315559d3 1262 if (!$newid) {
1263 $status = false;
1264 }
1265 }
1266
1267 return $status;
1268 }
1269
b9bd6da4 1270 /**
1271 * Runs all the code required to set up and save an essay question for testing purposes.
1272 * Alternate DB table prefix may be used to facilitate data deletion.
1273 */
1274 function generate_test($name, $courseid = null) {
1275 global $DB;
1276 list($form, $question) = default_questiontype::generate_test($name, $courseid);
1277 $question->category = $form->category;
1278
1279 $form->questiontext = "What is 674 * 36?";
1280 $form->generalfeedback = "Thank you";
1281 $form->penalty = 0.1;
1282 $form->defaultgrade = 1;
1283 $form->noanswers = 3;
1284 $form->answer = array('24264', '24264', '1');
1285 $form->tolerance = array(10, 100, 0);
1286 $form->fraction = array(1, 0.5, 0);
1287 $form->nounits = 2;
1288 $form->unit = array(0 => null, 1 => null);
1289 $form->multiplier = array(1, 0);
1290 $form->feedback = array('Very good', 'Close, but not quite there', 'Well at least you tried....');
1291
1292 if ($courseid) {
1293 $course = $DB->get_record('course', array('id' => $courseid));
1294 }
1295
1296 return $this->save_question($question, $form, $course);
1297 }
fe6ce234
DC
1298 /**
1299 * When move the category of questions, the belonging files should be moved as well
1300 * @param object $question, question information
1301 * @param object $newcategory, target category information
1302 */
1303 function move_files($question, $newcategory) {
1304 global $DB;
1305 parent::move_files($question, $newcategory);
1306
1307 $fs = get_file_storage();
1308 // process files in answer
1309 if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) {
1310 $oldanswers = array();
1311 }
1312 $component = 'question';
1313 $filearea = 'answerfeedback';
1314 foreach ($oldanswers as $answer) {
1315 $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id);
1316 foreach ($files as $storedfile) {
1317 if (!$storedfile->is_directory()) {
1318 $newfile = new object();
1319 $newfile->contextid = (int)$newcategory->contextid;
1320 $fs->create_file_from_storedfile($newfile, $storedfile);
1321 $storedfile->delete();
1322 }
1323 }
1324 }
1325 $component = 'qtype_numerical';
1326 $filearea = 'instruction';
1327 $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id);
1328 foreach ($files as $storedfile) {
1329 if (!$storedfile->is_directory()) {
1330 $newfile = new object();
1331 $newfile->contextid = (int)$newcategory->contextid;
1332 $fs->create_file_from_storedfile($newfile, $storedfile);
1333 $storedfile->delete();
1334 }
1335 }
1336 }
1337
1338 function check_file_access($question, $state, $options, $contextid, $component,
1339 $filearea, $args) {
1340 $itemid = reset($args);
1341 if ($component == 'question' && $filearea == 'answerfeedback') {
1342 $result = $options->feedback && array_key_exists($itemid, $question->options->answers);
1343 if (!$result) {
1344 return false;
1345 }
1346 foreach($question->options->answers as $answer) {
1347 if ($this->test_response($question, $state, $answer)) {
1348 return true;
1349 }
1350 }
1351 return false;
1352 } else if ($filearea == 'instruction') {
1353 if ($itemid != $question->id) {
1354 return false;
1355 } else {
1356 return true;
1357 }
1358 } else {
1359 return parent::check_file_access($question, $state, $options, $contextid, $component,
1360 $filearea, $args);
1361 }
1362 }
516cf3eb 1363}
516cf3eb 1364
1fe641f7 1365// INITIATION - Without this line the question type is not in use.
a2156789 1366question_register_questiontype(new question_numerical_qtype());
04e91671
PP
1367if ( ! defined ("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY")) {
1368 define("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY", 0);
1369}
1370if ( ! defined ("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY")) {
1371 define("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY", 1);
1372}
1373if ( ! defined ("NUMERICALQUESTIONUNITTEXTDISPLAY")) {
1374 define("NUMERICALQUESTIONUNITTEXTDISPLAY", 2);
1375}
1376if ( ! defined ("NUMERICALQUESTIONUNITNODISPLAY")) {
1377 define("NUMERICALQUESTIONUNITNODISPLAY", 3);
1378}