MDL-22164 backup - finished cleaning of questions backup code
[moodle.git] / question / type / numerical / questiontype.php
1 <?php
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/>.
18 /**
19  * @author Martin Dougiamas and many others. Tim Hunt.
20  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
21  * @package questionbank
22  * @subpackage questiontypes
23  */
25 require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php");
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...
35  * @package questionbank
36  * @subpackage questiontypes
37  */
39 class question_numerical_qtype extends question_shortanswer_qtype {
41     public $virtualqtype = false;
42     function name() {
43         return 'numerical';
44     }
46     function has_wildcards_in_responses() {
47         return true;
48     }
50     function requires_qtypes() {
51         return array('shortanswer');
52     }
54     function get_question_options(&$question) {
55         // Get the question answers and their respective tolerances
56         // Note: question_numerical is an extension of the answer table rather than
57         //       the question table as is usually the case for qtype
58         //       specific tables.
59         global $CFG, $DB, $OUTPUT;
60         if (!$question->options->answers = $DB->get_records_sql(
61                                 "SELECT a.*, n.tolerance " .
62                                 "FROM {question_answers} a, " .
63                                 "     {question_numerical} n " .
64                                 "WHERE a.question = ? " .
65                                 "    AND   a.id = n.answer " .
66                                 "ORDER BY a.id ASC", array($question->id))) {
67             echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!');
68             return false;
69         }
70         $this->get_numerical_units($question);
71         //get_numerical_options() need to know if there are units
72         // to set correctly default values
73         $this->get_numerical_options($question);
75         // If units are defined we strip off the default unit from the answer, if
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);
81                 if ($length && substr($answer, -$length) == $defaultunit->unit) {
82                     $question->options->answers[$key]->answer =
83                             substr($answer, 0, strlen($answer)-$length);
84                 }
85             }
86         }
88         return true;
89     }
90     function get_numerical_options(&$question) {
91         global $DB;
92         if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) {
93             $question->options->unitgradingtype = 0; // total grade
94             $question->options->unitpenalty = 0;
95             // the default
96             if ($defaultunit = $this->get_default_numerical_unit($question)) {
97                 // so units can be graded
98                 $question->options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ;
99                 $question->options->unitpenalty = 1;
100             }else {
101                 // only numerical will be graded
102                 $question->options->showunits = NUMERICALQUESTIONUNITNODISPLAY ;
103             }
104             $question->options->unitsleft = 0 ;
105             $question->options->instructions = '';
106             $question->options->instructionsformat = editors_get_preferred_format();
107         } else {
108             $question->options->unitgradingtype = $options->unitgradingtype;
109             $question->options->unitpenalty = $options->unitpenalty;
110             $question->options->showunits = $options->showunits;
111             $question->options->unitsleft = $options->unitsleft;
112             $question->options->instructions = $options->instructions;
113             $question->options->instructionsformat = $options->instructionsformat;
114         }
116         return true;
117     }
119     function get_numerical_units(&$question) {
120         global $DB;
121         if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) {
122             $units  = array_values($units);
123         } else {
124             $units = array();
125         }
126         foreach ($units as $key => $unit) {
127             $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_NUMBER);
128         }
129         $question->options->units = $units;
130         return true;
131     }
133     function get_default_numerical_unit(&$question) {
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;
138                 }
139             }
140         }
141         return false;
142     }
144     /**
145      * Save the units and the answers associated with this question.
146      */
147     function save_question_options($question) {
148         global $DB;
149         $context = $question->context;
151         // Get old versions of the objects
152         if (!$oldanswers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
153             $oldanswers = array();
154         }
156         if (!$oldoptions = $DB->get_records('question_numerical', array('question' =>  $question->id), 'answer ASC')) {
157             $oldoptions = array();
158         }
160         // Save the units.
161         $result = $this->save_numerical_units($question);
162         if (isset($result->error)) {
163             return $result;
164         } else {
165             $units = &$result->units;
166         }
168         // Insert all the new answers
169         foreach ($question->answer as $key => $dataanswer) {
170             // Check for, and ingore, completely blank answer from the form.
171             if (trim($dataanswer) == '' && $question->fraction[$key] == 0 &&
172                     html_is_blank($question->feedback[$key]['text'])) {
173                 continue;
174             }
176             $answer = new stdClass;
177             $answer->question = $question->id;
178             if (trim($dataanswer) === '*') {
179                 $answer->answer = '*';
180             } else {
181                 $answer->answer = $this->apply_unit_old($dataanswer, $units);
182                 if ($answer->answer === false) {
183                     $result->notice = get_string('invalidnumericanswer', 'quiz');
184                 }
185             }
186             $answer->fraction = $question->fraction[$key];
188             $feedbacktext = trim($question->feedback[$key]['text']);
189             $draftid = $question->feedback[$key]['itemid'];
192             $answer->feedbackformat = $question->feedback[$key]['format'];
194             if ($oldanswer = array_shift($oldanswers)) {  // Existing answer, so reuse it
195                 $feedbacktext = file_save_draft_area_files($draftid, $context->id, 'question', 'answerfeedback', $oldanswer->id, self::$fileoptions, $feedbacktext);
196                 $answer->feedback = $feedbacktext;
197                 $answer->id = $oldanswer->id;
198                 $DB->update_record("question_answers", $answer);
199             } else { // This is a completely new answer
200                 $answer->feedback = $feedbacktext;
201                 $answer->id = $DB->insert_record("question_answers", $answer);
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));
204             }
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 {
215                 $options->tolerance = $this->apply_unit_old($question->tolerance[$key], $units);
216                 if ($options->tolerance === false) {
217                     $result->notice = get_string('invalidnumerictolerance', 'quiz');
218                 }
219             }
221             // Save options
222             if (isset($options->id)) { // reusing existing record
223                 $DB->update_record('question_numerical', $options);
224             } else { // new options
225                 $DB->insert_record('question_numerical', $options);
226             }
227         }
228         // delete old answer records
229         if (!empty($oldanswers)) {
230             foreach($oldanswers as $oa) {
231                 $DB->delete_records('question_answers', array('id' => $oa->id));
232             }
233         }
235         // delete old answer records
236         if (!empty($oldoptions)) {
237             foreach($oldoptions as $oo) {
238                 $DB->delete_records('question_numerical', array('id' => $oo->id));
239             }
240         }
241         $result = $this->save_numerical_options($question);
242         if (isset($result->error)) {
243             return $result;
244         }
245         // Report any problems.
246         if (!empty($result->notice)) {
247             return $result;
248         }
249         return true;
250     }
252     function save_numerical_options(&$question) {
253         global $DB;
255         $result = new stdClass;
256         // numerical options
257         $update = true ;
258         $options = $DB->get_record('question_numerical_options', array('question' => $question->id));
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         }
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             }
281         }else {
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
287                     $options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ;
288                 }else {
289                     // only numerical will be graded
290                     $options->showunits = NUMERICALQUESTIONUNITNODISPLAY ;
291                 }
292             }
293         }
294         if(isset($question->unitsleft)){
295             $options->unitsleft = $question->unitsleft;
296         }else {
297             $options->unitsleft = 0 ;
298         }
299         $options->instructionsformat = $question->instructions['format'];
300         if(isset($question->instructions)){
301             $options->instructions = trim($question->instructions['text']);
302         }else {
303             $options->instructions = '' ;
304         }
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         );
314         if ($update) {
315             $DB->update_record("question_numerical_options", $options);
316         } else {
317             $id = $DB->insert_record("question_numerical_options", $options);
318         }
320         return $result;
321     }
323     function save_numerical_units($question) {
324         global $DB;
325         $result = new stdClass;
327         // Delete the units previously saved for this question.
328         $DB->delete_records('question_numerical_units', array('question' => $question->id));
330         // Nothing to do.
331         if (!isset($question->multiplier)) {
332             $result->units = array();
333             return $result;
334         }
336         // Save the new units.
337         $units = array();
338         $unitalreadyinsert = array();
339         foreach ($question->multiplier as $i => $multiplier) {
340             // Discard any unit which doesn't specify the unit or the multiplier
341             if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])&& !array_key_exists($question->unit[$i],$unitalreadyinsert)) {
342                 $unitalreadyinsert[$question->unit[$i]] = 1 ;
343                 $units[$i] = new stdClass;
344                 $units[$i]->question = $question->id;
345                 $units[$i]->multiplier = $this->apply_unit_old($question->multiplier[$i], array());
346                 $units[$i]->unit = $question->unit[$i];
347                 $DB->insert_record('question_numerical_units', $units[$i]);
348             }
349         }
350         unset($question->multiplier, $question->unit);
352         $result->units = &$units;
353         return $result;
354     }
356     function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
357         $state->responses = array();
358         $state->responses['answer'] =  '';
359         $state->responses['unit'] = '';
361         return true;
362     }
363     function restore_session_and_responses(&$question, &$state) {
364        if(false === strpos($state->responses[''], '|||||')){
365              $state->responses['answer']= $state->responses[''];
366              $state->responses['unit'] = '';
367              $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
368        }else {
369             $responses = explode('|||||', $state->responses['']);
370             $state->responses['answer']= $responses[0];
371             $state->responses['unit'] = $responses[1];
372        }
374        return true;
375     }
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                 }
384             }
385         return 0 ;
386     }
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)) {
396             if(isset($responseparts[5]) ){
397                 $unit = $responseparts[5] ;
398             }
399             if(isset($responseparts[1]) ){
400                 $answer = $responseparts[1] ;
401             }
402         }
403         return ;
404     }
407     function save_session_and_responses(&$question, &$state) {
408         global $DB;
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
419         $DB->set_field('question_states', 'answer', $responses, array('id' => $state->id));
420         return true;
421     }
423     /**
424      * Deletes question from the question-type specific tables
425      *
426      * @return boolean Success/Failure
427      * @param object $question  The question being deleted
428      */
429     function delete_question($questionid) {
430         global $DB;
431         $DB->delete_records("question_numerical", array("question" => $questionid));
432         $DB->delete_records("question_numerical_options", array("question" => $questionid));
433         $DB->delete_records("question_numerical_units", array("question" => $questionid));
434         return true;
435     }
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;
442         $context = $this->get_context_by_category_id($question->category);
443         $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
444         $formatoptions = new stdClass;
445         $formatoptions->noclean = true;
446         $formatoptions->para = false;
447         $nameprefix = $question->name_prefix;
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);
452         /// Print question text and media
454         $questiontext = format_text($question->questiontext,
455                 $question->questiontextformat,
456                 $formatoptions, $cmoptions->course);
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
461         // If $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY
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.
467         $nameanswer = "name=\"".$question->name_prefix."answer\"";
468         $nameunit   = "name=\"".$question->name_prefix."unit\"";
469         if (isset($state->responses['']) && $state->responses[''] != '' && !isset($state->responses['answer'])){
470               $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
471         }
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']).'" ';
479         } else {
480             $valueunit = ' value="" ';
481             if ($question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ){
482               $valueunit = ' value="'.s($question->options->units[0]->unit).'" ';
483             }
484         }
486         $feedback = '';
487         $class = '';
488         $classunit = '' ;
489         $classunitvalue = '' ;
490         $feedbackimg = '';
491         $feedbackimgunit = '' ;
492         $answerasterisk = false ;
493         $response = '' ;
494         $valid_numerical_unit = false ;
495         $rawgrade = 0 ;
496         if ($options->feedback) {
497             $class = question_get_feedback_class(0);
498             $classunit = question_get_feedback_class(0);
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']);
505             $break = 0 ;
506             foreach($question->options->answers as $answer) {
507                 // if * then everything has the $answer->fraction value
508                 if ($answer->answer !== '*' ) {
509                     $this->get_tolerance_interval($answer);
510                 }
512                 $answer->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id);
513                 if ($answer->answer === '*') {
514                     $answerasterisk = true ;
515                     $rawgrade = $answer->fraction ;
516                     $class = question_get_feedback_class($answer->fraction);
517                     $feedbackimg = question_get_feedback_image($answer->fraction);
518                     $classunitvalue = $class ;
519                     $classunit = question_get_feedback_class($answer->fraction);
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                     }
524                     if ( isset($question->options->units))
525                     {
526                         $valid_numerical_unit = true ;
527                     }
528                     $break = 1 ;
529                 } else if ($response !== false && isset($question->options->units) && count($question->options->units) > 0) {
530                     $hasunits = 1 ;
531                     foreach($question->options->units as $key => $unit){
532                         // The student did type a number, so check it with tolerances.
533                         $testresponse = $response /$unit->multiplier ;
534                         if($answer->min <= $testresponse && $testresponse <= $answer->max) {
535                             $unittested = $unit->unit ;
536                             $rawgrade = $answer->fraction ;
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                             }
542                             if($state->responses['unit'] == $unit->unit){
543                                 $classunitvalue = $answer->fraction ;
544                             }else {
545                                 $classunitvalue == 0 ;
546                             }
547                             $classunit = question_get_feedback_class($classunitvalue);
548                             $feedbackimgunit = question_get_feedback_image($classunitvalue, $options->feedback);
549                             $break = 1 ;
550                             break;
551                         }
552                     }
553                 } else if($response !== false && ($answer->min <= $response && $response <= $answer->max) ) {
554                     $rawgrade = $answer->fraction ;
555                     $class = question_get_feedback_class($answer->fraction);
556                     $feedbackimg = question_get_feedback_image($answer->fraction);
557                     if ($answer->feedback) {
558                         $feedback = format_text($answer->feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course);
559                     }
560                    $break = 1 ;
561                 }
562                 if ($break) {
563                     break;
564                 }
565             }
566         }
567         $state->options->raw_unitpenalty = 0 ;
568         $raw_unitpenalty = 0 ;
569         if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY ||
570                 $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) {
571                     $classunitvalue = 1 ;
572         }
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             }
581             $state->options->raw_unitpenalty = $raw_unitpenalty ;
582         }
584         /// Removed correct answer, to be displayed later MDL-7496
585         include("$CFG->dirroot/question/type/numerical/display.html");
586     }
589     function compare_responses(&$question, $state, $teststate) {
591                if ($question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY && isset($question->options->units) && isset($question->options->units[$state->responses['unit']] )){
592             $state->responses['unit']=$question->options->units[$state->responses['unit']]->unit;
593         };
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         }
611         if ( isset($responses)  && isset($testresponses )) {
613             return $responses == $testresponses ;
614         }
615         return false;
616     }
618     /**
619      * Checks whether a response matches a given answer, taking the tolerance
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.
623      * if unit != -1 then the test is done only on this unit
624      */
625     function test_response(&$question, &$state, $answer ) {
626         // Deal with the match anything answer.
627         if ($answer->answer === '*') {
628             return true;
629         }
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['']
632         * which contains the response which is analyzed by extract_numerical_response()
633         * If the data comes from the numerical or calculated display
634         * the $state->responses['unit'] comes from either
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
641         *
642         */
644         if (!isset($state->responses['answer']) && isset($state->responses[''])){
645            $state->responses['answer'] =  $state->responses[''];
646         }
647         $response = $this->extract_numerical_response($state->responses['answer']);
648         if ($response === false) {
649             return false; // The student did not type a number.
650         }
651         // The student did type a number, so check it with tolerances.
652         $this->get_tolerance_interval($answer);
653         if ($answer->min <= $response && $response <= $answer->max){
654            return true;
655         }
656         // testing for other units
657         if ( isset($question->options->units) && count($question->options->units) > 0) {
658             foreach($question->options->units as $key =>$unit){
659                 $testresponse = $response /$unit->multiplier ;
660                 if($answer->min <= $testresponse && $testresponse<= $answer->max) {
661                     return true;
662                 }
663             }
664         }
665         return false;
666     }
668     /**
669     * Performs response processing and grading
670     * The function was redefined for handling correctly the two parts
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.
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     */
691     function grade_responses(&$question, &$state, $cmoptions) {
692         if (!isset($state->responses['answer']) && isset($state->responses[''])){
693            $state->responses['answer'] =  $state->responses[''];
694         }
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
698         // however it is used to detect good or bad numbers but also
699         // gives false
700         $state->raw_grade = 0;
701         $valid_numerical_unit = false ;
702         $break = 0 ;
703         $unittested = '';
704         $hasunits = 0 ;
705         $response = $this->extract_numerical_response($state->responses['answer']);
706         $answerasterisk = false ;
708         $break = 0 ;
709         foreach($question->options->answers as $answer) {
710             if ($answer->answer !== '*' ) {
711             // The student did type a number, so check it with tolerances.
712                 $this->get_tolerance_interval($answer);
713             }
715             // if * then everything is OK even unit
716             if ($answer->answer === '*') {
717                 $state->raw_grade = $answer->fraction;
718                 if ( isset($question->options->units)){
719                     $valid_numerical_unit = true ;
720                 }
721                 $answerasterisk = true ;
722                 $break = 1 ;
723             }else if ($response !== false  &&  isset($question->options->units) && count($question->options->units) > 0) {
724                 $hasunits = 1 ;
725                 foreach($question->options->units as $key => $unit){
726                     $testresponse = $response /$unit->multiplier ;
728                     if($answer->min <= $testresponse && $testresponse <= $answer->max) {
729                         $state->raw_grade = $answer->fraction;
730                         $unittested = $unit->unit ;
731                         $break = 1 ;
732                         break;
733                     }
734                 }
735             }else if ($response !== false)  {
736                 if($this->test_response($question, $state, $answer)) {
737                     $state->raw_grade = $answer->fraction;
738                     break;
739                 }
740             }
741             if ($break) break;
742         } //foreach($question->options
744         // in all cases the unit should be tested
745         if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY ||
746                 $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) {
747             $valid_numerical_unit = true ;
748         }else {
749             // $valid_numerical_unit means that the grading was done with the unit defined
750             //
751             if ($hasunits && !$answerasterisk ){
752                 $valid_numerical_unit = ($state->responses['unit'] == $unittested) ;
753             } else {
754                 $valid_numerical_unit = true ;
755             }
756         }
758         // apply unit penalty
759         $raw_unitpenalty = 0 ;
760         if(!empty($question->options->unitpenalty)&& $valid_numerical_unit != true ){
761             if($question->options->unitgradingtype == 1){
762                 $raw_unitpenalty = $question->options->unitpenalty * $state->raw_grade ;
763             }else {
764                 $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade;
765             }
766             $state->raw_grade -= $raw_unitpenalty ;
767         }
769         // Make sure we don't assign negative or too high marks.
770         $state->raw_grade = min(max((float) $state->raw_grade,
771                             0.0), 1.0) * $question->maxgrade;
773         // Update the penalty.
774         $state->penalty = $question->penalty * $question->maxgrade;
776         // mark the state as graded
777         $state->event = ($state->event ==  QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
779         return true;
780     }
783     function get_correct_responses(&$question, &$state) {
784         $correct = parent::get_correct_responses($question, $state);
785         $unit = $this->get_default_numerical_unit($question);
786         if (isset($correct['']) && $correct[''] != '*' && $unit) {
787             $correct[''] .= ' '.$unit->unit;
788         }
789         return $correct;
790     }
792     // ULPGC ecastro
793     function get_all_responses(&$question, &$state) {
794         $result = new stdClass;
795         $answers = array();
796         $unit = $this->get_default_numerical_unit($question);
797         if (is_array($question->options->answers)) {
798             foreach ($question->options->answers as $aid=>$answer) {
799                 $r = new stdClass;
800                 $r->answer = $answer->answer;
801                 $r->credit = $answer->fraction;
802                 $this->get_tolerance_interval($answer);
803                 if ($r->answer != '*' && $unit) {
804                     $r->answer .= ' ' . $unit->unit;
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             }
813         }
814         $result->id = $question->id;
815         $result->responses = $answers;
816         return $result;
817     }
818     function get_actual_response($question, $state) {
819        if (!empty($state->responses) && !empty($state->responses[''])) {
820            if(false === strpos($state->responses[''], '|||||')){
821                 $responses[] = $state->responses[''];
822             }else {
823                 $resp = explode('|||||', $state->responses['']);
824                 $responses[] = $resp[0].$resp[1];
825             }
826        } else {
827            $responses[] = '';
828         }
830        return $responses;
831     }
834     function get_tolerance_interval(&$answer) {
835         // No tolerance
836         if (empty($answer->tolerance)) {
837             $answer->tolerance = 0;
838         }
840         // Calculate the interval of correct responses (min/max)
841         if (!isset($answer->tolerancetype)) {
842             $answer->tolerancetype = 2; // nominal
843         }
845         // We need to add a tiny fraction depending on the set precision to make the
846         // comparison work correctly. Otherwise seemingly equal values can yield
847         // false. (fixes bug #3225)
848         $tolerance = (float)$answer->tolerance + ("1.0e-".ini_get('precision'));
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;
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;
859             case '2': case 'nominal':
860                 $tolerance = abs($tolerance); // important - otherwise min and max are swapped
861                 // $answer->tolerance 0 or something else
862                 if ((float)$answer->tolerance == 0.0  &&  abs((float)$answer->answer) <= $tolerance ){
863                     $tolerance = (float) ("1.0e-".ini_get('precision')) * abs((float)$answer->answer) ; //tiny fraction
864                 } else if ((float)$answer->tolerance != 0.0 && abs((float)$answer->tolerance) < abs((float)$answer->answer) &&  abs((float)$answer->answer) <= $tolerance){
865                     $tolerance = (1+("1.0e-".ini_get('precision')) )* abs((float) $answer->tolerance) ;//tiny fraction
866                }
868                 $max = $answer->answer + $tolerance;
869                 $min = $answer->answer - $tolerance;
870                 break;
871             case '3': case 'geometric':
872                 $quotient = 1 + abs($tolerance);
873                 $max = $answer->answer * $quotient;
874                 $min = $answer->answer / $quotient;
875                 break;
876             default:
877                 print_error('unknowntolerance', 'question', '', $answer->tolerancetype);
878         }
880         $answer->min = $min;
881         $answer->max = $max;
882         return true;
883     }
885     /**
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      */
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('', '');
900         }else { // remove spaces and normalise , to a . .
901             $replace = array('', '.');
902         }
903         $rawresponse = str_replace($search, $replace, $rawresponse);
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     }
912     /**
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) {
922         // Make units more useful
923         $tmpunits = array();
924         foreach ($units as $unit) {
925             $tmpunits[$unit->unit] = $unit->multiplier;
926         }
927         // remove spaces and normalise decimal places.
928         $rawresponse = trim($rawresponse) ;
929         $search  = array(' ', ',');
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('', '');
933         }else { // remove spaces and normalise , to a . .
934             $replace = array('', '.');
935         }
936         $rawresponse = str_replace($search, $replace, $rawresponse);
939         // Apply any unit that is present.
940         if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$',
941                 $rawresponse, $responseparts)) {
943             if (!empty($responseparts[5])) {
945                 if (isset($tmpunits[$responseparts[5]])) {
946                     // Valid number with unit.
947                     return (float)$responseparts[1] / $tmpunits[$responseparts[5]];
948                 } else {
949                     // Valid number with invalid unit. Must be wrong.
950                     return false;
951                 }
953             } else {
954                 // Valid number without unit.
955                 return (float)$responseparts[1];
956             }
957         }
958         // Invalid number. Must be wrong.
959         return false;
960     }
962     /**
963     * function used in function definition_inner()
964     * of edit_..._form.php for
965     * numerical, calculated, calculatedsimple
966     */
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);
981         $mform->addElement('editor', 'instructions', get_string('instructions', 'qtype_numerical'), null, $that->editoroptions);
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 );
992          $currentgrp1 = array();
994         $mform->setType('unitpenalty', PARAM_NUMBER);
995         $mform->setDefault('unitpenalty', 0.1);
996         $mform->setDefault('unitgradingtype', 1);
997         // $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical'); // TODO help did not exist before MDL-21695
998         $mform->setDefault('showunits0', 0);
999         $mform->setDefault('showunits1', 3);
1000         $mform->setDefault('unitsleft', 0);
1001         $mform->setType('instructions', PARAM_RAW);
1002         $mform->addHelpButton('instructions', 'numericalinstructions', 'qtype_numerical');
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');
1011     }
1013     /**
1014      * function used in in function definition_inner()
1015      * of edit_..._form.php for
1016      * numerical, calculated, calculatedsimple
1017      */
1018     function add_units_elements(& $mform,& $that) {
1019         $repeated = array();
1020         $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}'));
1022         $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz'));
1023         $mform->setType('unit', PARAM_NOTAGS);
1025         $repeated[] =& $mform->createElement('text', 'multiplier', get_string('multiplier', 'quiz'));
1026         $mform->setType('multiplier', PARAM_NUMBER);
1028         if (isset($that->question->options)){
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}'));
1040         if ($mform->elementExists('multiplier[0]')){
1041             $firstunit =& $mform->getElement('multiplier[0]');
1042             $firstunit->freeze();
1043             $firstunit->setValue('1.0');
1044             $firstunit->setPersistantFreeze(true);
1045             $mform->addHelpButton('multiplier[0]', 'numericalmultiplier', 'qtype_numerical');
1046         }
1047     }
1049     /**
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){
1055         list($categoryid) = explode(',', $question->category);
1056         $context = $this->get_context_by_category_id($categoryid);
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' :
1063                 case '1' :
1064                     $default_values['showunits0'] = $question->options->showunits ;
1065                     $default_values['unitrole'] = 0 ;
1066                     break;
1067                 case '2' :
1068                 case '3' :
1069                     $default_values['showunits1'] = $question->options->showunits ;
1070                     $default_values['unitrole'] = 1 ;
1071                     break;
1072             }
1073             $default_values['unitsleft'] = $question->options->unitsleft ;
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;
1091             if (isset($question->options->units)) {
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     }
1103     /**
1104       * function use in in function validation()
1105       * of edit_..._form.php for
1106       * numerical, calculated, calculatedsimple
1107       */
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         }
1117         if (($showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY) ||
1118                 ($showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY ) ||
1119                 ($showunits == NUMERICALQUESTIONUNITTEXTDISPLAY )){
1120            if (trim($units[0]) == ''){
1121              $errors['unit[0]'] = 'You must set a valid unit name' ;
1122             }
1123         }
1124         if ($showunits == NUMERICALQUESTIONUNITNODISPLAY ){
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         }
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                         }
1166                     }
1167                 }
1168             }
1170     }
1173     function valid_unit($rawresponse, $units) {
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));
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)) {
1188             if (!empty($responseparts[5])) {
1190                 if (isset($tmpunits[$responseparts[5]])) {
1191                     // Valid number with unit.
1192                     return true ; //(float)$responseparts[1] / $tmpunits[$responseparts[5]];
1193                 } else {
1194                     // Valid number with invalid unit. Must be wrong.
1195                     return false;
1196                 }
1198             } else {
1199                 // Valid number without unit.
1200                 return false ; //(float)$responseparts[1];
1201             }
1202         }
1203         // Invalid number. Must be wrong.
1204         return false;
1205     }
1207     /// RESTORE FUNCTIONS /////////////////
1209     /**
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) {
1215         global $DB;
1217         $status = true;
1219         //Get the numerical array
1220         if (isset($info['#']['NUMERICAL'])) {
1221             $numericals = $info['#']['NUMERICAL'];
1222         } else {
1223             $numericals = array();
1224         }
1226         //Iterate over numericals
1227         for($i = 0; $i < sizeof($numericals); $i++) {
1228             $num_info = $numericals[$i];
1230             //Now, build the question_numerical record structure
1231             $numerical = new stdClass;
1232             $numerical->question = $new_question_id;
1233             $numerical->answer = backup_todb($num_info['#']['ANSWER']['0']['#']);
1234             $numerical->tolerance = backup_todb($num_info['#']['TOLERANCE']['0']['#']);
1236             //We have to recode the answer field
1237             $answer = backup_getid($restore->backup_unique_code,"question_answers",$numerical->answer);
1238             if ($answer) {
1239                 $numerical->answer = $answer->new_id;
1240             }
1242             //The structure is equal to the db, so insert the question_numerical
1243             $newid = $DB->insert_record ("question_numerical", $numerical);
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             }
1256             //Now restore numerical_units
1257             $status = question_restore_numerical_units ($old_question_id,$new_question_id,$num_info,$restore);
1259             //Now restore numerical_options
1260             $status = question_restore_numerical_options ($old_question_id,$new_question_id,$num_info,$restore);
1262             if (!$newid) {
1263                 $status = false;
1264             }
1265         }
1267         return $status;
1268     }
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;
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....');
1292         if ($courseid) {
1293             $course = $DB->get_record('course', array('id' => $courseid));
1294         }
1296         return $this->save_question($question, $form, $course);
1297     }
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);
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     }
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     }
1365 // INITIATION - Without this line the question type is not in use.
1366 question_register_questiontype(new question_numerical_qtype());
1367 if ( ! defined ("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY")) {
1368     define("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY",   0);
1370 if ( ! defined ("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY")) {
1371     define("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY",   1);
1373 if ( ! defined ("NUMERICALQUESTIONUNITTEXTDISPLAY")) {
1374     define("NUMERICALQUESTIONUNITTEXTDISPLAY",   2);
1376 if ( ! defined ("NUMERICALQUESTIONUNITNODISPLAY")) {
1377     define("NUMERICALQUESTIONUNITNODISPLAY",    3);