diff --git a/question/type/calculated/classes/qtype_calculated_answer.php b/question/type/calculated/classes/qtype_calculated_answer.php new file mode 100644 index 00000000000..fc42766ab56 --- /dev/null +++ b/question/type/calculated/classes/qtype_calculated_answer.php @@ -0,0 +1,38 @@ +. + +declare(strict_types=1); + +namespace qtype_calculated; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/question/type/numerical/question.php'); + +/** + * Class to represent a calculated question answer. + * + * @package qtype_calculated + * @copyright 2023 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qtype_calculated_answer extends \qtype_numerical_answer { + /** @var int The length of the correct answer. */ + public $correctanswerlength; + + /** @var int The format of the correct answer. */ + public $correctanswerformat; +} diff --git a/question/type/calculated/questiontype.php b/question/type/calculated/questiontype.php index 76d2fc1127b..9708f9d44dd 100644 --- a/question/type/calculated/questiontype.php +++ b/question/type/calculated/questiontype.php @@ -332,11 +332,27 @@ class qtype_calculated extends question_type { } } + /** + * Initializes calculated answers for a given question. + * + * @param question_definition $question The question definition object. + * @param stdClass $questiondata The question data object. + */ + protected function initialise_calculated_answers(question_definition $question, stdClass $questiondata) { + $question->answers = array(); + if (empty($questiondata->options->answers)) { + return; + } + foreach ($questiondata->options->answers as $a) { + $question->answers[$a->id] = new \qtype_calculated\qtype_calculated_answer($a->id, $a->answer, + $a->fraction, $a->feedback, $a->feedbackformat, $a->tolerance); + } + } + protected function initialise_question_instance(question_definition $question, $questiondata) { parent::initialise_question_instance($question, $questiondata); + $this->initialise_calculated_answers($question, $questiondata); - question_bank::get_qtype('numerical')->initialise_numerical_answers( - $question, $questiondata); foreach ($questiondata->options->answers as $a) { $question->answers[$a->id]->tolerancetype = $a->tolerancetype; $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength; diff --git a/question/type/calculated/tests/helper.php b/question/type/calculated/tests/helper.php index d8dbe725690..623ed446fd0 100644 --- a/question/type/calculated/tests/helper.php +++ b/question/type/calculated/tests/helper.php @@ -56,12 +56,11 @@ class qtype_calculated_test_helper extends question_test_helper { $q->questiontext = 'What is {a} + {b}?'; $q->generalfeedback = 'Generalfeedback: {={a} + {b}} is the right answer.'; - $q->answers = array( - 13 => new qtype_numerical_answer(13, '{a} + {b}', 1.0, 'Very good.', FORMAT_HTML, 0), - 14 => new qtype_numerical_answer(14, '{a} - {b}', 0.0, 'Add. not subtract!.', - FORMAT_HTML, 0), - 17 => new qtype_numerical_answer(17, '*', 0.0, 'Completely wrong.', FORMAT_HTML, 0), - ); + $q->answers = [ + 13 => new \qtype_calculated\qtype_calculated_answer(13, '{a} + {b}', 1.0, 'Very good.', FORMAT_HTML, 0), + 14 => new \qtype_calculated\qtype_calculated_answer(14, '{a} - {b}', 0.0, 'Add. not subtract!.', FORMAT_HTML, 0), + 17 => new \qtype_calculated\qtype_calculated_answer(17, '*', 0.0, 'Completely wrong.', FORMAT_HTML, 0), + ]; foreach ($q->answers as $answer) { $answer->correctanswerlength = 2; $answer->correctanswerformat = 1; @@ -106,12 +105,11 @@ class qtype_calculated_test_helper extends question_test_helper { $qdata->options->unitsleft = 0; $qdata->options->synchronize = 0; - $qdata->options->answers = array( - 13 => new qtype_numerical_answer(13, '{a} + {b}', 1.0, 'Very good.', FORMAT_HTML, 0.001), - 14 => new qtype_numerical_answer(14, '{a} - {b}', 0.0, 'Add. not subtract!.', - FORMAT_HTML, 0.001), - 17 => new qtype_numerical_answer(17, '*', 0.0, 'Completely wrong.', FORMAT_HTML, 0), - ); + $qdata->options->answers = [ + 13 => new \qtype_calculated\qtype_calculated_answer(13, '{a} + {b}', 1.0, 'Very good.', FORMAT_HTML, 0.001), + 14 => new \qtype_calculated\qtype_calculated_answer(14, '{a} - {b}', 0.0, 'Add. not subtract!.', FORMAT_HTML, 0.001), + 17 => new \qtype_calculated\qtype_calculated_answer(17, '*', 0.0, 'Completely wrong.', FORMAT_HTML, 0), + ]; foreach ($qdata->options->answers as $answer) { $answer->correctanswerlength = 2; $answer->correctanswerformat = 1; diff --git a/question/type/numerical/question.php b/question/type/numerical/question.php index 3acdfe44a31..16125facca0 100644 --- a/question/type/numerical/question.php +++ b/question/type/numerical/question.php @@ -207,10 +207,8 @@ class qtype_numerical_question extends question_graded_automatically { } foreach ($this->answers as $answer) { if ($answer->within_tolerance($scaledvalue)) { - $answer->unitisright = !is_null($multiplier); return $answer; } else if ($answer->within_tolerance($value)) { - $answer->unitisright = false; return $answer; } } @@ -218,6 +216,23 @@ class qtype_numerical_question extends question_graded_automatically { return null; } + /** + * Checks if the provided $multiplier is appropriate for the unit of the given $value, + * ensuring that multiplying $value by the $multiplier yields the expected $answer. + * + * @param qtype_numerical_answer $answer The expected result when multiplying $value by the appropriate $multiplier. + * @param float $value The provided value + * @param float|null $multiplier The multiplier value for the unit of $value. + * @return bool Returns true if the $multiplier is correct for the unit of $value, false otherwise. + */ + public function is_unit_right(qtype_numerical_answer $answer, float $value, ?float $multiplier): bool { + if (is_null($multiplier)) { + return false; + } + + return $answer->within_tolerance($multiplier * $value); + } + public function get_correct_answer() { foreach ($this->answers as $answer) { $state = question_state::graded_state_for_fraction($answer->fraction); @@ -256,12 +271,14 @@ class qtype_numerical_question extends question_graded_automatically { list($value, $unit, $multiplier) = $this->ap->apply_units( $response['answer'], $selectedunit); + /** @var qtype_numerical_answer $answer */ $answer = $this->get_matching_answer($value, $multiplier); if (!$answer) { return array(0, question_state::$gradedwrong); } - $fraction = $this->apply_unit_penalty($answer->fraction, $answer->unitisright); + $unitisright = $this->is_unit_right($answer, $value, $multiplier); + $fraction = $this->apply_unit_penalty($answer->fraction, $unitisright); return array($fraction, question_state::graded_state_for_fraction($fraction)); } @@ -276,6 +293,7 @@ class qtype_numerical_question extends question_graded_automatically { $selectedunit = null; } list($value, $unit, $multiplier) = $this->ap->apply_units($response['answer'], $selectedunit); + /** @var qtype_numerical_answer $ans */ $ans = $this->get_matching_answer($value, $multiplier); $resp = $response['answer']; @@ -291,9 +309,10 @@ class qtype_numerical_question extends question_graded_automatically { return array($this->id => new question_classified_response(0, $resp, 0)); } - return array($this->id => new question_classified_response($ans->id, - $resp, - $this->apply_unit_penalty($ans->fraction, $ans->unitisright))); + $unitisright = $this->is_unit_right($ans, $value, $multiplier); + return [ + $this->id => new question_classified_response($ans->id, $resp, $this->apply_unit_penalty($ans->fraction, $unitisright)) + ]; } public function check_file_access($qa, $options, $component, $filearea, $args, diff --git a/question/type/numerical/renderer.php b/question/type/numerical/renderer.php index 98f48921c00..cedf1bf0a69 100644 --- a/question/type/numerical/renderer.php +++ b/question/type/numerical/renderer.php @@ -62,7 +62,8 @@ class qtype_numerical_renderer extends qtype_renderer { $currentanswer, $selectedunit); $answer = $question->get_matching_answer($value, $multiplier); if ($answer) { - $fraction = $question->apply_unit_penalty($answer->fraction, $answer->unitisright); + $unitisright = $question->is_unit_right($answer, $value, $multiplier); + $fraction = $question->apply_unit_penalty($answer->fraction, $unitisright); } else { $fraction = 0; }