Completed
Push — master ( 4e608a...fcc2ac )
by
unknown
02:01 queued 37s
created

ExerciseShowFunctions::displayUploadAnswer()   C

Complexity

Conditions 16
Paths 6

Size

Total Lines 67
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 46
nop 6
dl 0
loc 67
rs 5.5666
c 0
b 0
f 0
nc 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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