• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FormulasQuestion / moodle-qtype_formulas / 8765823742

20 Apr 2024 02:47PM UTC coverage: 75.03%. Remained the same
8765823742

push

github

web-flow
prepare v5.3.3

2524 of 3364 relevant lines covered (75.03%)

158.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.94
/renderer.php
1
<?php
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

17
/**
18
 * Formulas question renderer class.
19
 *
20
 * @package    qtype_formulas
21
 * @copyright  2009 The Open University
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24

25
use qtype_formulas\answer_unit_conversion;
26

27
/**
28
 * Base class for generating the bits of output for formulas questions.
29
 *
30
 * @copyright  2009 The Open University
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32
 */
33
class qtype_formulas_renderer extends qtype_with_combined_feedback_renderer {
34
    /**
35
     * Generate the display of the formulation part of the question. This is the
36
     * area that contains the question text, and the controls for students to
37
     * input their answers. Some question types also embed bits of feedback, for
38
     * example ticks and crosses, in this area.
39
     *
40
     * @param question_attempt $qa the question attempt to display.
41
     * @param question_display_options $options controls what should and should not be displayed.
42
     * @return string HTML fragment.
43
     */
44
    public function formulation_and_controls(question_attempt $qa,
45
            question_display_options $options) {
46
        global $OUTPUT;
76✔
47

48
        $question = $qa->get_question();
76✔
49

50
        $globalvars = $question->get_global_variables();
76✔
51

52
        // TODO: is this really necessary here ? If question is damaged it should have been detected before.
53
        if (count($question->textfragments) != $question->get_number_of_parts() + 1) {
76✔
54
            $OUTPUT->notification(get_string('error_question_damaged', 'qtype_formulas'), 'error');
×
55
            return;
×
56
        }
57

58
        $questiontext = '';
76✔
59
        foreach ($question->parts as $part) {
76✔
60
            $questiontext .= $question->formulas_format_text(
76✔
61
                    $globalvars,
76✔
62
                    $question->textfragments[$part->partindex],
76✔
63
                    FORMAT_HTML,
76✔
64
                    $qa,
76✔
65
                    'question',
76✔
66
                    'questiontext',
76✔
67
                    $question->id,
76✔
68
                    false);
76✔
69
            $questiontext .= $this->part_formulation_and_controls($qa, $options, $part);
76✔
70
        }
71
        $questiontext .= $question->formulas_format_text(
76✔
72
                $globalvars,
76✔
73
                $question->textfragments[$question->get_number_of_parts()],
76✔
74
                FORMAT_HTML,
76✔
75
                $qa,
76✔
76
                'question',
76✔
77
                'questiontext',
76✔
78
                $question->id,
76✔
79
                false);
76✔
80

81
        $result = html_writer::tag('div', $questiontext, array('class' => 'qtext'));
76✔
82
        if ($qa->get_state() == question_state::$invalid) {
76✔
83
            $result .= html_writer::nonempty_tag('div',
×
84
                    $question->get_validation_error($qa->get_last_qt_data()),
×
85
                    array('class' => 'validationerror'));
×
86
        }
87
        return $result;
76✔
88
    }
89

90
    public function head_code(question_attempt $qa) {
91
        $this->page->requires->js('/question/type/formulas/script/formatcheck.js');
×
92
    }
93

94
    // Return the part text, controls, grading details and feedbacks.
95
    public function part_formulation_and_controls(question_attempt $qa,
96
            question_display_options $options, $part) {
97

98
        $question = $qa->get_question();
76✔
99
        $partoptions = clone $options;
76✔
100
        // If using adaptivemultipart behaviour, adjust feedback display options for this part.
101
        if ($qa->get_behaviour_name() == 'adaptivemultipart') {
76✔
102
            $qa->get_behaviour()->adjust_display_options_for_part($part->partindex, $partoptions);
76✔
103
        }
104
        $sub = $this->get_part_image_and_class($qa, $partoptions, $part);
76✔
105
        $localvars = $question->get_local_variables($part);
76✔
106

107
        $output = $this->get_part_formulation(
76✔
108
                $qa,
76✔
109
                $partoptions,
76✔
110
                $part->partindex,
76✔
111
                $localvars,
76✔
112
                $sub);
76✔
113
        // Place for the right/wrong feeback image or appended at part's end.
114
        if (strpos($output, '{_m}') !== false) {
76✔
115
            $output = str_replace('{_m}', $sub->feedbackimage, $output);
×
116
        } else {
117
            $output .= $sub->feedbackimage;
76✔
118
        }
119

120
        $feedback = $this->part_combined_feedback($qa, $partoptions, $part, $sub->fraction);
76✔
121
        $feedback .= $this->part_general_feedback($qa, $partoptions, $part);
76✔
122
        // If one of the part's coordinates is a MC or select question, the correct answer
123
        // stored in the database is not the right answer, but the index of the right answer,
124
        // so in that case, we need to calculate the right answer.
125
        if ($partoptions->rightanswer) {
76✔
126
            $feedback .= $this->part_correct_response($part->partindex, $qa);
19✔
127
        }
128
        $output .= html_writer::nonempty_tag('div', $feedback,
76✔
129
                array('class' => 'formulaspartoutcome'));
76✔
130
        return html_writer::tag('div', $output , array('class' => 'formulaspart'));
76✔
131
    }
132

133
    // Return class and image for the part feedback.
134
    public function get_part_image_and_class($qa, $options, $part) {
135
        $question = $qa->get_question();
76✔
136

137
        $sub = new StdClass;
76✔
138

139
        $response = $qa->get_last_qt_data();
76✔
140
        $question->rationalize_responses($response);
76✔
141
        $checkunit = new answer_unit_conversion;
76✔
142

143
        list( $sub->anscorr, $sub->unitcorr) = $question->grade_responses_individually($part, $response, $checkunit);
76✔
144
        $sub->fraction = $sub->anscorr * ($sub->unitcorr ? 1 : (1 - $part->unitpenalty));
76✔
145

146
        // Get the class and image for the feedback.
147
        if ($options->correctness) {
76✔
148
            $sub->feedbackimage = $this->feedback_image($sub->fraction);
76✔
149
            $sub->feedbackclass = $this->feedback_class($sub->fraction);
76✔
150
            if ($part->unitpenalty >= 1) { // All boxes must be correct at the same time, so they are of the same color.
76✔
151
                $sub->unitfeedbackclass = $sub->feedbackclass;
76✔
152
                $sub->boxfeedbackclass = $sub->feedbackclass;
76✔
153
            } else {  // Show individual color, all four color combinations are possible.
154
                $sub->unitfeedbackclass = $this->feedback_class($sub->unitcorr);
×
155
                $sub->boxfeedbackclass = $this->feedback_class($sub->anscorr);
52✔
156
            }
157
        } else {  // There should be no feedback if options->correctness is not set for this part.
158
            $sub->feedbackimage = '';
76✔
159
            $sub->feedbackclass = '';
76✔
160
            $sub->unitfeedbackclass = '';
76✔
161
            $sub->boxfeedbackclass = '';
76✔
162
        }
163
        return $sub;
76✔
164
    }
165

166
    /**
167
     * @param int $num The number, starting at 0.
168
     * @param string $style The style to render the number in. One of the
169
     * options returned by {@link qtype_multichoice:;get_numbering_styles()}.
170
     * @return string the number $num in the requested style.
171
     */
172
    protected function number_in_style($num, $style) {
173
        switch($style) {
174
            case 'abc':
×
175
                $number = chr(ord('a') + $num);
×
176
                break;
×
177
            case 'ABCD':
×
178
                $number = chr(ord('A') + $num);
×
179
                break;
×
180
            case '123':
×
181
                $number = $num + 1;
×
182
                break;
×
183
            case 'iii':
×
184
                $number = question_utils::int_to_roman($num + 1);
×
185
                break;
×
186
            case 'IIII':
×
187
                $number = strtoupper(question_utils::int_to_roman($num + 1));
×
188
                break;
×
189
            case 'none':
×
190
                return '';
×
191
            default:
192
                // Default similar to none for compatibility with old questions.
193
                return '';
×
194
        }
195
        return $number . '. ';
×
196
    }
197

198
    // Return the part's text with variables replaced by their values.
199
    public function get_part_formulation(question_attempt $qa, question_display_options $options, $i, $vars, $sub) {
200
        $question = $qa->get_question();
76✔
201
        $part = &$question->parts[$i];
76✔
202
        $localvars = $question->get_local_variables($part);
76✔
203

204
        $subqreplaced = $question->formulas_format_text($localvars, $part->subqtext,
76✔
205
                $part->subqtextformat, $qa, 'qtype_formulas', 'answersubqtext', $part->id, false);
76✔
206
        $types = array(0 => 'number', 10 => 'numeric', 100 => 'numerical_formula', 1000 => 'algebraic_formula');
76✔
207
        $gradingtype = ($part->answertype != 10 && $part->answertype != 100 && $part->answertype != 1000) ? 0 : $part->answertype;
76✔
208
        $gtype = $types[$gradingtype];
76✔
209

210
        // Get the set of defined placeholders and their options.
211
        $boxes = $part->part_answer_boxes($subqreplaced);
76✔
212
        // Append missing placholders at the end of part.
213
        foreach (range(0, $part->numbox) as $j => $notused) {
76✔
214
            $placeholder = ($j == $part->numbox) ? "_u" : "_$j";
76✔
215
            if (!array_key_exists($placeholder, $boxes)) {
76✔
216
                $boxes[$placeholder] = (object)array('pattern' => "{".$placeholder."}", 'options' => '', 'stype' => '');
76✔
217
                $subqreplaced .= "{".$placeholder."}";  // Appended at the end.
76✔
218
            }
219
        }
220

221
        // If part has combined unit answer input.
222
        if ($part->part_has_combined_unit_field()) {
76✔
223
            $variablename = "{$i}_";
×
224
            $currentanswer = $qa->get_last_qt_var($variablename);
×
225
            $inputname = $qa->get_qt_field_name($variablename);
×
226
            $inputattributes = array(
×
227
                'type' => 'text',
×
228
                'name' => $inputname,
×
229
                'title' => get_string($gtype.($part->postunit == '' ? '' : '_unit'), 'qtype_formulas'),
×
230
                'value' => $currentanswer,
×
231
                'id' => $inputname,
×
232
                'class' => 'formulas_' . $gtype . '_unit ' . $sub->feedbackclass,
×
233
                'maxlength' => 128,
×
234
                'aria-labelledby' => 'lbl_' . str_replace(':', '__', $inputname)
×
235
            );
×
236

237
            if ($options->readonly) {
×
238
                $inputattributes['readonly'] = 'readonly';
×
239
            }
240
            // Create a meaningful label for accessibility.
241
            $a = new stdClass();
×
242
            $a->part = $i + 1;
×
243
            $a->numanswer = '';
×
244
            if ($question->get_number_of_parts() == 1) {
×
245
                $label = get_string('answercombinedunitsingle', 'qtype_formulas', $a);
×
246
            } else {
247
                $label = get_string('answercombinedunitmulti', 'qtype_formulas', $a);
×
248
            }
249
            $input = html_writer::tag(
×
250
                'label',
×
251
                $label,
×
252
                array(
×
253
                    'class' => 'subq accesshide',
×
254
                    'for' => $inputattributes['id'],
×
255
                    'id' => 'lbl_' . str_replace(':', '__', $inputattributes['id'])
×
256
                )
×
257
            );
×
258
            $input .= html_writer::empty_tag('input', $inputattributes);
×
259
            $subqreplaced = str_replace("{_0}{_u}", $input, $subqreplaced);
×
260
        }
261

262
        // Get the set of string for each candidate input box {_0}, {_1}, ..., {_u}.
263
        $inputs = array();
76✔
264
        foreach (range(0, $part->numbox) as $j) {    // Replace the input box for each placeholder {_0}, {_1} ...
76✔
265
            $placeholder = ($j == $part->numbox) ? "_u" : "_$j";    // The last one is unit.
76✔
266
            $variablename = "{$i}_$j";
76✔
267
            $currentanswer = $qa->get_last_qt_var($variablename);
76✔
268
            $inputname = $qa->get_qt_field_name($variablename);
76✔
269
            $inputattributes = array(
76✔
270
                'name' => $inputname,
76✔
271
                'value' => $currentanswer,
76✔
272
                'id' => $inputname,
76✔
273
                'maxlength' => 128,
76✔
274
                'aria-labelledby' => 'lbl_' . str_replace(':', '__', $inputname)
76✔
275
            );
76✔
276
            if ($options->readonly) {
76✔
277
                $inputattributes['readonly'] = 'readonly';
19✔
278
            }
279

280
            $stexts = null;
76✔
281
            if (strlen($boxes[$placeholder]->options) != 0) { // Then it's a multichoice answer..
76✔
282
                try {
283
                    $stexts = $question->qv->evaluate_general_expression($vars, substr($boxes[$placeholder]->options, 1));
×
284
                } catch (Exception $e) { // @codingStandardsIgnoreLine
×
285
                    // The $stexts variable will be null if evaluation fails.
286
                }
287
            }
288
            // Coordinate as multichoice options.
289
            if ($stexts != null) {
76✔
290
                if ($boxes[$placeholder]->stype != ':SL') {
×
291
                    if ($boxes[$placeholder]->stype == ':MCE') {
×
292
                        // Select menu.
293
                        if ($options->readonly) {
×
294
                            $inputattributes['disabled'] = 'disabled';
×
295
                        }
296
                        $choices = array();
×
297
                        foreach ($stexts->value as $x => $mctxt) {
×
298
                            $choices[$x] = $question->format_text($mctxt, $part->subqtextformat , $qa,
×
299
                                    'qtype_formulas', 'answersubqtext', $part->id, false);
×
300
                        }
301
                        $select = html_writer::select($choices, $inputname,
×
302
                                $currentanswer, array('' => ''), $inputattributes);
×
303
                        $output = html_writer::start_tag('span', array('class' => 'formulas_menu'));
×
304
                        $a = new stdClass();
×
305
                        $a->numanswer = $j + 1;
×
306
                        $a->part = $i + 1;
×
307
                        if (count($question->parts) > 1) {
×
308
                            $labeltext = get_string('answercoordinatemulti', 'qtype_formulas', $a);
×
309
                        } else {
310
                            $labeltext = get_string('answercoordinatesingle', 'qtype_formulas', $a);
×
311
                        }
312
                        $output .= html_writer::tag(
×
313
                            'label',
×
314
                            $labeltext,
×
315
                            array(
×
316
                                'class' => 'subq accesshide',
×
317
                                'for' => $inputattributes['id'],
×
318
                                'id' => 'lbl_' . str_replace(':', '__', $inputattributes['id'])
×
319
                            )
×
320
                        );
×
321
                        $output .= $select;
×
322
                        $output .= html_writer::end_tag('span');
×
323
                        $inputs[$placeholder] = $output;
×
324
                    } else {
325
                        // Multichoice single question.
326
                        $inputattributes['type'] = 'radio';
×
327
                        if ($options->readonly) {
×
328
                            $inputattributes['disabled'] = 'disabled';
×
329
                        }
330
                        $output = $this->all_choices_wrapper_start();
×
331
                        foreach ($stexts->value as $x => $mctxt) {
×
332
                            $mctxt = html_writer::span($this->number_in_style($x, $question->answernumbering), 'answernumber')
×
333
                                    . $question->format_text($mctxt, $part->subqtextformat , $qa,
×
334
                                    'qtype_formulas', 'answersubqtext', $part->id, false);
×
335
                            $inputattributes['id'] = $inputname.'_'.$x;
×
336
                            $inputattributes['value'] = $x;
×
337
                            $inputattributes['aria-labelledby'] = 'lbl_' . str_replace(':', '__', $inputattributes['id']);
×
338
                            $isselected = ($currentanswer != '' && $x == $currentanswer);
×
339
                            $class = 'r' . ($x % 2);
×
340
                            if ($isselected) {
×
341
                                $inputattributes['checked'] = 'checked';
×
342
                            } else {
343
                                unset($inputattributes['checked']);
×
344
                            }
345
                            if ($options->correctness && $isselected) {
×
346
                                $class .= ' ' . $sub->feedbackclass;
×
347
                            }
348
                            $output .= $this->choice_wrapper_start($class);
×
349
                            $output .= html_writer::empty_tag('input', $inputattributes);
×
350
                            $output .= html_writer::tag(
×
351
                                'label',
×
352
                                $mctxt,
×
353
                                array(
×
354
                                    'for' => $inputattributes['id'],
×
355
                                    'class' => 'm-l-1',
×
356
                                    'id' => 'lbl_' . str_replace(':', '__', $inputattributes['id'])
×
357
                                )
×
358
                            );
×
359
                            $output .= $this->choice_wrapper_end();
×
360
                        }
361
                        $output .= $this->all_choices_wrapper_end();
×
362
                        $inputs[$placeholder] = $output;
×
363
                    }
364
                }
365
                continue;
×
366
            }
367

368
            // Coordinate as shortanswer question.
369
            $inputs[$placeholder] = '';
76✔
370
            $inputattributes['type'] = 'text';
76✔
371
            if ($options->readonly) {
76✔
372
                $inputattributes['readonly'] = 'readonly';
19✔
373
            }
374
            if ($j == $part->numbox) {
76✔
375
                // Check if it's an input for unit.
376
                if (strlen($part->postunit) > 0) {
76✔
377
                    $inputattributes['title'] = get_string('unit', 'qtype_formulas');
×
378
                    $inputattributes['class'] = 'formulas_unit '.$sub->unitfeedbackclass;
×
379
                    $a = new stdClass();
×
380
                    $a->part = $i + 1;
×
381
                    $a->numanswer = $j + 1;
×
382
                    if ($question->get_number_of_parts() == 1) {
×
383
                        $label = get_string('answerunitsingle', 'qtype_formulas', $a);
×
384
                    } else {
385
                        $label = get_string('answerunitmulti', 'qtype_formulas', $a);
×
386
                    }
387
                    $inputs[$placeholder] = html_writer::tag(
×
388
                        'label',
×
389
                        $label,
×
390
                        array(
×
391
                            'class' => 'subq accesshide',
×
392
                            'for' => $inputattributes['id'],
×
393
                            'id' => 'lbl_' . str_replace(':', '__', $inputattributes['id'])
×
394
                        )
×
395
                    );
×
396
                    $inputs[$placeholder] .= html_writer::empty_tag('input', $inputattributes);
52✔
397
                }
398
            } else {
399
                $inputattributes['title'] = get_string($gtype, 'qtype_formulas');
76✔
400
                $inputattributes['class'] = 'formulas_'.$gtype.' '.$sub->boxfeedbackclass;
76✔
401
                $inputattributes['aria-labelledby'] = 'lbl_' . str_replace(':', '__', $inputattributes['id']);
76✔
402
                $a = new stdClass();
76✔
403
                $a->part = $i + 1;
76✔
404
                $a->numanswer = $j + 1;
76✔
405
                if ($part->numbox == 1) {
76✔
406
                    if ($question->get_number_of_parts() == 1) {
76✔
407
                        $label = get_string('answersingle', 'qtype_formulas', $a);
76✔
408
                    } else {
409
                        $label = get_string('answermulti', 'qtype_formulas', $a);
52✔
410
                    }
411
                } else {
412
                    if ($question->get_number_of_parts() == 1) {
×
413
                        $label = get_string('answercoordinatesingle', 'qtype_formulas', $a);
×
414
                    } else {
415
                        $label = get_string('answercoordinatemulti', 'qtype_formulas', $a);
×
416
                    }
417
                }
418
                $inputs[$placeholder] = html_writer::tag(
76✔
419
                    'label',
76✔
420
                    $label,
76✔
421
                    array(
76✔
422
                        'class' => 'subq accesshide',
76✔
423
                        'for' => $inputattributes['id'],
76✔
424
                        'id' => 'lbl_' . str_replace(':', '__', $inputattributes['id'])
76✔
425
                    )
76✔
426
                );
76✔
427
                $inputs[$placeholder] .= html_writer::empty_tag('input', $inputattributes);
76✔
428
            }
429
        }
430

431
        foreach ($inputs as $placeholder => $replacement) {
76✔
432
            $subqreplaced = preg_replace('/'.$boxes[$placeholder]->pattern.'/', $replacement, $subqreplaced, 1);
76✔
433
        }
434
        return $subqreplaced;
76✔
435
    }
436

437
    /**
438
     * @param string $class class attribute value.
439
     * @return string HTML to go before each choice.
440
     */
441
    protected function choice_wrapper_start($class) {
442
        return html_writer::start_tag('div', array('class' => $class));
×
443
    }
444

445
    /**
446
     * @return string HTML to go after each choice.
447
     */
448
    protected function choice_wrapper_end() {
449
        return html_writer::end_tag('div');
×
450
    }
451

452
    /**
453
     * @return string HTML to go before all the choices.
454
     */
455
    protected function all_choices_wrapper_start() {
456
        return html_writer::start_tag('div', array('class' => 'multichoice_answer'));
×
457
    }
458

459
    /**
460
     * @return string HTML to go after all the choices.
461
     */
462
    protected function all_choices_wrapper_end() {
463
        return html_writer::end_tag('div');
×
464
    }
465
    /**
466
     * Correct response is provided by each question part.
467
     *
468
     * @param question_attempt $qa the question attempt to display.
469
     * @return string HTML fragment.
470
     */
471
    public function correct_response(question_attempt $qa) {
472
        return '';
19✔
473
    }
474

475
    /**
476
     * Generate an automatic description of the correct response for this part.
477
     *
478
     * @param int $i the part index.
479
     * @param question_attempt $qa the question attempt to display.
480
     * @return string HTML fragment.
481
     */
482
    public function part_correct_response($i, question_attempt $qa) {
483
        $question = $qa->get_question();
19✔
484
        $part = $question->parts[$i];
19✔
485

486
        $correctanswer = $question->format_text($question->correct_response_formatted($part),
19✔
487
                $part->subqtextformat , $qa, 'qtype_formulas', 'answersubqtext', $part->id, false);
19✔
488

489
        if ($part->answernotunique) {
19✔
490
            $string = 'correctansweris';
19✔
491
        } else {
492
            $string = 'uniquecorrectansweris';
×
493
        }
494
        return html_writer::nonempty_tag('div', get_string($string, 'qtype_formulas', $correctanswer),
19✔
495
                    array('class' => 'formulaspartcorrectanswer'));
19✔
496
    }
497

498
    /**
499
     * Generate a brief statement of how many sub-parts of this question the
500
     * student got right.
501
     * @param question_attempt $qa the question attempt to display.
502
     * @return string HTML fragment.
503
     */
504
    protected function num_parts_correct(question_attempt $qa) {
505
        $response = $qa->get_last_qt_data();
76✔
506
        if (!$qa->get_question()->is_gradable_response($response)) {
76✔
507
            return '';
76✔
508
        }
509
        $a = new stdClass();
76✔
510
        list($a->num, $a->outof) = $qa->get_question()->get_num_parts_right(
76✔
511
                $response);
76✔
512
        if (is_null($a->outof)) {
76✔
513
            return '';
×
514
        } else {
515
            return get_string('yougotnright', 'qtype_formulas', $a);
76✔
516
        }
517
    }
518

519
    /**
520
     * We need to owerwrite this method to replace global variables by their value
521
     * @param question_attempt $qa the question attempt to display.
522
     * @return string HTML fragment.
523
     */
524
    protected function hint(question_attempt $qa, question_hint $hint) {
525
        $question = $qa->get_question();
×
526
        $globalvars = $question->get_global_variables();
×
527
        $hint->hint = $question->qv->substitute_variables_in_text($globalvars, $hint->hint);
×
528
        return html_writer::nonempty_tag('div',
×
529
                $qa->get_question()->format_hint($hint, $qa), array('class' => 'hint'));
×
530
    }
531

532
    protected function combined_feedback(question_attempt $qa) {
533
        $question = $qa->get_question();
76✔
534

535
        $state = $qa->get_state();
76✔
536

537
        if (!$state->is_finished()) {
76✔
538
            $response = $qa->get_last_qt_data();
76✔
539
            if (!$qa->get_question()->is_gradable_response($response)) {
76✔
540
                return '';
76✔
541
            }
542
            list($notused, $state) = $qa->get_question()->grade_response($response);
76✔
543
        }
544

545
        $feedback = '';
76✔
546
        $field = $state->get_feedback_class() . 'feedback';
76✔
547
        $format = $state->get_feedback_class() . 'feedbackformat';
76✔
548
        if ($question->$field) {
76✔
549
            $globalvars = $question->get_global_variables();
76✔
550
            $feedback .= $question->formulas_format_text($globalvars, $question->$field, $question->$format,
76✔
551
                    $qa, 'question', $field, $question->id, false);
76✔
552
        }
553

554
        return $feedback;
76✔
555
    }
556

557
    public function specific_feedback(question_attempt $qa) {
558
        return $this->combined_feedback($qa);
76✔
559
    }
560

561
    /**
562
     * @param int $i the part index.
563
     * @param question_attempt $qa the question attempt to display.
564
     * @param question_definition $question the question being displayed.
565
     * @param question_display_options $options controls what should and should not be displayed.
566
     * @return string nicely formatted feedback, for display.
567
     */
568
    protected function part_general_feedback(question_attempt $qa, question_display_options $options, $part) {
569
        if ($part->feedback == '') {
76✔
570
            return '';
76✔
571
        }
572

573
        $feedback = '';
×
574
        $gradingdetails = '';
×
575
        $question = $qa->get_question();
×
576
        $state = $qa->get_state();
×
577

578
        if ($qa->get_behaviour_name() == 'adaptivemultipart') {
×
579
            // This is rather a hack, but it will probably work.
580
            $renderer = $this->page->get_renderer('qbehaviour_adaptivemultipart');
×
581
            $details = $qa->get_behaviour()->get_part_mark_details($part->partindex);
×
582
            $gradingdetails = $renderer->render_adaptive_marks($details, $options);
×
583
            $state = $details->state;
×
584
        }
585
        $showfeedback = $options->feedback && $state->get_feedback_class() != '';
×
586
        if ($showfeedback) {
×
587
            $localvars = $question->get_local_variables($part);
×
588
            $feedbacktext = $question->formulas_format_text(
×
589
              $localvars,
×
590
              $part->feedback,
×
591
              FORMAT_HTML,
×
592
              $qa,
×
593
              'qtype_formulas',
×
594
              'answerfeedback',
×
595
              $part->id,
×
596
              false
×
597
            );
×
598
            $feedback = html_writer::tag('div', $feedbacktext , array('class' => 'feedback formulaslocalfeedback'));
×
599
            return html_writer::nonempty_tag('div', $feedback . $gradingdetails,
×
600
                    array('class' => 'formulaspartfeedback formulaspartfeedback-' . $part->partindex));
×
601
        }
602
        return '';
×
603
    }
604

605
    /**
606
     * @param int $i the part index.
607
     * @param question_attempt $qa the question attempt to display.
608
     * @param question_definition $question the question being displayed.
609
     * @param question_display_options $options controls what should and should not be displayed.
610
     * @return string nicely formatted feedback, for display.
611
     */
612
    protected function part_combined_feedback(question_attempt $qa, question_display_options $options, $part, $fraction) {
613
        $feedback = '';
76✔
614
        $showfeedback = false;
76✔
615
        $gradingdetails = '';
76✔
616
        $question = $qa->get_question();
76✔
617
        $localvars = $question->get_local_variables($part);
76✔
618
        $state = $qa->get_state();
76✔
619
        $feedbackclass = $state->get_feedback_class();
76✔
620

621
        if ($qa->get_behaviour_name() == 'adaptivemultipart') {
76✔
622
            // This is rather a hack, but it will probably work.
623
            $renderer = $this->page->get_renderer('qbehaviour_adaptivemultipart');
76✔
624
            $details = $qa->get_behaviour()->get_part_mark_details($part->partindex);
76✔
625
            $feedbackclass = $details->state->get_feedback_class();
76✔
626
        } else {
627
            $state = question_state::graded_state_for_fraction($fraction);
×
628
            $feedbackclass = $state->get_feedback_class();
×
629
        }
630
        if ($feedbackclass != '') {
76✔
631
            $showfeedback = $options->feedback;
76✔
632
            $field = 'part' . $feedbackclass . 'fb';
76✔
633
            $format = 'part' . $feedbackclass . 'fbformat';
76✔
634
            if ($part->$field) {
76✔
635
                $feedback = $question->formulas_format_text($localvars, $part->$field, $part->$format,
76✔
636
                        $qa, 'qtype_formulas', $field, $part->id, false);
76✔
637
            }
638
        }
639
        if ($showfeedback && $feedback) {
76✔
640
                $feedback = html_writer::tag('div', $feedback , array('class' => 'feedback formulaslocalfeedback'));
76✔
641
                return html_writer::nonempty_tag('div', $feedback,
76✔
642
                        array('class' => 'formulaspartfeedback formulaspartfeedback-' . $part->partindex));
76✔
643
        }
644
        return '';
76✔
645
    }
646
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc