From 82129f93e03e707875e52cd02a47d0edd1317386 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Wed, 5 Feb 2025 15:13:40 +0000 Subject: [PATCH] MDL-83541 qtypes: Define excluded hash fields and test hints --- .../restore_qtype_calculated_plugin.class.php | 42 +++++++++ question/type/calculated/tests/helper.php | 16 +++- .../calculated/tests/question_type_test.php | 12 ++- ...ore_qtype_calculatedmulti_plugin.class.php | 5 + .../type/calculatedmulti/tests/helper.php | 91 +++++++++++++++++++ .../type/calculatedsimple/tests/helper.php | 7 ++ .../tests/question_type_test.php | 7 +- ...store_qtype_ddimageortext_plugin.class.php | 25 +++++ .../restore_qtype_ddmarker_plugin.class.php | 24 +++++ question/type/ddwtos/tests/helper.php | 7 ++ question/type/gapselect/tests/helper.php | 7 ++ .../restore_qtype_match_plugin.class.php | 19 ++++ question/type/match/tests/helper.php | 7 ++ .../type/match/tests/question_type_test.php | 7 +- ...restore_qtype_multianswer_plugin.class.php | 15 +++ .../restore_qtype_numerical_plugin.class.php | 14 +++ question/type/numerical/tests/helper.php | 7 ++ question/type/shortanswer/tests/helper.php | 7 +- .../shortanswer/tests/question_type_test.php | 7 +- .../restore_qtype_truefalse_plugin.class.php | 9 ++ 20 files changed, 327 insertions(+), 8 deletions(-) diff --git a/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php b/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php index f9afd70ac6828..5092dacc1a964 100644 --- a/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php +++ b/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php @@ -113,4 +113,46 @@ public function process_calculated_option($data) { $newitemid = $DB->insert_record('question_calculated_options', $data); } } + + #[\Override] + public static function convert_backup_to_questiondata(array $backupdata): \stdClass { + $questiondata = parent::convert_backup_to_questiondata($backupdata); + $qtype = $questiondata->qtype; + foreach ($backupdata["plugin_qtype_{$qtype}_question"]['calculated_records']['calculated_record'] as $record) { + foreach ($questiondata->options->answers as &$answer) { + if ($answer->id == $record['answer']) { + $answer->tolerance = $record['tolerance']; + $answer->tolerancetype = $record['tolerancetype']; + $answer->correctanswerlength = $record['correctanswerlength']; + $answer->correctanswerformat = $record['correctanswerformat']; + continue 2; + } + } + } + if (isset($backupdata["plugin_qtype_{$qtype}_question"]['calculated_options'])) { + $questiondata->options = (object) array_merge( + (array) $questiondata->options, + $backupdata["plugin_qtype_{$qtype}_question"]['calculated_options']['calculated_option'][0], + ); + } + return $questiondata; + } + + #[\Override] + protected function define_excluded_identity_hash_fields(): array { + return [ + // These option fields are present in the database, but are only used by calculatedmulti. + '/options/synchronize', + '/options/single', + '/options/shuffleanswers', + '/options/correctfeedback', + '/options/correctfeedbackformat', + '/options/partiallycorrectfeedback', + '/options/partiallycorrectfeedbackformat', + '/options/incorrectfeedback', + '/options/incorrectfeedbackformat', + '/options/answernumbering', + '/options/shownumcorrect', + ]; + } } diff --git a/question/type/calculated/tests/helper.php b/question/type/calculated/tests/helper.php index cbbe06180217b..8b93bfbf3cbfc 100644 --- a/question/type/calculated/tests/helper.php +++ b/question/type/calculated/tests/helper.php @@ -133,7 +133,7 @@ public function get_calculated_question_form_data_sum() { $fromform->defaultmark = 1.0; $fromform->generalfeedback = 'Generalfeedback: {={a} + {b}} is the right answer.'; - $fromform->unitrole = '3'; + $fromform->unitrole = '0'; $fromform->unitpenalty = 0.1; $fromform->unitgradingtypes = '1'; $fromform->unitsleft = '0'; @@ -187,6 +187,20 @@ public function get_calculated_question_form_data_sum() { $fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + $fromform->hint = [ + [ + 'text' => 'Add', + 'format' => FORMAT_HTML, + ], + ]; + + $fromform->unit = [ + 'x', + ]; + $fromform->multiplier = [ + '1.0', + ]; + return $fromform; } diff --git a/question/type/calculated/tests/question_type_test.php b/question/type/calculated/tests/question_type_test.php index b3cf88561c310..6f74e5686fde8 100644 --- a/question/type/calculated/tests/question_type_test.php +++ b/question/type/calculated/tests/question_type_test.php @@ -106,12 +106,18 @@ public function test_load_question(): void { $this->assertEquals($question->createdby, $questiondata->modifiedby); $this->assertEquals('', $questiondata->idnumber); $this->assertEquals($syscontext->id, $questiondata->contextid); - $this->assertEquals([], $questiondata->hints); + $this->assertCount(1, $questiondata->hints); + $hint = array_pop($questiondata->hints); + $this->assertEquals('Add', $hint->hint); + $this->assertEquals(FORMAT_HTML, $hint->hintformat); // Options. $this->assertEquals($questiondata->id, $questiondata->options->question); - $this->assertEquals([], $questiondata->options->units); - $this->assertEquals(qtype_numerical::UNITNONE, $questiondata->options->showunits); + $this->assertCount(1, $questiondata->options->units); + $unit = array_pop($questiondata->options->units); + $this->assertEquals($unit->unit, 'x'); + $this->assertEquals($unit->multiplier, '1.0'); + $this->assertEquals(qtype_numerical::UNITOPTIONAL, $questiondata->options->showunits); $this->assertEquals(0, $questiondata->options->unitgradingtype); // Unit role is none, so this is 0. $this->assertEquals($fromform->unitpenalty, $questiondata->options->unitpenalty); $this->assertEquals($fromform->unitsleft, $questiondata->options->unitsleft); diff --git a/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php b/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php index cc5b9ba1a706b..a2b04cb9fa647 100644 --- a/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php +++ b/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php @@ -69,4 +69,9 @@ public function recode_legacy_state_answer($state) { } return $result ? $result : $answer; } + + #[\Override] + protected function define_excluded_identity_hash_fields(): array { + return []; + } } diff --git a/question/type/calculatedmulti/tests/helper.php b/question/type/calculatedmulti/tests/helper.php index 92330cf2a92b4..6eafbd4c28e2c 100644 --- a/question/type/calculatedmulti/tests/helper.php +++ b/question/type/calculatedmulti/tests/helper.php @@ -127,4 +127,95 @@ public function make_calculatedmulti_question_multiresponse() { return $q; } + + /** + * Return the form data for a question with a single response. + * + * @return stdClass + */ + public function get_calculatedmulti_question_form_data_singleresponse(): stdClass { + question_bank::load_question_definition_classes('calculated'); + $fromform = new stdClass(); + + $fromform->name = 'Simple sum'; + $fromform->questiontext['text'] = 'What is {a} + {b}?'; + $fromform->questiontext['format'] = FORMAT_HTML; + $fromform->defaultmark = 1.0; + $fromform->generalfeedback['text'] = 'Generalfeedback: {={a} + {b}} is the right answer.'; + $fromform->generalfeedback['format'] = FORMAT_HTML; + + $fromform->unitrole = '3'; + $fromform->unitpenalty = 0.1; + $fromform->unitgradingtypes = '1'; + $fromform->unitsleft = '0'; + $fromform->nounits = 1; + $fromform->multiplier = []; + $fromform->multiplier[0] = '1.0'; + $fromform->synchronize = 0; + $fromform->answernumbering = 0; + $fromform->shuffleanswers = 0; + $fromform->single = 1; + $fromform->correctfeedback['text'] = 'Very good'; + $fromform->correctfeedback['format'] = FORMAT_HTML; + $fromform->partiallycorrectfeedback['text'] = 'Mostly good'; + $fromform->partiallycorrectfeedback['format'] = FORMAT_HTML; + $fromform->incorrectfeedback['text'] = 'Completely Wrong'; + $fromform->incorrectfeedback['format'] = FORMAT_HTML; + $fromform->shownumcorrect = 1; + + $fromform->noanswers = 6; + $fromform->answer = []; + $fromform->answer[0]['text'] = '{a} + {b}'; + $fromform->answer[0]['format'] = FORMAT_HTML; + $fromform->answer[1]['text'] = '{a} - {b}'; + $fromform->answer[1]['format'] = FORMAT_HTML; + $fromform->answer[2]['text'] = '*'; + $fromform->answer[2]['format'] = FORMAT_HTML; + + $fromform->fraction = []; + $fromform->fraction[0] = '1.0'; + $fromform->fraction[1] = '0.0'; + $fromform->fraction[2] = '0.0'; + + $fromform->tolerance = []; + $fromform->tolerance[0] = 0.001; + $fromform->tolerance[1] = 0.001; + $fromform->tolerance[2] = 0; + + $fromform->tolerancetype[0] = 1; + $fromform->tolerancetype[1] = 1; + $fromform->tolerancetype[2] = 1; + + $fromform->correctanswerlength[0] = 2; + $fromform->correctanswerlength[1] = 2; + $fromform->correctanswerlength[2] = 2; + + $fromform->correctanswerformat[0] = 1; + $fromform->correctanswerformat[1] = 1; + $fromform->correctanswerformat[2] = 1; + + $fromform->feedback = []; + $fromform->feedback[0] = []; + $fromform->feedback[0]['format'] = FORMAT_HTML; + $fromform->feedback[0]['text'] = 'Very good.'; + + $fromform->feedback[1] = []; + $fromform->feedback[1]['format'] = FORMAT_HTML; + $fromform->feedback[1]['text'] = 'Add. not subtract!'; + + $fromform->feedback[2] = []; + $fromform->feedback[2]['format'] = FORMAT_HTML; + $fromform->feedback[2]['text'] = 'Completely wrong.'; + + $fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + + $fromform->hint = [ + [ + 'text' => 'Add', + 'format' => FORMAT_HTML, + ], + ]; + + return $fromform; + } } diff --git a/question/type/calculatedsimple/tests/helper.php b/question/type/calculatedsimple/tests/helper.php index eaa3d6af1fec1..0cb15e3a1a3eb 100644 --- a/question/type/calculatedsimple/tests/helper.php +++ b/question/type/calculatedsimple/tests/helper.php @@ -205,6 +205,13 @@ public function get_calculatedsimple_question_form_data_sumwithvariants() { $form->definition[19] = '1-0-b'; $form->definition[20] = '1-0-a'; + $form->hint = [ + [ + 'text' => 'Add', + 'format' => FORMAT_HTML, + ], + ]; + $form->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; return $form; diff --git a/question/type/calculatedsimple/tests/question_type_test.php b/question/type/calculatedsimple/tests/question_type_test.php index b1ebb1c73007c..391a6d980980c 100644 --- a/question/type/calculatedsimple/tests/question_type_test.php +++ b/question/type/calculatedsimple/tests/question_type_test.php @@ -90,7 +90,7 @@ public function test_question_saving_sumwithvariants(): void { $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { - if (!in_array($property, array('id', 'timemodified', 'timecreated', 'options', 'idnumber'))) { + if (!in_array($property, ['id', 'timemodified', 'timecreated', 'options', 'idnumber', 'hints'])) { $this->assertEquals($value, $actualquestiondata->$property); } } @@ -110,6 +110,11 @@ public function test_question_saving_sumwithvariants(): void { } } + $this->assertCount(1, $actualquestiondata->hints); + $hint = array_pop($actualquestiondata->hints); + $this->assertEquals($formdata->hint[0]['text'], $hint->hint); + $this->assertEquals($formdata->hint[0]['format'], $hint->hintformat); + $datasetloader = new qtype_calculated_dataset_loader($actualquestiondata->id); $this->assertEquals(10, $datasetloader->get_number_of_items()); diff --git a/question/type/ddimageortext/backup/moodle2/restore_qtype_ddimageortext_plugin.class.php b/question/type/ddimageortext/backup/moodle2/restore_qtype_ddimageortext_plugin.class.php index 4be59e8e83457..cbb1395cc21ae 100644 --- a/question/type/ddimageortext/backup/moodle2/restore_qtype_ddimageortext_plugin.class.php +++ b/question/type/ddimageortext/backup/moodle2/restore_qtype_ddimageortext_plugin.class.php @@ -166,4 +166,29 @@ public static function define_decode_contents() { return $contents; } + + #[\Override] + public static function convert_backup_to_questiondata(array $backupdata): \stdClass { + $questiondata = parent::convert_backup_to_questiondata($backupdata); + $questiondata->options->drags = array_map( + fn($drag) => (object) $drag, + $backupdata['plugin_qtype_ddimageortext_question']['drags']['drag'], + ); + $questiondata->options->drops = array_map( + fn($drop) => (object) $drop, + $backupdata['plugin_qtype_ddimageortext_question']['drops']['drop'], + ); + return $questiondata; + } + + #[\Override] + protected function define_excluded_identity_hash_fields(): array { + return [ + '/options/drags/id', + '/options/drags/questionid', + '/options/drops/id', + '/options/drops/questionid', + + ]; + } } diff --git a/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php b/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php index 0b25db86147ad..4a9b2c592438a 100644 --- a/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php +++ b/question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php @@ -168,4 +168,28 @@ public static function define_decode_contents() { return $contents; } + + #[\Override] + public static function convert_backup_to_questiondata(array $backupdata): \stdClass { + $questiondata = parent::convert_backup_to_questiondata($backupdata); + $questiondata->options->drags = array_map( + fn($drag) => (object) $drag, + $backupdata['plugin_qtype_ddmarker_question']['drags']['drag'], + ); + $questiondata->options->drops = array_map( + fn($drop) => (object) $drop, + $backupdata['plugin_qtype_ddmarker_question']['drops']['drop'], + ); + return $questiondata; + } + + #[\Override] + protected function define_excluded_identity_hash_fields(): array { + return [ + '/options/drags/id', + '/options/drags/questionid', + '/options/drops/id', + '/options/drops/questionid', + ]; + } } diff --git a/question/type/ddwtos/tests/helper.php b/question/type/ddwtos/tests/helper.php index 9517a9720c735..8db840b31d0e3 100644 --- a/question/type/ddwtos/tests/helper.php +++ b/question/type/ddwtos/tests/helper.php @@ -99,6 +99,13 @@ public function get_ddwtos_question_form_data_fox() { $fromform->penalty = 0.3333333; $fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + $fromform->hint = [ + [ + 'text' => 'Fast', + 'format' => FORMAT_HTML, + ], + ]; + return $fromform; } diff --git a/question/type/gapselect/tests/helper.php b/question/type/gapselect/tests/helper.php index 90de2c2332cff..5034840c35371 100644 --- a/question/type/gapselect/tests/helper.php +++ b/question/type/gapselect/tests/helper.php @@ -112,6 +112,13 @@ public function get_gapselect_question_form_data_missingchoiceno() { $fromform->penalty = 0.3333333; $fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + $fromform->hint = [ + [ + 'text' => 'Cat', + 'format' => FORMAT_HTML, + ], + ]; + return $fromform; } diff --git a/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php index f9dc84bf59a95..d5fb9e56be820 100644 --- a/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php +++ b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php @@ -246,4 +246,23 @@ public static function define_decode_contents() { return $contents; } + + #[\Override] + public static function convert_backup_to_questiondata(array $backupdata): \stdClass { + $questiondata = parent::convert_backup_to_questiondata($backupdata); + $questiondata->options = (object) $backupdata["plugin_qtype_match_question"]['matchoptions'][0]; + $questiondata->options->subquestions = array_map( + fn($match) => (object) $match, + $backupdata["plugin_qtype_match_question"]['matches']['match'], + ); + return $questiondata; + } + + #[\Override] + protected function define_excluded_identity_hash_fields(): array { + return [ + '/options/subquestions/id', + '/options/subquestions/questionid', + ]; + } } diff --git a/question/type/match/tests/helper.php b/question/type/match/tests/helper.php index 883a535911e36..eb31c11949815 100644 --- a/question/type/match/tests/helper.php +++ b/question/type/match/tests/helper.php @@ -131,6 +131,13 @@ public function get_match_question_form_data_foursubq() { $q->noanswers = 4; + $q->hint = [ + [ + 'text' => 'Frog and newt are the same', + 'format' => FORMAT_HTML, + ], + ]; + return $q; } diff --git a/question/type/match/tests/question_type_test.php b/question/type/match/tests/question_type_test.php index 49ac7daca328b..f67f98242cf3d 100644 --- a/question/type/match/tests/question_type_test.php +++ b/question/type/match/tests/question_type_test.php @@ -190,7 +190,7 @@ public function test_question_saving_foursubq(): void { foreach ($questiondata as $property => $value) { if (!in_array($property, ['id', 'timemodified', 'timecreated', 'options', 'stamp', - 'versionid', 'questionbankentryid'])) { + 'versionid', 'questionbankentryid', 'hints'])) { if (!empty($actualquestiondata)) { $this->assertEquals($value, $actualquestiondata->$property); } @@ -203,6 +203,11 @@ public function test_question_saving_foursubq(): void { } } + $this->assertCount(1, $actualquestiondata->hints); + $hint = array_pop($actualquestiondata->hints); + $this->assertEquals($formdata->hint[0]['text'], $hint->hint); + $this->assertEquals($formdata->hint[0]['format'], $hint->hintformat); + $this->assertObjectHasProperty('subquestions', $actualquestiondata->options); $subqpropstoignore = array('id'); diff --git a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php index f51ec0272f8ca..a3bf9adb63651 100644 --- a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php +++ b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php @@ -199,4 +199,19 @@ public function recode_legacy_state_answer($state) { return implode(',', $resultarr); } + #[\Override] + public function define_excluded_identity_hash_fields(): array { + return [ + '/options/sequence', + '/options/question', + ]; + } + + #[\Override] + public static function remove_excluded_question_data(stdClass $questiondata, array $excludefields = []): stdClass { + if (isset($questiondata->options->questions)) { + unset($questiondata->options->questions); + } + return parent::remove_excluded_question_data($questiondata, $excludefields); + } } diff --git a/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php b/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php index 34c6acf4a9fc8..42cdcfeb4ca2f 100644 --- a/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php +++ b/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php @@ -80,4 +80,18 @@ public function process_numerical($data) { $newitemid = $DB->insert_record('question_numerical', $data); } } + + #[\Override] + public static function convert_backup_to_questiondata(array $backupdata): \stdClass { + $questiondata = parent::convert_backup_to_questiondata($backupdata); + foreach ($backupdata['plugin_qtype_numerical_question']['numerical_records']['numerical_record'] as $record) { + foreach ($questiondata->options->answers as &$answer) { + if ($answer->id == $record['answer']) { + $answer->tolerance = $record['tolerance']; + continue 2; + } + } + } + return $questiondata; + } } diff --git a/question/type/numerical/tests/helper.php b/question/type/numerical/tests/helper.php index 4e26e8c7cb84a..fc22c672351f8 100644 --- a/question/type/numerical/tests/helper.php +++ b/question/type/numerical/tests/helper.php @@ -164,6 +164,13 @@ public function get_numerical_question_form_data_pi() { $form->qtype = 'numerical'; $form->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + $form->hint = [ + [ + 'text' => 'Just over 3', + 'format' => FORMAT_HTML, + ], + ]; + return $form; } diff --git a/question/type/shortanswer/tests/helper.php b/question/type/shortanswer/tests/helper.php index 666d6d85f65c8..203a59f1217d9 100644 --- a/question/type/shortanswer/tests/helper.php +++ b/question/type/shortanswer/tests/helper.php @@ -110,7 +110,12 @@ public function get_shortanswer_question_form_data_frogtoad() { array('text' => 'That is a bad answer.', 'format' => FORMAT_HTML), ); $form->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; - + $form->hint = [ + [ + 'text' => 'Rhymes with dog', + 'format' => FORMAT_HTML, + ], + ]; return $form; } diff --git a/question/type/shortanswer/tests/question_type_test.php b/question/type/shortanswer/tests/question_type_test.php index 6a135c4a90bc9..1b4488b70133a 100644 --- a/question/type/shortanswer/tests/question_type_test.php +++ b/question/type/shortanswer/tests/question_type_test.php @@ -120,7 +120,7 @@ public function test_question_saving_frogtoad(): void { $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { - if (!in_array($property, array('id', 'timemodified', 'timecreated', 'options'))) { + if (!in_array($property, ['id', 'timemodified', 'timecreated', 'options', 'hints'])) { $this->assertEquals($value, $actualquestiondata->$property); } } @@ -140,6 +140,11 @@ public function test_question_saving_frogtoad(): void { } } } + + $this->assertCount(1, $actualquestiondata->hints); + $hint = array_pop($actualquestiondata->hints); + $this->assertEquals($formdata->hint[0]['text'], $hint->hint); + $this->assertEquals($formdata->hint[0]['format'], $hint->hintformat); } public function test_question_saving_trims_answers(): void { diff --git a/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php b/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php index 39e5352d3cf04..f2cd7f2bb34f5 100644 --- a/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php +++ b/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php @@ -93,4 +93,13 @@ public function recode_legacy_state_answer($state) { } return $result; } + + #[\Override] + public function define_excluded_identity_hash_fields(): array { + return [ + '/options/trueanswer', + '/options/falseanswer', + '/options/question', + ]; + } }