Skip to content

Commit 4dac713

Browse files
author
Mahmoud Kassaei
committed
Qtype_drawlines: Implement shownumcorrect, showmisplaced #882022
1 parent 6fc86aa commit 4dac713

11 files changed

+468
-50
lines changed

classes/line.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public static function is_item_positioned_correctly_on_axis($responsecoord, $lin
177177
* Parse the input and return the parts in a list of 'cx', 'cy' with or whothout 'r'.
178178
*
179179
* @param string $dropzone, the string in a given format with or whithout radius
180-
* @param bool $radius, if set to true, return the list with radius, otherwise with radius
180+
* @param bool $radius, if set to true, return the list with radius, otherwise without radius
181181
* @return int[], a list of 'cx', 'cy' with or whothout 'r'.
182182
*/
183183
public static function parse_into_cx_cy_with_or_without_radius(string $dropzone, bool $radius = false): array {

edit_drawlines_form.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ protected function get_per_line_fields(MoodleQuickForm $mform, string $label, ar
172172
/**
173173
* Create the form elements required by one hint.
174174
*
175-
* @param string $withclearwrong whether this quesiton type uses the 'Clear wrong' option on hints.
175+
* @param string $withshowmisplaced whether this quesiton type uses the 'show misplaced' option on hints.
176176
* @param string $withshownumpartscorrect whether this quesiton type uses the 'Show num parts correct' option on hints.
177177
* @return array form field elements for one hint.
178178
*/
179-
protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) {
179+
protected function get_hint_fields($withshowmisplaced = true, $withshownumpartscorrect = false) {
180180
$mform = $this->_form;
181181

182182
$repeated = [];
@@ -187,7 +187,7 @@ protected function get_hint_fields($withclearwrong = false, $withshownumpartscor
187187
$repeated[] = $mform->createElement('checkbox', 'hintshownumcorrect',
188188
get_string('options', 'question'),
189189
get_string('shownumpartscorrect', 'question'));
190-
$repeated[] = $mform->createElement('checkbox', 'hintoptions', '',
190+
$repeated[] = $mform->createElement('checkbox', 'hintshowmisplaced', '',
191191
get_string('showmisplaced', 'qtype_' . $this->qtype()));
192192

193193
return [$repeated, $repeatedoptions];
@@ -256,16 +256,18 @@ protected function data_preprocessing_lines(stdClass $question): object {
256256
}
257257

258258
#[\Override]
259-
protected function data_preprocessing_hints($question, $withclearwrong = false,
260-
$withshownumpartscorrect = false) {
259+
protected function data_preprocessing_hints($question, $withshowmisplaced = false,
260+
$withshownumpartscorrect = false) {
261261
if (empty($question->hints)) {
262262
return $question;
263263
}
264-
parent::data_preprocessing_hints($question, $withclearwrong, $withshownumpartscorrect);
264+
parent::data_preprocessing_hints($question, false, $withshownumpartscorrect);
265265

266266
$question->hintoptions = [];
267-
foreach ($question->hints as $hint) {
268-
$question->hintoptions[] = $hint->options;
267+
foreach ($question->hints as $key => $hint) {
268+
if ($withshowmisplaced) {
269+
$question->hintshowmisplaced[] = $hint->options;
270+
}
269271
}
270272

271273
return $question;

lang/en/qtype_drawlines.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
$string['correctanswersare'] = 'The correct answers are: {$a}';
3232
$string['dropbackground'] = 'Background image for Draw lines';
3333

34-
$string['formerror_nobgimage'] = 'You need to select an image to use as the background for the drag and drop area.';
3534
$string['formerror_invalidimagesize'] = 'Image file should not be larger than 600x600 px. The uploaded image file size is {$a->width}x{$a->height} px.';
35+
$string['formerror_nobgimage'] = 'You need to select an image to use as the background for the drag and drop area.';
3636
$string['formerror_nolines'] = 'You need to expand \'Line 1\' and fill the form for it';
3737
$string['formerror_notype'] = 'You have to select a type for Line {$a}';
3838
$string['formerror_zoneend'] = 'End zone coordinates should be in x,y;r format, where x,y are the coordinates of the centre of a circle and r is the radius.';
@@ -75,6 +75,10 @@
7575
$string['refresh'] = 'Refresh preview';
7676

7777
$string['showmisplaced'] = 'State which zones are incorrectly placed';
78+
$string['showmisplacedcoordinate'] = 'The coordinate {$a} is placed incorrectly.';
79+
$string['showmisplacedcoordinates'] = 'The coordinates {$a} are placed incorrectly.';
80+
$string['showmisplacedline'] = 'The {$a} is placed incorrectly.';
81+
$string['showmisplacedlines'] = 'The {$a} are placed incorrectly.';
7882
$string['summarisechoice'] = '{$a->no}. {$a->text}';
7983
$string['summarisechoiceno'] = 'Item {$a}';
8084
$string['summariseplace'] = '{$a->no}. {$a->text}';
@@ -89,9 +93,9 @@
8993

9094
$string['xleft'] = 'Left';
9195

92-
$string['yougot1right'] = 'You have correctly selected one point.';
96+
$string['yougot1right'] = 'You have correctly selected one coordinate.';
9397
$string['yougot1rightline'] = 'You have correctly selected one line.';
94-
$string['yougotnright'] = 'You have correctly selected {$a->num} points.';
98+
$string['yougotnright'] = 'You have correctly selected {$a->num} coordinates.';
9599
$string['yougotnrightline'] = 'You have correctly selected {$a->num} lines.';
96100
$string['ytop'] = 'Top';
97101

questiontype.php

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
use qtype_drawlines\line;
1818

19+
defined('MOODLE_INTERNAL') || die();
20+
require_once($CFG->libdir.'/questionlib.php');
21+
1922
/**
2023
* The Draw lines question type class.
2124
*
@@ -115,32 +118,31 @@ public function save_hints($fromform, $withparts = false) {
115118
}
116119

117120
if ($withparts) {
118-
if (!empty($fromform->hintclearwrong)) {
119-
$numclears = max(array_keys($fromform->hintclearwrong)) + 1;
120-
} else {
121-
$numclears = 0;
122-
}
123121
if (!empty($fromform->hintshownumcorrect)) {
124122
$numshows = max(array_keys($fromform->hintshownumcorrect)) + 1;
125123
} else {
126124
$numshows = 0;
127125
}
128-
$numhints = max($numhints, $numclears, $numshows);
129-
}
126+
// The hintshowmisplaced variable uses hintoptions from the DB in hint table.
127+
$fromform->hintshowmisplaced = $fromform->hintoptions;
128+
if ($fromform->hintshowmisplaced) {
129+
$nummisplaced = max(array_keys($fromform->hintshowmisplaced)) + 1;
130+
} else {
131+
$nummisplaced = 0;
132+
}
130133

134+
$numhints = max($numhints, $nummisplaced, $numshows);
135+
}
131136
for ($i = 0; $i < $numhints; $i += 1) {
132137
if (html_is_blank($fromform->hint[$i]['text'])) {
133138
$fromform->hint[$i]['text'] = '';
134139
}
135140

136141
if ($withparts) {
137-
$clearwrong = !empty($fromform->hintclearwrong[$i]);
138142
$shownumcorrect = !empty($fromform->hintshownumcorrect[$i]);
139-
$statewhichincorrect = !empty($fromform->hintoptions[$i]);
143+
$showmisplaced = !empty($fromform->hintshowmisplaced[$i]);
140144
}
141-
142-
if (empty($fromform->hint[$i]['text']) && empty($clearwrong) &&
143-
empty($shownumcorrect) && empty($statewhichincorrect)) {
145+
if (empty($fromform->hint[$i]['text']) && empty($shownumcorrect) && empty($showmisplaced)) {
144146
continue;
145147
}
146148

@@ -157,9 +159,8 @@ public function save_hints($fromform, $withparts = false) {
157159
$context, 'question', 'hint', $hint->id);
158160
$hint->hintformat = $fromform->hint[$i]['format'];
159161
if ($withparts) {
160-
$hint->clearwrong = $clearwrong;
161162
$hint->shownumcorrect = $shownumcorrect;
162-
$hint->options = $statewhichincorrect;
163+
$hint->options = $showmisplaced;
163164
}
164165
$DB->update_record('question_hints', $hint);
165166
}
@@ -211,6 +212,11 @@ public function make_line(stdClass $line): line {
211212
$line->zonestart, $line->zoneend);
212213
}
213214

215+
#[\Override]
216+
protected function make_hint($hint) {
217+
return question_hint_drawlines::load_from_record($hint);
218+
}
219+
214220
#[\Override]
215221
public function delete_question($questionid, $contextid) {
216222
global $DB;
@@ -394,3 +400,44 @@ public function get_possible_responses($questiondata) {
394400
return $parts;
395401
}
396402
}
403+
404+
/**
405+
* Question hint for drawlines.
406+
* An extension of {@link question_hint} for questions like match and multiple
407+
* choice with multiple answers, where there are options for whether to show the
408+
* number of parts right at each stage, and to reset the wrong parts.
409+
*
410+
* @package qtype_drawlines
411+
* @copyright 2025 The Open University
412+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
413+
*/
414+
class question_hint_drawlines extends question_hint_with_parts {
415+
416+
/** @var bool option to display the parts of the question that were wrong on retry.*/
417+
public $showmisplaced;
418+
419+
/**
420+
* Constructor.
421+
* @param int the hint id from the database.
422+
* @param string $hint The hint text
423+
* @param int the corresponding text FORMAT_... type.
424+
* @param bool $shownumcorrect whether the number of right parts should be shown
425+
* @param bool $clearwrong whether the wrong parts should be reset.
426+
* @param bool $showmisplaced whether the show the wrong parts.
427+
*/
428+
public function __construct($id, $hint, $hintformat, $shownumcorrect,
429+
$clearwrong, $showmisplaced) {
430+
parent::__construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong);
431+
$this->showmisplaced = $showmisplaced;
432+
}
433+
434+
/**
435+
* Create a basic hint from a row loaded from the question_hints table in the database.
436+
* @param object $row with property options as well as hint, shownumcorrect and clearwrong set.
437+
* @return question_hint_drawlines
438+
*/
439+
public static function load_from_record($row) {
440+
return new question_hint_drawlines($row->id, $row->hint, $row->hintformat,
441+
$row->shownumcorrect, $row->clearwrong, $row->options);
442+
}
443+
}

renderer.php

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ public function formulation_and_controls(question_attempt $qa, question_display_
164164

165165
#[\Override]
166166
public function specific_feedback(question_attempt $qa) {
167-
return $this->combined_feedback($qa);
167+
$output = '';
168+
$output .= $this->combined_feedback($qa);
169+
$hint = $qa->get_applicable_hint();
170+
return $output;
168171
}
169172

170173
#[\Override]
@@ -193,6 +196,72 @@ public function correct_response(question_attempt $qa) {
193196
return $this->correct_choices($rightanswers);
194197
}
195198

199+
#[\Override]
200+
protected function hint(question_attempt $qa, question_hint $hint) {
201+
$output = '';
202+
$question = $qa->get_question();
203+
$response = $qa->get_last_qt_data();
204+
$grademethod = $qa->get_question()->grademethod;
205+
// Accumulate the wrong coords for each lines to be displayed and hint options.
206+
$wrongcoords = [];
207+
if ($hint->showmisplaced) {
208+
foreach ($question->lines as $key => $line) {
209+
if (in_array($question->field($key), array_keys($response))) {
210+
$coords = explode(' ', $response[$question->field($key)]);
211+
if ($question->grademethod === 'partial') {
212+
// Label the line.
213+
$linelabelstart = null;
214+
$linelabelend = null;
215+
if (!line::is_dragitem_in_the_right_place($coords[0], $line->zonestart)) {
216+
$linelabelstart = ' Line ' . $line->number;
217+
$wrongcoords[] = html_writer::tag('span', $linelabelstart .
218+
' start(' . $coords[0] . ')', ['class' => 'misplaced']);
219+
}
220+
if (!line::is_dragitem_in_the_right_place($coords[1], $line->zoneend)) {
221+
if (is_null($linelabelstart)) {
222+
// Do not repeat the line number for the end-prt of the line coordinates.
223+
$linelabelend = ' Line ' . $line->number;
224+
}
225+
$wrongcoords[] = html_writer::tag('span', $linelabelend .
226+
' end(' . $coords[1] . ')', ['class' => 'misplaced']);
227+
}
228+
} else {
229+
if (!(line::is_dragitem_in_the_right_place($coords[0], $line->zonestart) &&
230+
line::is_dragitem_in_the_right_place($coords[1], $line->zoneend))) {
231+
$wrongcoords[] = html_writer::tag('span', ' Line ' . $line->number .
232+
' start(' . $coords[0] . ') end(' . $coords[1] . ')', ['class' => 'misplaced']);
233+
}
234+
}
235+
}
236+
}
237+
if (empty($wrongcoords)) {
238+
$output .= '';
239+
} else if (count($wrongcoords) === 1) {
240+
if ($grademethod === 'partial') {
241+
$output .= html_writer::tag('div',
242+
get_string('showmisplacedcoordinate', 'qtype_drawlines', implode($wrongcoords)),
243+
['class' => 'misplacedinfo']);
244+
} else {
245+
$output .= html_writer::tag('div',
246+
get_string('showmisplacedline', 'qtype_drawlines', implode($wrongcoords)),
247+
['class' => 'misplacedinfo']);
248+
}
249+
} else {
250+
if ($grademethod === 'partial') {
251+
$output .= html_writer::tag('div',
252+
get_string('showmisplacedcoordinates', 'qtype_drawlines', implode(',', $wrongcoords)),
253+
['class' => 'misplacedinfo']);
254+
} else {
255+
$output .= html_writer::tag('div',
256+
get_string('showmisplacedlines', 'qtype_drawlines', implode(',', $wrongcoords)),
257+
['class' => 'misplacedinfo']);
258+
}
259+
}
260+
}
261+
$output .= parent::hint($qa, $hint);
262+
return $output;
263+
}
264+
196265
/**
197266
* Function returns string based on number of correct answers.
198267
*
@@ -214,16 +283,29 @@ protected function correct_choices(array $right): string {
214283
#[\Override]
215284
protected function num_parts_correct(question_attempt $qa): string {
216285
$a = new stdClass();
217-
list($a->num, $a->outof) = $qa->get_question()->get_num_parts_right(
218-
$qa->get_last_qt_data());
219-
if (is_null($a->outof)) {
286+
$grademethod = $qa->get_question()->grademethod;
287+
if ($grademethod === 'partial') {
288+
[$a->num, $a->outof] = $qa->get_question()->get_num_parts_right_grade_partial($qa->get_last_qt_data());
289+
} else {
290+
[$a->num, $a->outof] = $qa->get_question()->get_num_parts_right_grade_allornone($qa->get_last_qt_data());
291+
}
292+
if ($a->num === 0 || is_null($a->outof)) {
220293
return '';
221-
} else if ($a->num == 1) {
222-
return html_writer::tag('p', get_string('yougot1right', 'qtype_drawlines'));
294+
}
295+
if ($a->num == 1) {
296+
if ($grademethod === 'partial') {
297+
return html_writer::tag('p', get_string('yougot1right', 'qtype_drawlines', $a));
298+
} else {
299+
return html_writer::tag('p', get_string('yougot1rightline', 'qtype_drawlines', $a));
300+
}
223301
} else {
224302
$f = new NumberFormatter(current_language(), NumberFormatter::SPELLOUT);
225303
$a->num = $f->format($a->num);
226-
return html_writer::tag('p', get_string('yougotnright', 'qtype_drawlines', $a));
304+
if ($grademethod === 'partial') {
305+
return html_writer::tag('p', get_string('yougotnright', 'qtype_drawlines', $a));
306+
} else {
307+
return html_writer::tag('p', get_string('yougotnrightline', 'qtype_drawlines', $a));
308+
}
227309
}
228310
}
229311
}

styles.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,13 @@ form.mform fieldset#id_previewareaheader .dropbackground {
6565
form.mform fieldset#id_previewareaheader div.ddarea {
6666
text-align: center;
6767
}
68+
69+
.que.drawlines dive.misplacedinfo {
70+
margin: 2px;
71+
}
72+
73+
.que.drawlines span.misplaced {
74+
background-color: #c8ff00;
75+
opacity: .6;
76+
margin: 2px;
77+
}

tests/behat/backup_and_restore.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ Feature: Test duplicating a course containing a draw lines question
6464
And I click on "Multiple tries" "link"
6565
And the following fields match these values:
6666
| Hint 1 | You are trying to draw 2 lines by placing the start and end markers for each line on the map. |
67-
| Hint 2 | You have to find the positins for start and end of each line as described in the question text. |
67+
| Hint 2 | You have to find the positions for start and end of each line as described in the question text. |

tests/behat/import.feature

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,20 @@ Feature: Test importing draw lines questions
2727
And I should see "1. Draw 2 lines on the map. A line segment from A (line starting point) to B (line Ending point), and another one from C to D."
2828
And I press "Continue"
2929
And I should see "Drawlines to edit"
30+
31+
@javascript @_file_upload
32+
Scenario: Verify the imported question for hints.
33+
Given I am on the "Course 1" "core_question > course question import" page logged in as teacher
34+
And I set the field "id_format_xml" to "1"
35+
And I upload "question/type/drawlines/tests/fixtures/testquestion_drawlines_mkmap_twolines.xml" file to "Import" filemanager
36+
And I expand all fieldsets
37+
And I press "id_submitbutton"
38+
And I should see "Parsing questions from import file."
39+
And I press "Continue"
40+
And I should see "Drawlines to edit"
41+
When I am on the "Drawlines to edit" "core_question > edit" page logged in as "teacher"
42+
And I expand all fieldsets
43+
Then the following fields match these values:
44+
| hintshownumcorrect[0] | Checked |
45+
| hintshownumcorrect[1] | Checked |
46+
| hintshowmisplaced[1] | Checked |

0 commit comments

Comments
 (0)