From 40d6ba95051acdc734564932dc72ff54fddba095 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 20 Feb 2023 17:32:35 +0000 Subject: [PATCH] MDL-76298 drag-drop questions: validate the questions are complete Previously, it was possible to create drag-drop markers and onto image questions without any drag items or drop zones. This was non-sensical, and broke statistics calculations. So, missing validation added, and random guess score calculation made robust. --- .../ddimageortext/edit_ddimageortext_form.php | 22 +++++++++++++++++-- .../lang/en/qtype_ddimageortext.php | 2 ++ question/type/ddimageortext/questionbase.php | 7 +++++- .../ddimageortext/tests/behat/add.feature | 13 +++++++++++ .../tests/form/edit_form_test.php | 17 +++++++++----- .../ddimageortext/tests/question_test.php | 12 ++++++++++ question/type/ddmarker/edit_ddmarker_form.php | 16 +++++++++++++- .../type/ddmarker/lang/en/qtype_ddmarker.php | 2 ++ .../type/ddmarker/tests/behat/add.feature | 13 +++++++++++ 9 files changed, 95 insertions(+), 9 deletions(-) diff --git a/question/type/ddimageortext/edit_ddimageortext_form.php b/question/type/ddimageortext/edit_ddimageortext_form.php index 1c364f2305f..21a2f46cb17 100644 --- a/question/type/ddimageortext/edit_ddimageortext_form.php +++ b/question/type/ddimageortext/edit_ddimageortext_form.php @@ -205,9 +205,10 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { public function validation($data, $files) { $errors = parent::validation($data, $files); if (!self::file_uploaded($data['bgimage'])) { - $errors["bgimage"] = get_string('formerror_nobgimage', 'qtype_'.$this->qtype()); + $errors['bgimage'] = get_string('formerror_nobgimage', 'qtype_'.$this->qtype()); } + $dropfound = false; $allchoices = array(); for ($i = 0; $i < $data['nodropzone']; $i++) { $ytoppresent = (trim($data['drops'][$i]['ytop']) !== ''); @@ -219,6 +220,7 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { $imagechoicepresent = ($choice !== '0'); if ($imagechoicepresent) { + $dropfound = true; if (!$ytoppresent) { $errors["drops[$i]"] = get_string('formerror_noytop', 'qtype_ddimageortext'); } else if (!$ytopisint) { @@ -247,25 +249,41 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { $allchoices[$choice] = $i; } else { if ($ytoppresent || $xleftpresent || $labelpresent) { + $dropfound = true; $errors["drops[$i]"] = get_string('formerror_noimageselected', 'qtype_ddimageortext'); } } } + if (!$dropfound) { + $errors['drops[0]'] = get_string('formerror_droprequired', 'qtype_ddimageortext'); + } + + $dragfound = false; for ($dragindex = 0; $dragindex < $data['noitems']; $dragindex++) { $label = $data['draglabel'][$dragindex]; if ($data['drags'][$dragindex]['dragitemtype'] == 'word') { + if ($label !== '') { + $dragfound = true; + } $allowedtags = '
'; $errormessage = get_string('formerror_disallowedtags', 'qtype_ddimageortext', s($allowedtags)); } else { + if (self::file_uploaded($data['dragitem'][$dragindex])) { + $dragfound = true; + } $allowedtags = ''; $errormessage = get_string('formerror_noallowedtags', 'qtype_ddimageortext'); } if ($label != strip_tags($label, $allowedtags)) { - $errors["drags[{$dragindex}]"] = $errormessage; + $errors["draglabel[{$dragindex}]"] = $errormessage; } } + if (!$dragfound) { + $errors['drags[0]'] = get_string('formerror_dragrequired', 'qtype_ddimageortext'); + } + return $errors; } } diff --git a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php index 681281555a7..b55c0494a6c 100644 --- a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php +++ b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php @@ -39,6 +39,8 @@ $string['dropbackground'] = 'Background image for dragging markers onto'; $string['dropzone'] = 'Drop zone {$a}'; $string['dropzoneheader'] = 'Drop zones'; $string['formerror_disallowedtags'] = 'Only "{$a}" tags are allowed in this draggable text.'; +$string['formerror_dragrequired'] = 'You must add at least one draggable item to this question.'; +$string['formerror_droprequired'] = 'You must define at least one drop zone for this question.'; $string['formerror_noallowedtags'] = 'HTML tags are not allowed in this text which is the alt text for a draggable image.'; $string['formerror_noytop'] = 'You must provide a value for the y coordinate for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.'; $string['formerror_noxleft'] = 'You must provide a value for the x coordinate for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.'; diff --git a/question/type/ddimageortext/questionbase.php b/question/type/ddimageortext/questionbase.php index 4754bf5760c..499f2c17195 100644 --- a/question/type/ddimageortext/questionbase.php +++ b/question/type/ddimageortext/questionbase.php @@ -138,8 +138,13 @@ class qtype_ddtoimage_question_base extends qtype_gapselect_question_base { } public function get_random_guess_score() { - $accum = 0; + if (empty($this->places)) { + // Having no places would be nonsensical. However, it used to be possible + // to create questions like that, so avoid errors in this case. + return null; + } + $accum = 0; foreach ($this->places as $place) { foreach ($this->choices[$place->group] as $choice) { if ($choice->infinite) { diff --git a/question/type/ddimageortext/tests/behat/add.feature b/question/type/ddimageortext/tests/behat/add.feature index 1aa0de85545..c948c845b20 100644 --- a/question/type/ddimageortext/tests/behat/add.feature +++ b/question/type/ddimageortext/tests/behat/add.feature @@ -99,3 +99,16 @@ Feature: Test creating a drag and drop onto image question And I click on "Add" "button" in the "Choose a question type to add" "dialogue" And the following fields match these values: | id_shuffleanswers | 1 | + + @javascript @_file_upload + Scenario: Question must have at least one drag item and one drop zone + When I am on the "Course 1" "core_question > course question bank" page logged in as teacher + And I press "Create a new question ..." + And I set the field "Drag and drop onto image" to "1" + And I click on "Add" "button" in the "Choose a question type to add" "dialogue" + And I set the field "Question name" to "Test question" + And I set the field "Question text" to "Identify the features in this cross-section." + And I upload "question/type/ddimageortext/tests/fixtures/oceanfloorbase.jpg" file to "Background image" filemanager + And I press "Save changes" + Then I should see "You must add at least one draggable item to this question." + And I should see "You must define at least one drop zone for this question." diff --git a/question/type/ddimageortext/tests/form/edit_form_test.php b/question/type/ddimageortext/tests/form/edit_form_test.php index f5eebe03aec..920ee99012d 100644 --- a/question/type/ddimageortext/tests/form/edit_form_test.php +++ b/question/type/ddimageortext/tests/form/edit_form_test.php @@ -83,6 +83,13 @@ class edit_form_test extends \advanced_testcase { ['dragitemtype' => 'word'], ['dragitemtype' => 'word'], ], + 'dragitem' => [ + 0, + 0, + 0, + 0, + 0, + ], 'draglabel' => [ 'frog', 'toad', @@ -94,12 +101,12 @@ class edit_form_test extends \advanced_testcase { $errors = $form->validation($submitteddata, []); - $this->assertArrayNotHasKey('drags[0]', $errors); + $this->assertArrayNotHasKey('draglabel[0]', $errors); $this->assertEquals('HTML tags are not allowed in this text which is the alt text for a draggable image.', - $errors['drags[1]']); - $this->assertArrayNotHasKey('drags[2]', $errors); - $this->assertArrayNotHasKey('drags[3]', $errors); + $errors['draglabel[1]']); + $this->assertArrayNotHasKey('draglabel[2]', $errors); + $this->assertArrayNotHasKey('draglabel[3]', $errors); $this->assertEquals('Only "<br><sub><sup><b><i><strong><em><span>" ' . - 'tags are allowed in this draggable text.', $errors['drags[4]']); + 'tags are allowed in this draggable text.', $errors['draglabel[4]']); } } diff --git a/question/type/ddimageortext/tests/question_test.php b/question/type/ddimageortext/tests/question_test.php index 0b0af344833..bf7f521ad07 100644 --- a/question/type/ddimageortext/tests/question_test.php +++ b/question/type/ddimageortext/tests/question_test.php @@ -33,6 +33,7 @@ require_once($CFG->dirroot . '/question/type/ddimageortext/tests/helper.php'); * @package qtype_ddimageortext * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers qtype_ddimageortext_question */ class question_test extends \basic_testcase { @@ -91,6 +92,17 @@ class question_test extends \basic_testcase { $this->assertEquals(0.5, $dd->get_random_guess_score()); } + public function test_get_random_guess_score_broken_question() { + // Before MDL-76298 was fixed, it was possible to create a question with + // no drop zones, and that caused a fatal division by zero error. Because + // people might have questions like that in their database, we need to + // check the calculation is robust to it. + /** @var \qtype_ddimageortext_question $dd */ + $dd = \test_question_maker::make_question('ddimageortext'); + $dd->places = []; + $this->assertNull($dd->get_random_guess_score()); + } + public function test_get_right_choice_for() { $dd = \test_question_maker::make_question('ddimageortext'); $dd->shufflechoices = false; diff --git a/question/type/ddmarker/edit_ddmarker_form.php b/question/type/ddmarker/edit_ddmarker_form.php index a30c81d87ef..e6cd7d0682d 100644 --- a/question/type/ddmarker/edit_ddmarker_form.php +++ b/question/type/ddmarker/edit_ddmarker_form.php @@ -215,11 +215,13 @@ class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base { $errors["bgimage"] = get_string('formerror_nobgimage', 'qtype_ddmarker'); } + $dropfound = false; for ($i = 0; $i < $data['nodropzone']; $i++) { $choice = $data['drops'][$i]['choice']; $choicepresent = ($choice !== '0'); if ($choicepresent) { + $dropfound = true; // Test coords here. if ($bgimagesize !== null) { $shape = $data['drops'][$i]['shape']; @@ -236,20 +238,32 @@ class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base { } } else { if (trim($data['drops'][$i]['coords']) !== '') { + $dropfound = true; $errorcode = 'noitemselected'; $errors["drops[{$i}]"] = get_string('formerror_'.$errorcode, 'qtype_ddmarker'); } } - } + if (!$dropfound) { + $errors['drops[0]'] = get_string('formerror_droprequired', 'qtype_ddmarker'); + } + + $markerfound = false; for ($dragindex = 0; $dragindex < $data['noitems']; $dragindex++) { $label = $data['drags'][$dragindex]['label']; + if ($label !== '') { + $markerfound = true; + } if ($label != strip_tags($label, QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER)) { $errors["drags[{$dragindex}]"] = get_string('formerror_onlysometagsallowed', 'qtype_ddmarker', s(QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER)); } } + if (!$markerfound) { + $errors['drags[0]'] = get_string('formerror_dragrequired', 'qtype_ddmarker'); + } + return $errors; } diff --git a/question/type/ddmarker/lang/en/qtype_ddmarker.php b/question/type/ddmarker/lang/en/qtype_ddmarker.php index e1461580955..679cbfa181c 100644 --- a/question/type/ddmarker/lang/en/qtype_ddmarker.php +++ b/question/type/ddmarker/lang/en/qtype_ddmarker.php @@ -53,6 +53,8 @@ For information the three shapes use coordinates in this way: Selecting a Marker text will add that text to the shape in the preview.'; $string['followingarewrong'] = 'The following markers have been placed in the wrong area : {$a}.'; $string['followingarewrongandhighlighted'] = 'The following markers were incorrectly placed : {$a}. Highlighted marker(s) are now shown with the correct placement(s).
Click on the marker to highlight the allowed area.'; +$string['formerror_dragrequired'] = 'You must add at least one marker to this question.'; +$string['formerror_droprequired'] = 'You must define at least one drop zone for this question.'; $string['formerror_nobgimage'] = 'You need to select an image to use as the background for the drag and drop area.'; $string['formerror_noitemselected'] = 'You have specified a drop zone but not chosen a marker that must be dragged to the zone.'; $string['formerror_nosemicolons'] = 'There are no semicolons in your coordinates string. Your coordinates for a {$a->shape} should be expressed as - {$a->coordsstring}.'; diff --git a/question/type/ddmarker/tests/behat/add.feature b/question/type/ddmarker/tests/behat/add.feature index 5ec52ddfc7c..601f813f277 100644 --- a/question/type/ddmarker/tests/behat/add.feature +++ b/question/type/ddmarker/tests/behat/add.feature @@ -63,3 +63,16 @@ Feature: Test creating a drag and drop markers question And the following fields match these values: | id_showmisplaced | 1 | | id_shuffleanswers | 1 | + + @javascript @_file_upload + Scenario: Question must have at least one marker and one drop zone + When I am on the "Course 1" "core_question > course question bank" page logged in as teacher + And I press "Create a new question ..." + And I set the field "Drag and drop markers" to "1" + And I click on "Add" "button" in the "Choose a question type to add" "dialogue" + And I set the field "Question name" to "Drag and drop markers" + And I set the field "Question text" to "Markers, who need markers?" + And I upload "question/type/ddmarker/tests/fixtures/mkmap.png" file to "Background image" filemanager + And I press "Save changes" + Then I should see "You must add at least one marker to this question." + And I should see "You must define at least one drop zone for this question."