MDL-55533 qtype_multianswer: fix feedback for multiresponse
[moodle.git] / question / type / multianswer / renderer.php
CommitLineData
12c6e008 1<?php
ab50232b
TH
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
ab50232b
TH
17/**
18 * Multianswer question renderer classes.
19 * Handle shortanswer, numerical and various multichoice subquestions
20 *
42a5b055
TH
21 * @package qtype
22 * @subpackage multianswer
23 * @copyright 2010 Pierre Pichet
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
ab50232b
TH
25 */
26
42a5b055
TH
27
28require_once($CFG->dirroot . '/question/type/shortanswer/renderer.php');
29
30
ab50232b
TH
31/**
32 * Base class for generating the bits of output common to multianswer
33 * (Cloze) questions.
34 * This render the main question text and transfer to the subquestions
12c6e008 35 * the task of display their input elements and status
ab50232b
TH
36 * feedback, grade, correct answer(s)
37 *
fa6c8620 38 * @copyright 2010 Pierre Pichet
ab50232b
TH
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 */
12c6e008 41class qtype_multianswer_renderer extends qtype_renderer {
ab50232b
TH
42
43 public function formulation_and_controls(question_attempt $qa,
44 question_display_options $options) {
ab50232b
TH
45 $question = $qa->get_question();
46
42a5b055 47 $output = '';
530a2cb0 48 $subquestions = array();
42a5b055
TH
49 foreach ($question->textfragments as $i => $fragment) {
50 if ($i > 0) {
51 $index = $question->places[$i];
530a2cb0 52 $token = 'qtypemultianswer' . $i . 'marker';
365377bc 53 $token = '<span class="nolink">' . $token . '</span>';
530a2cb0
DT
54 $output .= $token;
55 $subquestions[$token] = $this->subquestion($qa, $options, $index,
42a5b055
TH
56 $question->subquestions[$index]);
57 }
530a2cb0 58 $output .= $fragment;
42a5b055 59 }
530a2cb0
DT
60 $output = $question->format_text($output, $question->questiontextformat,
61 $qa, 'question', 'questiontext', $question->id);
62 $output = str_replace(array_keys($subquestions), array_values($subquestions), $output);
ab50232b 63
86969816
TH
64 if ($qa->get_state() == question_state::$invalid) {
65 $output .= html_writer::nonempty_tag('div',
66 $question->get_validation_error($qa->get_last_qt_data()),
67 array('class' => 'validationerror'));
68 }
69
42a5b055
TH
70 $this->page->requires->js_init_call('M.qtype_multianswer.init',
71 array('#q' . $qa->get_slot()), false, array(
72 'name' => 'qtype_multianswer',
73 'fullpath' => '/question/type/multianswer/module.js',
74 'requires' => array('base', 'node', 'event', 'overlay'),
75 ));
ab50232b 76
42a5b055
TH
77 return $output;
78 }
ab50232b 79
42a5b055
TH
80 public function subquestion(question_attempt $qa,
81 question_display_options $options, $index, question_graded_automatically $subq) {
7ac7977c 82
42a5b055
TH
83 $subtype = $subq->qtype->name();
84 if ($subtype == 'numerical' || $subtype == 'shortanswer') {
85 $subrenderer = 'textfield';
86 } else if ($subtype == 'multichoice') {
946ab15c
DS
87 if ($subq instanceof qtype_multichoice_multi_question) {
88 if ($subq->layout == qtype_multichoice_base::LAYOUT_VERTICAL) {
89 $subrenderer = 'multiresponse_vertical';
90 } else {
91 $subrenderer = 'multiresponse_horizontal';
92 }
ab50232b 93 } else {
946ab15c
DS
94 if ($subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) {
95 $subrenderer = 'multichoice_inline';
96 } else if ($subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL) {
97 $subrenderer = 'multichoice_horizontal';
98 } else {
99 $subrenderer = 'multichoice_vertical';
100 }
42a5b055
TH
101 }
102 } else {
103 throw new coding_exception('Unexpected subquestion type.', $subq);
104 }
105 $renderer = $this->page->get_renderer('qtype_multianswer', $subrenderer);
106 return $renderer->subquestion($qa, $options, $index, $subq);
ab50232b
TH
107 }
108
ab50232b 109 public function correct_response(question_attempt $qa) {
fa6c8620 110 return '';
ab50232b 111 }
ab50232b
TH
112}
113
114
115/**
116 * Subclass for generating the bits of output specific to shortanswer
117 * subquestions.
118 *
42a5b055 119 * @copyright 2011 The Open University
ab50232b
TH
120 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
121 */
7ac7977c
TH
122abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer {
123
124 abstract public function subquestion(question_attempt $qa,
125 question_display_options $options, $index,
126 question_graded_automatically $subq);
127
128 /**
129 * Render the feedback pop-up contents.
36e91f1d 130 *
7ac7977c
TH
131 * @param question_graded_automatically $subq the subquestion.
132 * @param float $fraction the mark the student got. null if this subq was not answered.
133 * @param string $feedbacktext the feedback text, already processed with format_text etc.
134 * @param string $rightanswer the right answer, already processed with format_text etc.
135 * @param question_display_options $options the display options.
136 * @return string the HTML for the feedback popup.
137 */
138 protected function feedback_popup(question_graded_automatically $subq,
139 $fraction, $feedbacktext, $rightanswer, question_display_options $options) {
140
7ac7977c
TH
141 $feedback = array();
142 if ($options->correctness) {
143 if (is_null($fraction)) {
144 $state = question_state::$gaveup;
145 } else {
146 $state = question_state::graded_state_for_fraction($fraction);
147 }
148 $feedback[] = $state->default_string(true);
149 }
150
6b290a49 151 if ($options->feedback && $feedbacktext) {
bca80658
TH
152 $feedback[] = $feedbacktext;
153 }
154
7ac7977c
TH
155 if ($options->rightanswer) {
156 $feedback[] = get_string('correctansweris', 'qtype_shortanswer', $rightanswer);
157 }
158
159 $subfraction = '';
6b290a49
TH
160 if ($options->marks >= question_display_options::MARK_AND_MAX && $subq->maxmark > 0
161 && (!is_null($fraction) || $feedback)) {
7ac7977c
TH
162 $a = new stdClass();
163 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp);
bd156853 164 $a->max = format_float($subq->maxmark, $options->markdp);
7ac7977c
TH
165 $feedback[] = get_string('markoutofmax', 'question', $a);
166 }
167
bca80658
TH
168 if (!$feedback) {
169 return '';
170 }
171
7ac7977c
TH
172 return html_writer::tag('span', implode('<br />', $feedback),
173 array('class' => 'feedbackspan accesshide'));
174 }
175}
176
177
178/**
179 * Subclass for generating the bits of output specific to shortanswer
180 * subquestions.
181 *
182 * @copyright 2011 The Open University
183 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
184 */
185class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_renderer_base {
ab50232b 186
42a5b055
TH
187 public function subquestion(question_attempt $qa, question_display_options $options,
188 $index, question_graded_automatically $subq) {
189
190 $fieldprefix = 'sub' . $index . '_';
191 $fieldname = $fieldprefix . 'answer';
7ac7977c 192
42a5b055 193 $response = $qa->get_last_qt_var($fieldname);
7ac7977c
TH
194 if ($subq->qtype->name() == 'shortanswer') {
195 $matchinganswer = $subq->get_matching_answer(array('answer' => $response));
3a6eb8ef 196 } else if ($subq->qtype->name() == 'numerical') {
b2a79cc1
TH
197 list($value, $unit, $multiplier) = $subq->ap->apply_units($response, '');
198 $matchinganswer = $subq->get_matching_answer($value, 1);
7ac7977c
TH
199 } else {
200 $matchinganswer = $subq->get_matching_answer($response);
201 }
202
42a5b055 203 if (!$matchinganswer) {
b2a79cc1
TH
204 if (is_null($response) || $response === '') {
205 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML);
206 } else {
207 $matchinganswer = new question_answer(0, '', 0.0, '', FORMAT_HTML);
208 }
ab50232b 209 }
42a5b055
TH
210
211 // Work out a good input field size.
2f1e464a 212 $size = max(1, core_text::strlen(trim($response)) + 1);
42a5b055 213 foreach ($subq->answers as $ans) {
2f1e464a 214 $size = max($size, core_text::strlen(trim($ans->answer)));
ab50232b 215 }
bd156853 216 $size = min(60, round($size + rand(0, $size * 0.15)));
1649a4f7 217 // The rand bit is to make guessing harder.
42a5b055 218
ab50232b
TH
219 $inputattributes = array(
220 'type' => 'text',
42a5b055 221 'name' => $qa->get_qt_field_name($fieldname),
ab50232b 222 'value' => $response,
42a5b055 223 'id' => $qa->get_qt_field_name($fieldname),
ab50232b
TH
224 'size' => $size,
225 );
ab50232b
TH
226 if ($options->readonly) {
227 $inputattributes['readonly'] = 'readonly';
ab50232b 228 }
ab50232b 229
42a5b055
TH
230 $feedbackimg = '';
231 if ($options->correctness) {
7ac7977c
TH
232 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction);
233 $feedbackimg = $this->feedback_image($matchinganswer->fraction);
ab50232b 234 }
42a5b055 235
7ac7977c
TH
236 if ($subq->qtype->name() == 'shortanswer') {
237 $correctanswer = $subq->get_matching_answer($subq->get_correct_response());
238 } else {
239 $correctanswer = $subq->get_correct_answer();
240 }
42a5b055 241
7ac7977c
TH
242 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction,
243 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat,
244 $qa, 'question', 'answerfeedback', $matchinganswer->id),
245 s($correctanswer->answer), $options);
246
588d1b59 247 $output = html_writer::start_tag('span', array('class' => 'subquestion'));
c3cdf1e4
FM
248 $output .= html_writer::tag('label', get_string('answer'),
249 array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
7ac7977c
TH
250 $output .= html_writer::empty_tag('input', $inputattributes);
251 $output .= $feedbackimg;
252 $output .= $feedbackpopup;
588d1b59 253 $output .= html_writer::end_tag('span');
42a5b055 254
7ac7977c
TH
255 return $output;
256 }
257}
258
259
260/**
261 * Render an embedded multiple-choice question that is displayed as a select menu.
262 *
dcedbb0e 263 * @copyright 2011 The Open University
7ac7977c
TH
264 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
265 */
266class qtype_multianswer_multichoice_inline_renderer
267 extends qtype_multianswer_subq_renderer_base {
268
269 public function subquestion(question_attempt $qa, question_display_options $options,
270 $index, question_graded_automatically $subq) {
271
272 $fieldprefix = 'sub' . $index . '_';
273 $fieldname = $fieldprefix . 'answer';
274
275 $response = $qa->get_last_qt_var($fieldname);
276 $choices = array();
277 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML);
278 $rightanswer = null;
279 foreach ($subq->get_order($qa) as $value => $ansid) {
280 $ans = $subq->answers[$ansid];
281 $choices[$value] = $subq->format_text($ans->answer, $ans->answerformat,
282 $qa, 'question', 'answer', $ansid);
283 if ($subq->is_choice_selected($response, $value)) {
284 $matchinganswer = $ans;
ab50232b 285 }
7ac7977c 286 }
42a5b055 287
7ac7977c
TH
288 $inputattributes = array(
289 'id' => $qa->get_qt_field_name($fieldname),
290 );
291 if ($options->readonly) {
292 $inputattributes['disabled'] = 'disabled';
ab50232b 293 }
ab50232b 294
7ac7977c
TH
295 $feedbackimg = '';
296 if ($options->correctness) {
297 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction);
298 $feedbackimg = $this->feedback_image($matchinganswer->fraction);
299 }
3211569a 300 $select = html_writer::select($choices, $qa->get_qt_field_name($fieldname),
7ac7977c
TH
301 $response, array('' => ''), $inputattributes);
302
303 $order = $subq->get_order($qa);
b8447700
TH
304 $correctresponses = $subq->get_correct_response();
305 $rightanswer = $subq->answers[$order[reset($correctresponses)]];
b2a79cc1
TH
306 if (!$matchinganswer) {
307 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML);
308 }
7ac7977c
TH
309 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction,
310 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat,
311 $qa, 'question', 'answerfeedback', $matchinganswer->id),
312 $subq->format_text($rightanswer->answer, $rightanswer->answerformat,
313 $qa, 'question', 'answer', $rightanswer->id), $options);
314
588d1b59 315 $output = html_writer::start_tag('span', array('class' => 'subquestion'));
c3cdf1e4
FM
316 $output .= html_writer::tag('label', get_string('answer'),
317 array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
7ac7977c 318 $output .= $select;
42a5b055
TH
319 $output .= $feedbackimg;
320 $output .= $feedbackpopup;
588d1b59 321 $output .= html_writer::end_tag('span');
ab50232b 322
42a5b055
TH
323 return $output;
324 }
ab50232b
TH
325}
326
12c6e008 327
ab50232b 328/**
dcedbb0e
TH
329 * Render an embedded multiple-choice question vertically, like for a normal
330 * multiple-choice question.
12c6e008
TH
331 *
332 * @copyright 2010 Pierre Pichet
333 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
ab50232b 334 */
dcedbb0e 335class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_subq_renderer_base {
ab50232b 336
dcedbb0e
TH
337 public function subquestion(question_attempt $qa, question_display_options $options,
338 $index, question_graded_automatically $subq) {
12c6e008 339
dcedbb0e
TH
340 $fieldprefix = 'sub' . $index . '_';
341 $fieldname = $fieldprefix . 'answer';
342 $response = $qa->get_last_qt_var($fieldname);
12c6e008 343
ab50232b 344 $inputattributes = array(
dcedbb0e
TH
345 'type' => 'radio',
346 'name' => $qa->get_qt_field_name($fieldname),
12c6e008 347 );
ab50232b
TH
348 if ($options->readonly) {
349 $inputattributes['disabled'] = 'disabled';
350 }
dcedbb0e
TH
351
352 $result = $this->all_choices_wrapper_start();
353 $fraction = null;
354 foreach ($subq->get_order($qa) as $value => $ansid) {
355 $ans = $subq->answers[$ansid];
356
357 $inputattributes['value'] = $value;
358 $inputattributes['id'] = $inputattributes['name'] . $value;
359
360 $isselected = $subq->is_choice_selected($response, $value);
ab50232b
TH
361 if ($isselected) {
362 $inputattributes['checked'] = 'checked';
dcedbb0e 363 $fraction = $ans->fraction;
ab50232b
TH
364 } else {
365 unset($inputattributes['checked']);
366 }
ab50232b 367
ab50232b 368 $class = 'r' . ($value % 2);
dcedbb0e
TH
369 if ($options->correctness && $isselected) {
370 $feedbackimg = $this->feedback_image($ans->fraction);
371 $class .= ' ' . $this->feedback_class($ans->fraction);
372 } else {
373 $feedbackimg = '';
ab50232b 374 }
12c6e008 375
dcedbb0e
TH
376 $result .= $this->choice_wrapper_start($class);
377 $result .= html_writer::empty_tag('input', $inputattributes);
378 $result .= html_writer::tag('label', $subq->format_text($ans->answer,
379 $ans->answerformat, $qa, 'question', 'answer', $ansid),
380 array('for' => $inputattributes['id']));
381 $result .= $feedbackimg;
382
383 if ($options->feedback && $isselected && trim($ans->feedback)) {
384 $result .= html_writer::tag('div',
385 $subq->format_text($ans->feedback, $ans->feedbackformat,
386 $qa, 'question', 'answerfeedback', $ansid),
387 array('class' => 'specificfeedback'));
ab50232b 388 }
12c6e008 389
dcedbb0e 390 $result .= $this->choice_wrapper_end();
12c6e008
TH
391 }
392
dcedbb0e 393 $result .= $this->all_choices_wrapper_end();
12c6e008 394
b2a79cc1 395 $feedback = array();
dcedbb0e
TH
396 if ($options->feedback && $options->marks >= question_display_options::MARK_AND_MAX &&
397 $subq->maxmark > 0) {
398 $a = new stdClass();
399 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp);
bd156853 400 $a->max = format_float($subq->maxmark, $options->markdp);
ab50232b 401
b2a79cc1
TH
402 $feedback[] = html_writer::tag('div', get_string('markoutofmax', 'question', $a));
403 }
404
405 if ($options->rightanswer) {
406 foreach ($subq->answers as $ans) {
407 if (question_state::graded_state_for_fraction($ans->fraction) ==
408 question_state::$gradedright) {
409 $feedback[] = get_string('correctansweris', 'qtype_multichoice',
410 $subq->format_text($ans->answer, $ans->answerformat,
a3f92b2e 411 $qa, 'question', 'answer', $ansid));
b2a79cc1
TH
412 break;
413 }
414 }
12c6e008 415 }
ab50232b 416
b2a79cc1
TH
417 $result .= html_writer::nonempty_tag('div', implode('<br />', $feedback), array('class' => 'outcome'));
418
dcedbb0e 419 return $result;
12c6e008
TH
420 }
421
dcedbb0e
TH
422 /**
423 * @param string $class class attribute value.
424 * @return string HTML to go before each choice.
425 */
426 protected function choice_wrapper_start($class) {
427 return html_writer::start_tag('div', array('class' => $class));
12c6e008
TH
428 }
429
dcedbb0e
TH
430 /**
431 * @return string HTML to go after each choice.
432 */
433 protected function choice_wrapper_end() {
434 return html_writer::end_tag('div');
12c6e008
TH
435 }
436
dcedbb0e
TH
437 /**
438 * @return string HTML to go before all the choices.
439 */
440 protected function all_choices_wrapper_start() {
441 return html_writer::start_tag('div', array('class' => 'answer'));
12c6e008
TH
442 }
443
dcedbb0e
TH
444 /**
445 * @return string HTML to go after all the choices.
446 */
447 protected function all_choices_wrapper_end() {
448 return html_writer::end_tag('div');
12c6e008 449 }
dcedbb0e 450}
12c6e008 451
ab50232b 452
dcedbb0e
TH
453/**
454 * Render an embedded multiple-choice question vertically, like for a normal
455 * multiple-choice question.
456 *
457 * @copyright 2010 Pierre Pichet
458 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
459 */
460class qtype_multianswer_multichoice_horizontal_renderer
461 extends qtype_multianswer_multichoice_vertical_renderer {
12c6e008 462
dcedbb0e
TH
463 protected function choice_wrapper_start($class) {
464 return html_writer::start_tag('td', array('class' => $class));
ab50232b 465 }
12c6e008 466
dcedbb0e
TH
467 protected function choice_wrapper_end() {
468 return html_writer::end_tag('td');
469 }
12c6e008 470
dcedbb0e
TH
471 protected function all_choices_wrapper_start() {
472 return html_writer::start_tag('table', array('class' => 'answer')) .
473 html_writer::start_tag('tbody') . html_writer::start_tag('tr');
474 }
ab50232b 475
dcedbb0e
TH
476 protected function all_choices_wrapper_end() {
477 return html_writer::end_tag('tr') . html_writer::end_tag('tbody') .
478 html_writer::end_tag('table');
ab50232b 479 }
ab50232b 480}
946ab15c
DS
481
482/**
483 * Class qtype_multianswer_multiresponse_renderer
484 *
485 * @copyright 2016 Davo Smith, Synergy Learning
486 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
487 */
488class qtype_multianswer_multiresponse_vertical_renderer extends qtype_multianswer_subq_renderer_base {
489
490 /**
491 * Output the content of the subquestion.
492 *
493 * @param question_attempt $qa
494 * @param question_display_options $options
495 * @param int $index
496 * @param question_graded_automatically $subq
497 * @return string
498 */
499 public function subquestion(question_attempt $qa, question_display_options $options,
500 $index, question_graded_automatically $subq) {
501
502 if (!$subq instanceof qtype_multichoice_multi_question) {
503 throw new coding_exception('Expecting subquestion of type qtype_multichoice_multi_question');
504 }
505
506 $fieldprefix = 'sub' . $index . '_';
507 $fieldname = $fieldprefix . 'choice';
508
509 // Extract the responses that related to this question + strip off the prefix.
510 $fieldprefixlen = strlen($fieldprefix);
511 $response = [];
512 foreach ($qa->get_last_qt_data() as $name => $val) {
513 if (substr($name, 0, $fieldprefixlen) == $fieldprefix) {
514 $name = substr($name, $fieldprefixlen);
515 $response[$name] = $val;
516 }
517 }
518
519 $basename = $qa->get_qt_field_name($fieldname);
520 $inputattributes = array(
521 'type' => 'checkbox',
522 'value' => 1,
523 );
524 if ($options->readonly) {
525 $inputattributes['disabled'] = 'disabled';
526 }
527
528 $result = $this->all_choices_wrapper_start();
529
530 // Calculate the total score (as we need to know if choices should be marked as 'correct' or 'partial').
531 $fraction = 0;
532 foreach ($subq->get_order($qa) as $value => $ansid) {
533 $ans = $subq->answers[$ansid];
534 if ($subq->is_choice_selected($response, $value)) {
535 $fraction += $ans->fraction;
536 }
537 }
538 // Display 'correct' answers as correct, if we are at 100%, otherwise mark them as 'partial'.
539 $answerfraction = ($fraction > 0.999) ? 1.0 : 0.5;
540
541 foreach ($subq->get_order($qa) as $value => $ansid) {
542 $ans = $subq->answers[$ansid];
543
544 $name = $basename.$value;
545 $inputattributes['name'] = $name;
546 $inputattributes['id'] = $name;
547
548 $isselected = $subq->is_choice_selected($response, $value);
549 if ($isselected) {
550 $inputattributes['checked'] = 'checked';
551 } else {
552 unset($inputattributes['checked']);
553 }
554
555 $class = 'r' . ($value % 2);
556 if ($options->correctness && $isselected) {
557 $thisfrac = ($ans->fraction > 0) ? $answerfraction : 0;
558 $feedbackimg = $this->feedback_image($thisfrac);
559 $class .= ' ' . $this->feedback_class($thisfrac);
560 } else {
561 $feedbackimg = '';
562 }
563
564 $result .= $this->choice_wrapper_start($class);
565 $result .= html_writer::empty_tag('input', $inputattributes);
566 $result .= html_writer::tag('label', $subq->format_text($ans->answer,
567 $ans->answerformat, $qa, 'question', 'answer', $ansid),
568 array('for' => $inputattributes['id']));
569 $result .= $feedbackimg;
570
571 if ($options->feedback && $isselected && trim($ans->feedback)) {
572 $result .= html_writer::tag('div',
573 $subq->format_text($ans->feedback, $ans->feedbackformat,
574 $qa, 'question', 'answerfeedback', $ansid),
575 array('class' => 'specificfeedback'));
576 }
577
578 $result .= $this->choice_wrapper_end();
579 }
580
581 $result .= $this->all_choices_wrapper_end();
582
583 $feedback = array();
584 if ($options->feedback && $options->marks >= question_display_options::MARK_AND_MAX &&
585 $subq->maxmark > 0) {
586 $a = new stdClass();
587 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp);
588 $a->max = format_float($subq->maxmark, $options->markdp);
589
590 $feedback[] = html_writer::tag('div', get_string('markoutofmax', 'question', $a));
591 }
592
593 if ($options->rightanswer) {
594 $correct = [];
595 foreach ($subq->answers as $ans) {
a7b72fb2 596 if (question_state::graded_state_for_fraction($ans->fraction) != question_state::$gradedwrong) {
946ab15c
DS
597 $correct[] = $subq->format_text($ans->answer, $ans->answerformat, $qa, 'question', 'answer', $ans->id);
598 }
599 }
600 $correct = '<ul><li>'.implode('</li><li>', $correct).'</li></ul>';
601 $feedback[] = get_string('correctansweris', 'qtype_multichoice', $correct);
602 }
603
604 $result .= html_writer::nonempty_tag('div', implode('<br />', $feedback), array('class' => 'outcome'));
605
606 return $result;
607 }
608
609 /**
610 * @param string $class class attribute value.
611 * @return string HTML to go before each choice.
612 */
613 protected function choice_wrapper_start($class) {
614 return html_writer::start_tag('div', array('class' => $class));
615 }
616
617 /**
618 * @return string HTML to go after each choice.
619 */
620 protected function choice_wrapper_end() {
621 return html_writer::end_tag('div');
622 }
623
624 /**
625 * @return string HTML to go before all the choices.
626 */
627 protected function all_choices_wrapper_start() {
628 return html_writer::start_tag('div', array('class' => 'answer'));
629 }
630
631 /**
632 * @return string HTML to go after all the choices.
633 */
634 protected function all_choices_wrapper_end() {
635 return html_writer::end_tag('div');
636 }
637}
638
639/**
640 * Render an embedded multiple-response question horizontally.
641 *
642 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
643 */
644class qtype_multianswer_multiresponse_horizontal_renderer
645 extends qtype_multianswer_multiresponse_vertical_renderer {
646
647 protected function choice_wrapper_start($class) {
648 return html_writer::start_tag('td', array('class' => $class));
649 }
650
651 protected function choice_wrapper_end() {
652 return html_writer::end_tag('td');
653 }
654
655 protected function all_choices_wrapper_start() {
656 return html_writer::start_tag('table', array('class' => 'answer')) .
657 html_writer::start_tag('tbody') . html_writer::start_tag('tr');
658 }
659
660 protected function all_choices_wrapper_end() {
661 return html_writer::end_tag('tr') . html_writer::end_tag('tbody') .
662 html_writer::end_tag('table');
663 }
664}