displayDegreeChartChildren()   F
last analyzed

Complexity

Conditions 20
Paths 15360

Size

Total Lines 169
Code Lines 110

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 110
nc 15360
nop 9
dl 0
loc 169
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

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
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Framework\Container;
6
use Chamilo\CourseBundle\Entity\CQuizQuestion;
7
use ChamiloSession as Session;
8
9
/**
10
 * Class MultipleAnswerTrueFalseDegreeCertainty
11
 * This class allows to instantiate an object of type MULTIPLE_ANSWER
12
 * (MULTIPLE CHOICE, MULTIPLE ANSWER), extending the class question.
13
 */
14
class MultipleAnswerTrueFalseDegreeCertainty extends Question
15
{
16
    public const LEVEL_DARKGREEN = 1;
17
    public const LEVEL_LIGHTGREEN = 2;
18
    public const LEVEL_WHITE = 3;
19
    public const LEVEL_LIGHTRED = 4;
20
    public const LEVEL_DARKRED = 5;
21
22
    public $typePicture = 'mccert.png';
23
    public $explanationLangVar = 'Multiple answer true/false/degree of certainty';
24
    public $optionsTitle;
25
    public $options;
26
27
    public function __construct()
28
    {
29
        parent::__construct();
30
        $this->type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY;
31
        $this->isContent = $this->getIsContent();
32
        $this->optionsTitle = [1 => 'Answers', 2 => 'DegreeOfCertaintyThatMyAnswerIsCorrect'];
33
        $this->options = [
34
            1 => 'True',
35
            2 => 'False',
36
            3 => '50%',
37
            4 => '60%',
38
            5 => '70%',
39
            6 => '80%',
40
            7 => '90%',
41
            8 => '100%',
42
        ];
43
    }
44
45
    /**
46
     * Redefines Question::createAnswersForm: creates the HTML form to answer the question.
47
     *
48
     * @param FormValidator $form
49
     */
50
    public function createAnswersForm($form)
51
    {
52
        global $text;
53
54
        $nbAnswers = (int) ($_POST['nb_answers'] ?? 4);
55
        // The previous default value was 2. See task #1759.
56
        $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
57
58
        $courseId = api_get_course_int_id();
59
        $objEx = Session::read('objExercise');
60
        $renderer = &$form->defaultRenderer();
61
        $defaults = [];
62
63
        $form->addHeader(get_lang('Answers'));
64
65
        // Force native radios visibility even if global CSS resets form controls.
66
        $choiceInputAttrs = [
67
            'class' => 'h-4 w-4 cursor-pointer',
68
            'style' => 'appearance:auto;-webkit-appearance:auto;',
69
        ];
70
71
        // Determine if options exist already (edit) or not (first creation).
72
        $hasOptions = false;
73
        if (!empty($this->id)) {
74
            try {
75
                $opt = Question::readQuestionOption($this->id, $courseId);
76
            } catch (\Throwable $e) {
77
                $opt = Question::readQuestionOption($this->id);
78
            }
79
            $hasOptions = !empty($opt);
80
        }
81
82
        $tfIids = $this->getTrueFalseOptionIids($courseId);
83
84
        $html = '<table class="table table-striped table-hover">';
85
        $html .= '<thead><tr>';
86
        $html .= '<th width="10px">'.get_lang('number').'</th>';
87
        $html .= '<th width="10px">'.get_lang('True').'</th>';
88
        $html .= '<th width="10px">'.get_lang('False').'</th>';
89
        $html .= '<th width="50%">'.get_lang('Answer').'</th>';
90
91
        // Show column comment when feedback is enabled
92
        if (EXERCISE_FEEDBACK_TYPE_EXAM != $objEx->getFeedbackType()) {
93
            $html .= '<th width="50%">'.get_lang('Comment').'</th>';
94
        }
95
96
        $html .= '</tr></thead><tbody>';
97
        $form->addHtml($html);
98
99
        $answer = null;
100
        if (!empty($this->id)) {
101
            $answer = new Answer($this->id);
102
            $answer->read();
103
            if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
104
                $nbAnswers = (int) $answer->nbrAnswers;
105
            }
106
        }
107
108
        $form->addElement('hidden', 'nb_answers');
109
        if ($nbAnswers < 1) {
110
            $nbAnswers = 1;
111
            echo Display::return_message(get_lang('You have to create at least one answer'));
112
        }
113
114
        for ($i = 1; $i <= $nbAnswers; $i++) {
115
            $form->addElement('html', '<tr>');
116
117
            $renderer->setElementTemplate(
118
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
119
                'counter['.$i.']'
120
            );
121
122
            // Two radios will render as two <td> cells (True / False).
123
            $renderer->setElementTemplate(
124
                '<td style="text-align:center;"><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
125
                'correct['.$i.']'
126
            );
127
128
            $renderer->setElementTemplate(
129
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
130
                'answer['.$i.']'
131
            );
132
133
            $renderer->setElementTemplate(
134
                '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
135
                'comment['.$i.']'
136
            );
137
138
            $answerNumber = $form->addElement('text', 'counter['.$i.']', null, 'value="'.$i.'"');
139
            $answerNumber->freeze();
140
141
            $defaults['answer['.$i.']'] = '';
142
            $defaults['comment['.$i.']'] = '';
143
            $defaults['correct['.$i.']'] = '';
144
145
            if (is_object($answer)) {
146
                $defaults['answer['.$i.']'] = $answer->answer[$i] ?? '';
147
                $defaults['comment['.$i.']'] = $answer->comment[$i] ?? '';
148
                $defaults['correct['.$i.']'] = $answer->correct[$i] ?? '';
149
150
                if (isset($_POST['answer'][$i])) {
151
                    $defaults['answer['.$i.']'] = Security::remove_XSS($_POST['answer'][$i]);
152
                }
153
                if (isset($_POST['comment'][$i])) {
154
                    $defaults['comment['.$i.']'] = Security::remove_XSS($_POST['comment'][$i]);
155
                }
156
                if (isset($_POST['correct'][$i])) {
157
                    $defaults['correct['.$i.']'] = Security::remove_XSS($_POST['correct'][$i]);
158
                }
159
            }
160
161
            $trueValue = $hasOptions ? (int) $tfIids[1] : 1;
162
            $falseValue = $hasOptions ? (int) $tfIids[2] : 2;
163
164
            $form->addElement('radio', 'correct['.$i.']', null, null, $trueValue, $choiceInputAttrs);
165
            $form->addElement('radio', 'correct['.$i.']', null, null, $falseValue, $choiceInputAttrs);
166
167
            $form->addHtmlEditor(
168
                'answer['.$i.']',
169
                null,
170
                true,
171
                false,
172
                ['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100'],
173
                ['style' => 'vertical-align:middle;']
174
            );
175
            $form->addRule('answer['.$i.']', get_lang('Required field'), 'required');
176
            $form->applyFilter("answer[$i]", 'attr_on_filter');
177
178
            if (isset($_POST['answer'][$i])) {
179
                $form->getElement("answer[$i]")->setValue(Security::remove_XSS($_POST['answer'][$i]));
180
            }
181
182
            // Show comment when feedback is enabled
183
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $objEx->getFeedbackType()) {
184
                $form->addHtmlEditor(
185
                    'comment['.$i.']',
186
                    null,
187
                    false,
188
                    false,
189
                    ['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100'],
190
                    ['style' => 'vertical-align:middle;']
191
                );
192
193
                if (isset($_POST['comment'][$i])) {
194
                    $form->getElement("comment[$i]")->setValue(Security::remove_XSS($_POST['comment'][$i]));
195
                }
196
197
                $form->applyFilter("comment[$i]", 'attr_on_filter');
198
            }
199
200
            $form->addElement('html', '</tr>');
201
        }
202
203
        $form->addElement('html', '</tbody></table>');
204
        $form->addElement('html', '<br />');
205
206
        // Scores (Correct/Wrong). Option[3] is fixed to 0 here.
207
        $txtOption1 = $form->addElement('text', 'option[1]', get_lang('Correct'), ['value' => '1']);
208
        $txtOption2 = $form->addElement('text', 'option[2]', get_lang('Wrong'), ['value' => '-0.5']);
209
        $form->addElement('hidden', 'option[3]', 0);
210
211
        $form->addRule('option[1]', get_lang('Required field'), 'required');
212
        $form->addRule('option[2]', get_lang('Required field'), 'required');
213
214
        $form->addElement('hidden', 'options_count', 3);
215
        $form->addElement('html', '<br /><br />');
216
217
        // Load stored score values if present
218
        if (!empty($this->extra)) {
219
            $scores = explode(':', $this->extra);
220
            if (!empty($scores)) {
221
                $txtOption1->setValue($scores[0] ?? '1');
222
                $txtOption2->setValue($scores[1] ?? '-0.5');
223
            }
224
        }
225
226
        if (true === $objEx->edit_exercise_in_lp ||
227
            (empty($this->exerciseList) && empty($objEx->id))
228
        ) {
229
            $form->addElement('submit', 'lessAnswers', get_lang('Remove answer option'), 'class="btn btn--danger minus"');
230
            $form->addElement('submit', 'moreAnswers', get_lang('Add answer option'), 'class="btn btn--primary plus"');
231
            $form->addElement('submit', 'submitQuestion', $text, 'class="btn btn--primary"');
232
        }
233
234
        $renderer->setElementTemplate('{element}&nbsp;', 'lessAnswers');
235
        $renderer->setElementTemplate('{element}&nbsp;', 'submitQuestion');
236
        $renderer->setElementTemplate('{element}&nbsp;', 'moreAnswers');
237
238
        if (!empty($this->id) && !$form->isSubmitted()) {
239
            $form->setDefaults($defaults);
240
        }
241
242
        $form->setConstants(['nb_answers' => $nbAnswers]);
243
    }
244
245
    /**
246
     * abstract function which creates the form to create / edit the answers of the question.
247
     *
248
     * @param FormValidator $form
249
     * @param Exercise      $exercise
250
     */
251
    public function processAnswersCreation($form, $exercise)
252
    {
253
        $questionWeighting = 0.0;
254
        $objAnswer = new Answer($this->id);
255
256
        $nbAnswers = (int) $form->getSubmitValue('nb_answers');
257
        $courseId = api_get_course_int_id();
258
259
        $repo = Container::getQuestionRepository();
260
        /** @var CQuizQuestion $question */
261
        $question = $repo->find($this->id);
262
263
        $optionsCollection = $question->getOptions();
264
        $isFirstCreation = $optionsCollection->isEmpty();
265
266
        // Ensure default options exist on first creation (True/False + certainty levels).
267
        if ($isFirstCreation) {
268
            for ($i = 1; $i <= 8; $i++) {
269
                Question::saveQuestionOption($question, $this->options[$i], $i);
270
            }
271
        }
272
273
        // Load options and index them by position for mapping (1/2 => iid).
274
        $newOptions = Question::readQuestionOption($this->id, $courseId);
275
        $sortedByPosition = [];
276
        foreach ($newOptions as $item) {
277
            $sortedByPosition[(int) ($item['position'] ?? 0)] = $item;
278
        }
279
280
        // Save extra score values in the format "Correct:Wrong:0".
281
        $extraValues = [];
282
        for ($i = 1; $i <= 3; $i++) {
283
            $score = trim((string) $form->getSubmitValue('option['.$i.']'));
284
            $extraValues[] = $score;
285
        }
286
        $this->setExtra(implode(':', $extraValues));
287
288
        for ($i = 1; $i <= $nbAnswers; $i++) {
289
            $answer = trim((string) $form->getSubmitValue('answer['.$i.']'));
290
            $comment = trim((string) $form->getSubmitValue('comment['.$i.']'));
291
            $goodAnswer = trim((string) $form->getSubmitValue('correct['.$i.']'));
292
293
            if ($isFirstCreation) {
294
                // First creation: map submitted position (1/2) to the real option iid.
295
                $pos = (int) $goodAnswer;
296
                $goodAnswer = isset($sortedByPosition[$pos]) ? (string) ($sortedByPosition[$pos]['iid'] ?? '') : '';
297
            }
298
299
            // Total weighting = nbAnswers * "Correct" score (option[1]).
300
            $questionWeighting += (float) ($extraValues[0] ?? 0);
301
302
            $objAnswer->createAnswer($answer, $goodAnswer, $comment, '', $i);
303
        }
304
305
        // Save answers to DB.
306
        $objAnswer->save();
307
308
        // Save total weighting and question.
309
        $this->updateWeighting($questionWeighting);
310
        $this->save($exercise);
311
    }
312
313
    public function return_header(Exercise $exercise, $counter = null, $score = [])
314
    {
315
        $header = parent::return_header($exercise, $counter, $score);
316
        $header .= '<table class="'.$this->questionTableClass.'"><tr>';
317
        $header .= '<th>'.get_lang('Your choice').'</th>';
318
319
        if ($exercise->showExpectedChoiceColumn()) {
320
            $header .= '<th>'.get_lang('Expected choice').'</th>';
321
        }
322
323
        $header .= '<th>'
324
            .get_lang('Answer')
325
            .'</th><th colspan="2" style="text-align:center;">'
326
            .get_lang('Your degree of certainty')
327
            .'</th>'
328
        ;
329
        if (false === $exercise->hideComment) {
330
            if (EXERCISE_FEEDBACK_TYPE_EXAM != $exercise->getFeedbackType()) {
331
                $header .= '<th>'.get_lang('Comment').'</th>';
332
            }
333
        }
334
        $header .= '</tr>';
335
336
        return $header;
337
    }
338
339
    /**
340
     * Get color code, status, label and description for the current answer.
341
     *
342
     * @param string $studentAnswer
343
     * @param string $expectedAnswer
344
     * @param int    $studentDegreeChoicePosition
345
     *
346
     * @return array An array with indexes 'color', 'background-color', 'status', 'label' and 'description'
347
     */
348
    public function getResponseDegreeInfo($studentAnswer, $expectedAnswer, $studentDegreeChoicePosition)
349
    {
350
        $result = [];
351
        if (3 == $studentDegreeChoicePosition) {
352
            $result = [
353
                'color' => '#000000',
354
                'background-color' => '#F6BA2A',
355
                'status' => self::LEVEL_WHITE,
356
                'label' => get_lang('Declared ignorance'),
357
                'description' => get_lang('You didn\'t know the answer - only 50% sure'),
358
            ];
359
        } else {
360
            $checkResult = $studentAnswer == $expectedAnswer ? true : false;
361
            if ($checkResult) {
362
                if ($studentDegreeChoicePosition >= 6) {
363
                    $result = [
364
                        'color' => '#FFFFFF',
365
                        'background-color' => '#1E9C55',
366
                        'status' => self::LEVEL_DARKGREEN,
367
                        'label' => get_lang('Very sure'),
368
                        'description' => get_lang('Your answer was correct and you were 80% sure about it. Congratulations!'),
369
                    ];
370
                } elseif ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
371
                    $result = [
372
                        'color' => '#000000',
373
                        'background-color' => '#B1E183',
374
                        'status' => self::LEVEL_LIGHTGREEN,
375
                        'label' => get_lang('Pretty sure'),
376
                        'description' => get_lang('Your answer was correct but you were not completely sure (only 60% to 70% sure)'),
377
                    ];
378
                }
379
            } else {
380
                if ($studentDegreeChoicePosition >= 6) {
381
                    $result = [
382
                        'color' => '#FFFFFF',
383
                        'background-color' => '#ED4040',
384
                        'status' => self::LEVEL_DARKRED,
385
                        'label' => get_lang('Very unsure'),
386
                        'description' => get_lang('Your answer was incorrect although you were about 80% (or more) sure it was wrong'),
387
                    ];
388
                } elseif ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
389
                    $result = [
390
                        'color' => '#000000',
391
                        'background-color' => '#F79B88',
392
                        'status' => self::LEVEL_LIGHTRED,
393
                        'label' => get_lang('Unsure'),
394
                        'description' => get_lang('Your answer was incorrect, but you guessed it was (60% to 70% sure)'),
395
                    ];
396
                }
397
            }
398
        }
399
400
        return $result;
401
    }
402
403
    /**
404
     * Method to show the code color and his meaning for the test result.
405
     */
406
    public static function showColorCodes()
407
    {
408
        ?>
409
        <table class="fc-border-separate" cellspacing="0" style="width:600px;
410
            margin: auto; border: 3px solid #A39E9E;" >
411
            <tr style="border-bottom: 1px solid #A39E9E;">
412
                <td style="width:15%; height:30px; background-color: #088A08; border-right: 1px solid #A39E9E;">
413
                    &nbsp;
414
                </td>
415
                <td style="padding-left:10px;">
416
                    <b><?php echo get_lang('Very sure'); ?> :</b>
417
                    <?php echo get_lang('Your answer was correct and you were 80% sure about it. Congratulations!'); ?>
418
                </td>
419
            </tr>
420
            <tr style="border-bottom: 1px solid #A39E9E;">
421
                <td style="width:15%; height:30px; background-color: #A9F5A9; border-right: 1px solid #A39E9E;">
422
                    &nbsp;
423
                </td>
424
                <td style="padding-left:10px;">
425
                    <b><?php echo get_lang('Pretty sure'); ?> :</b>
426
                    <?php echo get_lang('Your answer was correct but you were not completely sure (only 60% to 70% sure)'); ?>
427
                </td>
428
            </tr>
429
            <tr style="border: 1px solid #A39E9E;">
430
                <td style="width:15%; height:30px; background-color: #FFFFFF; border-right: 1px solid #A39E9E;">
431
                    &nbsp;
432
                </td>
433
                <td style="padding-left:10px;">
434
                    <b><?php echo get_lang('Declared ignorance'); ?> :</b>
435
                    <?php echo get_lang('You didn\'t know the answer - only 50% sure'); ?>
436
                </td>
437
            </tr>
438
            <tr style="border: 1px solid #A39E9E;">
439
                <td style="width:15%; height:30px; background-color: #F6CECE; border-right: 1px solid #A39E9E;">
440
                    &nbsp;
441
                </td>
442
                <td style="padding-left:10px;">
443
                    <b><?php echo get_lang('Unsure'); ?> :</b>
444
                    <?php echo get_lang('Your answer was incorrect, but you guessed it was (60% to 70% sure)'); ?>
445
                </td>
446
            </tr>
447
            <tr style="border-bottom: 1px solid #A39E9E;">
448
                <td style="width:15%; height:30px; background-color: #FE2E2E; border-right: 1px solid #A39E9E;">
449
                    &nbsp;
450
                </td>
451
                <td style="padding-left:10px;">
452
                    <b><?php echo get_lang('Very unsure'); ?> :</b>
453
                    <?php echo get_lang('Your answer was incorrect although you were about 80% (or more) sure it was wrong'); ?>
454
                </td>
455
            </tr>
456
        </table><br/>
457
        <?php
458
    }
459
460
    /**
461
     * Display basic bar charts of results by category of questions.
462
     *
463
     * @param array  $scoreListAll
464
     * @param string $title        The block title
465
     * @param int    $sizeRatio
466
     *
467
     * @return string The HTML/CSS code for the charts block
468
     */
469
    public static function displayDegreeChartByCategory($scoreListAll, $title, $sizeRatio = 1)
470
    {
471
        $maxHeight = 0;
472
        $groupCategoriesByBracket = false;
473
        if ($groupCategoriesByBracket) {
474
            $scoreList = [];
475
            $categoryPrefixList = [];
476
            // categoryPrefix['Math'] = firstCategoryId for this prefix
477
            // rebuild $scoreList factorizing data with category prefix
478
            foreach ($scoreListAll as $categoryId => $scoreListForCategory) {
479
                $objCategory = new Testcategory();
480
                $objCategoryNum = $objCategory->getCategory($categoryId);
481
                preg_match("/^\[([^]]+)\]/", $objCategoryNum->name, $matches);
482
483
                if (count($matches) > 1) {
484
                    // check if we have already see this prefix
485
                    if (array_key_exists($matches[1], $categoryPrefixList)) {
486
                        // add the result color for this entry
487
                        $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_DARKGREEN] +=
488
                            $scoreListForCategory[self::LEVEL_DARKGREEN];
489
                        $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_LIGHTGREEN] +=
490
                            $scoreListForCategory[self::LEVEL_LIGHTGREEN];
491
                        $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_WHITE] +=
492
                            $scoreListForCategory[self::LEVEL_WHITE];
493
                        $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_LIGHTRED] +=
494
                            $scoreListForCategory[self::LEVEL_LIGHTRED];
495
                        $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_DARKRED] +=
496
                            $scoreListForCategory[self::LEVEL_DARKRED];
497
                    } else {
498
                        $categoryPrefixList[$matches[1]] = $categoryId;
499
                        $scoreList[$categoryId] = $scoreListAll[$categoryId];
500
                    }
501
                } else {
502
                    // doesn't match the prefix '[math] Math category'
503
                    $scoreList[$categoryId] = $scoreListAll[$categoryId];
504
                }
505
            }
506
        } else {
507
            $scoreList = $scoreListAll;
508
        }
509
510
        // get the max height of item to have each table the same height if displayed side by side
511
        foreach ($scoreList as $categoryId => $scoreListForCategory) {
512
            [$noValue, $height] = self::displayDegreeChartChildren(
513
                $scoreListForCategory,
514
                300,
515
                '',
516
                1,
517
                0,
518
                false,
519
                true,
520
                0
521
            );
522
            if ($height > $maxHeight) {
523
                $maxHeight = $height;
524
            }
525
        }
526
527
        $html = '<div class="row-chart">';
528
        $html .= '<h4 class="chart-title">'.$title.'</h4>';
529
530
        $legendTitle = [
531
            'Very unsure',
532
            'Unsure',
533
            'Declared ignorance',
534
            'Pretty sure',
535
            'Very sure',
536
        ];
537
        $html .= '<ul class="chart-legend">';
538
        foreach ($legendTitle as $i => $item) {
539
            $html .= '<li><i class="fa fa-square square_color'.$i.'" aria-hidden="true"></i> '.get_lang($item).'</li>';
540
        }
541
        $html .= '</ul>';
542
543
        // get the html of items
544
        $i = 0;
545
        $testCategory = new Testcategory();
546
        foreach ($scoreList as $categoryId => $scoreListForCategory) {
547
            $category = $testCategory->getCategory($categoryId);
548
            $categoryQuestionName = '';
549
            if ($category) {
550
                $categoryQuestionName = $category->name;
551
            }
552
553
            if ('' === $categoryQuestionName) {
554
                $categoryName = get_lang('Without category');
555
            } else {
556
                $categoryName = $categoryQuestionName;
557
            }
558
559
            $html .= '<div class="col-md-4">';
560
            $html .= self::displayDegreeChartChildren(
561
                $scoreListForCategory,
562
                300,
563
                $categoryName,
564
                1,
565
                $maxHeight,
566
                false,
567
                false,
568
                $groupCategoriesByBracket
569
            );
570
            $html .= '</div>';
571
572
            if (2 == $i) {
573
                $html .= '<div style="clear:both; height: 10px;">&nbsp;</div>';
574
                $i = 0;
575
            } else {
576
                $i++;
577
            }
578
        }
579
        $html .= '</div>';
580
581
        return $html.'<div style="clear:both; height: 10px;" >&nbsp;</div>';
582
    }
583
584
    /**
585
     * Return HTML code for the $scoreList of MultipleAnswerTrueFalseDegreeCertainty questions.
586
     *
587
     * @param        $scoreList
588
     * @param        $widthTable
589
     * @param string $title
590
     * @param int    $sizeRatio
591
     * @param int    $minHeight
592
     * @param bool   $displayExplanationText
593
     * @param bool   $returnHeight
594
     * @param bool   $groupCategoriesByBracket
595
     * @param int    $numberOfQuestions
596
     *
597
     * @return array|string
598
     */
599
    public static function displayDegreeChart(
600
        $scoreList,
601
        $widthTable,
602
        $title = '',
603
        $sizeRatio = 1,
604
        $minHeight = 0,
605
        $displayExplanationText = true,
606
        $returnHeight = false,
607
        $groupCategoriesByBracket = false,
608
        $numberOfQuestions = 0
609
    ) {
610
        $topAndBottomMargin = 10;
611
        $colorList = [
612
            self::LEVEL_DARKRED,
613
            self::LEVEL_LIGHTRED,
614
            self::LEVEL_WHITE,
615
            self::LEVEL_LIGHTGREEN,
616
            self::LEVEL_DARKGREEN,
617
        ];
618
619
        // get total attempt number
620
        $highterColorHeight = 0;
621
        foreach ($scoreList as $color => $number) {
622
            if ($number > $highterColorHeight) {
623
                $highterColorHeight = $number;
624
            }
625
        }
626
627
        $totalAttemptNumber = $numberOfQuestions;
628
        $verticalLineHeight = $highterColorHeight * $sizeRatio * 2 + 122 + $topAndBottomMargin * 2;
629
        if ($verticalLineHeight < $minHeight) {
630
            $minHeightCorrection = $minHeight - $verticalLineHeight;
631
            $verticalLineHeight += $minHeightCorrection;
632
        }
633
634
        // draw chart
635
        $html = '';
636
637
        if ($groupCategoriesByBracket) {
638
            $title = api_preg_replace('/[^]]*$/', '', $title);
639
            $title = ucfirst(api_preg_replace("/[\[\]]/", '', $title));
640
        }
641
642
        $titleDisplay = strpos($title, 'ensemble') > 0 ?
643
            $title."<br/>($totalAttemptNumber questions)" :
644
            $title;
645
        $textSize = strpos($title, 'ensemble') > 0 ||
646
            strpos($title, 'votre dernier résultat à ce test') > 0 ? 100 : 80;
647
648
        $html .= '<div class="row-chart">';
649
        $html .= '<h4 class="chart-title">'.$titleDisplay.'</h4>';
650
651
        $nbResponsesInc = 0;
652
        if (isset($scoreList[4])) {
653
            $nbResponsesInc += (int) $scoreList[4];
654
        }
655
        if (isset($scoreList[5])) {
656
            $nbResponsesInc += (int) $scoreList[5];
657
        }
658
659
        $nbResponsesIng = isset($scoreList[3]) ? $scoreList[3] : 0;
660
661
        $nbResponsesCor = 0;
662
        if (isset($scoreList[1])) {
663
            $nbResponsesCor += (int) $scoreList[1];
664
        }
665
        if (isset($scoreList[2])) {
666
            $nbResponsesCor += (int) $scoreList[2];
667
        }
668
669
        $IncorrectAnswers = sprintf(get_lang('Incorrect answers: %s'), $nbResponsesInc);
670
        $IgnoranceAnswers = sprintf(get_lang('Ignorance: %s'), $nbResponsesIng);
671
        $CorrectAnswers = sprintf(get_lang('Correct answers: %s'), $nbResponsesCor);
672
673
        $html .= '<div class="chart-grid">';
674
675
        $explainHistoList = null;
676
        if ($displayExplanationText) {
677
            // Display of histogram text
678
            $explainHistoList = [
679
                'Very unsure',
680
                'Unsure',
681
                'Declared ignorance',
682
                'Pretty sure',
683
                'Very sure',
684
            ];
685
        }
686
687
        foreach ($colorList as $i => $color) {
688
            if (array_key_exists($color, $scoreList)) {
689
                $scoreOnBottom = $scoreList[$color]; // height of the colored area on the bottom
690
            } else {
691
                $scoreOnBottom = 0;
692
            }
693
            $sizeBar = ($scoreOnBottom * $sizeRatio * 2).'px;';
694
695
            if (0 == $i) {
696
                $html .= '<div class="item">';
697
                $html .= '<div class="panel-certaint" style="min-height:'.$verticalLineHeight.'px; position: relative;">';
698
                $html .= '<div class="answers-title">'.$IncorrectAnswers.'</div>';
699
                $html .= '<ul class="certaint-list-two">';
700
            } elseif (3 == $i) {
701
                $html .= '<div class="item">';
702
                $html .= '<div class="panel-certaint" style="height:'.$verticalLineHeight.'px;  position: relative;">';
703
                $html .= '<div class="answers-title">'.$CorrectAnswers.'</div>';
704
                $html .= '<ul class="certaint-list-two">';
705
            } elseif (2 == $i) {
706
                $html .= '<div class="item">';
707
                $html .= '<div class="panel-certaint" style="height:'.$verticalLineHeight.'px;  position: relative;">';
708
                $html .= '<div class="answers-title">'.$IgnoranceAnswers.'</div>';
709
                $html .= '<ul class="certaint-list">';
710
            }
711
            $html .= '<li>';
712
            $html .= '<div class="certaint-score">';
713
            $html .= $scoreOnBottom;
714
            $html .= '</div>';
715
            $html .= '<div class="levelbar_'.$color.'" style="height:'.$sizeBar.'">&nbsp;</div>';
716
            $html .= '<div class="certaint-text">'.get_lang($explainHistoList[$i]).'</div>';
717
            $html .= '</li>';
718
719
            if (1 == $i || 2 == $i || 4 == $i) {
720
                $html .= '</ul>';
721
                $html .= '</div>';
722
                $html .= '</div>';
723
            }
724
        }
725
726
        $html .= '</div>';
727
        $html .= '</div>';
728
729
        if ($returnHeight) {
730
            return [$html, $verticalLineHeight];
731
        } else {
732
            return $html;
733
        }
734
    }
735
736
    /**
737
     * Return HTML code for the $scoreList of MultipleAnswerTrueFalseDegreeCertainty questions.
738
     *
739
     * @param        $scoreList
740
     * @param        $widthTable
741
     * @param string $title
742
     * @param int    $sizeRatio
743
     * @param int    $minHeight
744
     * @param bool   $displayExplanationText
745
     * @param bool   $returnHeight
746
     * @param bool   $groupCategoriesByBracket
747
     * @param int    $numberOfQuestions
748
     *
749
     * @return array|string
750
     */
751
    public static function displayDegreeChartChildren(
752
        $scoreList,
753
        $widthTable,
754
        $title = '',
755
        $sizeRatio = 1,
756
        $minHeight = 0,
757
        $displayExplanationText = true,
758
        $returnHeight = false,
759
        $groupCategoriesByBracket = false,
760
        $numberOfQuestions = 0
761
    ) {
762
        $topAndBottomMargin = 10;
763
        $colorList = [
764
            self::LEVEL_DARKRED,
765
            self::LEVEL_LIGHTRED,
766
            self::LEVEL_WHITE,
767
            self::LEVEL_LIGHTGREEN,
768
            self::LEVEL_DARKGREEN,
769
        ];
770
771
        // get total attempt number
772
        $highterColorHeight = 0;
773
        foreach ($scoreList as $color => $number) {
774
            if ($number > $highterColorHeight) {
775
                $highterColorHeight = $number;
776
            }
777
        }
778
779
        $totalAttemptNumber = $numberOfQuestions;
780
        $verticalLineHeight = $highterColorHeight * $sizeRatio * 2 + 122 + $topAndBottomMargin * 2;
781
        if ($verticalLineHeight < $minHeight) {
782
            $minHeightCorrection = $minHeight - $verticalLineHeight;
783
            $verticalLineHeight += $minHeightCorrection;
784
        }
785
786
        // draw chart
787
        $html = '';
788
789
        if ($groupCategoriesByBracket) {
790
            $title = api_preg_replace('/[^]]*$/', '', $title);
791
            $title = ucfirst(api_preg_replace("/[\[\]]/", '', $title));
792
        }
793
794
        $textSize = 80;
795
796
        $classGlobalChart = '';
797
        if ($displayExplanationText) {
798
            // global chart
799
            $classGlobalChart = 'globalChart';
800
        }
801
802
        $html .= '<table class="certaintyTable" style="height :'.$verticalLineHeight.'px; margin-bottom: 10px;" >';
803
        $html .= '<tr><th colspan="5" class="'.$classGlobalChart.'">'
804
            .$title
805
            .'</th><tr>'
806
        ;
807
808
        $nbResponsesInc = 0;
809
        if (isset($scoreList[4])) {
810
            $nbResponsesInc += (int) $scoreList[4];
811
        }
812
        if (isset($scoreList[5])) {
813
            $nbResponsesInc += (int) $scoreList[5];
814
        }
815
816
        $nbResponsesIng = isset($scoreList[3]) ? $scoreList[3] : 0;
817
818
        $nbResponsesCor = 0;
819
        if (isset($scoreList[1])) {
820
            $nbResponsesCor += (int) $scoreList[1];
821
        }
822
        if (isset($scoreList[2])) {
823
            $nbResponsesCor += (int) $scoreList[2];
824
        }
825
826
        $colWidth = $widthTable / 5;
827
828
        $html .= '<tr>
829
                <td class="firstLine borderRight '.$classGlobalChart.'"
830
                    colspan="2"
831
                    style="width:'.($colWidth * 2).'px; line-height: 15px; font-size:'.$textSize.'%;">'.
832
            sprintf(get_lang('Incorrect answers: %s'), $nbResponsesInc).'
833
                </td>
834
                <td class="firstLine borderRight '.$classGlobalChart.'"
835
                    style="width:'.$colWidth.'px; line-height: 15px; font-size :'.$textSize.'%;">'.
836
            sprintf(get_lang('Ignorance: %s'), $nbResponsesIng).'
837
                </td>
838
                <td class="firstLine '.$classGlobalChart.'"
839
                    colspan="2"
840
                    style="width:'.($colWidth * 2).'px; line-height: 15px; font-size:'.$textSize.'%;">'.
841
            sprintf(get_lang('Correct answers: %s'), $nbResponsesCor).'
842
                </td>
843
            </tr>';
844
        $html .= '<tr>';
845
846
        foreach ($colorList as $i => $color) {
847
            if (array_key_exists($color, $scoreList)) {
848
                $scoreOnBottom = $scoreList[$color]; // height of the colored area on the bottom
849
            } else {
850
                $scoreOnBottom = 0;
851
            }
852
            $sizeOnBottom = $scoreOnBottom * $sizeRatio * 2;
853
            if (1 == $i || 2 == $i) {
854
                $html .= '<td width="'
855
                    .$colWidth
856
                    .'px" style="border-right: 1px dotted #7FC5FF; vertical-align: bottom;font-size: '
857
                    .$textSize
858
                    .'%;">'
859
                ;
860
            } else {
861
                $html .= '<td width="'
862
                    .$colWidth
863
                    .'px" style="vertical-align: bottom;font-size: '
864
                    .$textSize
865
                    .'%;">'
866
                ;
867
            }
868
            $html .= '<div class="certaint-score">'
869
                .$scoreOnBottom
870
                .'</div><div class="levelbar_'
871
                .$color
872
                .'" style="height: '
873
                .$sizeOnBottom
874
                .'px;">&nbsp;</div>'
875
            ;
876
            $html .= '</td>';
877
        }
878
879
        $html .= '</tr>';
880
881
        if ($displayExplanationText) {
882
            // Display of histogram text
883
            $explainHistoList = [
884
                'Very unsure',
885
                'Unsure',
886
                'Declared ignorance',
887
                'Pretty sure',
888
                'Very sure',
889
            ];
890
            $html .= '<tr>';
891
            $i = 0;
892
            foreach ($explainHistoList as $explain) {
893
                if (1 == $i || 2 == $i) {
894
                    $class = 'borderRight';
895
                } else {
896
                    $class = '';
897
                }
898
                $html .= '<td class="firstLine '
899
                    .$class
900
                    .' '
901
                    .$classGlobalChart
902
                    .'" style="width="'
903
                    .$colWidth
904
                    .'px; font-size:'
905
                    .$textSize
906
                    .'%;">'
907
                ;
908
                $html .= get_lang($explain);
909
                $html .= '</td>';
910
                $i++;
911
            }
912
            $html .= '</tr>';
913
        }
914
        $html .= '</table></center>';
915
916
        if ($returnHeight) {
917
            return [$html, $verticalLineHeight];
918
        } else {
919
            return $html;
920
        }
921
    }
922
923
    /**
924
     * return previous attempt id for this test for student, 0 if no previous attempt.
925
     *
926
     * @param $exeId
927
     *
928
     * @return int
929
     */
930
    public static function getPreviousAttemptId($exeId)
931
    {
932
        $tblTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
933
        $exeId = (int) $exeId;
934
        $sql = "SELECT * FROM $tblTrackEExercise
935
                WHERE exe_id = ".$exeId;
936
        $res = Database::query($sql);
937
938
        if (empty(Database::num_rows($res))) {
939
            // if we cannot find the exe_id
940
            return 0;
941
        }
942
943
        $data = Database::fetch_assoc($res);
944
        $courseCode = $data['c_id'];
945
        $exerciseId = $data['exe_exo_id'];
946
        $userId = $data['exe_user_id'];
947
        $attemptDate = $data['exe_date'];
948
949
        if ('0000-00-00 00:00:00' === $attemptDate) {
950
            // incomplete attempt, close it before continue
951
            return 0;
952
        }
953
954
        // look for previous attempt
955
        $exerciseId = (int) $exerciseId;
956
        $userId = (int) $userId;
957
        $sql = "SELECT *
958
                FROM $tblTrackEExercise
959
                WHERE
960
                      c_id = '$courseCode' AND
961
                      exe_exo_id = $exerciseId AND
962
                      exe_user_id = $userId AND
963
                      status = '' AND
964
                      exe_date > '0000-00-00 00:00:00' AND
965
                      exe_date < '$attemptDate'
966
                ORDER BY exe_date DESC";
967
968
        $res = Database::query($sql);
969
970
        if (0 == Database::num_rows($res)) {
971
            // no previous attempt
972
            return 0;
973
        }
974
975
        $data = Database::fetch_assoc($res);
976
977
        return $data['exe_id'];
978
    }
979
980
    /**
981
     * return an array of number of answer color for exe attempt
982
     * for question type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
983
     * e.g.
984
     * [LEVEL_DARKGREEN => 3, LEVEL_LIGHTGREEN => 0, LEVEL_WHITE => 5, LEVEL_LIGHTRED => 12, LEVEL_DARKTRED => 0].
985
     *
986
     * @param $exeId
987
     *
988
     * @return array
989
     */
990
    public static function getColorNumberListForAttempt($exeId)
991
    {
992
        $result = [
993
            self::LEVEL_DARKGREEN => 0,
994
            self::LEVEL_LIGHTGREEN => 0,
995
            self::LEVEL_WHITE => 0,
996
            self::LEVEL_LIGHTRED => 0,
997
            self::LEVEL_DARKRED => 0,
998
        ];
999
1000
        $attemptInfoList = self::getExerciseAttemptInfo($exeId);
1001
1002
        foreach ($attemptInfoList as $attemptInfo) {
1003
            $oQuestion = new self();
1004
            $oQuestion->read($attemptInfo['question_id']);
1005
            if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $oQuestion->type) {
1006
                $answerColor = self::getAnswerColor($exeId, $attemptInfo['question_id'], $attemptInfo['position']);
1007
                if ($answerColor) {
1008
                    $result[$answerColor]++;
1009
                }
1010
            }
1011
        }
1012
1013
        return $result;
1014
    }
1015
1016
    /**
1017
     * return an array of number of color for question type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
1018
     * for each question category.
1019
     *
1020
     * e.g.
1021
     * [
1022
     *      (categoryId=)5 => [LEVEL_DARKGREEN => 3, LEVEL_WHITE => 5, LEVEL_LIGHTRED => 12]
1023
     *      (categoryId=)2 => [LEVEL_DARKGREEN => 8, LEVEL_LIGHTRED => 2, LEVEL_DARKTRED => 8]
1024
     *      (categoryId=)0 => [LEVEL_DARKGREEN => 1,
1025
     *          LEVEL_LIGHTGREEN => 2,
1026
     *          LEVEL_WHITE => 6,
1027
     *          LEVEL_LIGHTRED => 1,
1028
     *          LEVEL_DARKTRED => 9]
1029
     * ]
1030
     *
1031
     * @param int $exeId
1032
     *
1033
     * @return array
1034
     */
1035
    public static function getColorNumberListForAttemptByCategory($exeId)
1036
    {
1037
        $result = [];
1038
        $attemptInfoList = self::getExerciseAttemptInfo($exeId);
1039
1040
        foreach ($attemptInfoList as $attemptInfo) {
1041
            $oQuestion = new self();
1042
            $oQuestion->read($attemptInfo['question_id']);
1043
            if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $oQuestion->type) {
1044
                $questionCategory = Testcategory::getCategoryForQuestion($attemptInfo['question_id']);
1045
1046
                if (!array_key_exists($questionCategory, $result)) {
1047
                    $result[$questionCategory] = [];
1048
                }
1049
1050
                $answerColor = self::getAnswerColor($exeId, $attemptInfo['question_id'], $attemptInfo['position']);
1051
                if ($answerColor && isset($result[$questionCategory])) {
1052
                    if (!isset($result[$questionCategory][$answerColor])) {
1053
                        $result[$questionCategory][$answerColor] = 0;
1054
                    }
1055
                    $result[$questionCategory][$answerColor]++;
1056
                }
1057
            }
1058
        }
1059
1060
        return $result;
1061
    }
1062
1063
    /**
1064
     * Return true if answer of $exeId, $questionId, $position is correct, otherwise return false.
1065
     *
1066
     * @param $exeId
1067
     * @param $questionId
1068
     * @param $position
1069
     *
1070
     * @return int
1071
     */
1072
    public static function getAnswerColor($exeId, $questionId, $position)
1073
    {
1074
        $attemptInfoList = self::getExerciseAttemptInfo($exeId, $questionId, $position);
1075
1076
        if (1 != count($attemptInfoList)) {
1077
            // havent got the answer
1078
            return 0;
1079
        }
1080
1081
        $answerCodes = $attemptInfoList[0]['answer'];
1082
1083
        // student answer
1084
        $splitAnswer = preg_split('/:/', $answerCodes);
1085
        // get correct answer option id
1086
        $correctAnswerOptionId = self::getCorrectAnswerOptionId($splitAnswer[0]);
1087
        if (0 == $correctAnswerOptionId) {
1088
            // error returning the correct answer option id
1089
            return 0;
1090
        }
1091
1092
        // get student answer option id
1093
        $studentAnswerOptionId = $splitAnswer[1] ?? null;
1094
1095
        // we got the correct answer option id, let's compare ti with the student answer
1096
        $percentage = null;
1097
        if (isset($splitAnswer[2])) {
1098
            $percentage = self::getPercentagePosition($splitAnswer[2]);
1099
        }
1100
1101
        if ($studentAnswerOptionId == $correctAnswerOptionId) {
1102
            // yeah, student got correct answer
1103
            switch ($percentage) {
1104
                case 3:
1105
                    return self::LEVEL_WHITE;
1106
                case 4:
1107
                case 5:
1108
                    return self::LEVEL_LIGHTGREEN;
1109
                case 6:
1110
                case 7:
1111
                case 8:
1112
                    return self::LEVEL_DARKGREEN;
1113
                default:
1114
                    return 0;
1115
            }
1116
        } else {
1117
            // bummer, wrong answer dude
1118
            switch ($percentage) {
1119
                case 3:
1120
                    return self::LEVEL_WHITE;
1121
                case 4:
1122
                case 5:
1123
                    return self::LEVEL_LIGHTRED;
1124
                case 6:
1125
                case 7:
1126
                case 8:
1127
                    return self::LEVEL_DARKRED;
1128
                default:
1129
                    return 0;
1130
            }
1131
        }
1132
    }
1133
1134
    /**
1135
     * Return the position of certitude %age choose by student.
1136
     *
1137
     * @param $optionId
1138
     *
1139
     * @return int
1140
     */
1141
    public static function getPercentagePosition($optionId)
1142
    {
1143
        $tblAnswerOption = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1144
        $courseId = api_get_course_int_id();
1145
        $optionId = (int) $optionId;
1146
        $sql = "SELECT position
1147
                FROM $tblAnswerOption
1148
                WHERE c_id = $courseId AND id = $optionId";
1149
        $res = Database::query($sql);
1150
1151
        if (0 == Database::num_rows($res)) {
1152
            return 0;
1153
        }
1154
1155
        $data = Database::fetch_assoc($res);
1156
1157
        return $data['position'];
1158
    }
1159
1160
    /**
1161
     * return the correct id from c_quiz_question_option for question idAuto.
1162
     *
1163
     * @param $idAuto
1164
     *
1165
     * @return int
1166
     */
1167
    public static function getCorrectAnswerOptionId($idAuto)
1168
    {
1169
        $tblAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER);
1170
        $idAuto = (int) $idAuto;
1171
        $sql = "SELECT correct FROM $tblAnswer
1172
                WHERE iid = $idAuto";
1173
1174
        $res = Database::query($sql);
1175
        $data = Database::fetch_assoc($res);
1176
        if (Database::num_rows($res) > 0) {
1177
            return $data['correct'];
1178
        }
1179
1180
        return 0;
1181
    }
1182
1183
    /**
1184
     * return an array of exe info from track_e_attempt.
1185
     *
1186
     * @param int $exeId
1187
     * @param int $questionId
1188
     * @param int $position
1189
     *
1190
     * @return array
1191
     */
1192
    public static function getExerciseAttemptInfo($exeId, $questionId = -1, $position = -1)
1193
    {
1194
        $result = [];
1195
        $and = '';
1196
        $questionId = (int) $questionId;
1197
        $position = (int) $position;
1198
        $exeId = (int) $exeId;
1199
1200
        if ($questionId >= 0) {
1201
            $and .= " AND question_id = $questionId";
1202
        }
1203
        if ($position >= 0) {
1204
            $and .= " AND position = $position";
1205
        }
1206
1207
        $tblExeAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1208
        $sql = "SELECT * FROM $tblExeAttempt
1209
                WHERE exe_id = $exeId $and";
1210
1211
        $res = Database::query($sql);
1212
        while ($data = Database::fetch_assoc($res)) {
1213
            $result[] = $data;
1214
        }
1215
1216
        return $result;
1217
    }
1218
1219
    /**
1220
     * @param int $exeId
1221
     *
1222
     * @return int
1223
     */
1224
    public static function getNumberOfQuestionsForExeId($exeId)
1225
    {
1226
        $tableTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1227
        $exeId = (int) $exeId;
1228
1229
        $sql = "SELECT exe_exo_id
1230
                FROM $tableTrackEExercise
1231
                WHERE exe_id=".$exeId;
1232
        $res = Database::query($sql);
1233
        $data = Database::fetch_assoc($res);
1234
        if ($data) {
1235
            $exerciseId = $data['exe_exo_id'];
1236
1237
            $objectExercise = new Exercise();
1238
            $objectExercise->read($exerciseId);
1239
1240
            return $objectExercise->getQuestionCount();
1241
        }
1242
1243
        return 0;
1244
    }
1245
1246
    /**
1247
     * Display student chart results for these question types.
1248
     *
1249
     * @param int      $exeId
1250
     * @param Exercise $objExercice
1251
     *
1252
     * @return string
1253
     */
1254
    public static function displayStudentsChartResults($exeId, $objExercice)
1255
    {
1256
        $numberOfQuestions = self::getNumberOfQuestionsForExeId($exeId);
1257
        $globalScoreList = self::getColorNumberListForAttempt($exeId);
1258
        $html = self::displayDegreeChart(
1259
            $globalScoreList,
1260
            600,
1261
            get_lang('Your overall results for the test'),
1262
            2,
1263
            0,
1264
            true,
1265
            false,
1266
            false,
1267
            $numberOfQuestions
1268
        );
1269
        $html .= '<br/>';
1270
1271
        $previousAttemptId = self::getPreviousAttemptId($exeId);
1272
        if ($previousAttemptId > 0) {
1273
            $previousAttemptScoreList = self::getColorNumberListForAttempt(
1274
                $previousAttemptId
1275
            );
1276
            $html .= self::displayDegreeChart(
1277
                $previousAttemptScoreList,
1278
                600,
1279
                get_lang('In comparison, your latest results for this test'),
1280
                2
1281
            );
1282
            $html .= '<br/>';
1283
        }
1284
1285
        $list = self::getColorNumberListForAttemptByCategory($exeId);
1286
        $html .= self::displayDegreeChartByCategory(
1287
            $list,
1288
            get_lang('Your results by discipline'),
1289
            1,
1290
            $objExercice
1291
        );
1292
        $html .= '<br/>';
1293
1294
        return $html;
1295
    }
1296
1297
    /**
1298
     * send mail to student with degre certainty result test.
1299
     *
1300
     * @param int      $userId
1301
     * @param Exercise $objExercise
1302
     * @param int      $exeId
1303
     */
1304
    public static function sendQuestionCertaintyNotification($userId, $objExercise, $exeId)
1305
    {
1306
        $userInfo = api_get_user_info($userId);
1307
        $recipientName = api_get_person_name($userInfo['firstname'],
1308
            $userInfo['lastname'],
1309
            null,
1310
            PERSON_NAME_EMAIL_ADDRESS
1311
        );
1312
        $subject = '['.get_lang('Please do not reply').'] '
1313
            .html_entity_decode(get_lang('Results for the accomplished test').' "'.$objExercise->title.'"');
1314
1315
        // message sended to the student
1316
        $message = get_lang('Dear').' '.$recipientName.',<br /><br />';
1317
        $exerciseLink = "<a href='".api_get_path(WEB_CODE_PATH).'/exercise/result.php?show_headers=1&'
1318
            .api_get_cidreq()
1319
            ."&id=$exeId'>";
1320
        $exerciseTitle = $objExercise->title;
1321
1322
        $message .= sprintf(
1323
            get_lang('Please follow the instructions below to check your results for test %s.<br /><br />'),
1324
            $exerciseTitle,
1325
            api_get_path(WEB_PATH),
1326
            $exerciseLink
1327
        );
1328
1329
        // show histogram
1330
        $message .= self::displayStudentsChartResults($exeId, $objExercise);
1331
        $message .= get_lang('Kind regards,');
1332
        $message = api_preg_replace("/\\\n/", '', $message);
1333
1334
        MessageManager::send_message_simple($userId, $subject, $message);
1335
    }
1336
1337
    /**
1338
     * Return True/False option iids when they exist.
1339
     * Fallback to positions 1/2 when options are not available yet.
1340
     */
1341
    private function getTrueFalseOptionIids(int $courseId): array
1342
    {
1343
        // Fallback for first-creation cases (positions)
1344
        $map = [1 => 1, 2 => 2];
1345
1346
        if (empty($this->id)) {
1347
            return $map;
1348
        }
1349
1350
        // Try reading options with courseId if supported, fallback otherwise.
1351
        try {
1352
            $optionData = Question::readQuestionOption($this->id, $courseId);
1353
        } catch (\Throwable $e) {
1354
            $optionData = Question::readQuestionOption($this->id);
1355
        }
1356
1357
        if (!empty($optionData)) {
1358
            foreach ($optionData as $row) {
1359
                $pos = (int) ($row['position'] ?? 0);
1360
                $iid = (int) ($row['iid'] ?? 0);
1361
1362
                if (($pos === 1 || $pos === 2) && $iid > 0) {
1363
                    $map[$pos] = $iid;
1364
                }
1365
            }
1366
        }
1367
1368
        return $map;
1369
    }
1370
}
1371