for edit_numerical_form.php @@ -46,12 +46,31 @@ class qtype_numerical_edit_form extends question_edit_form { $this->add_interactive_settings(); } protected function get_per_answer_fields($mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) { - $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, - $repeatedoptions, $answersoption); + $repeated = array(); + $repeated[] = $mform->createElement('header', 'answerhdr', $label); + $repeated[] = $mform->createElement('text', 'answer', + "Numerical value as 1234.56 or 1.23456E3" , array('size' => 80)); //get_string('answer', 'question') + $localeoptions["dec_sep_,."]= "all" ; + $localeoptions["dec_sep_."]= "1234.56" ; + $localeoptions["dec_sep_,"]= "1234,56" ; + $repeated[] = $mform->createElement('select', 'locale', + "Response format", $localeoptions); + + $repeated[] = $mform->createElement('select', 'fraction', + get_string('grade'), $gradeoptions); + $repeated[] = $mform->createElement('editor', 'feedback', + get_string('feedback', 'question'), array('rows' => 5), $this->editoroptions); + $repeatedoptions['answer']['type'] = PARAM_RAW; + $repeatedoptions['fraction']['default'] = 0; + $repeatedoptions['locale']['default'] = 'dec_sep_,.'; + $answersoption = 'answers'; + +// $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, +// $repeatedoptions, $answersoption); $tolerance = $mform->createElement('text', 'tolerance', get_string('acceptederror', 'qtype_numerical')); $repeatedoptions['tolerance']['type'] = PARAM_NUMBER; array_splice($repeated, 3, 0, array($tolerance)); @@ -177,10 +196,11 @@ class qtype_numerical_edit_form extends question_edit_form { } $key = 0; foreach ($question->options->answers as $answer) { $question->tolerance[$key] = $answer->tolerance; + $question->locale[$key] = $answer->locale; $key++; } return $question; } for numerical/question.php @@ -37,10 +37,11 @@ require_once($CFG->dirroot . '/question/type/numerical/questiontype.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_numerical_question extends question_graded_automatically { /** @var array of question_answer. */ public $answers = array(); + public $units = array(); /** @var int one of the constants UNITNONE, UNITSELECT or UNITINPUT. */ public $unitdisplay; /** @var int one of the constants UNITGRADEDOUTOFMARK or UNITGRADEDOUTOFMAX. */ public $unitgradingtype; @@ -54,37 +55,36 @@ class qtype_numerical_question extends question_graded_automatically { parent::__construct(); } public function get_expected_data() { $expected = array('answer' => PARAM_RAW_TRIMMED); - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { + if ($this->unitdisplay != qtype_numerical::UNITNONE) { $expected['unit'] = PARAM_RAW_TRIMMED; } return $expected; } - public function start_attempt(question_attempt_step $step) { - $step->set_qt_var('_separators', - $this->ap->get_point() . '$' . $this->ap->get_separator()); - } + /* public function start_attempt(question_attempt_step $step) { + // $step->set_qt_var('_separators', + // $this->ap->get_point() . '$' . $this->ap->get_separator()); + }*/ - public function apply_attempt_state(question_attempt_step $step) { - list($point, $separator) = explode('$', $step->get_qt_var('_separators')); - $this->ap->set_characters($point, $separator); - } + /* public function apply_attempt_state(question_attempt_step $step) { + // list($point, $separator) = explode('$', $step->get_qt_var('_separators')); + // $this->ap->set_characters($point, $separator); + }*/ public function summarise_response(array $response) { if (isset($response['answer'])) { $resp = $response['answer']; } else { $resp = null; } - if ($this->unitdisplay == qtype_numerical::UNITSELECT && !empty($response['unit'])) { + if ($this->unitdisplay != qtype_numerical::UNITNONE && !empty($response['unit'])) { $resp = $this->ap->add_unit($resp, $response['unit']); } - return $resp; } public function is_gradable_response(array $response) { return array_key_exists('answer', $response) && @@ -93,17 +93,19 @@ class qtype_numerical_question extends question_graded_automatically { public function is_complete_response(array $response) { if (!$this->is_gradable_response($response)) { return false; } - - list($value, $unit) = $this->ap->apply_units($response['answer']); + // test for all valid decsep i.e. 2,0 + // list($value, $unit) = $this->ap->apply_units($response['answer']); + $units= array(); + $value = $this->apply_unit_20($response['answer'],$units); if (is_null($value)) { return false; } - if ($this->unitdisplay != qtype_numerical::UNITINPUT && $unit) { + if ($this->unitdisplay == qtype_numerical::UNITINPUT && is_null($response['unit']) { return false; } if ($this->unitdisplay == qtype_numerical::UNITSELECT && empty($response['unit'])) { return false; @@ -115,16 +117,19 @@ class qtype_numerical_question extends question_graded_automatically { public function get_validation_error(array $response) { if (!$this->is_gradable_response($response)) { return get_string('pleaseenterananswer', 'qtype_numerical'); } - list($value, $unit) = $this->ap->apply_units($response['answer']); + // test for all valid decsep i.e. 2,0 + // list($value, $unit) = $this->ap->apply_units($response['answer']); + $units= array(''); + $value = $this->apply_unit_20($response['answer'],$units); if (is_null($value)) { return get_string('invalidnumber', 'qtype_numerical'); } - if ($this->unitdisplay != qtype_numerical::UNITINPUT && $unit) { + if ($this->unitdisplay == qtype_numerical::UNITINPUT && is_null($response['unit']) { return get_string('invalidnumbernounit', 'qtype_numerical'); } if ($this->unitdisplay == qtype_numerical::UNITSELECT && empty($response['unit'])) { return get_string('unitnotselected', 'qtype_numerical'); @@ -137,11 +142,11 @@ class qtype_numerical_question extends question_graded_automatically { if (!question_utils::arrays_same_at_key_missing_is_blank( $prevresponse, $newresponse, 'answer')) { return false; } - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { + if ($this->unitdisplay != qtype_numerical::UNITNONE) { return question_utils::arrays_same_at_key_missing_is_blank( $prevresponse, $newresponse, 'unit'); } return false; @@ -153,14 +158,12 @@ class qtype_numerical_question extends question_graded_automatically { return array(); } $response = array('answer' => $answer->answer); - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { + if ($this->unitdisplay != qtype_numerical::UNITNONE) { $response['unit'] = $this->ap->get_default_unit(); - } else if ($this->unitdisplay == qtype_numerical::UNITINPUT) { - $response['answer'] = $this->ap->add_unit($answer->answer); } return $response; } @@ -168,14 +171,30 @@ class qtype_numerical_question extends question_graded_automatically { * Get an answer that contains the feedback and fraction that should be * awarded for this resonse. * @param number $value the numerical value of a response. * @return question_answer the matching answer. */ - public function get_matching_answer($value) { + public function get_matching_answer($number,$unit='') { +echo "

response answer ds get_matching_answer number $number unit $unit

";print_r($this->answers);echo  "

"; + foreach ($this->answers as $aid => $answer) { - if ($answer->within_tolerance($value)) { + $unitnull= array(); + if($answer->locale == 'dec_sep_,.'){ + echo "

matching apply_unit_20

"; + $value = $this->ap>apply_unit_20($number,$unitnull); + }else if ($answer->locale == 'dec_sep_.'){ + $this->ap->set_characters('.', ','); + $value = $this->ap->apply_units($number); + }else if ($answer->locale == 'dec_sep_,'){ + $this->ap->set_characters(',', ' ') ; + $value = $this->ap->apply_units($number); + } + echo "

matching answer value

";print_r($value);echo  "

"; + echo "

answer $value

";print_r($answer);echo  "

"; + if ($answer->within_tolerance($value[0])) { $answer->id = $aid; + echo "

answer within_tolerance

";print_r($value);echo  "

"; return $answer; } } return null; } @@ -202,49 +221,51 @@ class qtype_numerical_question extends question_graded_automatically { } return max($fraction, 0); } public function grade_response(array $response) { - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { + if ($this->unitdisplay != qtype_numerical::UNITNONE) { $selectedunit = $response['unit']; } else { $selectedunit = null; } - list($value, $unit) = $this->ap->apply_units($response['answer'], $selectedunit); - $answer = $this->get_matching_answer($value); + echo "

grade

";print_r($response);echo  "

"; + $answer = $this->get_matching_answer($response['answer'],$selectedunit); if (!$answer) { return array(0, question_state::$gradedwrong); } - $fraction = $this->apply_unit_penalty($answer->fraction, $unit); + $fraction = $this->apply_unit_penalty($answer->fraction, $selectedunit); + return array($fraction, question_state::graded_state_for_fraction($fraction)); } public function classify_response(array $response) { if (empty($response['answer'])) { return array($this->id => question_classified_response::no_response()); } - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { + if ($this->unitdisplay != qtype_numerical::UNITNONE) { $selectedunit = $response['unit']; } else { $selectedunit = null; } - list($value, $unit) = $this->ap->apply_units($response['answer'], $selectedunit); - $ans = $this->get_matching_answer($value); + echo "

classify_response

";print_r($response);echo  "

"; + /* list($value, $unit) = $this->ap->apply_units($response['answer'], $selectedunit);*/ + $ans = $this->get_matching_answer($response['answer'],$selectedunit); if (!$ans) { return array($this->id => question_classified_response::no_response()); } $resp = $response['answer']; - if ($this->unitdisplay == qtype_numerical::UNITSELECT) { - $resp = $this->ap->add_unit($resp, $unit); + if ($this->unitdisplay != qtype_numerical::UNITNONE) { + $resp = $this->ap->add_unit($resp, $selectedunit); } return array($this->id => new question_classified_response($ans->id, $resp, - $this->apply_unit_penalty($ans->fraction, $unit))); + $this->apply_unit_penalty($ans->fraction, $selectedunit))); } function check_file_access($question, $state, $options, $contextid, $component, $filearea, $args) { if ($component == 'question' && $filearea == 'answerfeedback') { @@ -258,10 +279,62 @@ class qtype_numerical_question extends question_graded_automatically { } else { return parent::check_file_access($qa, $options, $component, $filearea, $args, $forcedownload); } } + /** + * Checks if the $rawresponse has a unit and applys it if appropriate. + * + * @param string $rawresponse The response string to be converted to a float. + * @param array $units An array with the defined units, where the + * unit is the key and the multiplier the value. + * @return float The rawresponse with the unit taken into + * account as a float. + */ + function apply_unit_20($rawresponse, $units) { + + // Make units more useful + $tmpunits = array(); + foreach ($units as $unit) { + $tmpunits[$unit->unit] = $unit->multiplier; + } + // remove spaces and normalise decimal places. + $rawresponse = trim($rawresponse) ; + $search = array(' ', ','); + // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , + if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { + $replace = array('', ''); + }else { // remove spaces and normalise , to a . . + $replace = array('', '.'); + } + $rawresponse = str_replace($search, $replace, $rawresponse); + + + // Apply any unit that is present. + if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$', + $rawresponse, $responseparts)) { + echo"

responseparts

";print_r($responseparts) ;echo"

"; + + if (!empty($responseparts[5])) { + + if (isset($tmpunits[$responseparts[5]])) { + // Valid number with unit. + return (float)$responseparts[1] / $tmpunits[$responseparts[5]]; + } else { + // Valid number with invalid unit. Must be wrong. + return false; + } + + } else { + // Valid number without unit. + return (float)$responseparts[1]; + } + } + // Invalid number. Must be wrong. + return false; + } + } /** * Subclass of {@link question_answer} with the extra information required by @@ -273,14 +346,17 @@ class qtype_numerical_question extends question_graded_automatically { class qtype_numerical_answer extends question_answer { /** @var float allowable margin of error. */ public $tolerance; /** @var integer|string see {@link get_tolerance_interval()} for the meaning of this value. */ public $tolerancetype = 2; + + public $locale ; - public function __construct($id, $answer, $fraction, $feedback, $feedbackformat, $tolerance) { + public function __construct($id, $answer, $fraction, $feedback, $feedbackformat, $tolerance,$locale) { parent::__construct($id, $answer, $fraction, $feedback, $feedbackformat); $this->tolerance = abs($tolerance); + $this->locale = $locale; } public function get_tolerance_interval() { if ($this->answer === '*') { throw new coding_exception('Cannot work out tolerance interval for answer *.'); @@ -313,8 +389,9 @@ class qtype_numerical_answer extends question_answer { public function within_tolerance($value) { if ($this->answer === '*') { return true; } list($min, $max) = $this->get_tolerance_interval(); + echo "

answer within_tolerance $value , $min , $max

"; return $min <= $value && $value <= $max; } } For numerical/questiontype.php @@ -61,21 +61,20 @@ class qtype_numerical extends question_type { // Get the question answers and their respective tolerances // Note: question_numerical is an extension of the answer table rather than // the question table as is usually the case for qtype // specific tables. if (!$question->options->answers = $DB->get_records_sql( - "SELECT a.*, n.tolerance " . + "SELECT a.*, n.tolerance , n.locale " . "FROM {question_answers} a, " . " {question_numerical} n " . "WHERE a.question = ? " . " AND a.id = n.answer " . "ORDER BY a.id ASC", array($question->id))) { echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!'); return false; } - $question->hints = $DB->get_records('question_hints', array('questionid' => $question->id), 'id ASC'); $this->get_numerical_units($question); // get_numerical_options() need to know if there are units @@ -175,11 +174,11 @@ class qtype_numerical extends question_type { // Check for, and ingore, completely blank answer from the form. if (trim($answerdata) == '' && $question->fraction[$key] == 0 && html_is_blank($question->feedback[$key]['text'])) { continue; } - +echo "

answer question

";print_r($answerdata);echo  "

"; // Update an existing answer if possible. $answer = array_shift($oldanswers); if (!$answer) { $answer = new stdClass(); $answer->question = $question->id; @@ -189,12 +188,11 @@ class qtype_numerical extends question_type { } if (trim($answerdata) === '*') { $answer->answer = '*'; } else { - $answer->answer = $this->apply_unit($answerdata, $units, - !empty($question->unitsleft)); + $answer->answer = $this->apply_unit_20($answerdata, $units); if ($answer->answer === false) { $result->notice = get_string('invalidnumericanswer', 'quiz'); } } $answer->fraction = $question->fraction[$key]; @@ -207,15 +205,19 @@ class qtype_numerical extends question_type { if (!$options = array_shift($oldoptions)) { $options = new stdClass(); } $options->question = $question->id; $options->answer = $answer->id; + if(isset($question->locale[$key]) && $question->locale[$key] != ''){ + $options->locale = $question->locale[$key]; + }else { + $options->locale = 'dec_sep_,.' ; + } if (trim($question->tolerance[$key]) == '') { $options->tolerance = ''; } else { - $options->tolerance = $this->apply_unit($question->tolerance[$key], - $units, !empty($question->unitsleft)); + $options->tolerance = $this->apply_unit_20($question->tolerance[$key],$units); if ($options->tolerance === false) { $result->notice = get_string('invalidnumerictolerance', 'quiz'); } } if (isset($options->id)) { @@ -345,36 +347,38 @@ class qtype_numerical extends question_type { parent::initialise_question_instance($question, $questiondata); $this->initialise_numerical_answers($question, $questiondata); $question->unitdisplay = $questiondata->options->showunits; $question->unitgradingtype = $questiondata->options->unitgradingtype; $question->unitpenalty = $questiondata->options->unitpenalty; + $question->units = $questiondata->options->units ; + // $question->unitinput = $question->ap = $this->make_answer_processor($questiondata->options->units, - $questiondata->options->unitsleft); + $questiondata->options->unitsleft,'.'); } protected function initialise_numerical_answers(question_definition $question, $questiondata) { $question->answers = array(); if (empty($questiondata->options->answers)) { return; } foreach ($questiondata->options->answers as $a) { $question->answers[$a->id] = new qtype_numerical_answer($a->id, $a->answer, - $a->fraction, $a->feedback, $a->feedbackformat, $a->tolerance); + $a->fraction, $a->feedback, $a->feedbackformat, $a->tolerance,$a->locale); } } - protected function make_answer_processor($units, $unitsleft) { + protected function make_answer_processor($units, $unitsleft,$decsep) { if (empty($units)) { return new qtype_numerical_answer_processor(array()); } $cleanedunits = array(); foreach ($units as $unit) { $cleanedunits[$unit->unit] = $unit->multiplier; } - return new qtype_numerical_answer_processor($cleanedunits, $unitsleft); + return new qtype_numerical_answer_processor($cleanedunits, $unitsleft,$decsep); } function delete_question($questionid, $contextid) { global $DB; $DB->delete_records('question_numerical', array('question' => $questionid)); @@ -645,15 +649,20 @@ class qtype_numerical extends question_type { * unit is the key and the multiplier the value. * @return float The rawresponse with the unit taken into * account as a float. */ function apply_unit($rawresponse, $units, $unitsleft) { + $ap = $this->make_answer_processor($units, $unitsleft,$decsep); + list($value, $unit) = $ap->apply_units($rawresponse); + return $value; + } +/* function apply_unit_20($rawresponse, $units, $unitsleft) { $ap = $this->make_answer_processor($units, $unitsleft); list($value, $unit) = $ap->apply_units($rawresponse); return $value; } - +*/ function move_files($questionid, $oldcontextid, $newcontextid) { $fs = get_file_storage(); parent::move_files($questionid, $oldcontextid, $newcontextid); $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid); @@ -760,10 +769,11 @@ class qtype_numerical_answer_processor { return array(null, null, null, null); } $matches += array('', '', '', ''); // Fill in any missing matches. list($matchedpart, $beforepoint, $decimals, $exponent) = $matches; + echo"

matches $response ".$this->decsep."

";print_r($matches) ;echo"

"; // Strip out thousands separators. $beforepoint = str_replace($this->thousandssep, '', $beforepoint); // Must be either something before, or something after the decimal point. @@ -816,10 +826,61 @@ class qtype_numerical_answer_processor { return array($value, $unit); } /** + * Checks if the $rawresponse has a unit and applys it if appropriate. + * + * @param string $rawresponse The response string to be converted to a float. + * @param array $units An array with the defined units, where the + * unit is the key and the multiplier the value. + * @return float The rawresponse with the unit taken into + * account as a float. + */ + function apply_unit_20($rawresponse, $units) { + echo "

INSIDE 20

"; + // Make units more useful + $tmpunits = array(); + foreach ($units as $unit) { + $tmpunits[$unit->unit] = $unit->multiplier; + } + // remove spaces and normalise decimal places. + $rawresponse = trim($rawresponse) ; + $search = array(' ', ','); + // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , + if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { + $replace = array('', ''); + }else { // remove spaces and normalise , to a . . + $replace = array('', '.'); + } + $rawresponse = str_replace($search, $replace, $rawresponse); + + + // Apply any unit that is present. + if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$', + $rawresponse, $responseparts)) { + echo"

responseparts

";print_r($responseparts) ;echo"

"; + + if (!empty($responseparts[5])) { + + if (isset($tmpunits[$responseparts[5]])) { + // Valid number with unit. + return (float)$responseparts[1] / $tmpunits[$responseparts[5]]; + } else { + // Valid number with invalid unit. Must be wrong. + return false; + } + + } else { + // Valid number without unit. + return (float)$responseparts[1]; + } + } + // Invalid number. Must be wrong. + return false; + } + /** * @return string the default unit. */ public function get_default_unit() { reset($this->units); return key($this->units); For numerical renderer @@ -35,11 +35,11 @@ class qtype_numerical_renderer extends qtype_renderer { public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $currentanswer = $qa->get_last_qt_var('answer'); - if ($question->unitdisplay == qtype_numerical::UNITSELECT) { + if ($question->unitdisplay != qtype_numerical::UNITNONE) { $selectedunit = $qa->get_last_qt_var('unit'); } else { $selectedunit = null; } @@ -56,14 +56,15 @@ class qtype_numerical_renderer extends qtype_renderer { $inputattributes['readonly'] = 'readonly'; } $feedbackimg = ''; if ($options->correctness) { - list($value, $unit) = $question->ap->apply_units($currentanswer, $selectedunit); - $answer = $question->get_matching_answer($value); + echo "

renderer

";print_r($currentanswer);echo  "

"; + $answer = $question->get_matching_answer($currentanswer,$selectedunit); if ($answer) { - $fraction = $question->apply_unit_penalty($answer->fraction, $unit); + $fraction = $question->apply_unit_penalty($answer->fraction, $selectedunit ); + $fraction = $answer->fraction ; } else { $fraction = 0; } $inputattributes['class'] = $this->feedback_class($fraction); $feedbackimg = $this->feedback_image($fraction); @@ -75,27 +76,43 @@ class qtype_numerical_renderer extends qtype_renderer { $placeholder = $matches[0]; $inputattributes['size'] = round(strlen($placeholder) * 1.1); } $input = html_writer::empty_tag('input', $inputattributes) . $feedbackimg; + $unitselect = '' ; - if ($question->unitdisplay == qtype_numerical::UNITSELECT) { + if ($question->unitdisplay == qtype_numerical::UNITSELECT) { $unitselect = html_writer::select($question->ap->get_unit_options(), $qa->get_qt_field_name('unit'), $selectedunit, array(''=>'choosedots'), array('disabled' => $options->readonly)); - if ($question->ap->are_units_before()) { + } else if ($question->unitdisplay == qtype_numerical::UNITINPUT){ + $questiontext .= "ADD INPUT TEXT"; + $unitname = $qa->get_qt_field_name('unit'); + $unitattributes = array( + 'type' => 'text', + 'name' => $unitname, + 'value' => $selectedunit, + 'id' => $unitname, + 'size' => 10, + + ); + + $unitselect = html_writer::empty_tag('input', $unitattributes) . $feedbackimg; + } + if ($unitselect != '' ){ + if ( $question->ap->are_units_before()) { $input = $unitselect . ' ' . $input; } else { $input = $input . ' ' . $unitselect; } } if ($placeholder) { $questiontext = substr_replace($questiontext, $input, strpos($questiontext, $placeholder), strlen($placeholder)); } - + $questiontext .= "

renderer answer $currentanswer unitdisplay ".$question->unitdisplay."unitgradingtype ".$question->unitgradingtype."

"; $result = html_writer::tag('div', $questiontext, array('class' => 'qtext')); if (!$placeholder) { $result .= html_writer::start_tag('div', array('class' => 'ablock')); $result .= get_string('answer', 'qtype_shortanswer', @@ -113,11 +130,11 @@ class qtype_numerical_renderer extends qtype_renderer { } public function specific_feedback(question_attempt $qa) { $question = $qa->get_question(); - if ($question->unitdisplay == qtype_numerical::UNITSELECT) { + if ($question->unitdisplay != qtype_numerical::UNITNONE) { $selectedunit = $qa->get_last_qt_var('unit'); } else { $selectedunit = null; } list($value, $unit) = $question->ap->apply_units(