Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/question/type/numerical/question.php
Go to the documentation of this file.
00001 <?php
00002 // This file is part of Moodle - http://moodle.org/
00003 //
00004 // Moodle is free software: you can redistribute it and/or modify
00005 // it under the terms of the GNU General Public License as published by
00006 // the Free Software Foundation, either version 3 of the License, or
00007 // (at your option) any later version.
00008 //
00009 // Moodle is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 // GNU General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00016 
00027 defined('MOODLE_INTERNAL') || die();
00028 
00029 require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
00030 
00031 
00038 class qtype_numerical_question extends question_graded_automatically {
00040     public $answers = array();
00041 
00043     public $unitdisplay;
00045     public $unitgradingtype;
00047     public $unitpenalty;
00048 
00050     public $ap;
00051 
00052     public function get_expected_data() {
00053         $expected = array('answer' => PARAM_RAW_TRIMMED);
00054         if ($this->has_separate_unit_field()) {
00055             $expected['unit'] = PARAM_RAW_TRIMMED;
00056         }
00057         return $expected;
00058     }
00059 
00060     public function has_separate_unit_field() {
00061         return $this->unitdisplay == qtype_numerical::UNITRADIO ||
00062                 $this->unitdisplay == qtype_numerical::UNITSELECT;
00063     }
00064 
00065     public function start_attempt(question_attempt_step $step, $variant) {
00066         $step->set_qt_var('_separators',
00067                 $this->ap->get_point() . '$' . $this->ap->get_separator());
00068     }
00069 
00070     public function apply_attempt_state(question_attempt_step $step) {
00071         list($point, $separator) = explode('$', $step->get_qt_var('_separators'));
00072                 $this->ap->set_characters($point, $separator);
00073     }
00074 
00075     public function summarise_response(array $response) {
00076         if (isset($response['answer'])) {
00077             $resp = $response['answer'];
00078         } else {
00079             $resp = null;
00080         }
00081 
00082         if ($this->has_separate_unit_field() && !empty($response['unit'])) {
00083             $resp = $this->ap->add_unit($resp, $response['unit']);
00084         }
00085 
00086         return $resp;
00087     }
00088 
00089     public function is_gradable_response(array $response) {
00090         return array_key_exists('answer', $response) &&
00091                 ($response['answer'] || $response['answer'] === '0' || $response['answer'] === 0);
00092     }
00093 
00094     public function is_complete_response(array $response) {
00095         if (!$this->is_gradable_response($response)) {
00096             return false;
00097         }
00098 
00099         list($value, $unit) = $this->ap->apply_units($response['answer']);
00100         if (is_null($value)) {
00101             return false;
00102         }
00103 
00104         if ($this->unitdisplay != qtype_numerical::UNITINPUT && $unit) {
00105             return false;
00106         }
00107 
00108         if ($this->has_separate_unit_field() && empty($response['unit'])) {
00109             return false;
00110         }
00111 
00112         if ($this->ap->contains_thousands_seaparator($response['answer'])) {
00113             return false;
00114         }
00115 
00116         return true;
00117     }
00118 
00119     public function get_validation_error(array $response) {
00120         if (!$this->is_gradable_response($response)) {
00121             return get_string('pleaseenterananswer', 'qtype_numerical');
00122         }
00123 
00124         list($value, $unit) = $this->ap->apply_units($response['answer']);
00125         if (is_null($value)) {
00126             return get_string('invalidnumber', 'qtype_numerical');
00127         }
00128 
00129         if ($this->unitdisplay != qtype_numerical::UNITINPUT && $unit) {
00130             return get_string('invalidnumbernounit', 'qtype_numerical');
00131         }
00132 
00133         if ($this->has_separate_unit_field() && empty($response['unit'])) {
00134             return get_string('unitnotselected', 'qtype_numerical');
00135         }
00136 
00137         if ($this->ap->contains_thousands_seaparator($response['answer'])) {
00138             return get_string('pleaseenteranswerwithoutthousandssep', 'qtype_numerical',
00139                     $this->ap->get_separator());
00140         }
00141 
00142         return '';
00143     }
00144 
00145     public function is_same_response(array $prevresponse, array $newresponse) {
00146         if (!question_utils::arrays_same_at_key_missing_is_blank(
00147                 $prevresponse, $newresponse, 'answer')) {
00148             return false;
00149         }
00150 
00151         if ($this->has_separate_unit_field()) {
00152             return question_utils::arrays_same_at_key_missing_is_blank(
00153                 $prevresponse, $newresponse, 'unit');
00154         }
00155 
00156         return true;
00157     }
00158 
00159     public function get_correct_response() {
00160         $answer = $this->get_correct_answer();
00161         if (!$answer) {
00162             return array();
00163         }
00164 
00165         $response = array('answer' => str_replace('.', $this->ap->get_point(), $answer->answer));
00166 
00167         if ($this->has_separate_unit_field()) {
00168             $response['unit'] = $this->ap->get_default_unit();
00169         } else if ($this->unitdisplay == qtype_numerical::UNITINPUT) {
00170             $response['answer'] = $this->ap->add_unit($answer->answer);
00171         }
00172 
00173         return $response;
00174     }
00175 
00184     public function get_matching_answer($value, $multiplier) {
00185         if (!is_null($multiplier)) {
00186             $scaledvalue = $value * $multiplier;
00187         } else {
00188             $scaledvalue = $value;
00189         }
00190         foreach ($this->answers as $aid => $answer) {
00191             if ($answer->within_tolerance($scaledvalue)) {
00192                 $answer->unitisright = !is_null($multiplier);
00193                 return $answer;
00194             } else if ($answer->within_tolerance($value)) {
00195                 $answer->unitisright = false;
00196                 return $answer;
00197             }
00198         }
00199         return null;
00200     }
00201 
00202     public function get_correct_answer() {
00203         foreach ($this->answers as $answer) {
00204             $state = question_state::graded_state_for_fraction($answer->fraction);
00205             if ($state == question_state::$gradedright) {
00206                 return $answer;
00207             }
00208         }
00209         return null;
00210     }
00211 
00218     public function apply_unit_penalty($fraction, $unitisright) {
00219         if ($unitisright) {
00220             return $fraction;
00221         }
00222 
00223         if ($this->unitgradingtype == qtype_numerical::UNITGRADEDOUTOFMARK) {
00224             $fraction -= $this->unitpenalty * $fraction;
00225         } else if ($this->unitgradingtype == qtype_numerical::UNITGRADEDOUTOFMAX) {
00226             $fraction -= $this->unitpenalty;
00227         }
00228         return max($fraction, 0);
00229     }
00230 
00231     public function grade_response(array $response) {
00232         if ($this->has_separate_unit_field()) {
00233             $selectedunit = $response['unit'];
00234         } else {
00235             $selectedunit = null;
00236         }
00237         list($value, $unit, $multiplier) = $this->ap->apply_units(
00238                 $response['answer'], $selectedunit);
00239 
00240         $answer = $this->get_matching_answer($value, $multiplier);
00241         if (!$answer) {
00242             return array(0, question_state::$gradedwrong);
00243         }
00244 
00245         $fraction = $this->apply_unit_penalty($answer->fraction, $answer->unitisright);
00246         return array($fraction, question_state::graded_state_for_fraction($fraction));
00247     }
00248 
00249     public function classify_response(array $response) {
00250         if (empty($response['answer'])) {
00251             return array($this->id => question_classified_response::no_response());
00252         }
00253 
00254         if ($this->has_separate_unit_field()) {
00255             $selectedunit = $response['unit'];
00256         } else {
00257             $selectedunit = null;
00258         }
00259         list($value, $unit, $multiplier) = $this->ap->apply_units($response['answer'], $selectedunit);
00260         $ans = $this->get_matching_answer($value, $multiplier);
00261 
00262         $resp = $response['answer'];
00263         if ($this->has_separate_unit_field()) {
00264             $resp = $this->ap->add_unit($resp, $unit);
00265         }
00266 
00267         if (!$ans) {
00268             return array($this->id => new question_classified_response(0, $resp, 0));
00269         }
00270 
00271         return array($this->id => new question_classified_response($ans->id,
00272                 $resp,
00273                 $this->apply_unit_penalty($ans->fraction, $ans->unitisright)));
00274     }
00275 
00276     public function check_file_access($qa, $options, $component, $filearea, $args,
00277             $forcedownload) {
00278         if ($component == 'question' && $filearea == 'answerfeedback') {
00279             $question = $qa->get_question();
00280             $currentanswer = $qa->get_last_qt_var('answer');
00281             if ($this->has_separate_unit_field()) {
00282                 $selectedunit = $qa->get_last_qt_var('unit');
00283             } else {
00284                 $selectedunit = null;
00285             }
00286             list($value, $unit, $multiplier) = $question->ap->apply_units(
00287                     $currentanswer, $selectedunit);
00288             $answer = $question->get_matching_answer($value, $multiplier);
00289             $answerid = reset($args); // itemid is answer id.
00290             return $options->feedback && $answerid == $answer->id;
00291 
00292         } else if ($component == 'question' && $filearea == 'hint') {
00293             return $this->check_hint_file_access($qa, $options, $args);
00294 
00295         } else {
00296             return parent::check_file_access($qa, $options, $component, $filearea,
00297                     $args, $forcedownload);
00298         }
00299     }
00300 }
00301 
00302 
00310 class qtype_numerical_answer extends question_answer {
00312     public $tolerance;
00314     public $tolerancetype = 2;
00315 
00316     public function __construct($id, $answer, $fraction, $feedback, $feedbackformat, $tolerance) {
00317         parent::__construct($id, $answer, $fraction, $feedback, $feedbackformat);
00318         $this->tolerance = abs($tolerance);
00319     }
00320 
00321     public function get_tolerance_interval() {
00322         if ($this->answer === '*') {
00323             throw new coding_exception('Cannot work out tolerance interval for answer *.');
00324         }
00325 
00326         // We need to add a tiny fraction depending on the set precision to make
00327         // the comparison work correctly, otherwise seemingly equal values can
00328         // yield false. See MDL-3225.
00329         $tolerance = (float) $this->tolerance + pow(10, -1 * ini_get('precision'));
00330 
00331         switch ($this->tolerancetype) {
00332             case 1: case 'relative':
00333                 $range = abs($this->answer) * $tolerance;
00334                 return array($this->answer - $range, $this->answer + $range);
00335 
00336             case 2: case 'nominal':
00337                 $tolerance = $this->tolerance + pow(10, -1 * ini_get('precision')) *
00338                         max(1, abs($this->answer));
00339                 return array($this->answer - $tolerance, $this->answer + $tolerance);
00340 
00341             case 3: case 'geometric':
00342                 $quotient = 1 + abs($tolerance);
00343                 return array($this->answer / $quotient, $this->answer * $quotient);
00344 
00345             default:
00346                 throw new coding_exception('Unknown tolerance type ' . $this->tolerancetype);
00347         }
00348     }
00349 
00350     public function within_tolerance($value) {
00351         if ($this->answer === '*') {
00352             return true;
00353         }
00354         list($min, $max) = $this->get_tolerance_interval();
00355         return $min <= $value && $value <= $max;
00356     }
00357 }
 All Data Structures Namespaces Files Functions Variables Enumerations