Passed
Pull Request — master (#6637)
by
unknown
08:33
created

ExerciseShowFunctions   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 1091
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 566
dl 0
loc 1091
rs 2
c 0
b 0
f 0
wmc 198

12 Methods

Rating   Name   Duplication   Size   Complexity  
A display_fill_in_blanks_answer() 0 26 2
B display_calculated_answer() 0 45 6
B display_oral_expression_answer() 0 47 10
F display_multiple_answer_combination_true_false() 0 112 25
D displayUploadAnswer() 0 81 20
A display_free_answer() 0 22 5
A displayAnnotationAnswer() 0 11 4
F display_unique_or_multiple_answer() 0 163 35
F display_multiple_answer_true_false() 0 119 26
F displayMultipleAnswerTrueFalseDegreeCertainty() 0 86 16
F displayMultipleAnswerDropdown() 0 124 31
D display_hotspot_answer() 0 95 18

How to fix   Complexity   

Complex Class

Complex classes like ExerciseShowFunctions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExerciseShowFunctions, and based on these observations, apply Extract Interface, too.

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
     * Shows the answer to an upload question.
110
     *
111
     * @param float|null $questionScore   Only used to check if > 0
112
     * @param int        $resultsDisabled Unused
113
     */
114
    public static function displayUploadAnswer(
115
        string $feedbackType,
116
        string $answer,
117
        int $exeId,
118
        int $questionId,
119
               $questionScore = null,
120
               $resultsDisabled = 0
121
    ) {
122
        $urls = [];
123
124
        $trackExercise = Container::getTrackEExerciseRepository()->find($exeId);
125
        if (null !== $trackExercise) {
126
            $questionAttempt = $trackExercise->getAttemptByQuestionId($questionId);
127
            if (null !== $questionAttempt) {
128
                $assetRepo = Container::getAssetRepository();
129
                $basePath  = rtrim(api_get_path(WEB_PATH), '/');
130
131
                foreach ($questionAttempt->getAttemptFiles() as $attemptFile) {
132
                    $asset = $attemptFile->getAsset();
133
                    if (null !== $asset) {
134
                        $urls[] = $basePath.$assetRepo->getAssetUrl($asset);
135
                    }
136
                }
137
            }
138
        }
139
140
        if (!empty($urls)) {
141
            echo '<tr><td>';
142
            echo '<ul class="max-w-3xl rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden divide-y divide-gray-200">';
143
144
            foreach ($urls as $url) {
145
                $path = parse_url($url, PHP_URL_PATH);
146
                $name = $path ? basename($path) : $url;
147
                $safeName = api_htmlentities($name);
148
                $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
149
150
                // Simple inline icon per file type (SVGs kept tiny)
151
                switch ($ext) {
152
                    case 'pdf':
153
                        $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>';
154
                        break;
155
                    case 'jpg':
156
                    case 'jpeg':
157
                    case 'png':
158
                    case 'gif':
159
                    case 'webp':
160
                        $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>';
161
                        break;
162
                    case 'csv':
163
                    case 'xlsx':
164
                    case 'xls':
165
                        $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>';
166
                        break;
167
                    default:
168
                        $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>';
169
                        break;
170
                }
171
172
                echo '<li class="flex items-center justify-between px-4 py-3 hover:bg-gray-20 transition">';
173
                echo '  <div class="flex items-center gap-3 min-w-0">';
174
                echo '      <span class="inline-flex h-9 w-9 items-center justify-center rounded-md bg-gray-100 text-gray-600">'.$icon.'</span>';
175
                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>';
176
                echo '  </div>';
177
                echo '  <div class="flex items-center gap-2">';
178
                echo '      <a class="text-sm text-blue-600 hover:text-blue-800" href="'.$url.'" download>Download</a>';
179
                echo '  </div>';
180
                echo '</li>';
181
            }
182
183
            echo '</ul>';
184
            echo '</td></tr>';
185
        }
186
187
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $feedbackType) {
188
            $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

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