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

ExerciseShowFunctions::display_calculated_answer()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 45
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 29
nc 6
nop 10
dl 0
loc 45
rs 8.8337
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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