Passed
Pull Request — master (#7124)
by
unknown
09:39
created

ExerciseShowFunctions::display_free_answer()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 6
nop 6
dl 0
loc 22
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/* See license terms in /license.txt */
3
4
use Chamilo\CoreBundle\Entity\TrackEExercise;
5
use Chamilo\CoreBundle\Enums\StateIcon;
6
use Chamilo\CoreBundle\Framework\Container;
7
8
class ExerciseShowFunctions
9
{
10
    /**
11
     * Shows the answer to a fill-in-the-blanks question, as HTML.
12
     *
13
     * @param Exercise $exercise
14
     * @param int      $feedbackType
15
     * @param string   $answer
16
     * @param int      $id                           Exercise ID
17
     * @param int      $questionId                   Question ID
18
     * @param int      $resultsDisabled
19
     * @param bool     $showTotalScoreAndUserChoices
20
     * @param string   $originalStudentAnswer
21
     */
22
    public static function display_fill_in_blanks_answer(
23
        $exercise,
24
        $feedbackType,
25
        $answer,
26
        $id,
27
        $questionId,
28
        $resultsDisabled,
29
        $showTotalScoreAndUserChoices,
30
        $originalStudentAnswer = ''
31
    ) {
32
        $answerHTML = FillBlanks::getHtmlDisplayForAnswer(
33
            $answer,
34
            $feedbackType,
35
            $resultsDisabled,
36
            $showTotalScoreAndUserChoices
37
        );
38
39
        if (empty($id)) {
40
            echo '<tr><td>';
41
            echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
42
            echo '</td></tr>';
43
        } else {
44
            echo '<tr><td>';
45
            echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
46
            echo '</td>';
47
            echo '</tr>';
48
        }
49
    }
50
51
    /**
52
     * Shows the answer to a calculated question, as HTML.
53
     *
54
     *  @param Exercise $exercise
55
     * @param string    Answer text
56
     * @param int       Exercise ID
57
     * @param int       Question ID
58
     */
59
    public static function display_calculated_answer(
60
        $exercise,
61
        $feedback_type,
62
        $answer,
63
        $id,
64
        $questionId,
65
        $resultsDisabled,
66
        $showTotalScoreAndUserChoices,
67
        $expectedChoice = '',
68
        $choice = '',
69
        $status = ''
70
    ) {
71
        if ($exercise->showExpectedChoice()) {
72
            if (empty($id)) {
73
                echo '<tr><td>'.Security::remove_XSS($answer).'</td>';
74
                echo '<td>'.Security::remove_XSS($choice).'</td>';
75
                if ($exercise->showExpectedChoiceColumn()) {
76
                    echo '<td>'.Security::remove_XSS($expectedChoice).'</td>';
77
                }
78
79
                echo '<td>'.Security::remove_XSS($status).'</td>';
80
                echo '</tr>';
81
            } else {
82
                echo '<tr><td>';
83
                echo Security::remove_XSS($answer);
84
                echo '</td><td>';
85
                echo Security::remove_XSS($choice);
86
                echo '</td>';
87
                if ($exercise->showExpectedChoiceColumn()) {
88
                    echo '<td>';
89
                    echo Security::remove_XSS($expectedChoice);
90
                    echo '</td>';
91
                }
92
                echo '<td>';
93
                echo Security::remove_XSS($status);
94
                echo '</td>';
95
                echo '</tr>';
96
            }
97
        } else {
98
            if (empty($id)) {
99
                echo '<tr><td>'.Security::remove_XSS($answer).'</td></tr>';
100
            } else {
101
                echo '<tr><td>';
102
                echo Security::remove_XSS($answer);
103
                echo '</tr>';
104
            }
105
        }
106
    }
107
108
    /**
109
     * Display the uploaded files for an exercise question, using Tailwind-based markup.
110
     *
111
     * This method now relies on getUploadAnswerFiles() which already resolves
112
     * ResourceNode → ResourceFile → public URL.
113
     *
114
     * @param string $feedbackType
115
     * @param string $answer
116
     * @param int    $exeId
117
     * @param int    $questionId
118
     * @param mixed  $questionScore
119
     * @param int    $resultsDisabled
120
     *
121
     * @return void
122
     */
123
    public static function displayUploadAnswer(
124
        string $feedbackType,
125
        string $answer,
126
        int $exeId,
127
        int $questionId,
128
               $questionScore = null,
129
               $resultsDisabled = 0
130
    ) {
131
        // URLs resolved from ResourceNode (resource-based storage)
132
        $urls = ExerciseLib::getUploadAnswerFiles($exeId, $questionId, true);
133
134
        if (!empty($urls)) {
135
            echo '<tr><td>';
136
            echo '<ul class="max-w-3xl rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden divide-y divide-gray-200">';
137
138
            foreach ($urls as $url) {
139
                $path = parse_url($url, PHP_URL_PATH);
140
                $name = $path ? basename($path) : $url;
141
                $safeName = api_htmlentities($name);
142
                $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
143
144
                // Simple inline icon per file type (SVG kept small and neutral)
145
                switch ($ext) {
146
                    case 'pdf':
147
                        $icon = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8l6-6V4a2 2 0 00-2-2H4z"/><path d="M12 14v4l4-4h-4z"/></svg>';
148
                        break;
149
                    case 'jpg':
150
                    case 'jpeg':
151
                    case 'png':
152
                    case 'gif':
153
                    case 'webp':
154
                        $icon = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V8l-5-5H4z"/><path d="M8 13l2-2 3 3H5l2-3z"/></svg>';
155
                        break;
156
                    case 'csv':
157
                    case 'xlsx':
158
                    case 'xls':
159
                        $icon = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8l6-6V4a2 2 0 00-2-2H4z"/><text x="7" y="14" font-size="7" fill="white">X</text></svg>';
160
                        break;
161
                    default:
162
                        $icon = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8l6-6V4a2 2 0 00-2-2H4z"/></svg>';
163
                        break;
164
                }
165
166
                echo '<li class="flex items-center justify-between px-4 py-3 hover:bg-gray-20 transition">';
167
                echo '  <div class="flex items-center gap-3 min-w-0">';
168
                echo '      <span class="inline-flex h-9 w-9 items-center justify-center rounded-md bg-gray-100 text-gray-600">'.$icon.'</span>';
169
                echo '      <a class="truncate font-medium text-blue-600 hover:text-blue-700 hover:underline max-w-[36ch]" href="'.$url.'" target="_blank" rel="noopener noreferrer" title="'.$safeName.'">'.$safeName.'</a>';
170
                echo '  </div>';
171
                echo '  <div class="flex items-center gap-2">';
172
                echo '      <a class="text-sm text-blue-600 hover:text-blue-800" href="'.$url.'" download>Download</a>';
173
                echo '  </div>';
174
                echo '</li>';
175
            }
176
177
            echo '</ul>';
178
            echo '</td></tr>';
179
        }
180
181
        // Feedback / correction status (unchanged logic)
182
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
183
            $comments = Event::get_comments($exeId, $questionId);
0 ignored issues
show
Bug introduced by
The method get_comments() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
            /** @scrutinizer ignore-call */ 
184
            $comments = Event::get_comments($exeId, $questionId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
184
            if ($questionScore > 0 || !empty($comments)) {
185
                // Correction is handled elsewhere.
186
            } else {
187
                echo '<tr>';
188
                echo Display::tag('td', ExerciseLib::getNotCorrectedYetText());
189
                echo '</tr>';
190
            }
191
        }
192
    }
193
194
    /**
195
     * Shows the answer to a free-answer question, as HTML.
196
     *
197
     * @param string    Answer text
198
     * @param int       Exercise ID
199
     * @param int       Question ID
200
     */
201
    public static function display_free_answer(
202
        $feedback_type,
203
        $answer,
204
        $exe_id,
205
        $questionId,
206
        $questionScore = null,
207
        $resultsDisabled = 0
208
    ) {
209
        $comments = Event::get_comments($exe_id, $questionId);
210
211
        if (!empty($answer)) {
212
            echo '<tr><td>';
213
            echo Security::remove_XSS($answer);
214
            echo '</td></tr>';
215
        }
216
217
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedback_type) {
218
            if ($questionScore > 0 || !empty($comments)) {
219
            } else {
220
                echo '<tr>';
221
                echo Display::tag('td', ExerciseLib::getNotCorrectedYetText());
222
                echo '</tr>';
223
            }
224
        }
225
    }
226
227
    /**
228
     * Display oral expression answer: student audio, text answer, and correction status.
229
     *
230
     * @param string $feedback_type
231
     * @param string $answer
232
     * @param int    $trackExerciseId
233
     * @param int    $questionId
234
     * @param int    $resultsDisabled
235
     * @param float  $questionScore
236
     * @param bool   $showAlertIfNotCorrected
237
     *
238
     * @return void
239
     */
240
    public static function display_oral_expression_answer(
241
        $feedback_type,
242
        $answer,
243
        $trackExerciseId,
244
        $questionId,
245
        $resultsDisabled = 0,
246
        $questionScore = 0,
247
        $showAlertIfNotCorrected = false
248
    ) {
249
        /** @var TrackEExercise|null $trackExercise */
250
        $trackExercise = Container::getTrackEExerciseRepository()->find($trackExerciseId);
251
252
        if (null === $trackExercise) {
253
            return;
254
        }
255
256
        // Student recorded answer(s)
257
        // getOralFileAudio() ya NO debe incluir los ResourceNode usados en AttemptFeedback.
258
        $studentAudioHtml = ExerciseLib::getOralFileAudio($trackExerciseId, $questionId);
259
260
        if (!empty($studentAudioHtml)) {
261
            echo $studentAudioHtml;
262
        }
263
264
        // Optional text answer written by the student
265
        if (!empty($answer)) {
266
            echo Display::tag('p', Security::remove_XSS($answer));
267
        }
268
269
        // Teacher correction: comments and audio feedback
270
        $comment = Event::get_comments($trackExerciseId, $questionId);
271
        $teacherAudio = ExerciseLib::getOralFeedbackAudio(
272
            $trackExerciseId,
273
            $questionId
274
        );
275
276
        if (!empty($teacherAudio)) {
277
            echo $teacherAudio;
278
        }
279
280
        if (!empty($comment)) {
281
            echo Display::tag('p', Security::remove_XSS($comment));
282
        }
283
284
        // Optional "not corrected yet" warning
285
        if (
286
            $showAlertIfNotCorrected &&
287
            !$questionScore &&
288
            EXERCISE_FEEDBACK_TYPE_EXAM != $feedback_type &&
289
            empty($comment) &&
290
            empty($teacherAudio)
291
        ) {
292
            echo Display::tag('p', ExerciseLib::getNotCorrectedYetText());
293
        }
294
    }
295
296
    /**
297
     * Displays the answer to a hotspot question.
298
     *
299
     * @param int    $feedback_type
300
     * @param int    $answerId
301
     * @param string $answer
302
     * @param string $studentChoice
303
     * @param string $answerComment
304
     * @param int    $resultsDisabled
305
     * @param int    $orderColor
306
     * @param bool   $showTotalScoreAndUserChoices
307
     */
308
    public static function display_hotspot_answer(
309
        $exercise,
310
        $feedback_type,
311
        $answerId,
312
        $answer,
313
        $studentChoice,
314
        $answerComment,
315
        $resultsDisabled,
316
        $orderColor,
317
        $showTotalScoreAndUserChoices
318
    ) {
319
        $hide_expected_answer = false;
320
        switch ($resultsDisabled) {
321
            case RESULT_DISABLE_SHOW_SCORE_ONLY:
322
                if (0 == $feedback_type) {
323
                    $hide_expected_answer = true;
324
                }
325
                break;
326
            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
327
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
328
                $hide_expected_answer = true;
329
                if ($showTotalScoreAndUserChoices) {
330
                    $hide_expected_answer = false;
331
                }
332
                break;
333
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
334
                $hide_expected_answer = true;
335
                if ($showTotalScoreAndUserChoices) {
336
                    $hide_expected_answer = false;
337
                }
338
                if (false === $showTotalScoreAndUserChoices && empty($studentChoice)) {
339
                    return '';
340
                }
341
                break;
342
        }
343
344
        if (!$hide_expected_answer
345
            && !$studentChoice
346
            && in_array($resultsDisabled, [RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER])
347
        ) {
348
            return;
349
        }
350
351
        $hotspotColors = [
352
            '', // $i starts from 1 on next loop (ugly fix)
353
            '#4271B5',
354
            '#FE8E16',
355
            '#45C7F0',
356
            '#BCD631',
357
            '#D63173',
358
            '#D7D7D7',
359
            '#90AFDD',
360
            '#AF8640',
361
            '#4F9242',
362
            '#F4EB24',
363
            '#ED2024',
364
            '#3B3B3B',
365
            '#F7BDE2',
366
        ];
367
368
        $content = '<tr>';
369
        $content .= '<td class="text-center" width="5%">';
370
        $content .= '<span class="fa fa-square fa-fw fa-2x" aria-hidden="true" style="color:'.
371
            $hotspotColors[$orderColor].'"></span>';
372
        $content .= '</td>';
373
        $content .= '<td class="text-left" width="25%">';
374
        $content .= "$answerId - $answer";
375
        $content .= '</td>';
376
        if (false === $exercise->hideComment) {
377
            $content .= '<td class="text-left" width="10%">';
378
            if (!$hide_expected_answer) {
379
                $status = Display::label(get_lang('Incorrect'), 'danger');
380
                if ($studentChoice) {
381
                    $status = Display::label(get_lang('Correct'), 'success');
382
                }
383
                $content .= $status;
384
            } else {
385
                $content .= '&nbsp;';
386
            }
387
            $content .= '</td>';
388
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedback_type) {
389
                $content .= '<td class="text-left" width="60%">';
390
                if ($studentChoice) {
391
                    $content .= '<span style="font-weight: bold; color: #008000;">'.nl2br($answerComment).'</span>';
392
                } else {
393
                    $content .= '&nbsp;';
394
                }
395
                $content .= '</td>';
396
            } else {
397
                $content .= '<td class="text-left" width="60%">&nbsp;</td>';
398
            }
399
        }
400
        $content .= '</tr>';
401
402
        echo $content;
403
    }
404
405
    /**
406
     * Display the answers to a multiple choice question.
407
     *
408
     * @param Exercise $exercise
409
     * @param int      $feedbackType                 Feedback type
410
     * @param int      $answerType                   Answer type
411
     * @param int      $studentChoice                Student choice
412
     * @param string   $answer                       Textual answer
413
     * @param string   $answerComment                Comment on answer
414
     * @param string   $answerCorrect                Correct answer comment
415
     * @param int      $id                           Exercise ID
416
     * @param int      $questionId                   Question ID
417
     * @param bool     $ans                          Whether to show the answer comment or not
418
     * @param bool     $resultsDisabled
419
     * @param bool     $showTotalScoreAndUserChoices
420
     * @param bool     $export
421
     */
422
    public static function display_unique_or_multiple_answer(
423
        $exercise,
424
        $feedbackType,
425
        $answerType,
426
        $studentChoice,
427
        $answer,
428
        $answerComment,
429
        $answerCorrect,
430
        $id,
431
        $questionId,
432
        $ans,
433
        $resultsDisabled,
434
        $showTotalScoreAndUserChoices,
435
        $export = false
436
    ) {
437
        if (true === $exercise->hideNoAnswer && empty($studentChoice)) {
438
            return '';
439
        }
440
        if ($export) {
441
            $answer = strip_tags_blacklist($answer, ['title', 'head']);
442
            // Fix answers that contains this tags
443
            $tags = [
444
                '<html>',
445
                '</html>',
446
                '<body>',
447
                '</body>',
448
            ];
449
            $answer = str_replace($tags, '', $answer);
450
        }
451
452
        $studentChoiceInt = (int) $studentChoice;
453
        $answerCorrectChoice = (int) $answerCorrect;
454
        $hide_expected_answer = false;
455
        $showComment = false;
456
        switch ($resultsDisabled) {
457
            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
458
                $hide_expected_answer = true;
459
                $showComment = true;
460
                if (!$answerCorrect && empty($studentChoice)) {
461
                    return '';
462
                }
463
                break;
464
            case RESULT_DISABLE_SHOW_SCORE_ONLY:
465
                if (0 == $feedbackType) {
466
                    $hide_expected_answer = true;
467
                }
468
                break;
469
            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
470
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
471
                $hide_expected_answer = true;
472
                if ($showTotalScoreAndUserChoices) {
473
                    $hide_expected_answer = false;
474
                }
475
                break;
476
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
477
                if (false === $showTotalScoreAndUserChoices && empty($studentChoiceInt)) {
478
                    return '';
479
                }
480
                break;
481
        }
482
483
        if (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION])) {
484
            if ($studentChoice) {
485
                $icon = StateIcon::RADIOBOX_MARKED;
486
            } else {
487
                $icon = StateIcon::RADIOBOX_BLANK;
488
            }
489
        } else {
490
            if ($studentChoice) {
491
                $icon = StateIcon::CHECKBOX_MARKED;
492
            } else {
493
                $icon = StateIcon::CHECKBOX_BLANK;
494
            }
495
        }
496
        if (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION])) {
497
            if ($answerCorrect) {
498
                $iconAnswer = StateIcon::RADIOBOX_MARKED;
499
            } else {
500
                $iconAnswer = StateIcon::RADIOBOX_BLANK;
501
            }
502
        } else {
503
            if ($answerCorrect) {
504
                $iconAnswer = StateIcon::CHECKBOX_MARKED;
505
            } else {
506
                $iconAnswer = StateIcon::CHECKBOX_BLANK;
507
            }
508
509
        }
510
511
        $studentChoiceClass = '';
512
        if (in_array(
513
            $resultsDisabled,
514
            [
515
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
516
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
517
            ]
518
        )
519
        ) {
520
            if ($answerCorrect) {
521
                $studentChoiceClass = 'success';
522
            }
523
        }
524
525
        echo '<tr class="'.$studentChoiceClass.'">';
526
527
        echo '<td style="width:5%">';
528
        echo Display::getMdiIcon($icon, 'ch-tool-icon', null, ICON_SIZE_TINY);
529
        echo '</td>';
530
        if ($exercise->showExpectedChoiceColumn()) {
531
            if (false === $hide_expected_answer) {
532
                echo '<td style="width:5%">';
533
                echo Display::getMdiIcon($iconAnswer, 'ch-tool-icon', null, ICON_SIZE_TINY);
534
                echo '</td>';
535
            } else {
536
                echo '<td style="width:5%">';
537
                echo '-';
538
                echo '</td>';
539
            }
540
        }
541
542
        echo '<td style="width:40%">';
543
        echo $answer;
544
        echo '</td>';
545
546
        if ($exercise->showExpectedChoice()) {
547
            $status = Display::label(get_lang('Incorrect'), 'danger');
548
            if ($answerCorrect || ($answerCorrect && $studentChoiceInt === $answerCorrectChoice)) {
549
                $status = Display::label(get_lang('Correct'), 'success');
550
            }
551
            echo '<td class="text-center">';
552
            // Show only status for the selected student answer BT#16256
553
            if ($studentChoice) {
554
                echo $status;
555
            }
556
557
            echo '</td>';
558
        }
559
560
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
561
            $showComment = true;
562
        }
563
564
        if (false === $exercise->hideComment) {
565
            if ($showComment) {
566
                echo '<td style="width:20%">';
567
                $color = 'black';
568
                if ($answerCorrect) {
569
                    $color = 'green';
570
                }
571
                if ($hide_expected_answer) {
572
                    $color = '';
573
                }
574
                $comment = '<span style="font-weight: bold; color: '.$color.';">'.
575
                Security::remove_XSS($answerComment).
576
                '</span>';
577
                echo $comment;
578
                echo '</td>';
579
            } else {
580
                echo '<td>&nbsp;</td>';
581
            }
582
        }
583
584
        echo '</tr>';
585
    }
586
587
    /**
588
     * Display the answers to a multiple choice question.
589
     *
590
     * @param Exercise $exercise
591
     * @param int $feedbackType Feedback type
592
     * @param int $answerType Answer type
593
     * @param int $studentChoice Student choice
594
     * @param string  $answer Textual answer
595
     * @param string  $answerComment Comment on answer
596
     * @param string  $answerCorrect Correct answer comment
597
     * @param int $id Exercise ID
598
     * @param int $questionId Question ID
599
     * @param bool $ans Whether to show the answer comment or not
600
     * @param int $resultsDisabled
601
     * @param bool $showTotalScoreAndUserChoices
602
     */
603
    public static function display_multiple_answer_true_false(
604
        $exercise,
605
        $feedbackType,
606
        $answerType,
607
        $studentChoice,
608
        $answer,
609
        $answerComment,
610
        $answerCorrect,
611
        $id,
612
        $questionId,
613
        $ans,
614
        $resultsDisabled,
615
        $showTotalScoreAndUserChoices
616
    ) {
617
        $hide_expected_answer = false;
618
        $hideStudentChoice = false;
619
        switch ($resultsDisabled) {
620
            //case RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING:
621
            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
622
                $hideStudentChoice = false;
623
                $hide_expected_answer = true;
624
                break;
625
            case RESULT_DISABLE_SHOW_SCORE_ONLY:
626
                if (0 == $feedbackType) {
627
                    $hide_expected_answer = true;
628
                }
629
                break;
630
            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
631
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
632
                $hide_expected_answer = true;
633
                if ($showTotalScoreAndUserChoices) {
634
                    $hide_expected_answer = false;
635
                }
636
                break;
637
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
638
                if (false === $showTotalScoreAndUserChoices && empty($studentChoice)) {
639
                    return '';
640
                }
641
                break;
642
        }
643
644
        $content = '<tr>';
645
        if (false === $hideStudentChoice) {
646
            $content .= '<td width="5%">';
647
            $course_id = api_get_course_int_id();
648
            $new_options = [];
649
            $originOptions = Question::readQuestionOption($questionId);
650
651
            if (!empty($originOptions)) {
652
                foreach ($originOptions as $item) {
653
                    $new_options[$item['iid']] = $item;
654
                }
655
            }
656
657
            // Your choice
658
            if (isset($new_options[$studentChoice])) {
659
                $content .= get_lang($new_options[$studentChoice]['title']);
660
            } else {
661
                $content .= '-';
662
            }
663
            $content .= '</td>';
664
        }
665
666
        // Expected choice
667
        if ($exercise->showExpectedChoiceColumn()) {
668
            if (!$hide_expected_answer) {
669
                $content .= '<td width="5%">';
670
                if (isset($new_options[$answerCorrect])) {
671
                    $content .= get_lang($new_options[$answerCorrect]['title']);
672
                } else {
673
                    $content .= '-';
674
                }
675
                $content .= '</td>';
676
            }
677
        }
678
679
        $content .= '<td width="40%">';
680
        $content .= $answer;
681
        $content .= '</td>';
682
683
        if ($exercise->showExpectedChoice()) {
684
            $status = Display::label(get_lang('Incorrect'), 'danger');
685
            if (isset($new_options[$studentChoice])) {
686
                if ($studentChoice == $answerCorrect) {
687
                    $status = Display::label(get_lang('Correct'), 'success');
688
                }
689
            }
690
            $content .= '<td class="text-center">';
691
            $content .= $status;
692
            $content .= '</td>';
693
        }
694
695
        if (false === $exercise->hideComment) {
696
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
697
                $content .= '<td width="20%">';
698
                $color = 'black';
699
                if (isset($new_options[$studentChoice]) || in_array(
700
                    $exercise->results_disabled,
701
                    [
702
                        RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
703
                        RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
704
                    ]
705
                )
706
            ) {
707
                    if ($studentChoice == $answerCorrect) {
708
                        $color = 'green';
709
                    }
710
711
                    if ($hide_expected_answer) {
712
                        $color = '';
713
                    }
714
                    $content .= '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
715
                }
716
                $content .= '</td>';
717
            }
718
        }
719
        $content .= '</tr>';
720
721
        echo $content;
722
    }
723
724
    /**
725
     * Display the answers to a multiple choice question.
726
     *
727
     * @param Exercise $exercise
728
     * @param int      $feedbackType
729
     * @param int      $studentChoice
730
     * @param int      $studentChoiceDegree
731
     * @param string   $answer
732
     * @param string   $answerComment
733
     * @param int      $answerCorrect
734
     * @param int      $questionId
735
     * @param bool     $inResultsDisabled
736
     */
737
    public static function displayMultipleAnswerTrueFalseDegreeCertainty(
738
        $exercise,
739
        $feedbackType,
740
        $studentChoice,
741
        $studentChoiceDegree,
742
        $answer,
743
        $answerComment,
744
        $answerCorrect,
745
        $questionId,
746
        $inResultsDisabled
747
    ) {
748
        $hideExpectedAnswer = false;
749
        if (0 == $feedbackType && 2 == $inResultsDisabled) {
750
            $hideExpectedAnswer = true;
751
        }
752
753
        echo '<tr><td width="5%">';
754
        $question = new MultipleAnswerTrueFalseDegreeCertainty();
755
        $courseId = api_get_course_int_id();
756
        $newOptions = Question::readQuestionOption($questionId, $courseId);
757
758
        // Your choice
759
        if (isset($newOptions[$studentChoice])) {
760
            echo get_lang($newOptions[$studentChoice]['name']);
761
        } else {
762
            echo '-';
763
        }
764
        echo '</td>';
765
766
        // Expected choice
767
        if ($exercise->showExpectedChoiceColumn()) {
768
            echo '<td width="5%">';
769
            if (!$hideExpectedAnswer) {
770
                if (isset($newOptions[$answerCorrect])) {
771
                    echo get_lang($newOptions[$answerCorrect]['name']);
772
                } else {
773
                    echo '-';
774
                }
775
            } else {
776
                echo '-';
777
            }
778
            echo '</td>';
779
        }
780
781
        echo '<td width="20%">';
782
        echo $answer;
783
        echo '</td><td width="5%" style="text-align:center;">';
784
        if (isset($newOptions[$studentChoiceDegree])) {
785
            echo $newOptions[$studentChoiceDegree]['name'];
786
        }
787
        echo '</td>';
788
789
        $position = isset($newOptions[$studentChoiceDegree]) ? $newOptions[$studentChoiceDegree]['position'] : '';
790
        $degreeInfo = $question->getResponseDegreeInfo(
791
            $studentChoice,
792
            $answerCorrect,
793
            $position
794
        );
795
796
        $degreeInfo['color'] = isset($degreeInfo['color']) ? $degreeInfo['color'] : '';
797
        $degreeInfo['background-color'] = isset($degreeInfo['background-color']) ? $degreeInfo['background-color'] : '';
798
        $degreeInfo['description'] = isset($degreeInfo['description']) ? $degreeInfo['description'] : '';
799
        $degreeInfo['label'] = isset($degreeInfo['label']) ? $degreeInfo['label'] : '';
800
801
        echo '
802
            <td width="15%">
803
                <div style="text-align:center;color: '.$degreeInfo['color'].';
804
                    background-color: '.$degreeInfo['background-color'].';
805
                    line-height:30px;height:30px;width: 100%;margin:auto;"
806
                    title="'.$degreeInfo['description'].'">'.
807
                    nl2br($degreeInfo['label']).
808
                '</div>
809
            </td>';
810
811
        if (false === $exercise->hideComment) {
812
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
813
                echo '<td width="20%">';
814
                if (isset($newOptions[$studentChoice])) {
815
                    echo '<span style="font-weight: bold; color: black;">'.nl2br($answerComment).'</span>';
816
                }
817
                echo '</td>';
818
            } else {
819
                echo '<td>&nbsp;</td>';
820
            }
821
        }
822
        echo '</tr>';
823
    }
824
825
    /**
826
     * Display the answers to a multiple choice question.
827
     *
828
     * @param Exercise $exercise
829
     * @param int Answer type
830
     * @param int Student choice
831
     * @param string  Textual answer
832
     * @param string  Comment on answer
833
     * @param string  Correct answer comment
834
     * @param int Exercise ID
835
     * @param int Question ID
836
     * @param bool Whether to show the answer comment or not
837
     */
838
    public static function display_multiple_answer_combination_true_false(
839
        $exercise,
840
        $feedbackType,
841
        $answerType,
842
        $studentChoice,
843
        $answer,
844
        $answerComment,
845
        $answerCorrect,
846
        $id,
847
        $questionId,
848
        $ans,
849
        $resultsDisabled,
850
        $showTotalScoreAndUserChoices
851
    ) {
852
        $hide_expected_answer = false;
853
        $hideStudentChoice = false;
854
        switch ($resultsDisabled) {
855
            case RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING:
856
            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
857
                $hideStudentChoice = true;
858
                $hide_expected_answer = true;
859
                break;
860
            case RESULT_DISABLE_SHOW_SCORE_ONLY:
861
                if (0 == $feedbackType) {
862
                    $hide_expected_answer = true;
863
                }
864
                break;
865
            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
866
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
867
                $hide_expected_answer = true;
868
                if ($showTotalScoreAndUserChoices) {
869
                    $hide_expected_answer = false;
870
                }
871
                break;
872
            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
873
                if (false === $showTotalScoreAndUserChoices && empty($studentChoice)) {
874
                    return '';
875
                }
876
                break;
877
        }
878
879
        echo '<tr>';
880
881
        if (false === $hideStudentChoice) {
882
            echo '<td width="5%">';
883
            // Your choice
884
            $question = new MultipleAnswerCombinationTrueFalse();
885
            if (isset($question->options[$studentChoice])) {
886
                echo $question->options[$studentChoice];
887
            } else {
888
                echo $question->options[2];
889
            }
890
            echo '</td>';
891
        }
892
893
        // Expected choice
894
        if ($exercise->showExpectedChoiceColumn()) {
895
            if (!$hide_expected_answer) {
896
                echo '<td width="5%">';
897
                if (isset($question->options[$answerCorrect])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $question does not seem to be defined for all execution paths leading up to this point.
Loading history...
898
                    echo $question->options[$answerCorrect];
899
                } else {
900
                    echo $question->options[2];
901
                }
902
                echo '</td>';
903
            }
904
        }
905
906
        echo '<td width="40%">';
907
        echo $answer;
908
        echo '</td>';
909
910
        if ($exercise->showExpectedChoice()) {
911
            $status = '';
912
            if (isset($studentChoice)) {
913
                $status = Display::label(get_lang('Incorrect'), 'danger');
914
                if ($studentChoice == $answerCorrect) {
915
                    $status = Display::label(get_lang('Correct'), 'success');
916
                }
917
            }
918
            echo '<td class="text-center">';
919
            echo $status;
920
            echo '</td>';
921
        }
922
923
        if (false === $exercise->hideComment) {
924
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
925
                echo '<td width="20%">';
926
                //@todo replace this harcoded value
927
                if ($studentChoice || in_array(
928
                        $resultsDisabled,
929
                        [
930
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
931
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
932
                        ]
933
                    )
934
            ) {
935
                    $color = 'black';
936
                    if ($studentChoice == $answerCorrect) {
937
                        $color = 'green';
938
                    }
939
                    if ($hide_expected_answer) {
940
                        $color = '';
941
                    }
942
                    echo '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
943
                }
944
                echo '</td>';
945
            } else {
946
                echo '<td>&nbsp;</td>';
947
            }
948
        }
949
        echo '</tr>';
950
    }
951
952
    /**
953
     * @param int  $feedbackType
954
     * @param int  $exeId
955
     * @param int  $questionId
956
     * @param null $questionScore
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $questionScore is correct as it would always require null to be passed?
Loading history...
957
     * @param int  $resultsDisabled
958
     */
959
    public static function displayAnnotationAnswer(
960
        $feedbackType,
961
        $exeId,
962
        $questionId,
963
        $questionScore = null,
964
        $resultsDisabled = 0
965
    ) {
966
        $comments = Event::get_comments($exeId, $questionId);
967
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
968
            if ($questionScore <= 0 && empty($comments)) {
969
                echo '<br />'.ExerciseLib::getNotCorrectedYetText();
970
            }
971
        }
972
    }
973
974
    /**
975
     * Displays the answers for a Multiple Answer Dropdown question (result view).
976
     * Renders a row per choice, showing: student choice, expected choice (if allowed),
977
     * the textual answer, and status (Correct/Incorrect).
978
     *
979
     * Returned string contains <tr>...</tr> rows to be echoed in the answer table.
980
     */
981
    public static function displayMultipleAnswerDropdown(
982
        Exercise $exercise,
983
        Answer $answer,
984
        array $correctAnswers,
985
        array $studentChoices,
986
        bool $showTotalScoreAndUserChoices = true
987
    ): string {
988
        // Hide if teacher wants to hide empty answers and user gave no answer
989
        if (true === $exercise->hideNoAnswer && empty($studentChoices)) {
990
            return '';
991
        }
992
993
        // Normalize inputs
994
        $correctAnswers = array_map('intval', (array) $correctAnswers);
995
        $studentChoices = array_map(
996
            'intval',
997
            array_filter((array) $studentChoices, static fn ($v) => $v !== '' && $v !== null && (int)$v !== -1)
998
        );
999
1000
        // Build id => text map from Answer::getAnswers()
1001
        // getAnswers() typically returns rows with keys: iid, answer, correct, comment, weighting, position
1002
        $idToText = [];
1003
        if (method_exists($answer, 'getAnswers')) {
1004
            $rows = $answer->getAnswers();
1005
            if (is_array($rows)) {
1006
                foreach ($rows as $row) {
1007
                    if (isset($row['iid'])) {
1008
                        $id = (int) $row['iid'];
1009
                        $idToText[$id] = $row['answer'] ?? '';
1010
                    }
1011
                }
1012
            }
1013
        }
1014
1015
        // Union of expected + student choices to render a single row per unique option
1016
        $allChoices = array_values(array_unique(array_merge($correctAnswers, $studentChoices)));
1017
        sort($allChoices);
1018
1019
        // Icons/labels
1020
        $checkboxOn  = Display::getMdiIcon(StateIcon::CHECKBOX_MARKED, 'ch-tool-icon', null, ICON_SIZE_TINY);
1021
        $checkboxOff = Display::getMdiIcon(StateIcon::CHECKBOX_BLANK,  'ch-tool-icon', null, ICON_SIZE_TINY);
1022
        $labelOk     = Display::label(get_lang('Correct'), 'success');
1023
        $labelKo     = Display::label(get_lang('Incorrect'), 'danger');
1024
1025
        $html = '';
1026
1027
        foreach ($allChoices as $choiceId) {
1028
            $isStudentAnswer  = in_array($choiceId, $studentChoices, true);
1029
            $isExpectedAnswer = in_array($choiceId, $correctAnswers, true);
1030
            $isCorrectAnswer  = $isStudentAnswer && $isExpectedAnswer;
1031
1032
            // Resolve displayed text safely; fall back to "None" if not found
1033
            $answerText = $idToText[$choiceId] ?? get_lang('None');
1034
1035
            if ($exercise->export) {
1036
                // Strip potentially problematic wrappers on export
1037
                $answerText = strip_tags_blacklist($answerText, ['title', 'head']);
1038
                $answerText = str_replace(['<html>', '</html>', '<body>', '</body>'], '', $answerText);
1039
            }
1040
1041
            // Respect result-visibility policy
1042
            $hideExpected = false;
1043
            switch ($exercise->selectResultsDisabled()) {
1044
                case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
1045
                    $hideExpected = true;
1046
                    if (!$isCorrectAnswer && empty($studentChoices)) {
1047
                        continue 2;
1048
                    }
1049
                    break;
1050
                case RESULT_DISABLE_SHOW_SCORE_ONLY:
1051
                    if (0 == $exercise->getFeedbackType()) {
1052
                        $hideExpected = true;
1053
                    }
1054
                    break;
1055
                case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
1056
                case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
1057
                    $hideExpected = true;
1058
                    if ($showTotalScoreAndUserChoices) {
1059
                        $hideExpected = false;
1060
                    }
1061
                    break;
1062
                case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
1063
                    if (false === $showTotalScoreAndUserChoices && empty($studentChoices)) {
1064
                        continue 2;
1065
                    }
1066
                    break;
1067
            }
1068
1069
            // Highlight only when policy requires and the student/expected match
1070
            $rowClass = '';
1071
            if ($isCorrectAnswer
1072
                && in_array(
1073
                    $exercise->selectResultsDisabled(),
1074
                    [RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING],
1075
                    true
1076
                )
1077
            ) {
1078
                $rowClass = 'success';
1079
            }
1080
1081
            $html .= '<tr class="'.$rowClass.'">';
1082
1083
            // Student choice icon
1084
            $html .= '<td class="text-center">'.($isStudentAnswer ? $checkboxOn : $checkboxOff).'</td>';
1085
1086
            // Expected choice icon (optional)
1087
            if ($exercise->showExpectedChoiceColumn()) {
1088
                $html .= '<td class="text-center">';
1089
                $html .= $hideExpected ? '<span class="text-muted">&mdash;</span>' : ($isExpectedAnswer ? $checkboxOn : $checkboxOff);
1090
                $html .= '</td>';
1091
            }
1092
1093
            // Answer text
1094
            $html .= '<td>'.Security::remove_XSS($answerText).'</td>';
1095
1096
            // Status (optional)
1097
            if ($exercise->showExpectedChoice()) {
1098
                $html .= '<td class="text-center">'.($isCorrectAnswer ? $labelOk : $labelKo).'</td>';
1099
            }
1100
1101
            $html .= '</tr>';
1102
        }
1103
1104
        return $html;
1105
    }
1106
}
1107