Completed
Push — master ( 5f79f5...efcbbc )
by Julito
11:10
created

ExerciseLib::get_exam_results_data()   F

Complexity

Conditions 94

Size

Total Lines 725
Code Lines 456

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 94
eloc 456
c 2
b 0
f 0
nop 14
dl 0
loc 725
rs 3.3333

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\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\TrackEExercises;
7
use Chamilo\CoreBundle\Framework\Container;
8
use ChamiloSession as Session;
9
10
/**
11
 * Class ExerciseLib
12
 * shows a question and its answers.
13
 *
14
 * @author Olivier Brouckaert <[email protected]>
15
 * @author Hubert Borderiou 2011-10-21
16
 * @author ivantcholakov2009-07-20
17
 * @author Julio Montoya
18
 */
19
class ExerciseLib
20
{
21
    /**
22
     * Shows a question.
23
     *
24
     * @param Exercise $exercise
25
     * @param int      $questionId     $questionId question id
26
     * @param bool     $only_questions if true only show the questions, no exercise title
27
     * @param bool     $origin         i.e = learnpath
28
     * @param string   $current_item   current item from the list of questions
29
     * @param bool     $show_title
30
     * @param bool     $freeze
31
     * @param array    $user_choice
32
     * @param bool     $show_comment
33
     * @param bool     $show_answers
34
     *
35
     * @throws \Exception
36
     *
37
     * @return bool|int
38
     */
39
    public static function showQuestion(
40
        $exercise,
41
        $questionId,
42
        $only_questions = false,
43
        $origin = false,
44
        $current_item = '',
45
        $show_title = true,
46
        $freeze = false,
47
        $user_choice = [],
48
        $show_comment = false,
49
        $show_answers = false,
50
        $show_icon = false
51
    ) {
52
        $course_id = $exercise->course_id;
53
        $exerciseId = $exercise->iId;
54
55
        if (empty($course_id)) {
56
            return '';
57
        }
58
        $course = $exercise->course;
59
60
        // Change false to true in the following line to enable answer hinting
61
        $debug_mark_answer = $show_answers;
62
        // Reads question information
63
        if (!$objQuestionTmp = Question::read($questionId, $course)) {
64
            // Question not found
65
            return false;
66
        }
67
68
        if (EXERCISE_FEEDBACK_TYPE_END != $exercise->getFeedbackType()) {
69
            $show_comment = false;
70
        }
71
72
        $answerType = $objQuestionTmp->selectType();
73
        $s = '';
74
        if (HOT_SPOT != $answerType &&
75
            HOT_SPOT_DELINEATION != $answerType &&
76
            ANNOTATION != $answerType
77
        ) {
78
            // Question is not a hotspot
79
            if (!$only_questions) {
80
                $questionDescription = $objQuestionTmp->selectDescription();
81
                if ($show_title) {
82
                    if ($exercise->display_category_name) {
83
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
84
                    }
85
                    $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
86
                    if (READING_COMPREHENSION == $answerType) {
87
                        // In READING_COMPREHENSION, the title of the question
88
                        // contains the question itself, which can only be
89
                        // shown at the end of the given time, so hide for now
90
                        $titleToDisplay = Display::div(
91
                            $current_item.'. '.get_lang('Reading comprehension'),
92
                            ['class' => 'question_title']
93
                        );
94
                    }
95
                    echo $titleToDisplay;
96
                }
97
                if (!empty($questionDescription) && READING_COMPREHENSION != $answerType) {
98
                    echo Display::div(
99
                        $questionDescription,
100
                        ['class' => 'question_description']
101
                    );
102
                }
103
            }
104
105
            if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION]) && $freeze) {
106
                return '';
107
            }
108
109
            echo '<div class="question_options">';
110
            // construction of the Answer object (also gets all answers details)
111
            $objAnswerTmp = new Answer($questionId, $course_id, $exercise);
112
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
113
            $quizQuestionOptions = Question::readQuestionOption($questionId, $course_id);
114
115
            // For "matching" type here, we need something a little bit special
116
            // because the match between the suggestions and the answers cannot be
117
            // done easily (suggestions and answers are in the same table), so we
118
            // have to go through answers first (elems with "correct" value to 0).
119
            $select_items = [];
120
            //This will contain the number of answers on the left side. We call them
121
            // suggestions here, for the sake of comprehensions, while the ones
122
            // on the right side are called answers
123
            $num_suggestions = 0;
124
            switch ($answerType) {
125
                case MATCHING:
126
                case DRAGGABLE:
127
                case MATCHING_DRAGGABLE:
128
                    if (DRAGGABLE == $answerType) {
129
                        $isVertical = 'v' == $objQuestionTmp->extra;
130
                        $s .= '
131
                            <div class="col-md-12 ui-widget ui-helper-clearfix">
132
                                <div class="clearfix">
133
                                <ul class="exercise-draggable-answer '.($isVertical ? '' : 'list-inline').'"
134
                                    id="question-'.$questionId.'" data-question="'.$questionId.'">
135
                        ';
136
                    } else {
137
                        $s .= '<div id="drag'.$questionId.'_question" class="drag_question">
138
                               <table class="data_table">';
139
                    }
140
141
                    // Iterate through answers
142
                    $x = 1;
143
                    //mark letters for each answer
144
                    $letter = 'A';
145
                    $answer_matching = [];
146
                    $cpt1 = [];
147
                    for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
148
                        $answerCorrect = $objAnswerTmp->isCorrect($answerId);
149
                        $numAnswer = $objAnswerTmp->selectAutoId($answerId);
150
                        if (0 == $answerCorrect) {
151
                            // options (A, B, C, ...) that will be put into the list-box
152
                            // have the "correct" field set to 0 because they are answer
153
                            $cpt1[$x] = $letter;
154
                            $answer_matching[$x] = $objAnswerTmp->selectAnswerByAutoId($numAnswer);
155
                            $x++;
156
                            $letter++;
157
                        }
158
                    }
159
160
                    $i = 1;
161
                    $select_items[0]['id'] = 0;
162
                    $select_items[0]['letter'] = '--';
163
                    $select_items[0]['answer'] = '';
164
                    foreach ($answer_matching as $id => $value) {
165
                        $select_items[$i]['id'] = $value['id_auto'];
166
                        $select_items[$i]['letter'] = $cpt1[$id];
167
                        $select_items[$i]['answer'] = $value['answer'];
168
                        $i++;
169
                    }
170
171
                    $user_choice_array_position = [];
172
                    if (!empty($user_choice)) {
173
                        foreach ($user_choice as $item) {
174
                            $user_choice_array_position[$item['position']] = $item['answer'];
175
                        }
176
                    }
177
                    $num_suggestions = ($nbrAnswers - $x) + 1;
178
                    break;
179
                case FREE_ANSWER:
180
                    $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
181
                    $form = new FormValidator('free_choice_'.$questionId);
182
                    $config = [
183
                        'ToolbarSet' => 'TestFreeAnswer',
184
                        'id' => 'choice['.$questionId.']',
185
                    ];
186
                    $form->addHtmlEditor(
187
                        'choice['.$questionId.']',
188
                        null,
189
                        false,
190
                        false,
191
                        $config
192
                    );
193
                    $form->setDefaults(["choice[".$questionId."]" => $fck_content]);
194
                    $s .= $form->returnForm();
195
                    break;
196
                case ORAL_EXPRESSION:
197
                    // Add nanog
198
                    if ('true' === api_get_setting('enable_record_audio')) {
199
                        //@todo pass this as a parameter
200
                        global $exercise_stat_info;
201
                        if (!empty($exercise_stat_info)) {
202
                            $objQuestionTmp->initFile(
203
                                api_get_session_id(),
204
                                api_get_user_id(),
205
                                $exercise_stat_info['exe_exo_id'],
206
                                $exercise_stat_info['exe_id']
207
                            );
208
                        } else {
209
                            $objQuestionTmp->initFile(
210
                                api_get_session_id(),
211
                                api_get_user_id(),
212
                                $exerciseId,
213
                                'temp_exe'
214
                            );
215
                        }
216
217
                        echo $objQuestionTmp->returnRecorder();
218
                    }
219
220
                    $form = new FormValidator('free_choice_'.$questionId);
221
                    $config = ['ToolbarSet' => 'TestFreeAnswer'];
222
223
                    $form->addHtml('<div id="'.'hide_description_'.$questionId.'_options" style="display: none;">');
224
                    $form->addHtmlEditor(
225
                        "choice[$questionId]",
226
                        null,
227
                        false,
228
                        false,
229
                        $config
230
                    );
231
                    $form->addHtml('</div>');
232
                    $s .= $form->returnForm();
233
                    break;
234
            }
235
236
            // Now navigate through the possible answers, using the max number of
237
            // answers for the question as a limiter
238
            $lines_count = 1; // a counter for matching-type answers
239
            if (MULTIPLE_ANSWER_TRUE_FALSE == $answerType ||
240
                MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE == $answerType
241
            ) {
242
                $header = Display::tag('th', get_lang('Options'));
243
                foreach ($objQuestionTmp->options as $item) {
244
                    if (MULTIPLE_ANSWER_TRUE_FALSE == $answerType) {
245
                        if (in_array($item, $objQuestionTmp->options)) {
246
                            $header .= Display::tag('th', get_lang($item));
247
                        } else {
248
                            $header .= Display::tag('th', $item);
249
                        }
250
                    } else {
251
                        $header .= Display::tag('th', $item);
252
                    }
253
                }
254
                if ($show_comment) {
255
                    $header .= Display::tag('th', get_lang('Feedback'));
256
                }
257
                $s .= '<table class="table table-hover table-striped">';
258
                $s .= Display::tag(
259
                    'tr',
260
                    $header,
261
                    ['style' => 'text-align:left;']
262
                );
263
            } elseif (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType) {
264
                $header = Display::tag('th', get_lang('Options'), ['width' => '50%']);
265
                echo "
266
                <script>
267
                    function RadioValidator(question_id, answer_id)
268
                    {
269
                        var ShowAlert = '';
270
                        var typeRadioB = '';
271
                        var AllFormElements = window.document.getElementById('exercise_form').elements;
272
273
                        for (i = 0; i < AllFormElements.length; i++) {
274
                            if (AllFormElements[i].type == 'radio') {
275
                                var ThisRadio = AllFormElements[i].name;
276
                                var ThisChecked = 'No';
277
                                var AllRadioOptions = document.getElementsByName(ThisRadio);
278
279
                                for (x = 0; x < AllRadioOptions.length; x++) {
280
                                     if (AllRadioOptions[x].checked && ThisChecked == 'No') {
281
                                         ThisChecked = 'Yes';
282
                                         break;
283
                                     }
284
                                }
285
286
                                var AlreadySearched = ShowAlert.indexOf(ThisRadio);
287
                                if (ThisChecked == 'No' && AlreadySearched == -1) {
288
                                    ShowAlert = ShowAlert + ThisRadio;
289
                                }
290
                            }
291
                        }
292
                        if (ShowAlert != '') {
293
294
                        } else {
295
                            $('.question-validate-btn').removeAttr('disabled');
296
                        }
297
                    }
298
299
                    function handleRadioRow(event, question_id, answer_id) {
300
                        var t = event.target;
301
                        if (t && t.tagName == 'INPUT')
302
                            return;
303
                        while (t && t.tagName != 'TD') {
304
                            t = t.parentElement;
305
                        }
306
                        var r = t.getElementsByTagName('INPUT')[0];
307
                        r.click();
308
                        RadioValidator(question_id, answer_id);
309
                    }
310
311
                    $(function() {
312
                        var ShowAlert = '';
313
                        var typeRadioB = '';
314
                        var question_id = $('input[name=question_id]').val();
315
                        var AllFormElements = window.document.getElementById('exercise_form').elements;
316
317
                        for (i = 0; i < AllFormElements.length; i++) {
318
                            if (AllFormElements[i].type == 'radio') {
319
                                var ThisRadio = AllFormElements[i].name;
320
                                var ThisChecked = 'No';
321
                                var AllRadioOptions = document.getElementsByName(ThisRadio);
322
323
                                for (x = 0; x < AllRadioOptions.length; x++) {
324
                                    if (AllRadioOptions[x].checked && ThisChecked == 'No') {
325
                                        ThisChecked = \"Yes\";
326
                                        break;
327
                                    }
328
                                }
329
330
                                var AlreadySearched = ShowAlert.indexOf(ThisRadio);
331
                                if (ThisChecked == 'No' && AlreadySearched == -1) {
332
                                    ShowAlert = ShowAlert + ThisRadio;
333
                                }
334
                            }
335
                        }
336
337
                        if (ShowAlert != '') {
338
                             $('.question-validate-btn').attr('disabled', 'disabled');
339
                        } else {
340
                            $('.question-validate-btn').removeAttr('disabled');
341
                        }
342
                    });
343
                </script>";
344
345
                foreach ($objQuestionTmp->optionsTitle as $item) {
346
                    if (in_array($item, $objQuestionTmp->optionsTitle)) {
347
                        $properties = [];
348
                        if ('Answers' === $item) {
349
                            $properties['colspan'] = 2;
350
                            $properties['style'] = 'background-color: #F56B2A; color: #ffffff;';
351
                        } elseif ('DegreeOfCertaintyThatMyAnswerIsCorrect' == $item) {
352
                            $properties['colspan'] = 6;
353
                            $properties['style'] = 'background-color: #330066; color: #ffffff;';
354
                        }
355
                        $header .= Display::tag('th', get_lang($item), $properties);
356
                    } else {
357
                        $header .= Display::tag('th', $item);
358
                    }
359
                }
360
361
                if ($show_comment) {
362
                    $header .= Display::tag('th', get_lang('Feedback'));
363
                }
364
365
                $s .= '<table class="data_table">';
366
                $s .= Display::tag('tr', $header, ['style' => 'text-align:left;']);
367
368
                // ajout de la 2eme ligne d'entête pour true/falss et les pourcentages de certitude
369
                $header1 = Display::tag('th', '&nbsp;');
370
                $cpt1 = 0;
371
                foreach ($objQuestionTmp->options as $item) {
372
                    $colorBorder1 = ($cpt1 == (count($objQuestionTmp->options) - 1))
373
                        ? '' : 'border-right: solid #FFFFFF 1px;';
374
                    if ('True' === $item || 'False' === $item) {
375
                        $header1 .= Display::tag(
376
                            'th',
377
                            get_lang($item),
378
                            ['style' => 'background-color: #F7C9B4; color: black;'.$colorBorder1]
379
                        );
380
                    } else {
381
                        $header1 .= Display::tag(
382
                            'th',
383
                            $item,
384
                            ['style' => 'background-color: #e6e6ff; color: black;padding:5px; '.$colorBorder1]
385
                        );
386
                    }
387
                    $cpt1++;
388
                }
389
                if ($show_comment) {
390
                    $header1 .= Display::tag('th', '&nbsp;');
391
                }
392
393
                $s .= Display::tag('tr', $header1);
394
395
                // add explanation
396
                $header2 = Display::tag('th', '&nbsp;');
397
                $descriptionList = [
398
                    get_lang('I don\'t know the answer and I\'ve picked at random'),
399
                    get_lang('I am very unsure'),
400
                    get_lang('I am unsure'),
401
                    get_lang('I am pretty sure'),
402
                    get_lang('I am almost 100% sure'),
403
                    get_lang('I am totally sure'),
404
                ];
405
                $counter2 = 0;
406
                foreach ($objQuestionTmp->options as $item) {
407
                    if ('True' == $item || 'False' == $item) {
408
                        $header2 .= Display::tag('td',
409
                            '&nbsp;',
410
                            ['style' => 'background-color: #F7E1D7; color: black;border-right: solid #FFFFFF 1px;']);
411
                    } else {
412
                        $color_border2 = ($counter2 == (count($objQuestionTmp->options) - 1)) ?
413
                            '' : 'border-right: solid #FFFFFF 1px;font-size:11px;';
414
                        $header2 .= Display::tag(
415
                            'td',
416
                            nl2br($descriptionList[$counter2]),
417
                            ['style' => 'background-color: #EFEFFC; color: black; width: 110px; text-align:center;
418
                                vertical-align: top; padding:5px; '.$color_border2]);
419
                        $counter2++;
420
                    }
421
                }
422
                if ($show_comment) {
423
                    $header2 .= Display::tag('th', '&nbsp;');
424
                }
425
                $s .= Display::tag('tr', $header2);
426
            }
427
428
            if ($show_comment) {
429
                if (in_array(
430
                    $answerType,
431
                    [
432
                        MULTIPLE_ANSWER,
433
                        MULTIPLE_ANSWER_COMBINATION,
434
                        UNIQUE_ANSWER,
435
                        UNIQUE_ANSWER_IMAGE,
436
                        UNIQUE_ANSWER_NO_OPTION,
437
                        GLOBAL_MULTIPLE_ANSWER,
438
                    ]
439
                )) {
440
                    $header = Display::tag('th', get_lang('Options'));
441
                    if (EXERCISE_FEEDBACK_TYPE_END == $exercise->getFeedbackType()) {
442
                        $header .= Display::tag('th', get_lang('Feedback'));
443
                    }
444
                    $s .= '<table class="table table-hover table-striped">';
445
                    $s .= Display::tag(
446
                        'tr',
447
                        $header,
448
                        ['style' => 'text-align:left;']
449
                    );
450
                }
451
            }
452
453
            $matching_correct_answer = 0;
454
            $userChoiceList = [];
455
            if (!empty($user_choice)) {
456
                foreach ($user_choice as $item) {
457
                    $userChoiceList[] = $item['answer'];
458
                }
459
            }
460
461
            $hidingClass = '';
462
            if (READING_COMPREHENSION == $answerType) {
463
                $objQuestionTmp->setExerciseType($exercise->selectType());
464
                $objQuestionTmp->processText($objQuestionTmp->selectDescription());
465
                $hidingClass = 'hide-reading-answers';
466
                $s .= Display::div(
467
                    $objQuestionTmp->selectTitle(),
468
                    ['class' => 'question_title '.$hidingClass]
469
                );
470
            }
471
472
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
473
                $answer = $objAnswerTmp->selectAnswer($answerId);
474
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
475
                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
476
                $comment = $objAnswerTmp->selectComment($answerId);
477
                $attributes = [];
478
479
                switch ($answerType) {
480
                    case UNIQUE_ANSWER:
481
                    case UNIQUE_ANSWER_NO_OPTION:
482
                    case UNIQUE_ANSWER_IMAGE:
483
                    case READING_COMPREHENSION:
484
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
485
                        if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
486
                            $attributes = [
487
                                'id' => $input_id,
488
                                'checked' => 1,
489
                                'selected' => 1,
490
                            ];
491
                        } else {
492
                            $attributes = ['id' => $input_id];
493
                        }
494
495
                        if ($debug_mark_answer) {
496
                            if ($answerCorrect) {
497
                                $attributes['checked'] = 1;
498
                                $attributes['selected'] = 1;
499
                            }
500
                        }
501
502
                        if ($show_comment) {
503
                            $s .= '<tr><td>';
504
                        }
505
506
                        if (UNIQUE_ANSWER_IMAGE == $answerType) {
507
                            if ($show_comment) {
508
                                if (empty($comment)) {
509
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'"
510
                                            class="exercise-unique-answer-image" style="text-align: center">';
511
                                } else {
512
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'"
513
                                            class="exercise-unique-answer-image col-xs-6 col-sm-12"
514
                                            style="text-align: center">';
515
                                }
516
                            } else {
517
                                $s .= '<div id="answer'.$questionId.$numAnswer.'"
518
                                        class="exercise-unique-answer-image col-xs-6 col-md-3"
519
                                        style="text-align: center">';
520
                            }
521
                        }
522
523
                        if ($answerType != UNIQUE_ANSWER_IMAGE) {
524
                            $answer = Security::remove_XSS($answer, STUDENT);
525
                        }
526
                        $s .= Display::input(
527
                            'hidden',
528
                            'choice2['.$questionId.']',
529
                            '0'
530
                        );
531
532
                        $answer_input = null;
533
                        $attributes['class'] = 'checkradios';
534
                        if (UNIQUE_ANSWER_IMAGE == $answerType) {
535
                            $attributes['class'] = '';
536
                            $attributes['style'] = 'display: none;';
537
                            $answer = '<div class="thumbnail">'.$answer.'</div>';
538
                        }
539
540
                        $answer_input .= '<label class="radio '.$hidingClass.'">';
541
                        $answer_input .= Display::input(
542
                            'radio',
543
                            'choice['.$questionId.']',
544
                            $numAnswer,
545
                            $attributes
546
                        );
547
                        $answer_input .= $answer;
548
                        $answer_input .= '</label>';
549
550
                        if (UNIQUE_ANSWER_IMAGE == $answerType) {
551
                            $answer_input .= "</div>";
552
                        }
553
554
                        if ($show_comment) {
555
                            $s .= $answer_input;
556
                            $s .= '</td>';
557
                            $s .= '<td>';
558
                            $s .= $comment;
559
                            $s .= '</td>';
560
                            $s .= '</tr>';
561
                        } else {
562
                            $s .= $answer_input;
563
                        }
564
                        break;
565
                    case MULTIPLE_ANSWER:
566
                    case MULTIPLE_ANSWER_TRUE_FALSE:
567
                    case GLOBAL_MULTIPLE_ANSWER:
568
                    case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
569
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
570
                        $answer = Security::remove_XSS($answer, STUDENT);
571
572
                        if (in_array($numAnswer, $userChoiceList)) {
573
                            $attributes = [
574
                                'id' => $input_id,
575
                                'checked' => 1,
576
                                'selected' => 1,
577
                            ];
578
                        } else {
579
                            $attributes = ['id' => $input_id];
580
                        }
581
582
                        if ($debug_mark_answer) {
583
                            if ($answerCorrect) {
584
                                $attributes['checked'] = 1;
585
                                $attributes['selected'] = 1;
586
                            }
587
                        }
588
589
                        if (MULTIPLE_ANSWER == $answerType || GLOBAL_MULTIPLE_ANSWER == $answerType) {
590
                            $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
591
                            $attributes['class'] = 'checkradios';
592
                            $answer_input = '<label class="checkbox">';
593
                            $answer_input .= Display::input(
594
                                'checkbox',
595
                                'choice['.$questionId.']['.$numAnswer.']',
596
                                $numAnswer,
597
                                $attributes
598
                            );
599
                            $answer_input .= $answer;
600
                            $answer_input .= '</label>';
601
602
                            if ($show_comment) {
603
                                $s .= '<tr><td>';
604
                                $s .= $answer_input;
605
                                $s .= '</td>';
606
                                $s .= '<td>';
607
                                $s .= $comment;
608
                                $s .= '</td>';
609
                                $s .= '</tr>';
610
                            } else {
611
                                $s .= $answer_input;
612
                            }
613
                        } elseif (MULTIPLE_ANSWER_TRUE_FALSE == $answerType) {
614
                            $myChoice = [];
615
                            if (!empty($userChoiceList)) {
616
                                foreach ($userChoiceList as $item) {
617
                                    $item = explode(':', $item);
618
                                    if (!empty($item)) {
619
                                        $myChoice[$item[0]] = isset($item[1]) ? $item[1] : '';
620
                                    }
621
                                }
622
                            }
623
624
                            $s .= '<tr>';
625
                            $s .= Display::tag('td', $answer);
626
627
                            if (!empty($quizQuestionOptions)) {
628
                                foreach ($quizQuestionOptions as $id => $item) {
629
                                    if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) {
630
                                        $attributes = [
631
                                            'checked' => 1,
632
                                            'selected' => 1,
633
                                        ];
634
                                    } else {
635
                                        $attributes = [];
636
                                    }
637
638
                                    if ($debug_mark_answer) {
639
                                        if ($id == $answerCorrect) {
640
                                            $attributes['checked'] = 1;
641
                                            $attributes['selected'] = 1;
642
                                        }
643
                                    }
644
                                    $s .= Display::tag(
645
                                        'td',
646
                                        Display::input(
647
                                            'radio',
648
                                            'choice['.$questionId.']['.$numAnswer.']',
649
                                            $id,
650
                                            $attributes
651
                                        ),
652
                                        ['style' => '']
653
                                    );
654
                                }
655
                            }
656
657
                            if ($show_comment) {
658
                                $s .= '<td>';
659
                                $s .= $comment;
660
                                $s .= '</td>';
661
                            }
662
                            $s .= '</tr>';
663
                        } elseif (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType) {
664
                            $myChoice = [];
665
                            if (!empty($userChoiceList)) {
666
                                foreach ($userChoiceList as $item) {
667
                                    $item = explode(':', $item);
668
                                    $myChoice[$item[0]] = $item[1];
669
                                }
670
                            }
671
                            $myChoiceDegreeCertainty = [];
672
                            if (!empty($userChoiceList)) {
673
                                foreach ($userChoiceList as $item) {
674
                                    $item = explode(':', $item);
675
                                    $myChoiceDegreeCertainty[$item[0]] = $item[2];
676
                                }
677
                            }
678
                            $s .= '<tr>';
679
                            $s .= Display::tag('td', $answer);
680
681
                            if (!empty($quizQuestionOptions)) {
682
                                foreach ($quizQuestionOptions as $id => $item) {
683
                                    if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) {
684
                                        $attributes = ['checked' => 1, 'selected' => 1];
685
                                    } else {
686
                                        $attributes = [];
687
                                    }
688
                                    $attributes['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')';
689
690
                                    // radio button selection
691
                                    if (isset($myChoiceDegreeCertainty[$numAnswer]) &&
692
                                        $id == $myChoiceDegreeCertainty[$numAnswer]
693
                                    ) {
694
                                        $attributes1 = ['checked' => 1, 'selected' => 1];
695
                                    } else {
696
                                        $attributes1 = [];
697
                                    }
698
699
                                    $attributes1['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')';
700
701
                                    if ($debug_mark_answer) {
702
                                        if ($id == $answerCorrect) {
703
                                            $attributes['checked'] = 1;
704
                                            $attributes['selected'] = 1;
705
                                        }
706
                                    }
707
708
                                    if ('True' == $item['name'] || 'False' == $item['name']) {
709
                                        $s .= Display::tag('td',
710
                                            Display::input('radio',
711
                                                'choice['.$questionId.']['.$numAnswer.']',
712
                                                $id,
713
                                                $attributes
714
                                            ),
715
                                            ['style' => 'text-align:center; background-color:#F7E1D7;',
716
                                                'onclick' => 'handleRadioRow(event, '.
717
                                                    $questionId.', '.
718
                                                    $numAnswer.')',
719
                                            ]
720
                                        );
721
                                    } else {
722
                                        $s .= Display::tag('td',
723
                                            Display::input('radio',
724
                                                'choiceDegreeCertainty['.$questionId.']['.$numAnswer.']',
725
                                                $id,
726
                                                $attributes1
727
                                            ),
728
                                            ['style' => 'text-align:center; background-color:#EFEFFC;',
729
                                                'onclick' => 'handleRadioRow(event, '.
730
                                                    $questionId.', '.
731
                                                    $numAnswer.')',
732
                                            ]
733
                                        );
734
                                    }
735
                                }
736
                            }
737
738
                            if ($show_comment) {
739
                                $s .= '<td>';
740
                                $s .= $comment;
741
                                $s .= '</td>';
742
                            }
743
                            $s .= '</tr>';
744
                        }
745
                        break;
746
                    case MULTIPLE_ANSWER_COMBINATION:
747
                        // multiple answers
748
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
749
750
                        if (in_array($numAnswer, $userChoiceList)) {
751
                            $attributes = [
752
                                'id' => $input_id,
753
                                'checked' => 1,
754
                                'selected' => 1,
755
                            ];
756
                        } else {
757
                            $attributes = ['id' => $input_id];
758
                        }
759
760
                        if ($debug_mark_answer) {
761
                            if ($answerCorrect) {
762
                                $attributes['checked'] = 1;
763
                                $attributes['selected'] = 1;
764
                            }
765
                        }
766
767
                        $answer = Security::remove_XSS($answer, STUDENT);
768
                        $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
769
                        $answer_input .= '<label class="checkbox">';
770
                        $answer_input .= Display::input(
771
                            'checkbox',
772
                            'choice['.$questionId.']['.$numAnswer.']',
773
                            1,
774
                            $attributes
775
                        );
776
                        $answer_input .= $answer;
777
                        $answer_input .= '</label>';
778
779
                        if ($show_comment) {
780
                            $s .= '<tr>';
781
                            $s .= '<td>';
782
                            $s .= $answer_input;
783
                            $s .= '</td>';
784
                            $s .= '<td>';
785
                            $s .= $comment;
786
                            $s .= '</td>';
787
                            $s .= '</tr>';
788
                        } else {
789
                            $s .= $answer_input;
790
                        }
791
                        break;
792
                    case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
793
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
794
                        $myChoice = [];
795
                        if (!empty($userChoiceList)) {
796
                            foreach ($userChoiceList as $item) {
797
                                $item = explode(':', $item);
798
                                if (isset($item[1]) && isset($item[0])) {
799
                                    $myChoice[$item[0]] = $item[1];
800
                                }
801
                            }
802
                        }
803
                        $answer = Security::remove_XSS($answer, STUDENT);
804
                        $s .= '<tr>';
805
                        $s .= Display::tag('td', $answer);
806
                        foreach ($objQuestionTmp->options as $key => $item) {
807
                            if (isset($myChoice[$numAnswer]) && $key == $myChoice[$numAnswer]) {
808
                                $attributes = [
809
                                    'checked' => 1,
810
                                    'selected' => 1,
811
                                ];
812
                            } else {
813
                                $attributes = [];
814
                            }
815
816
                            if ($debug_mark_answer) {
817
                                if ($key == $answerCorrect) {
818
                                    $attributes['checked'] = 1;
819
                                    $attributes['selected'] = 1;
820
                                }
821
                            }
822
                            $s .= Display::tag(
823
                                'td',
824
                                Display::input(
825
                                    'radio',
826
                                    'choice['.$questionId.']['.$numAnswer.']',
827
                                    $key,
828
                                    $attributes
829
                                )
830
                            );
831
                        }
832
833
                        if ($show_comment) {
834
                            $s .= '<td>';
835
                            $s .= $comment;
836
                            $s .= '</td>';
837
                        }
838
                        $s .= '</tr>';
839
                        break;
840
                    case FILL_IN_BLANKS:
841
                        // display the question, with field empty, for student to fill it,
842
                        // or filled to display the answer in the Question preview of the exercise/admin.php page
843
                        $displayForStudent = true;
844
                        $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
845
                        // Correct answers
846
                        $correctAnswerList = $listAnswerInfo['words'];
847
                        // Student's answer
848
                        $studentAnswerList = [];
849
                        if (isset($user_choice[0]['answer'])) {
850
                            $arrayStudentAnswer = FillBlanks::getAnswerInfo(
851
                                $user_choice[0]['answer'],
852
                                true
853
                            );
854
                            $studentAnswerList = $arrayStudentAnswer['student_answer'];
855
                        }
856
857
                        // If the question must be shown with the answer (in page exercise/admin.php)
858
                        // for teacher preview set the student-answer to the correct answer
859
                        if ($debug_mark_answer) {
860
                            $studentAnswerList = $correctAnswerList;
861
                            $displayForStudent = false;
862
                        }
863
864
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
865
                            $answer = '';
866
                            for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
867
                                // display the common word
868
                                $answer .= $listAnswerInfo['common_words'][$i];
869
                                // display the blank word
870
                                $correctItem = $listAnswerInfo['words'][$i];
871
                                if (isset($studentAnswerList[$i])) {
872
                                    // If student already started this test and answered this question,
873
                                    // fill the blank with his previous answers
874
                                    // may be "" if student viewed the question, but did not fill the blanks
875
                                    $correctItem = $studentAnswerList[$i];
876
                                }
877
                                $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
878
                                $answer .= FillBlanks::getFillTheBlankHtml(
879
                                    $current_item,
880
                                    $questionId,
881
                                    $correctItem,
882
                                    $attributes,
883
                                    $answer,
884
                                    $listAnswerInfo,
885
                                    $displayForStudent,
886
                                    $i
887
                                );
888
                            }
889
                            // display the last common word
890
                            $answer .= $listAnswerInfo['common_words'][$i];
891
                        } else {
892
                            // display empty [input] with the right width for student to fill it
893
                            $answer = '';
894
                            for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
895
                                // display the common words
896
                                $answer .= $listAnswerInfo['common_words'][$i];
897
                                // display the blank word
898
                                $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
899
                                $answer .= FillBlanks::getFillTheBlankHtml(
900
                                    $current_item,
901
                                    $questionId,
902
                                    '',
903
                                    $attributes,
904
                                    $answer,
905
                                    $listAnswerInfo,
906
                                    $displayForStudent,
907
                                    $i
908
                                );
909
                            }
910
                            // display the last common word
911
                            $answer .= $listAnswerInfo['common_words'][$i];
912
                        }
913
                        $s .= $answer;
914
                        break;
915
                    case CALCULATED_ANSWER:
916
                        /*
917
                         * In the CALCULATED_ANSWER test
918
                         * you mustn't have [ and ] in the textarea
919
                         * you mustn't have @@ in the textarea
920
                         * the text to find mustn't be empty or contains only spaces
921
                         * the text to find mustn't contains HTML tags
922
                         * the text to find mustn't contains char "
923
                         */
924
                        if (null !== $origin) {
925
                            global $exe_id;
926
                            $exe_id = (int) $exe_id;
927
                            $trackAttempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
928
                            $sql = "SELECT answer FROM $trackAttempts
929
                                    WHERE exe_id = $exe_id AND question_id= $questionId";
930
                            $rsLastAttempt = Database::query($sql);
931
                            $rowLastAttempt = Database::fetch_array($rsLastAttempt);
932
933
                            $answer = null;
934
                            if (isset($rowLastAttempt['answer'])) {
935
                                $answer = $rowLastAttempt['answer'];
936
                            }
937
938
                            if (empty($answer)) {
939
                                $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(
940
                                    1,
941
                                    $nbrAnswers
942
                                );
943
                                $answer = $objAnswerTmp->selectAnswer(
944
                                    $_SESSION['calculatedAnswerId'][$questionId]
945
                                );
946
                            }
947
                        }
948
949
                        list($answer) = explode('@@', $answer);
950
                        // $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
951
                        api_preg_match_all(
952
                            '/\[[^]]+\]/',
953
                            $answer,
954
                            $correctAnswerList
955
                        );
956
957
                        // get student answer to display it if student go back
958
                        // to previous calculated answer question in a test
959
                        if (isset($user_choice[0]['answer'])) {
960
                            api_preg_match_all(
961
                                '/\[[^]]+\]/',
962
                                $answer,
963
                                $studentAnswerList
964
                            );
965
                            $studentAnswerListToClean = $studentAnswerList[0];
966
                            $studentAnswerList = [];
967
968
                            $maxStudents = count($studentAnswerListToClean);
969
                            for ($i = 0; $i < $maxStudents; $i++) {
970
                                $answerCorrected = $studentAnswerListToClean[$i];
971
                                $answerCorrected = api_preg_replace(
972
                                    '| / <font color="green"><b>.*$|',
973
                                    '',
974
                                    $answerCorrected
975
                                );
976
                                $answerCorrected = api_preg_replace(
977
                                    '/^\[/',
978
                                    '',
979
                                    $answerCorrected
980
                                );
981
                                $answerCorrected = api_preg_replace(
982
                                    '|^<font color="red"><s>|',
983
                                    '',
984
                                    $answerCorrected
985
                                );
986
                                $answerCorrected = api_preg_replace(
987
                                    '|</s></font>$|',
988
                                    '',
989
                                    $answerCorrected
990
                                );
991
                                $answerCorrected = '['.$answerCorrected.']';
992
                                $studentAnswerList[] = $answerCorrected;
993
                            }
994
                        }
995
996
                        // If display preview of answer in test view for exemple,
997
                        // set the student answer to the correct answers
998
                        if ($debug_mark_answer) {
999
                            // contain the rights answers surronded with brackets
1000
                            $studentAnswerList = $correctAnswerList[0];
1001
                        }
1002
1003
                        /*
1004
                        Split the response by bracket
1005
                        tabComments is an array with text surrounding the text to find
1006
                        we add a space before and after the answerQuestion to be sure to
1007
                        have a block of text before and after [xxx] patterns
1008
                        so we have n text to find ([xxx]) and n+1 block of texts before,
1009
                        between and after the text to find
1010
                        */
1011
                        $tabComments = api_preg_split(
1012
                            '/\[[^]]+\]/',
1013
                            ' '.$answer.' '
1014
                        );
1015
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
1016
                            $answer = '';
1017
                            $i = 0;
1018
                            foreach ($studentAnswerList as $studentItem) {
1019
                                // Remove surronding brackets
1020
                                $studentResponse = api_substr(
1021
                                    $studentItem,
1022
                                    1,
1023
                                    api_strlen($studentItem) - 2
1024
                                );
1025
                                $size = strlen($studentItem);
1026
                                $attributes['class'] = self::detectInputAppropriateClass($size);
1027
                                $answer .= $tabComments[$i].
1028
                                    Display::input(
1029
                                        'text',
1030
                                        "choice[$questionId][]",
1031
                                        $studentResponse,
1032
                                        $attributes
1033
                                    );
1034
                                $i++;
1035
                            }
1036
                            $answer .= $tabComments[$i];
1037
                        } else {
1038
                            // display exercise with empty input fields
1039
                            // every [xxx] are replaced with an empty input field
1040
                            foreach ($correctAnswerList[0] as $item) {
1041
                                $size = strlen($item);
1042
                                $attributes['class'] = self::detectInputAppropriateClass($size);
1043
                                if (EXERCISE_FEEDBACK_TYPE_POPUP == $exercise->getFeedbackType()) {
1044
                                    $attributes['id'] = "question_$questionId";
1045
                                    $attributes['class'] .= ' checkCalculatedQuestionOnEnter ';
1046
                                }
1047
1048
                                $answer = str_replace(
1049
                                    $item,
1050
                                    Display::input(
1051
                                        'text',
1052
                                        "choice[$questionId][]",
1053
                                        '',
1054
                                        $attributes
1055
                                    ),
1056
                                    $answer
1057
                                );
1058
                            }
1059
                        }
1060
                        if (null !== $origin) {
1061
                            $s = $answer;
1062
                            break;
1063
                        } else {
1064
                            $s .= $answer;
1065
                        }
1066
                        break;
1067
                    case MATCHING:
1068
                        // matching type, showing suggestions and answers
1069
                        // TODO: replace $answerId by $numAnswer
1070
                        if (0 != $answerCorrect) {
1071
                            // only show elements to be answered (not the contents of
1072
                            // the select boxes, who are correct = 0)
1073
                            $s .= '<tr><td width="45%" valign="top">';
1074
                            $parsed_answer = $answer;
1075
                            // Left part questions
1076
                            $s .= '<p class="indent">'.$lines_count.'.&nbsp;'.$parsed_answer.'</p></td>';
1077
                            // Middle part (matches selects)
1078
                            // Id of select is # question + # of option
1079
                            $s .= '<td width="10%" valign="top" align="center">
1080
                                <div class="select-matching">
1081
                                <select
1082
                                    id="choice_id_'.$current_item.'_'.$lines_count.'"
1083
                                    name="choice['.$questionId.']['.$numAnswer.']">';
1084
1085
                            // fills the list-box
1086
                            foreach ($select_items as $key => $val) {
1087
                                // set $debug_mark_answer to true at function start to
1088
                                // show the correct answer with a suffix '-x'
1089
                                $selected = '';
1090
                                if ($debug_mark_answer) {
1091
                                    if ($val['id'] == $answerCorrect) {
1092
                                        $selected = 'selected="selected"';
1093
                                    }
1094
                                }
1095
                                //$user_choice_array_position
1096
                                if (isset($user_choice_array_position[$numAnswer]) &&
1097
                                    $val['id'] == $user_choice_array_position[$numAnswer]
1098
                                ) {
1099
                                    $selected = 'selected="selected"';
1100
                                }
1101
                                $s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
1102
                            }
1103
1104
                            $s .= '</select></div></td><td width="5%" class="separate">&nbsp;</td>';
1105
                            $s .= '<td width="40%" valign="top" >';
1106
                            if (isset($select_items[$lines_count])) {
1107
                                $s .= '<div class="text-right">
1108
                                        <p class="indent">'.
1109
                                    $select_items[$lines_count]['letter'].'.&nbsp; '.
1110
                                    $select_items[$lines_count]['answer'].'
1111
                                        </p>
1112
                                        </div>';
1113
                            } else {
1114
                                $s .= '&nbsp;';
1115
                            }
1116
                            $s .= '</td>';
1117
                            $s .= '</tr>';
1118
                            $lines_count++;
1119
                            // If the left side of the "matching" has been completely
1120
                            // shown but the right side still has values to show...
1121
                            if (($lines_count - 1) == $num_suggestions) {
1122
                                // if it remains answers to shown at the right side
1123
                                while (isset($select_items[$lines_count])) {
1124
                                    $s .= '<tr>
1125
                                      <td colspan="2"></td>
1126
                                      <td valign="top">';
1127
                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.
1128
                                        $select_items[$lines_count]['answer'];
1129
                                    $s .= "</td>
1130
                                </tr>";
1131
                                    $lines_count++;
1132
                                }
1133
                            }
1134
                            $matching_correct_answer++;
1135
                        }
1136
                        break;
1137
                    case DRAGGABLE:
1138
                        if ($answerCorrect) {
1139
                            $windowId = $questionId.'_'.$lines_count;
1140
                            $s .= '<li class="touch-items" id="'.$windowId.'">';
1141
                            $s .= Display::div(
1142
                                $answer,
1143
                                [
1144
                                    'id' => "window_$windowId",
1145
                                    'class' => "window{$questionId}_question_draggable exercise-draggable-answer-option",
1146
                                ]
1147
                            );
1148
1149
                            $draggableSelectOptions = [];
1150
                            $selectedValue = 0;
1151
                            $selectedIndex = 0;
1152
                            if ($user_choice) {
1153
                                foreach ($user_choice as $userChoiceKey => $chosen) {
1154
                                    $userChoiceKey++;
1155
                                    if ($lines_count != $userChoiceKey) {
1156
                                        continue;
1157
                                    }
1158
                                    /*if ($answerCorrect != $chosen['answer']) {
1159
                                        continue;
1160
                                    }*/
1161
                                    $selectedValue = $chosen['answer'];
1162
                                }
1163
                            }
1164
                            foreach ($select_items as $key => $select_item) {
1165
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
1166
                            }
1167
1168
                            foreach ($draggableSelectOptions as $value => $text) {
1169
                                if ($value == $selectedValue) {
1170
                                    break;
1171
                                }
1172
                                $selectedIndex++;
1173
                            }
1174
1175
                            $s .= Display::select(
1176
                                "choice[$questionId][$numAnswer]",
1177
                                $draggableSelectOptions,
1178
                                $selectedValue,
1179
                                [
1180
                                    'id' => "window_{$windowId}_select",
1181
                                    'class' => 'select_option hidden',
1182
                                ],
1183
                                false
1184
                            );
1185
1186
                            if ($selectedValue && $selectedIndex) {
1187
                                $s .= "
1188
                                    <script>
1189
                                        $(function() {
1190
                                            DraggableAnswer.deleteItem(
1191
                                                $('#{$questionId}_$lines_count'),
1192
                                                $('#drop_{$questionId}_{$selectedIndex}')
1193
                                            );
1194
                                        });
1195
                                    </script>
1196
                                ";
1197
                            }
1198
1199
                            if (isset($select_items[$lines_count])) {
1200
                                $s .= Display::div(
1201
                                    Display::tag(
1202
                                        'b',
1203
                                        $select_items[$lines_count]['letter']
1204
                                    ).$select_items[$lines_count]['answer'],
1205
                                    [
1206
                                        'id' => "window_{$windowId}_answer",
1207
                                        'class' => 'hidden',
1208
                                    ]
1209
                                );
1210
                            } else {
1211
                                $s .= '&nbsp;';
1212
                            }
1213
1214
                            $lines_count++;
1215
                            if (($lines_count - 1) == $num_suggestions) {
1216
                                while (isset($select_items[$lines_count])) {
1217
                                    $s .= Display::tag('b', $select_items[$lines_count]['letter']);
1218
                                    $s .= $select_items[$lines_count]['answer'];
1219
                                    $lines_count++;
1220
                                }
1221
                            }
1222
1223
                            $matching_correct_answer++;
1224
                            $s .= '</li>';
1225
                        }
1226
                        break;
1227
                    case MATCHING_DRAGGABLE:
1228
                        if (1 == $answerId) {
1229
                            echo $objAnswerTmp->getJs();
1230
                        }
1231
                        if (0 != $answerCorrect) {
1232
                            $windowId = "{$questionId}_{$lines_count}";
1233
                            $s .= <<<HTML
1234
                            <tr>
1235
                                <td width="45%">
1236
                                    <div id="window_{$windowId}"
1237
                                        class="window window_left_question window{$questionId}_question">
1238
                                        <strong>$lines_count.</strong>
1239
                                        $answer
1240
                                    </div>
1241
                                </td>
1242
                                <td width="10%">
1243
HTML;
1244
1245
                            $draggableSelectOptions = [];
1246
                            $selectedValue = 0;
1247
                            $selectedIndex = 0;
1248
1249
                            if ($user_choice) {
1250
                                foreach ($user_choice as $chosen) {
1251
                                    if ($numAnswer == $chosen['position']) {
1252
                                        $selectedValue = $chosen['answer'];
1253
                                        break;
1254
                                    }
1255
                                }
1256
                            }
1257
1258
                            foreach ($select_items as $key => $selectItem) {
1259
                                $draggableSelectOptions[$selectItem['id']] = $selectItem['letter'];
1260
                            }
1261
1262
                            foreach ($draggableSelectOptions as $value => $text) {
1263
                                if ($value == $selectedValue) {
1264
                                    break;
1265
                                }
1266
                                $selectedIndex++;
1267
                            }
1268
1269
                            $s .= Display::select(
1270
                                "choice[$questionId][$numAnswer]",
1271
                                $draggableSelectOptions,
1272
                                $selectedValue,
1273
                                [
1274
                                    'id' => "window_{$windowId}_select",
1275
                                    'class' => 'hidden',
1276
                                ],
1277
                                false
1278
                            );
1279
1280
                            if (!empty($answerCorrect) && !empty($selectedValue)) {
1281
                                // Show connect if is not freeze (question preview)
1282
                                if (!$freeze) {
1283
                                    $s .= "
1284
                                        <script>
1285
                                            $(function() {
1286
                                                $(window).on('load', function() {
1287
                                                    jsPlumb.connect({
1288
                                                        source: 'window_$windowId',
1289
                                                        target: 'window_{$questionId}_{$selectedIndex}_answer',
1290
                                                        endpoint: ['Blank', {radius: 15}],
1291
                                                        anchors: ['RightMiddle', 'LeftMiddle'],
1292
                                                        paintStyle: {strokeStyle: '#8A8888', lineWidth: 8},
1293
                                                        connector: [
1294
                                                            MatchingDraggable.connectorType,
1295
                                                            {curvines: MatchingDraggable.curviness}
1296
                                                        ]
1297
                                                    });
1298
                                                });
1299
                                            });
1300
                                        </script>
1301
                                    ";
1302
                                }
1303
                            }
1304
1305
                            $s .= '</td><td width="45%">';
1306
                            if (isset($select_items[$lines_count])) {
1307
                                $s .= <<<HTML
1308
                                <div id="window_{$windowId}_answer" class="window window_right_question">
1309
                                    <strong>{$select_items[$lines_count]['letter']}.</strong>
1310
                                    {$select_items[$lines_count]['answer']}
1311
                                </div>
1312
HTML;
1313
                            } else {
1314
                                $s .= '&nbsp;';
1315
                            }
1316
1317
                            $s .= '</td></tr>';
1318
                            $lines_count++;
1319
                            if (($lines_count - 1) == $num_suggestions) {
1320
                                while (isset($select_items[$lines_count])) {
1321
                                    $s .= <<<HTML
1322
                                    <tr>
1323
                                        <td colspan="2"></td>
1324
                                        <td>
1325
                                            <strong>{$select_items[$lines_count]['letter']}</strong>
1326
                                            {$select_items[$lines_count]['answer']}
1327
                                        </td>
1328
                                    </tr>
1329
HTML;
1330
                                    $lines_count++;
1331
                                }
1332
                            }
1333
                            $matching_correct_answer++;
1334
                        }
1335
                        break;
1336
                }
1337
            }
1338
1339
            if ($show_comment) {
1340
                $s .= '</table>';
1341
            } elseif (in_array(
1342
                $answerType,
1343
                [
1344
                    MATCHING,
1345
                    MATCHING_DRAGGABLE,
1346
                    UNIQUE_ANSWER_NO_OPTION,
1347
                    MULTIPLE_ANSWER_TRUE_FALSE,
1348
                    MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE,
1349
                    MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY,
1350
                ]
1351
            )) {
1352
                $s .= '</table>';
1353
            }
1354
1355
            if (DRAGGABLE == $answerType) {
1356
                $isVertical = 'v' == $objQuestionTmp->extra;
1357
                $s .= "</ul>";
1358
                $s .= "</div></div>"; // col-md-12
1359
                $counterAnswer = 1;
1360
                $s .= $isVertical ? '' : '<div class="row">';
1361
                for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1362
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
1363
                    $windowId = $questionId.'_'.$counterAnswer;
1364
                    if ($answerCorrect) {
1365
                        $s .= $isVertical ? '<div class="row">' : '';
1366
                        $s .= '
1367
                            <div class="'.($isVertical ? 'col-md-12' : 'col-xs-12 col-sm-4 col-md-3 col-lg-2').'">
1368
                                <div class="droppable-item">
1369
                                    <span class="number">'.$counterAnswer.'.</span>
1370
                                    <div id="drop_'.$windowId.'" class="droppable">
1371
                                    </div>
1372
                                 </div>
1373
                            </div>
1374
                        ';
1375
                        $s .= $isVertical ? '</div>' : '';
1376
                        $counterAnswer++;
1377
                    }
1378
                }
1379
1380
                $s .= $isVertical ? '' : '</div>'; // row
1381
                $s .= '</div>'; // col-md-12 ui-widget ui-helper-clearfix
1382
            }
1383
1384
            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
1385
                $s .= '</div>'; //drag_question
1386
            }
1387
1388
            $s .= '</div>'; //question_options row
1389
1390
            // destruction of the Answer object
1391
            unset($objAnswerTmp);
1392
            // destruction of the Question object
1393
            unset($objQuestionTmp);
1394
            if ('export' == $origin) {
1395
                return $s;
1396
            }
1397
            echo $s;
1398
        } elseif (HOT_SPOT == $answerType || HOT_SPOT_DELINEATION == $answerType) {
1399
            global $exe_id;
1400
            $questionDescription = $objQuestionTmp->selectDescription();
1401
            // Get the answers, make a list
1402
            $objAnswerTmp = new Answer($questionId, $course_id);
1403
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1404
1405
            // get answers of hotpost
1406
            $answers_hotspot = [];
1407
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1408
                $answers = $objAnswerTmp->selectAnswerByAutoId(
1409
                    $objAnswerTmp->selectAutoId($answerId)
1410
                );
1411
                $answers_hotspot[$answers['id']] = $objAnswerTmp->selectAnswer(
1412
                    $answerId
1413
                );
1414
            }
1415
1416
            $answerList = '';
1417
            $hotspotColor = 0;
1418
            if (HOT_SPOT_DELINEATION != $answerType) {
1419
                $answerList = '
1420
                    <div class="well well-sm">
1421
                        <h5 class="page-header">'.get_lang('Image zones').'</h5>
1422
                        <ol>
1423
                ';
1424
1425
                if (!empty($answers_hotspot)) {
1426
                    Session::write("hotspot_ordered$questionId", array_keys($answers_hotspot));
1427
                    foreach ($answers_hotspot as $value) {
1428
                        $answerList .= '<li>';
1429
                        if ($freeze) {
1430
                            $answerList .= '<span class="hotspot-color-'.$hotspotColor
1431
                                .' fa fa-square" aria-hidden="true"></span>'.PHP_EOL;
1432
                        }
1433
                        $answerList .= $value;
1434
                        $answerList .= '</li>';
1435
                        $hotspotColor++;
1436
                    }
1437
                }
1438
1439
                $answerList .= '
1440
                        </ul>
1441
                    </div>
1442
                ';
1443
                if ($freeze) {
1444
                    $relPath = api_get_path(WEB_CODE_PATH);
1445
                    echo "
1446
                        <div class=\"row\">
1447
                            <div class=\"col-sm-9\">
1448
                                <div id=\"hotspot-preview-$questionId\"></div>
1449
                            </div>
1450
                            <div class=\"col-sm-3\">
1451
                                $answerList
1452
                            </div>
1453
                        </div>
1454
                        <script>
1455
                            new ".(HOT_SPOT == $answerType ? "HotspotQuestion" : "DelineationQuestion")."({
1456
                                questionId: $questionId,
1457
                                exerciseId: $exerciseId,
1458
                                exeId: 0,
1459
                                selector: '#hotspot-preview-$questionId',
1460
                                for: 'preview',
1461
                                relPath: '$relPath'
1462
                            });
1463
                        </script>
1464
                    ";
1465
1466
                    return;
1467
                }
1468
            }
1469
1470
            if (!$only_questions) {
1471
                if ($show_title) {
1472
                    if ($exercise->display_category_name) {
1473
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1474
                    }
1475
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1476
                }
1477
                //@todo I need to the get the feedback type
1478
                echo <<<HOTSPOT
1479
                    <input type="hidden" name="hidden_hotspot_id" value="$questionId" />
1480
                    <div class="exercise_questions">
1481
                        $questionDescription
1482
                        <div class="row">
1483
HOTSPOT;
1484
            }
1485
1486
            $relPath = api_get_path(WEB_CODE_PATH);
1487
            $s .= "<div class=\"col-sm-8 col-md-9\">
1488
                   <div class=\"hotspot-image\"></div>
1489
                    <script>
1490
                        $(function() {
1491
                            new ".(HOT_SPOT_DELINEATION == $answerType ? 'DelineationQuestion' : 'HotspotQuestion')."({
1492
                                questionId: $questionId,
1493
                                exerciseId: $exerciseId,
1494
                                selector: '#question_div_' + $questionId + ' .hotspot-image',
1495
                                for: 'user',
1496
                                relPath: '$relPath'
1497
                            });
1498
                        });
1499
                    </script>
1500
                </div>
1501
                <div class=\"col-sm-4 col-md-3\">
1502
                    $answerList
1503
                </div>
1504
            ";
1505
1506
            echo <<<HOTSPOT
1507
                            $s
1508
                        </div>
1509
                    </div>
1510
HOTSPOT;
1511
        } elseif (ANNOTATION == $answerType) {
1512
            global $exe_id;
1513
            $relPath = api_get_path(WEB_CODE_PATH);
1514
            if (api_is_platform_admin() || api_is_course_admin()) {
1515
                $questionRepo = Container::getQuestionRepository();
1516
                $questionEntity = $questionRepo->find($questionId);
1517
                if ($freeze) {
1518
                    echo Display::img(
1519
                        $questionRepo->getHotSpotImageUrl($questionEntity),
1520
                        $objQuestionTmp->selectTitle(),
1521
                        ['width' => '600px']
1522
                    );
1523
1524
                    return 0;
1525
                }
1526
            }
1527
1528
            if (!$only_questions) {
1529
                if ($show_title) {
1530
                    if ($exercise->display_category_name) {
1531
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1532
                    }
1533
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1534
                }
1535
                echo '
1536
                    <input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />
1537
                    <div class="exercise_questions">
1538
                        '.$objQuestionTmp->selectDescription().'
1539
                        <div class="row">
1540
                            <div class="col-sm-8 col-md-9">
1541
                                <div id="annotation-canvas-'.$questionId.'" class="annotation-canvas center-block">
1542
                                </div>
1543
                                <script>
1544
                                    AnnotationQuestion({
1545
                                        questionId: '.$questionId.',
1546
                                        exerciseId: '.$exerciseId.',
1547
                                        relPath: \''.$relPath.'\',
1548
                                        courseId: '.$course_id.',
1549
                                    });
1550
                                </script>
1551
                            </div>
1552
                            <div class="col-sm-4 col-md-3">
1553
                                <div class="well well-sm" id="annotation-toolbar-'.$questionId.'">
1554
                                    <div class="btn-toolbar">
1555
                                        <div class="btn-group" data-toggle="buttons">
1556
                                            <label class="btn btn-default active"
1557
                                                aria-label="'.get_lang('Add annotation path').'">
1558
                                                <input
1559
                                                    type="radio" value="0"
1560
                                                    name="'.$questionId.'-options" autocomplete="off" checked>
1561
                                                <span class="fa fa-pencil" aria-hidden="true"></span>
1562
                                            </label>
1563
                                            <label class="btn btn-default"
1564
                                                aria-label="'.get_lang('Add annotation text').'">
1565
                                                <input
1566
                                                    type="radio" value="1"
1567
                                                    name="'.$questionId.'-options" autocomplete="off">
1568
                                                <span class="fa fa-font fa-fw" aria-hidden="true"></span>
1569
                                            </label>
1570
                                        </div>
1571
                                    </div>
1572
                                    <ul class="list-unstyled"></ul>
1573
                                </div>
1574
                            </div>
1575
                        </div>
1576
                    </div>
1577
                ';
1578
            }
1579
            $objAnswerTmp = new Answer($questionId);
1580
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1581
            unset($objAnswerTmp, $objQuestionTmp);
1582
        }
1583
1584
        return $nbrAnswers;
1585
    }
1586
1587
    /**
1588
     * @param int $exeId
1589
     *
1590
     * @return array
1591
     */
1592
    public static function get_exercise_track_exercise_info($exeId)
1593
    {
1594
        $quizTable = Database::get_course_table(TABLE_QUIZ_TEST);
1595
        $trackExerciseTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1596
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1597
        $exeId = (int) $exeId;
1598
        $result = [];
1599
        if (!empty($exeId)) {
1600
            $sql = " SELECT q.*, tee.*
1601
                FROM $quizTable as q
1602
                INNER JOIN $trackExerciseTable as tee
1603
                ON q.id = tee.exe_exo_id
1604
                INNER JOIN $courseTable c
1605
                ON c.id = tee.c_id
1606
                WHERE tee.exe_id = $exeId
1607
                AND q.c_id = c.id";
1608
1609
            $sqlResult = Database::query($sql);
1610
            if (Database::num_rows($sqlResult)) {
1611
                $result = Database::fetch_array($sqlResult, 'ASSOC');
1612
                $result['duration_formatted'] = '';
1613
                if (!empty($result['exe_duration'])) {
1614
                    $time = api_format_time($result['exe_duration'], 'js');
1615
                    $result['duration_formatted'] = $time;
1616
                }
1617
            }
1618
        }
1619
1620
        return $result;
1621
    }
1622
1623
    /**
1624
     * Validates the time control key.
1625
     *
1626
     * @param int $exercise_id
1627
     * @param int $lp_id
1628
     * @param int $lp_item_id
1629
     *
1630
     * @return bool
1631
     */
1632
    public static function exercise_time_control_is_valid(
1633
        $exercise_id,
1634
        $lp_id = 0,
1635
        $lp_item_id = 0
1636
    ) {
1637
        $course_id = api_get_course_int_id();
1638
        $exercise_id = (int) $exercise_id;
1639
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1640
        $sql = "SELECT expired_time FROM $table
1641
                WHERE c_id = $course_id AND id = $exercise_id";
1642
        $result = Database::query($sql);
1643
        $row = Database::fetch_array($result, 'ASSOC');
1644
        if (!empty($row['expired_time'])) {
1645
            $current_expired_time_key = self::get_time_control_key(
1646
                $exercise_id,
1647
                $lp_id,
1648
                $lp_item_id
1649
            );
1650
            if (isset($_SESSION['expired_time'][$current_expired_time_key])) {
1651
                $current_time = time();
1652
                $expired_time = api_strtotime(
1653
                    $_SESSION['expired_time'][$current_expired_time_key],
1654
                    'UTC'
1655
                );
1656
                $total_time_allowed = $expired_time + 30;
1657
                if ($total_time_allowed < $current_time) {
1658
                    return false;
1659
                }
1660
1661
                return true;
1662
            }
1663
1664
            return false;
1665
        }
1666
1667
        return true;
1668
    }
1669
1670
    /**
1671
     * Deletes the time control token.
1672
     *
1673
     * @param int $exercise_id
1674
     * @param int $lp_id
1675
     * @param int $lp_item_id
1676
     */
1677
    public static function exercise_time_control_delete(
1678
        $exercise_id,
1679
        $lp_id = 0,
1680
        $lp_item_id = 0
1681
    ) {
1682
        $current_expired_time_key = self::get_time_control_key(
1683
            $exercise_id,
1684
            $lp_id,
1685
            $lp_item_id
1686
        );
1687
        unset($_SESSION['expired_time'][$current_expired_time_key]);
1688
    }
1689
1690
    /**
1691
     * Generates the time control key.
1692
     *
1693
     * @param int $exercise_id
1694
     * @param int $lp_id
1695
     * @param int $lp_item_id
1696
     *
1697
     * @return string
1698
     */
1699
    public static function get_time_control_key(
1700
        $exercise_id,
1701
        $lp_id = 0,
1702
        $lp_item_id = 0
1703
    ) {
1704
        $exercise_id = (int) $exercise_id;
1705
        $lp_id = (int) $lp_id;
1706
        $lp_item_id = (int) $lp_item_id;
1707
1708
        return
1709
            api_get_course_int_id().'_'.
1710
            api_get_session_id().'_'.
1711
            $exercise_id.'_'.
1712
            api_get_user_id().'_'.
1713
            $lp_id.'_'.
1714
            $lp_item_id;
1715
    }
1716
1717
    /**
1718
     * Get session time control.
1719
     *
1720
     * @param int $exercise_id
1721
     * @param int $lp_id
1722
     * @param int $lp_item_id
1723
     *
1724
     * @return int
1725
     */
1726
    public static function get_session_time_control_key(
1727
        $exercise_id,
1728
        $lp_id = 0,
1729
        $lp_item_id = 0
1730
    ) {
1731
        $return_value = 0;
1732
        $time_control_key = self::get_time_control_key(
1733
            $exercise_id,
1734
            $lp_id,
1735
            $lp_item_id
1736
        );
1737
        if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) {
1738
            $return_value = $_SESSION['expired_time'][$time_control_key];
1739
        }
1740
1741
        return $return_value;
1742
    }
1743
1744
    /**
1745
     * Gets count of exam results.
1746
     *
1747
     * @param int    $exerciseId
1748
     * @param array  $conditions
1749
     * @param string $courseCode
1750
     * @param bool   $showSession
1751
     *
1752
     * @return array
1753
     */
1754
    public static function get_count_exam_results($exerciseId, $conditions, $courseCode = '', $showSession = false)
1755
    {
1756
        $count = self::get_exam_results_data(
1757
            null,
1758
            null,
1759
            null,
1760
            null,
1761
            $exerciseId,
1762
            $conditions,
1763
            true,
1764
            $courseCode,
1765
            $showSession
1766
        );
1767
1768
        return $count;
1769
    }
1770
1771
    /**
1772
     * Gets the exam'data results.
1773
     *
1774
     * @todo this function should be moved in a library  + no global calls
1775
     *
1776
     * @param int    $from
1777
     * @param int    $number_of_items
1778
     * @param int    $column
1779
     * @param string $direction
1780
     * @param int    $exercise_id
1781
     * @param null   $extra_where_conditions
1782
     * @param bool   $get_count
1783
     * @param string $courseCode
1784
     * @param bool   $showSessionField
1785
     * @param bool   $showExerciseCategories
1786
     * @param array  $userExtraFieldsToAdd
1787
     * @param bool   $useCommaAsDecimalPoint
1788
     * @param bool   $roundValues
1789
     * @param bool   $getOnyIds
1790
     *
1791
     * @return array
1792
     */
1793
    public static function get_exam_results_data(
1794
        $from,
1795
        $number_of_items,
1796
        $column,
1797
        $direction,
1798
        $exercise_id,
1799
        $extra_where_conditions = null,
1800
        $get_count = false,
1801
        $courseCode = null,
1802
        $showSessionField = false,
1803
        $showExerciseCategories = false,
1804
        $userExtraFieldsToAdd = [],
1805
        $useCommaAsDecimalPoint = false,
1806
        $roundValues = false,
1807
        $getOnyIds = false
1808
    ) {
1809
        //@todo replace all this globals
1810
        global $filter;
1811
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
1812
        $courseInfo = api_get_course_info($courseCode);
1813
1814
        if (empty($courseInfo)) {
1815
            return [];
1816
        }
1817
1818
        //$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
1819
1820
        $course_id = $courseInfo['real_id'];
1821
        $sessionId = api_get_session_id();
1822
        $exercise_id = (int) $exercise_id;
1823
1824
        $is_allowedToEdit =
1825
            api_is_allowed_to_edit(null, true) ||
1826
            api_is_allowed_to_edit(true) ||
1827
            api_is_drh() ||
1828
            api_is_student_boss() ||
1829
            api_is_session_admin();
1830
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
1831
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1832
        $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
1833
        $TBL_GROUP = Database::get_course_table(TABLE_GROUP);
1834
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1835
        $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1836
1837
        $session_id_and = '';
1838
        $sessionCondition = '';
1839
        if (!$showSessionField) {
1840
            $session_id_and = " AND te.session_id = $sessionId ";
1841
            $sessionCondition = " AND ttte.session_id = $sessionId";
1842
        }
1843
1844
        $exercise_where = '';
1845
        if (!empty($exercise_id)) {
1846
            $exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.'  ';
1847
        }
1848
1849
        // sql for chamilo-type tests for teacher / tutor view
1850
        $sql_inner_join_tbl_track_exercices = "
1851
        (
1852
            SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised
1853
            FROM $TBL_TRACK_EXERCICES ttte
1854
            LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
1855
            ON (ttte.exe_id = tr.exe_id)
1856
            WHERE
1857
                c_id = $course_id AND
1858
                exe_exo_id = $exercise_id
1859
                $sessionCondition
1860
        )";
1861
1862
        if ($is_allowedToEdit) {
1863
            //@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries
1864
            // Hack in order to filter groups
1865
            $sql_inner_join_tbl_user = '';
1866
            if (strpos($extra_where_conditions, 'group_id')) {
1867
                $sql_inner_join_tbl_user = "
1868
                (
1869
                    SELECT
1870
                        u.user_id,
1871
                        firstname,
1872
                        lastname,
1873
                        official_code,
1874
                        email,
1875
                        username,
1876
                        g.name as group_name,
1877
                        g.id as group_id
1878
                    FROM $TBL_USER u
1879
                    INNER JOIN $TBL_GROUP_REL_USER gru
1880
                    ON (gru.user_id = u.user_id AND gru.c_id= $course_id )
1881
                    INNER JOIN $TBL_GROUP g
1882
                    ON (gru.group_id = g.id AND g.c_id= $course_id )
1883
                )";
1884
            }
1885
1886
            if (strpos($extra_where_conditions, 'group_all')) {
1887
                $extra_where_conditions = str_replace(
1888
                    "AND (  group_id = 'group_all'  )",
1889
                    '',
1890
                    $extra_where_conditions
1891
                );
1892
                $extra_where_conditions = str_replace(
1893
                    "AND group_id = 'group_all'",
1894
                    '',
1895
                    $extra_where_conditions
1896
                );
1897
                $extra_where_conditions = str_replace(
1898
                    "group_id = 'group_all' AND",
1899
                    '',
1900
                    $extra_where_conditions
1901
                );
1902
1903
                $sql_inner_join_tbl_user = "
1904
                (
1905
                    SELECT
1906
                        u.user_id,
1907
                        firstname,
1908
                        lastname,
1909
                        official_code,
1910
                        email,
1911
                        username,
1912
                        '' as group_name,
1913
                        '' as group_id
1914
                    FROM $TBL_USER u
1915
                )";
1916
                $sql_inner_join_tbl_user = null;
1917
            }
1918
1919
            if (strpos($extra_where_conditions, 'group_none')) {
1920
                $extra_where_conditions = str_replace(
1921
                    "AND (  group_id = 'group_none'  )",
1922
                    "AND (  group_id is null  )",
1923
                    $extra_where_conditions
1924
                );
1925
                $extra_where_conditions = str_replace(
1926
                    "AND group_id = 'group_none'",
1927
                    "AND (  group_id is null  )",
1928
                    $extra_where_conditions
1929
                );
1930
                $sql_inner_join_tbl_user = "
1931
            (
1932
                SELECT
1933
                    u.user_id,
1934
                    firstname,
1935
                    lastname,
1936
                    official_code,
1937
                    email,
1938
                    username,
1939
                    g.name as group_name,
1940
                    g.id as group_id
1941
                FROM $TBL_USER u
1942
                LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
1943
                ON ( gru.user_id = u.user_id AND gru.c_id= $course_id )
1944
                LEFT OUTER JOIN $TBL_GROUP g
1945
                ON (gru.group_id = g.id AND g.c_id = $course_id )
1946
            )";
1947
            }
1948
1949
            // All
1950
            $is_empty_sql_inner_join_tbl_user = false;
1951
            if (empty($sql_inner_join_tbl_user)) {
1952
                $is_empty_sql_inner_join_tbl_user = true;
1953
                $sql_inner_join_tbl_user = "
1954
            (
1955
                SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code
1956
                FROM $TBL_USER u
1957
                WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
1958
            )";
1959
            }
1960
1961
            $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
1962
            $sqlWhereOption = "  AND gru.c_id = $course_id AND gru.user_id = user.user_id ";
1963
            $first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
1964
1965
            if ($get_count) {
1966
                $sql_select = 'SELECT count(te.exe_id) ';
1967
            } else {
1968
                $sql_select = "SELECT DISTINCT
1969
                    user_id,
1970
                    $first_and_last_name,
1971
                    official_code,
1972
                    ce.title,
1973
                    username,
1974
                    te.score,
1975
                    te.max_score,
1976
                    te.exe_date,
1977
                    te.exe_id,
1978
                    te.session_id,
1979
                    email as exemail,
1980
                    te.start_date,
1981
                    ce.expired_time,
1982
                    steps_counter,
1983
                    exe_user_id,
1984
                    te.exe_duration,
1985
                    te.status as completion_status,
1986
                    propagate_neg,
1987
                    revised,
1988
                    group_name,
1989
                    group_id,
1990
                    orig_lp_id,
1991
                    te.user_ip";
1992
            }
1993
1994
            $sql = " $sql_select
1995
                FROM $TBL_EXERCICES AS ce
1996
                INNER JOIN $sql_inner_join_tbl_track_exercices AS te
1997
                ON (te.exe_exo_id = ce.id)
1998
                INNER JOIN $sql_inner_join_tbl_user AS user
1999
                ON (user.user_id = exe_user_id)
2000
                WHERE
2001
                    te.c_id = $course_id $session_id_and AND
2002
                    ce.active <> -1 AND
2003
                    ce.c_id = $course_id
2004
                    $exercise_where
2005
                    $extra_where_conditions
2006
                ";
2007
        }
2008
2009
        if (empty($sql)) {
2010
            return false;
2011
        }
2012
2013
        if ($get_count) {
2014
            $resx = Database::query($sql);
2015
            $rowx = Database::fetch_row($resx, 'ASSOC');
2016
2017
            return $rowx[0];
2018
        }
2019
2020
        $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
2021
        $teacher_id_list = [];
2022
        if (!empty($teacher_list)) {
2023
            foreach ($teacher_list as $teacher) {
2024
                $teacher_id_list[] = $teacher['user_id'];
2025
            }
2026
        }
2027
2028
        $scoreDisplay = new ScoreDisplay();
2029
        $decimalSeparator = '.';
2030
        $thousandSeparator = ',';
2031
2032
        if ($useCommaAsDecimalPoint) {
2033
            $decimalSeparator = ',';
2034
            $thousandSeparator = '';
2035
        }
2036
2037
        $listInfo = [];
2038
        $column = !empty($column) ? Database::escape_string($column) : null;
2039
        $from = (int) $from;
2040
        $number_of_items = (int) $number_of_items;
2041
2042
        if (!empty($column)) {
2043
            $sql .= " ORDER BY $column $direction ";
2044
        }
2045
2046
        if (!$getOnyIds) {
2047
            $sql .= " LIMIT $from, $number_of_items";
2048
        }
2049
2050
        $results = [];
2051
        $resx = Database::query($sql);
2052
        while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
2053
            $results[] = $rowx;
2054
        }
2055
2056
        $group_list = GroupManager::get_group_list(null, $courseInfo);
2057
        $clean_group_list = [];
2058
        if (!empty($group_list)) {
2059
            foreach ($group_list as $group) {
2060
                $clean_group_list[$group['id']] = $group['name'];
2061
            }
2062
        }
2063
2064
        $lp_list_obj = new LearnpathList(api_get_user_id());
2065
        $lp_list = $lp_list_obj->get_flat_list();
2066
        $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
2067
2068
        if (is_array($results)) {
2069
            $users_array_id = [];
2070
            $from_gradebook = false;
2071
            if (isset($_GET['gradebook']) && 'view' == $_GET['gradebook']) {
2072
                $from_gradebook = true;
2073
            }
2074
            $sizeof = count($results);
2075
            $locked = api_resource_is_locked_by_gradebook(
2076
                $exercise_id,
2077
                LINK_EXERCISE
2078
            );
2079
2080
            $timeNow = strtotime(api_get_utc_datetime());
2081
            // Looping results
2082
            for ($i = 0; $i < $sizeof; $i++) {
2083
                $revised = $results[$i]['revised'];
2084
                if ('incomplete' == $results[$i]['completion_status']) {
2085
                    // If the exercise was incomplete, we need to determine
2086
                    // if it is still into the time allowed, or if its
2087
                    // allowed time has expired and it can be closed
2088
                    // (it's "unclosed")
2089
                    $minutes = $results[$i]['expired_time'];
2090
                    if (0 == $minutes) {
2091
                        // There's no time limit, so obviously the attempt
2092
                        // can still be "ongoing", but the teacher should
2093
                        // be able to choose to close it, so mark it as
2094
                        // "unclosed" instead of "ongoing"
2095
                        $revised = 2;
2096
                    } else {
2097
                        $allowedSeconds = $minutes * 60;
2098
                        $timeAttemptStarted = strtotime($results[$i]['start_date']);
2099
                        $secondsSinceStart = $timeNow - $timeAttemptStarted;
2100
                        if ($secondsSinceStart > $allowedSeconds) {
2101
                            $revised = 2; // mark as "unclosed"
2102
                        } else {
2103
                            $revised = 3; // mark as "ongoing"
2104
                        }
2105
                    }
2106
                }
2107
2108
                if ($from_gradebook && ($is_allowedToEdit)) {
2109
                    if (in_array(
2110
                        $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'],
2111
                        $users_array_id
2112
                    )) {
2113
                        continue;
2114
                    }
2115
                    $users_array_id[] = $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'];
2116
                }
2117
2118
                $lp_obj = isset($results[$i]['orig_lp_id']) && isset($lp_list[$results[$i]['orig_lp_id']]) ? $lp_list[$results[$i]['orig_lp_id']] : null;
2119
                if (empty($lp_obj)) {
2120
                    // Try to get the old id (id instead of iid)
2121
                    $lpNewId = isset($results[$i]['orig_lp_id']) && isset($oldIds[$results[$i]['orig_lp_id']]) ? $oldIds[$results[$i]['orig_lp_id']] : null;
2122
                    if ($lpNewId) {
2123
                        $lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
2124
                    }
2125
                }
2126
                $lp_name = null;
2127
                if ($lp_obj) {
2128
                    $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$results[$i]['orig_lp_id'];
2129
                    $lp_name = Display::url(
2130
                        $lp_obj['lp_name'],
2131
                        $url,
2132
                        ['target' => '_blank']
2133
                    );
2134
                }
2135
2136
                // Add all groups by user
2137
                $group_name_list = '';
2138
                if ($is_empty_sql_inner_join_tbl_user) {
2139
                    $group_list = GroupManager::get_group_ids(
2140
                        api_get_course_int_id(),
2141
                        $results[$i]['user_id']
2142
                    );
2143
2144
                    foreach ($group_list as $id) {
2145
                        if (isset($clean_group_list[$id])) {
2146
                            $group_name_list .= $clean_group_list[$id].'<br/>';
2147
                        }
2148
                    }
2149
                    $results[$i]['group_name'] = $group_name_list;
2150
                }
2151
2152
                $results[$i]['exe_duration'] = !empty($results[$i]['exe_duration']) ? round($results[$i]['exe_duration'] / 60) : 0;
2153
                $id = $results[$i]['exe_id'];
2154
                $dt = api_convert_and_format_date($results[$i]['max_score']);
2155
2156
                // we filter the results if we have the permission to
2157
                $result_disabled = 0;
2158
                if (isset($results[$i]['results_disabled'])) {
2159
                    $result_disabled = (int) $results[$i]['results_disabled'];
2160
                }
2161
                if (0 == $result_disabled) {
2162
                    $my_res = $results[$i]['score'];
2163
                    $my_total = $results[$i]['max_score'];
2164
                    $results[$i]['start_date'] = api_get_local_time($results[$i]['start_date']);
2165
                    $results[$i]['exe_date'] = api_get_local_time($results[$i]['exe_date']);
2166
2167
                    if (!$results[$i]['propagate_neg'] && $my_res < 0) {
2168
                        $my_res = 0;
2169
                    }
2170
2171
                    $score = self::show_score(
2172
                        $my_res,
2173
                        $my_total,
2174
                        true,
2175
                        true,
2176
                        false,
2177
                        false,
2178
                        $decimalSeparator,
2179
                        $thousandSeparator,
2180
                        $roundValues
2181
                    );
2182
2183
                    $actions = '<div class="pull-right">';
2184
                    if ($is_allowedToEdit) {
2185
                        if (isset($teacher_id_list)) {
2186
                            if (in_array(
2187
                                $results[$i]['exe_user_id'],
2188
                                $teacher_id_list
2189
                            )) {
2190
                                $actions .= Display::return_icon('teacher.png', get_lang('Trainer'));
2191
                            }
2192
                        }
2193
                        $revisedLabel = '';
2194
                        switch ($revised) {
2195
                            case 0:
2196
                                $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=qualify&id=$id'>".
2197
                                    Display:: return_icon(
2198
                                        'quiz.png',
2199
                                        get_lang('Grade activity')
2200
                                    );
2201
                                $actions .= '</a>';
2202
                                $revisedLabel = Display::label(
2203
                                    get_lang('Not validated'),
2204
                                    'info'
2205
                                );
2206
                                break;
2207
                            case 1:
2208
                                $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=edit&id=$id'>".
2209
                                    Display:: return_icon(
2210
                                        'edit.png',
2211
                                        get_lang('Edit'),
2212
                                        [],
2213
                                        ICON_SIZE_SMALL
2214
                                    );
2215
                                $actions .= '</a>';
2216
                                $revisedLabel = Display::label(
2217
                                    get_lang('Validated'),
2218
                                    'success'
2219
                                );
2220
                                break;
2221
                            case 2: //finished but not marked as such
2222
                                $actions .= '<a href="exercise_report.php?'
2223
                                    .api_get_cidreq()
2224
                                    .'&exerciseId='
2225
                                    .$exercise_id
2226
                                    .'&a=close&id='
2227
                                    .$id
2228
                                    .'">'.
2229
                                    Display:: return_icon(
2230
                                        'lock.png',
2231
                                        get_lang('Mark attempt as closed'),
2232
                                        [],
2233
                                        ICON_SIZE_SMALL
2234
                                    );
2235
                                $actions .= '</a>';
2236
                                $revisedLabel = Display::label(
2237
                                    get_lang('Unclosed'),
2238
                                    'warning'
2239
                                );
2240
                                break;
2241
                            case 3: //still ongoing
2242
                                $actions .= Display:: return_icon(
2243
                                    'clock.png',
2244
                                    get_lang('Attempt still going on. Please wait.'),
2245
                                    [],
2246
                                    ICON_SIZE_SMALL
2247
                                );
2248
                                $actions .= '';
2249
                                $revisedLabel = Display::label(
2250
                                    get_lang('Ongoing'),
2251
                                    'danger'
2252
                                );
2253
                                break;
2254
                        }
2255
2256
                        if (2 == $filter) {
2257
                            $actions .= ' <a href="exercise_history.php?'.api_get_cidreq().'&exe_id='.$id.'">'.
2258
                                Display:: return_icon(
2259
                                    'history.png',
2260
                                    get_lang('View changes history')
2261
                                ).'</a>';
2262
                        }
2263
2264
                        // Admin can always delete the attempt
2265
                        if ((false == $locked || api_is_platform_admin()) && !api_is_student_boss()) {
2266
                            $ip = Tracking::get_ip_from_user_event(
2267
                                $results[$i]['exe_user_id'],
2268
                                api_get_utc_datetime(),
2269
                                false
2270
                            );
2271
                            $actions .= '<a href="http://www.whatsmyip.org/ip-geo-location/?ip='.$ip.'" target="_blank">'
2272
                                .Display::return_icon('info.png', $ip)
2273
                                .'</a>';
2274
2275
                            $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'.
2276
                                api_get_cidreq().'&'.
2277
                                http_build_query([
2278
                                    'id' => $id,
2279
                                    'exercise' => $exercise_id,
2280
                                    'user' => $results[$i]['exe_user_id'],
2281
                                ]);
2282
                            $actions .= Display::url(
2283
                                Display::return_icon('reload.png', get_lang('Recalculate results')),
2284
                                $recalculateUrl,
2285
                                [
2286
                                    'data-exercise' => $exercise_id,
2287
                                    'data-user' => $results[$i]['exe_user_id'],
2288
                                    'data-id' => $id,
2289
                                    'class' => 'exercise-recalculate',
2290
                                ]
2291
                            );
2292
2293
                            $filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0;
2294
                            $delete_link = '<a href="exercise_report.php?'.api_get_cidreq().'&filter_by_user='.$filterByUser.'&filter='.$filter.'&id='.$exercise_id.'&delete=delete&did='.$id.'"
2295
                            onclick="javascript:if(!confirm(\''.sprintf(
2296
                                addslashes(get_lang('Delete attempt?')),
2297
                                $results[$i]['username'],
2298
                                $dt
2299
                            ).'\')) return false;">';
2300
                            $delete_link .= Display::return_icon(
2301
                                'delete.png',
2302
                                    addslashes(get_lang('Delete'))
2303
                            ).'</a>';
2304
2305
                            if (api_is_drh() && !api_is_platform_admin()) {
2306
                                $delete_link = null;
2307
                            }
2308
                            if (api_is_session_admin()) {
2309
                                $delete_link = '';
2310
                            }
2311
                            if (3 == $revised) {
2312
                                $delete_link = null;
2313
                            }
2314
                            $actions .= $delete_link;
2315
                        }
2316
                    } else {
2317
                        $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.api_get_cidreq().'&id='.$results[$i]['exe_id'].'&sid='.$sessionId;
2318
                        $attempt_link = Display::url(
2319
                            get_lang('Show'),
2320
                            $attempt_url,
2321
                            [
2322
                                'class' => 'ajax btn btn-default',
2323
                                'data-title' => get_lang('Show'),
2324
                            ]
2325
                        );
2326
                        $actions .= $attempt_link;
2327
                    }
2328
                    $actions .= '</div>';
2329
2330
                    if (!empty($userExtraFieldsToAdd)) {
2331
                        foreach ($userExtraFieldsToAdd as $variable) {
2332
                            $extraFieldValue = new ExtraFieldValue('user');
2333
                            $values = $extraFieldValue->get_values_by_handler_and_field_variable(
2334
                                $results[$i]['user_id'],
2335
                                $variable
2336
                            );
2337
                            if (isset($values['value'])) {
2338
                                $results[$i][$variable] = $values['value'];
2339
                            }
2340
                        }
2341
                    }
2342
2343
                    $exeId = $results[$i]['exe_id'];
2344
                    $results[$i]['id'] = $exeId;
2345
                    $category_list = [];
2346
                    if ($is_allowedToEdit) {
2347
                        $sessionName = '';
2348
                        $sessionStartAccessDate = '';
2349
                        if (!empty($results[$i]['session_id'])) {
2350
                            $sessionInfo = api_get_session_info($results[$i]['session_id']);
2351
                            if (!empty($sessionInfo)) {
2352
                                $sessionName = $sessionInfo['name'];
2353
                                $sessionStartAccessDate = api_get_local_time($sessionInfo['access_start_date']);
2354
                            }
2355
                        }
2356
2357
                        $objExercise = new Exercise($course_id);
2358
                        if ($showExerciseCategories) {
2359
                            // Getting attempt info
2360
                            $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
2361
                            if (!empty($exercise_stat_info['data_tracking'])) {
2362
                                $question_list = explode(',', $exercise_stat_info['data_tracking']);
2363
                                if (!empty($question_list)) {
2364
                                    foreach ($question_list as $questionId) {
2365
                                        $objQuestionTmp = Question::read($questionId, $objExercise->course);
2366
                                        // We're inside *one* question. Go through each possible answer for this question
2367
                                        $result = $objExercise->manage_answer(
2368
                                            $exeId,
2369
                                            $questionId,
2370
                                            null,
2371
                                            'exercise_result',
2372
                                            false,
2373
                                            false,
2374
                                            true,
2375
                                            false,
2376
                                            $objExercise->selectPropagateNeg(),
2377
                                            null,
2378
                                            true
2379
                                        );
2380
2381
                                        $my_total_score = $result['score'];
2382
                                        $my_total_weight = $result['weight'];
2383
2384
                                        // Category report
2385
                                        $category_was_added_for_this_test = false;
2386
                                        if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
2387
                                            if (!isset($category_list[$objQuestionTmp->category]['score'])) {
2388
                                                $category_list[$objQuestionTmp->category]['score'] = 0;
2389
                                            }
2390
                                            if (!isset($category_list[$objQuestionTmp->category]['total'])) {
2391
                                                $category_list[$objQuestionTmp->category]['total'] = 0;
2392
                                            }
2393
                                            $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
2394
                                            $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
2395
                                            $category_was_added_for_this_test = true;
2396
                                        }
2397
2398
                                        if (isset($objQuestionTmp->category_list) &&
2399
                                            !empty($objQuestionTmp->category_list)
2400
                                        ) {
2401
                                            foreach ($objQuestionTmp->category_list as $category_id) {
2402
                                                $category_list[$category_id]['score'] += $my_total_score;
2403
                                                $category_list[$category_id]['total'] += $my_total_weight;
2404
                                                $category_was_added_for_this_test = true;
2405
                                            }
2406
                                        }
2407
2408
                                        // No category for this question!
2409
                                        if (false == $category_was_added_for_this_test) {
2410
                                            if (!isset($category_list['none']['score'])) {
2411
                                                $category_list['none']['score'] = 0;
2412
                                            }
2413
                                            if (!isset($category_list['none']['total'])) {
2414
                                                $category_list['none']['total'] = 0;
2415
                                            }
2416
2417
                                            $category_list['none']['score'] += $my_total_score;
2418
                                            $category_list['none']['total'] += $my_total_weight;
2419
                                        }
2420
                                    }
2421
                                }
2422
                            }
2423
                        }
2424
2425
                        foreach ($category_list as $categoryId => $result) {
2426
                            $scoreToDisplay = self::show_score(
2427
                                $result['score'],
2428
                                $result['total'],
2429
                                true,
2430
                                true,
2431
                                false,
2432
                                false,
2433
                                $decimalSeparator,
2434
                                $thousandSeparator,
2435
                                $roundValues
2436
                            );
2437
                            $results[$i]['category_'.$categoryId] = $scoreToDisplay;
2438
                            $results[$i]['category_'.$categoryId.'_score_percentage'] = self::show_score(
2439
                                $result['score'],
2440
                                $result['total'],
2441
                                true,
2442
                                true,
2443
                                true, // $show_only_percentage = false
2444
                                true, // hide % sign
2445
                                $decimalSeparator,
2446
                                $thousandSeparator,
2447
                                $roundValues
2448
                            );
2449
                            $results[$i]['category_'.$categoryId.'_only_score'] = $result['score'];
2450
                            $results[$i]['category_'.$categoryId.'_total'] = $result['total'];
2451
                        }
2452
                        $results[$i]['session'] = $sessionName;
2453
                        $results[$i]['session_access_start_date'] = $sessionStartAccessDate;
2454
                        $results[$i]['status'] = $revisedLabel;
2455
                        $results[$i]['score'] = $score;
2456
                        $results[$i]['score_percentage'] = self::show_score(
2457
                            $my_res,
2458
                            $my_total,
2459
                            true,
2460
                            true,
2461
                            true,
2462
                            true,
2463
                            $decimalSeparator,
2464
                            $thousandSeparator,
2465
                            $roundValues
2466
                        );
2467
2468
                        if ($roundValues) {
2469
                            $whole = floor($my_res); // 1
2470
                            $fraction = $my_res - $whole; // .25
2471
                            if ($fraction >= 0.5) {
2472
                                $onlyScore = ceil($my_res);
2473
                            } else {
2474
                                $onlyScore = round($my_res);
2475
                            }
2476
                        } else {
2477
                            $onlyScore = $scoreDisplay->format_score(
2478
                                $my_res,
2479
                                false,
2480
                                $decimalSeparator,
2481
                                $thousandSeparator
2482
                            );
2483
                        }
2484
2485
                        $results[$i]['only_score'] = $onlyScore;
2486
2487
                        if ($roundValues) {
2488
                            $whole = floor($my_total); // 1
2489
                            $fraction = $my_total - $whole; // .25
2490
                            if ($fraction >= 0.5) {
2491
                                $onlyTotal = ceil($my_total);
2492
                            } else {
2493
                                $onlyTotal = round($my_total);
2494
                            }
2495
                        } else {
2496
                            $onlyTotal = $scoreDisplay->format_score(
2497
                                $my_total,
2498
                                false,
2499
                                $decimalSeparator,
2500
                                $thousandSeparator
2501
                            );
2502
                        }
2503
                        $results[$i]['total'] = $onlyTotal;
2504
                        $results[$i]['lp'] = $lp_name;
2505
                        $results[$i]['actions'] = $actions;
2506
                        $listInfo[] = $results[$i];
2507
                    } else {
2508
                        $results[$i]['status'] = $revisedLabel;
2509
                        $results[$i]['score'] = $score;
2510
                        $results[$i]['actions'] = $actions;
2511
                        $listInfo[] = $results[$i];
2512
                    }
2513
                }
2514
            }
2515
        }
2516
2517
        return $listInfo;
2518
    }
2519
2520
    /**
2521
     * @param $score
2522
     * @param $weight
2523
     *
2524
     * @return array
2525
     */
2526
    public static function convertScoreToPlatformSetting($score, $weight)
2527
    {
2528
        $maxNote = api_get_setting('exercise_max_score');
2529
        $minNote = api_get_setting('exercise_min_score');
2530
2531
        if ('' != $maxNote && '' != $minNote) {
2532
            if (!empty($weight) && 0 != intval($weight)) {
2533
                $score = $minNote + ($maxNote - $minNote) * $score / $weight;
2534
            } else {
2535
                $score = $minNote;
2536
            }
2537
            $weight = $maxNote;
2538
        }
2539
2540
        return ['score' => $score, 'weight' => $weight];
2541
    }
2542
2543
    /**
2544
     * Converts the score with the exercise_max_note and exercise_min_score
2545
     * the platform settings + formats the results using the float_format function.
2546
     *
2547
     * @param float  $score
2548
     * @param float  $weight
2549
     * @param bool   $show_percentage       show percentage or not
2550
     * @param bool   $use_platform_settings use or not the platform settings
2551
     * @param bool   $show_only_percentage
2552
     * @param bool   $hidePercentageSign    hide "%" sign
2553
     * @param string $decimalSeparator
2554
     * @param string $thousandSeparator
2555
     * @param bool   $roundValues           This option rounds the float values into a int using ceil()
2556
     *
2557
     * @return string an html with the score modified
2558
     */
2559
    public static function show_score(
2560
        $score,
2561
        $weight,
2562
        $show_percentage = true,
2563
        $use_platform_settings = true,
2564
        $show_only_percentage = false,
2565
        $hidePercentageSign = false,
2566
        $decimalSeparator = '.',
2567
        $thousandSeparator = ',',
2568
        $roundValues = false
2569
    ) {
2570
        if (is_null($score) && is_null($weight)) {
2571
            return '-';
2572
        }
2573
2574
        if ($use_platform_settings) {
2575
            $result = self::convertScoreToPlatformSetting($score, $weight);
2576
            $score = $result['score'];
2577
            $weight = $result['weight'];
2578
        }
2579
2580
        $percentage = (100 * $score) / (0 != $weight ? $weight : 1);
2581
        // Formats values
2582
        $percentage = float_format($percentage, 1);
2583
        $score = float_format($score, 1);
2584
        $weight = float_format($weight, 1);
2585
2586
        if ($roundValues) {
2587
            $whole = floor($percentage); // 1
2588
            $fraction = $percentage - $whole; // .25
2589
2590
            // Formats values
2591
            if ($fraction >= 0.5) {
2592
                $percentage = ceil($percentage);
2593
            } else {
2594
                $percentage = round($percentage);
2595
            }
2596
2597
            $whole = floor($score); // 1
2598
            $fraction = $score - $whole; // .25
2599
            if ($fraction >= 0.5) {
2600
                $score = ceil($score);
2601
            } else {
2602
                $score = round($score);
2603
            }
2604
2605
            $whole = floor($weight); // 1
2606
            $fraction = $weight - $whole; // .25
2607
            if ($fraction >= 0.5) {
2608
                $weight = ceil($weight);
2609
            } else {
2610
                $weight = round($weight);
2611
            }
2612
        } else {
2613
            // Formats values
2614
            $percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator);
2615
            $score = float_format($score, 1, $decimalSeparator, $thousandSeparator);
2616
            $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
2617
        }
2618
2619
        if ($show_percentage) {
2620
            $percentageSign = '%';
2621
            if ($hidePercentageSign) {
2622
                $percentageSign = '';
2623
            }
2624
            $html = $percentage."$percentageSign ($score / $weight)";
2625
            if ($show_only_percentage) {
2626
                $html = $percentage.$percentageSign;
2627
            }
2628
        } else {
2629
            $html = $score.' / '.$weight;
2630
        }
2631
2632
        // Over write score
2633
        $scoreBasedInModel = self::convertScoreToModel($percentage);
2634
        if (!empty($scoreBasedInModel)) {
2635
            $html = $scoreBasedInModel;
2636
        }
2637
2638
        // Ignore other formats and use the configuratio['exercise_score_format'] value
2639
        // But also keep the round values settings.
2640
        $format = api_get_configuration_value('exercise_score_format');
2641
        if (!empty($format)) {
2642
            $html = ScoreDisplay::instance()->display_score([$score, $weight], $format);
2643
        }
2644
2645
        return Display::span($html, ['class' => 'score_exercise']);
2646
    }
2647
2648
    /**
2649
     * @param array $model
2650
     * @param float $percentage
2651
     *
2652
     * @return string
2653
     */
2654
    public static function getModelStyle($model, $percentage)
2655
    {
2656
        return '<span class="'.$model['css_class'].'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>';
2657
    }
2658
2659
    /**
2660
     * @param float $percentage value between 0 and 100
2661
     *
2662
     * @return string
2663
     */
2664
    public static function convertScoreToModel($percentage)
2665
    {
2666
        $model = self::getCourseScoreModel();
2667
        if (!empty($model)) {
2668
            $scoreWithGrade = [];
2669
            foreach ($model['score_list'] as $item) {
2670
                if ($percentage >= $item['min'] && $percentage <= $item['max']) {
2671
                    $scoreWithGrade = $item;
2672
                    break;
2673
                }
2674
            }
2675
2676
            if (!empty($scoreWithGrade)) {
2677
                return self::getModelStyle($scoreWithGrade, $percentage);
2678
            }
2679
        }
2680
2681
        return '';
2682
    }
2683
2684
    /**
2685
     * @return array
2686
     */
2687
    public static function getCourseScoreModel()
2688
    {
2689
        $modelList = self::getScoreModels();
2690
        if (empty($modelList)) {
2691
            return [];
2692
        }
2693
2694
        $courseInfo = api_get_course_info();
2695
        if (!empty($courseInfo)) {
2696
            $scoreModelId = api_get_course_setting('score_model_id');
2697
            if (-1 != $scoreModelId) {
2698
                $modelIdList = array_column($modelList['models'], 'id');
2699
                if (in_array($scoreModelId, $modelIdList)) {
2700
                    foreach ($modelList['models'] as $item) {
2701
                        if ($item['id'] == $scoreModelId) {
2702
                            return $item;
2703
                        }
2704
                    }
2705
                }
2706
            }
2707
        }
2708
2709
        return [];
2710
    }
2711
2712
    /**
2713
     * @return array
2714
     */
2715
    public static function getScoreModels()
2716
    {
2717
        return api_get_configuration_value('score_grade_model');
2718
    }
2719
2720
    /**
2721
     * @param float  $score
2722
     * @param float  $weight
2723
     * @param string $passPercentage
2724
     *
2725
     * @return bool
2726
     */
2727
    public static function isSuccessExerciseResult($score, $weight, $passPercentage)
2728
    {
2729
        $percentage = float_format(
2730
            ($score / (0 != $weight ? $weight : 1)) * 100,
2731
            1
2732
        );
2733
        if (isset($passPercentage) && !empty($passPercentage)) {
2734
            if ($percentage >= $passPercentage) {
2735
                return true;
2736
            }
2737
        }
2738
2739
        return false;
2740
    }
2741
2742
    /**
2743
     * @param string $name
2744
     * @param $weight
2745
     * @param $selected
2746
     *
2747
     * @return bool
2748
     */
2749
    public static function addScoreModelInput(
2750
        FormValidator $form,
2751
        $name,
2752
        $weight,
2753
        $selected
2754
    ) {
2755
        $model = self::getCourseScoreModel();
2756
        if (empty($model)) {
2757
            return false;
2758
        }
2759
2760
        /** @var HTML_QuickForm_select $element */
2761
        $element = $form->createElement(
2762
            'select',
2763
            $name,
2764
            get_lang('Score'),
2765
            [],
2766
            ['class' => 'exercise_mark_select']
2767
        );
2768
2769
        foreach ($model['score_list'] as $item) {
2770
            $i = api_number_format($item['score_to_qualify'] / 100 * $weight, 2);
2771
            $label = self::getModelStyle($item, $i);
2772
            $attributes = [
2773
                'class' => $item['css_class'],
2774
            ];
2775
            if ($selected == $i) {
2776
                $attributes['selected'] = 'selected';
2777
            }
2778
            $element->addOption($label, $i, $attributes);
2779
        }
2780
        $form->addElement($element);
2781
    }
2782
2783
    /**
2784
     * @return string
2785
     */
2786
    public static function getJsCode()
2787
    {
2788
        // Filling the scores with the right colors.
2789
        $models = self::getCourseScoreModel();
2790
        $cssListToString = '';
2791
        if (!empty($models)) {
2792
            $cssList = array_column($models['score_list'], 'css_class');
2793
            $cssListToString = implode(' ', $cssList);
2794
        }
2795
2796
        if (empty($cssListToString)) {
2797
            return '';
2798
        }
2799
        $js = <<<EOT
2800
2801
        function updateSelect(element) {
2802
            var spanTag = element.parent().find('span.filter-option');
2803
            var value = element.val();
2804
            var selectId = element.attr('id');
2805
            var optionClass = $('#' + selectId + ' option[value="'+value+'"]').attr('class');
2806
            spanTag.removeClass('$cssListToString');
2807
            spanTag.addClass(optionClass);
2808
        }
2809
2810
        $(function() {
2811
            // Loading values
2812
            $('.exercise_mark_select').on('loaded.bs.select', function() {
2813
                updateSelect($(this));
2814
            });
2815
            // On change
2816
            $('.exercise_mark_select').on('changed.bs.select', function() {
2817
                updateSelect($(this));
2818
            });
2819
        });
2820
EOT;
2821
2822
        return $js;
2823
    }
2824
2825
    /**
2826
     * @param float  $score
2827
     * @param float  $weight
2828
     * @param string $pass_percentage
2829
     *
2830
     * @return string
2831
     */
2832
    public static function showSuccessMessage($score, $weight, $pass_percentage)
2833
    {
2834
        $res = '';
2835
        if (self::isPassPercentageEnabled($pass_percentage)) {
2836
            $isSuccess = self::isSuccessExerciseResult(
2837
                $score,
2838
                $weight,
2839
                $pass_percentage
2840
            );
2841
2842
            if ($isSuccess) {
2843
                $html = get_lang('Congratulations you passed the test!');
2844
                $icon = Display::return_icon(
2845
                    'completed.png',
2846
                    get_lang('Correct'),
2847
                    [],
2848
                    ICON_SIZE_MEDIUM
2849
                );
2850
            } else {
2851
                $html = get_lang('You didn\'t reach the minimum score');
2852
                $icon = Display::return_icon(
2853
                    'warning.png',
2854
                    get_lang('Wrong'),
2855
                    [],
2856
                    ICON_SIZE_MEDIUM
2857
                );
2858
            }
2859
            $html = Display::tag('h4', $html);
2860
            $html .= Display::tag(
2861
                'h5',
2862
                $icon,
2863
                ['style' => 'width:40px; padding:2px 10px 0px 0px']
2864
            );
2865
            $res = $html;
2866
        }
2867
2868
        return $res;
2869
    }
2870
2871
    /**
2872
     * Return true if pass_pourcentage activated (we use the pass pourcentage feature
2873
     * return false if pass_percentage = 0 (we don't use the pass pourcentage feature.
2874
     *
2875
     * @param $value
2876
     *
2877
     * @return bool
2878
     *              In this version, pass_percentage and show_success_message are disabled if
2879
     *              pass_percentage is set to 0
2880
     */
2881
    public static function isPassPercentageEnabled($value)
2882
    {
2883
        return $value > 0;
2884
    }
2885
2886
    /**
2887
     * Converts a numeric value in a percentage example 0.66666 to 66.67 %.
2888
     *
2889
     * @param $value
2890
     *
2891
     * @return float Converted number
2892
     */
2893
    public static function convert_to_percentage($value)
2894
    {
2895
        $return = '-';
2896
        if ('' != $value) {
2897
            $return = float_format($value * 100, 1).' %';
2898
        }
2899
2900
        return $return;
2901
    }
2902
2903
    /**
2904
     * Getting all active exercises from a course from a session
2905
     * (if a session_id is provided we will show all the exercises in the course +
2906
     * all exercises in the session).
2907
     *
2908
     * @param array  $course_info
2909
     * @param int    $session_id
2910
     * @param bool   $check_publication_dates
2911
     * @param string $search                  Search exercise name
2912
     * @param bool   $search_all_sessions     Search exercises in all sessions
2913
     * @param   int 0 = only inactive exercises
2914
     *                  1 = only active exercises,
2915
     *                  2 = all exercises
2916
     *                  3 = active <> -1
2917
     *
2918
     * @return array array with exercise data
2919
     */
2920
    public static function get_all_exercises(
2921
        $course_info = null,
2922
        $session_id = 0,
2923
        $check_publication_dates = false,
2924
        $search = '',
2925
        $search_all_sessions = false,
2926
        $active = 2
2927
    ) {
2928
        $course_id = api_get_course_int_id();
2929
2930
        if (!empty($course_info) && !empty($course_info['real_id'])) {
2931
            $course_id = $course_info['real_id'];
2932
        }
2933
2934
        if (-1 == $session_id) {
2935
            $session_id = 0;
2936
        }
2937
2938
        $now = api_get_utc_datetime();
2939
        $timeConditions = '';
2940
        if ($check_publication_dates) {
2941
            // Start and end are set
2942
            $timeConditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' )  OR ";
2943
            // only start is set
2944
            $timeConditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
2945
            // only end is set
2946
            $timeConditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
2947
            // nothing is set
2948
            $timeConditions .= ' (start_time IS NULL AND end_time IS NULL)) ';
2949
        }
2950
2951
        $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
2952
        $needle = !empty($search) ? "%".$search."%" : '';
2953
2954
        // Show courses by active status
2955
        $active_sql = '';
2956
        if (3 == $active) {
2957
            $active_sql = ' active <> -1 AND';
2958
        } else {
2959
            if (2 != $active) {
2960
                $active_sql = sprintf(' active = %d AND', $active);
2961
            }
2962
        }
2963
2964
        if (true == $search_all_sessions) {
2965
            $conditions = [
2966
                'where' => [
2967
                    $active_sql.' c_id = ? '.$needle_where.$timeConditions => [
2968
                        $course_id,
2969
                        $needle,
2970
                    ],
2971
                ],
2972
                'order' => 'title',
2973
            ];
2974
        } else {
2975
            if (empty($session_id)) {
2976
                $conditions = [
2977
                    'where' => [
2978
                        $active_sql.' (session_id = 0 OR session_id IS NULL) AND c_id = ? '.$needle_where.$timeConditions => [
2979
                            $course_id,
2980
                            $needle,
2981
                        ],
2982
                    ],
2983
                    'order' => 'title',
2984
                ];
2985
            } else {
2986
                $conditions = [
2987
                    'where' => [
2988
                        $active_sql.' (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ? '.$needle_where.$timeConditions => [
2989
                            $session_id,
2990
                            $course_id,
2991
                            $needle,
2992
                        ],
2993
                    ],
2994
                    'order' => 'title',
2995
                ];
2996
            }
2997
        }
2998
2999
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3000
3001
        return Database::select('*', $table, $conditions);
3002
    }
3003
3004
    /**
3005
     * Getting all exercises (active only or all)
3006
     * from a course from a session
3007
     * (if a session_id is provided we will show all the exercises in the
3008
     * course + all exercises in the session).
3009
     *
3010
     * @param   array   course data
3011
     * @param   int     session id
3012
     * @param    int        course c_id
3013
     * @param bool $only_active_exercises
3014
     *
3015
     * @return array array with exercise data
3016
     *               modified by Hubert Borderiou
3017
     */
3018
    public static function get_all_exercises_for_course_id(
3019
        $course_info = null,
3020
        $session_id = 0,
3021
        $course_id = 0,
3022
        $only_active_exercises = true
3023
    ) {
3024
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3025
3026
        if ($only_active_exercises) {
3027
            // Only active exercises.
3028
            $sql_active_exercises = "active = 1 AND ";
3029
        } else {
3030
            // Not only active means visible and invisible NOT deleted (-2)
3031
            $sql_active_exercises = "active IN (1, 0) AND ";
3032
        }
3033
3034
        if (-1 == $session_id) {
3035
            $session_id = 0;
3036
        }
3037
3038
        $params = [
3039
            $session_id,
3040
            $course_id,
3041
        ];
3042
3043
        if (empty($session_id)) {
3044
            $conditions = [
3045
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL) AND c_id = ?" => [$course_id]],
3046
                'order' => 'title',
3047
            ];
3048
        } else {
3049
            // All exercises
3050
            $conditions = [
3051
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id=?" => $params],
3052
                'order' => 'title',
3053
            ];
3054
        }
3055
3056
        return Database::select('*', $table, $conditions);
3057
    }
3058
3059
    /**
3060
     * Gets the position of the score based in a given score (result/weight)
3061
     * and the exe_id based in the user list
3062
     * (NO Exercises in LPs ).
3063
     *
3064
     * @param float  $my_score      user score to be compared *attention*
3065
     *                              $my_score = score/weight and not just the score
3066
     * @param int    $my_exe_id     exe id of the exercise
3067
     *                              (this is necessary because if 2 students have the same score the one
3068
     *                              with the minor exe_id will have a best position, just to be fair and FIFO)
3069
     * @param int    $exercise_id
3070
     * @param string $course_code
3071
     * @param int    $session_id
3072
     * @param array  $user_list
3073
     * @param bool   $return_string
3074
     *
3075
     * @return int the position of the user between his friends in a course
3076
     *             (or course within a session)
3077
     */
3078
    public static function get_exercise_result_ranking(
3079
        $my_score,
3080
        $my_exe_id,
3081
        $exercise_id,
3082
        $course_code,
3083
        $session_id = 0,
3084
        $user_list = [],
3085
        $return_string = true
3086
    ) {
3087
        //No score given we return
3088
        if (is_null($my_score)) {
3089
            return '-';
3090
        }
3091
        if (empty($user_list)) {
3092
            return '-';
3093
        }
3094
3095
        $best_attempts = [];
3096
        foreach ($user_list as $user_data) {
3097
            $user_id = $user_data['user_id'];
3098
            $best_attempts[$user_id] = self::get_best_attempt_by_user(
3099
                $user_id,
3100
                $exercise_id,
3101
                $course_code,
3102
                $session_id
3103
            );
3104
        }
3105
3106
        if (empty($best_attempts)) {
3107
            return 1;
3108
        } else {
3109
            $position = 1;
3110
            $my_ranking = [];
3111
            foreach ($best_attempts as $user_id => $result) {
3112
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3113
                    $my_ranking[$user_id] = $result['score'] / $result['max_score'];
3114
                } else {
3115
                    $my_ranking[$user_id] = 0;
3116
                }
3117
            }
3118
            //if (!empty($my_ranking)) {
3119
            asort($my_ranking);
3120
            $position = count($my_ranking);
3121
            if (!empty($my_ranking)) {
3122
                foreach ($my_ranking as $user_id => $ranking) {
3123
                    if ($my_score >= $ranking) {
3124
                        if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
3125
                            $exe_id = $best_attempts[$user_id]['exe_id'];
3126
                            if ($my_exe_id < $exe_id) {
3127
                                $position--;
3128
                            }
3129
                        } else {
3130
                            $position--;
3131
                        }
3132
                    }
3133
                }
3134
            }
3135
            //}
3136
            $return_value = [
3137
                'position' => $position,
3138
                'count' => count($my_ranking),
3139
            ];
3140
3141
            if ($return_string) {
3142
                if (!empty($position) && !empty($my_ranking)) {
3143
                    $return_value = $position.'/'.count($my_ranking);
3144
                } else {
3145
                    $return_value = '-';
3146
                }
3147
            }
3148
3149
            return $return_value;
3150
        }
3151
    }
3152
3153
    /**
3154
     * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
3155
     * (NO Exercises in LPs ) old functionality by attempt.
3156
     *
3157
     * @param   float   user score to be compared attention => score/weight
3158
     * @param   int     exe id of the exercise
3159
     * (this is necessary because if 2 students have the same score the one
3160
     * with the minor exe_id will have a best position, just to be fair and FIFO)
3161
     * @param   int     exercise id
3162
     * @param   string  course code
3163
     * @param   int     session id
3164
     * @param bool $return_string
3165
     *
3166
     * @return int the position of the user between his friends in a course (or course within a session)
3167
     */
3168
    public static function get_exercise_result_ranking_by_attempt(
3169
        $my_score,
3170
        $my_exe_id,
3171
        $exercise_id,
3172
        $courseId,
3173
        $session_id = 0,
3174
        $return_string = true
3175
    ) {
3176
        if (empty($session_id)) {
3177
            $session_id = 0;
3178
        }
3179
        if (is_null($my_score)) {
3180
            return '-';
3181
        }
3182
        $user_results = Event::get_all_exercise_results(
3183
            $exercise_id,
3184
            $courseId,
3185
            $session_id,
3186
            false
3187
        );
3188
        $position_data = [];
3189
        if (empty($user_results)) {
3190
            return 1;
3191
        } else {
3192
            $position = 1;
3193
            $my_ranking = [];
3194
            foreach ($user_results as $result) {
3195
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3196
                    $my_ranking[$result['exe_id']] = $result['score'] / $result['max_score'];
3197
                } else {
3198
                    $my_ranking[$result['exe_id']] = 0;
3199
                }
3200
            }
3201
            asort($my_ranking);
3202
            $position = count($my_ranking);
3203
            if (!empty($my_ranking)) {
3204
                foreach ($my_ranking as $exe_id => $ranking) {
3205
                    if ($my_score >= $ranking) {
3206
                        if ($my_score == $ranking) {
3207
                            if ($my_exe_id < $exe_id) {
3208
                                $position--;
3209
                            }
3210
                        } else {
3211
                            $position--;
3212
                        }
3213
                    }
3214
                }
3215
            }
3216
            $return_value = [
3217
                'position' => $position,
3218
                'count' => count($my_ranking),
3219
            ];
3220
3221
            if ($return_string) {
3222
                if (!empty($position) && !empty($my_ranking)) {
3223
                    return $position.'/'.count($my_ranking);
3224
                }
3225
            }
3226
3227
            return $return_value;
3228
        }
3229
    }
3230
3231
    /**
3232
     * Get the best attempt in a exercise (NO Exercises in LPs ).
3233
     *
3234
     * @param int $exercise_id
3235
     * @param int $courseId
3236
     * @param int $session_id
3237
     *
3238
     * @return array
3239
     */
3240
    public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id)
3241
    {
3242
        $user_results = Event::get_all_exercise_results(
3243
            $exercise_id,
3244
            $courseId,
3245
            $session_id,
3246
            false
3247
        );
3248
3249
        $best_score_data = [];
3250
        $best_score = 0;
3251
        if (!empty($user_results)) {
3252
            foreach ($user_results as $result) {
3253
                if (!empty($result['max_score']) &&
3254
                    0 != intval($result['max_score'])
3255
                ) {
3256
                    $score = $result['score'] / $result['max_score'];
3257
                    if ($score >= $best_score) {
3258
                        $best_score = $score;
3259
                        $best_score_data = $result;
3260
                    }
3261
                }
3262
            }
3263
        }
3264
3265
        return $best_score_data;
3266
    }
3267
3268
    /**
3269
     * Get the best score in a exercise (NO Exercises in LPs ).
3270
     *
3271
     * @param int $user_id
3272
     * @param int $exercise_id
3273
     * @param int $courseId
3274
     * @param int $session_id
3275
     *
3276
     * @return array
3277
     */
3278
    public static function get_best_attempt_by_user(
3279
        $user_id,
3280
        $exercise_id,
3281
        $courseId,
3282
        $session_id
3283
    ) {
3284
        $user_results = Event::get_all_exercise_results(
3285
            $exercise_id,
3286
            $courseId,
3287
            $session_id,
3288
            false,
3289
            $user_id
3290
        );
3291
        $best_score_data = [];
3292
        $best_score = 0;
3293
        if (!empty($user_results)) {
3294
            foreach ($user_results as $result) {
3295
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3296
                    $score = $result['score'] / $result['max_score'];
3297
                    if ($score >= $best_score) {
3298
                        $best_score = $score;
3299
                        $best_score_data = $result;
3300
                    }
3301
                }
3302
            }
3303
        }
3304
3305
        return $best_score_data;
3306
    }
3307
3308
    /**
3309
     * Get average score (NO Exercises in LPs ).
3310
     *
3311
     * @param    int    exercise id
3312
     * @param int $courseId
3313
     * @param    int    session id
3314
     *
3315
     * @return float Average score
3316
     */
3317
    public static function get_average_score($exercise_id, $courseId, $session_id)
3318
    {
3319
        $user_results = Event::get_all_exercise_results(
3320
            $exercise_id,
3321
            $courseId,
3322
            $session_id
3323
        );
3324
        $avg_score = 0;
3325
        if (!empty($user_results)) {
3326
            foreach ($user_results as $result) {
3327
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3328
                    $score = $result['score'] / $result['max_score'];
3329
                    $avg_score += $score;
3330
                }
3331
            }
3332
            $avg_score = float_format($avg_score / count($user_results), 1);
3333
        }
3334
3335
        return $avg_score;
3336
    }
3337
3338
    /**
3339
     * Get average score by score (NO Exercises in LPs ).
3340
     *
3341
     * @param int $courseId
3342
     * @param    int    session id
3343
     *
3344
     * @return float Average score
3345
     */
3346
    public static function get_average_score_by_course($courseId, $session_id)
3347
    {
3348
        $user_results = Event::get_all_exercise_results_by_course(
3349
            $courseId,
3350
            $session_id,
3351
            false
3352
        );
3353
        $avg_score = 0;
3354
        if (!empty($user_results)) {
3355
            foreach ($user_results as $result) {
3356
                if (!empty($result['max_score']) && 0 != intval(
3357
                        $result['max_score']
3358
                    )
3359
                ) {
3360
                    $score = $result['score'] / $result['max_score'];
3361
                    $avg_score += $score;
3362
                }
3363
            }
3364
            // We assume that all max_score
3365
            $avg_score = $avg_score / count($user_results);
3366
        }
3367
3368
        return $avg_score;
3369
    }
3370
3371
    /**
3372
     * @param int $user_id
3373
     * @param int $courseId
3374
     * @param int $session_id
3375
     *
3376
     * @return float|int
3377
     */
3378
    public static function get_average_score_by_course_by_user(
3379
        $user_id,
3380
        $courseId,
3381
        $session_id
3382
    ) {
3383
        $user_results = Event::get_all_exercise_results_by_user(
3384
            $user_id,
3385
            $courseId,
3386
            $session_id
3387
        );
3388
        $avg_score = 0;
3389
        if (!empty($user_results)) {
3390
            foreach ($user_results as $result) {
3391
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3392
                    $score = $result['score'] / $result['max_score'];
3393
                    $avg_score += $score;
3394
                }
3395
            }
3396
            // We assume that all max_score
3397
            $avg_score = ($avg_score / count($user_results));
3398
        }
3399
3400
        return $avg_score;
3401
    }
3402
3403
    /**
3404
     * Get average score by score (NO Exercises in LPs ).
3405
     *
3406
     * @param int $exercise_id
3407
     * @param int $courseId
3408
     * @param int $session_id
3409
     * @param int $user_count
3410
     *
3411
     * @return float Best average score
3412
     */
3413
    public static function get_best_average_score_by_exercise(
3414
        $exercise_id,
3415
        $courseId,
3416
        $session_id,
3417
        $user_count
3418
    ) {
3419
        $user_results = Event::get_best_exercise_results_by_user(
3420
            $exercise_id,
3421
            $courseId,
3422
            $session_id
3423
        );
3424
        $avg_score = 0;
3425
        if (!empty($user_results)) {
3426
            foreach ($user_results as $result) {
3427
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3428
                    $score = $result['score'] / $result['max_score'];
3429
                    $avg_score += $score;
3430
                }
3431
            }
3432
            // We asumme that all max_score
3433
            if (!empty($user_count)) {
3434
                $avg_score = float_format($avg_score / $user_count, 1) * 100;
3435
            } else {
3436
                $avg_score = 0;
3437
            }
3438
        }
3439
3440
        return $avg_score;
3441
    }
3442
3443
    /**
3444
     * Get average score by score (NO Exercises in LPs ).
3445
     *
3446
     * @param int $exercise_id
3447
     * @param int $courseId
3448
     * @param int $session_id
3449
     *
3450
     * @return float Best average score
3451
     */
3452
    public static function getBestScoreByExercise(
3453
        $exercise_id,
3454
        $courseId,
3455
        $session_id
3456
    ) {
3457
        $user_results = Event::get_best_exercise_results_by_user(
3458
            $exercise_id,
3459
            $courseId,
3460
            $session_id
3461
        );
3462
        $avg_score = 0;
3463
        if (!empty($user_results)) {
3464
            foreach ($user_results as $result) {
3465
                if (!empty($result['max_score']) && 0 != intval($result['max_score'])) {
3466
                    $score = $result['score'] / $result['max_score'];
3467
                    $avg_score += $score;
3468
                }
3469
            }
3470
        }
3471
3472
        return $avg_score;
3473
    }
3474
3475
    /**
3476
     * @param string $course_code
3477
     * @param int    $session_id
3478
     *
3479
     * @return array
3480
     */
3481
    public static function get_exercises_to_be_taken($course_code, $session_id)
3482
    {
3483
        $course_info = api_get_course_info($course_code);
3484
        $exercises = self::get_all_exercises($course_info, $session_id);
3485
        $result = [];
3486
        $now = time() + 15 * 24 * 60 * 60;
3487
        foreach ($exercises as $exercise_item) {
3488
            if (isset($exercise_item['end_time']) &&
3489
                !empty($exercise_item['end_time']) &&
3490
                api_strtotime($exercise_item['end_time'], 'UTC') < $now
3491
            ) {
3492
                $result[] = $exercise_item;
3493
            }
3494
        }
3495
3496
        return $result;
3497
    }
3498
3499
    /**
3500
     * Get student results (only in completed exercises) stats by question.
3501
     *
3502
     * @param int    $question_id
3503
     * @param int    $exercise_id
3504
     * @param string $course_code
3505
     * @param int    $session_id
3506
     * @param bool   $onlyStudent Filter only enrolled students
3507
     *
3508
     * @return array
3509
     */
3510
    public static function get_student_stats_by_question(
3511
        $question_id,
3512
        $exercise_id,
3513
        $course_code,
3514
        $session_id,
3515
        $onlyStudent = false
3516
    ) {
3517
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3518
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3519
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3520
3521
        $question_id = (int) $question_id;
3522
        $exercise_id = (int) $exercise_id;
3523
        $course_code = Database::escape_string($course_code);
3524
        $session_id = (int) $session_id;
3525
        $courseId = api_get_course_int_id($course_code);
3526
3527
        $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
3528
    		FROM $track_exercises e
3529
    		";
3530
        if (true == $onlyStudent) {
3531
            $courseCondition = '';
3532
            if (empty($session_id)) {
3533
                $courseCondition = "
3534
            INNER JOIN $courseUser c
3535
            ON (
3536
                        e.exe_user_id = c.user_id AND
3537
                        e.c_id = c.c_id AND
3538
                        c.status = ".STUDENT."
3539
                        AND relation_type <> 2
3540
                )";
3541
            } else {
3542
                $courseCondition = "
3543
            INNER JOIN $courseUser c
3544
            ON (
3545
                        e.exe_user_id = c.user_id AND
3546
                        e.c_id = c.c_id AND
3547
                        c.status = 0
3548
                )";
3549
            }
3550
            $sql .= $courseCondition;
3551
        }
3552
        $sql .= "
3553
    		INNER JOIN $track_attempt a
3554
    		ON (
3555
    		    a.exe_id = e.exe_id AND
3556
    		    e.c_id = a.c_id AND
3557
    		    e.session_id  = a.session_id
3558
            )
3559
    		WHERE
3560
    		    exe_exo_id 	= $exercise_id AND
3561
                a.c_id = $courseId AND
3562
                e.session_id = $session_id AND
3563
                question_id = $question_id AND
3564
                e.status = ''
3565
            LIMIT 1";
3566
        $result = Database::query($sql);
3567
        $return = [];
3568
        if ($result) {
3569
            $return = Database::fetch_array($result, 'ASSOC');
3570
        }
3571
3572
        return $return;
3573
    }
3574
3575
    /**
3576
     * Get the correct answer count for a fill blanks question.
3577
     *
3578
     * @param int $question_id
3579
     * @param int $exercise_id
3580
     *
3581
     * @return array
3582
     */
3583
    public static function getNumberStudentsFillBlanksAnswerCount(
3584
        $question_id,
3585
        $exercise_id
3586
    ) {
3587
        $listStudentsId = [];
3588
        $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3589
            api_get_course_id(),
3590
            true
3591
        );
3592
        foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3593
            $listStudentsId[] = $listStudentInfo['user_id'];
3594
        }
3595
3596
        $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
3597
            $exercise_id,
3598
            $question_id,
3599
            $listStudentsId,
3600
            '1970-01-01',
3601
            '3000-01-01'
3602
        );
3603
3604
        $arrayCount = [];
3605
3606
        foreach ($listFillTheBlankResult as $resultCount) {
3607
            foreach ($resultCount as $index => $count) {
3608
                //this is only for declare the array index per answer
3609
                $arrayCount[$index] = 0;
3610
            }
3611
        }
3612
3613
        foreach ($listFillTheBlankResult as $resultCount) {
3614
            foreach ($resultCount as $index => $count) {
3615
                $count = (0 === $count) ? 1 : 0;
3616
                $arrayCount[$index] += $count;
3617
            }
3618
        }
3619
3620
        return $arrayCount;
3621
    }
3622
3623
    /**
3624
     * Get the number of questions with answers.
3625
     *
3626
     * @param int    $question_id
3627
     * @param int    $exercise_id
3628
     * @param string $course_code
3629
     * @param int    $session_id
3630
     * @param string $questionType
3631
     *
3632
     * @return int
3633
     */
3634
    public static function get_number_students_question_with_answer_count(
3635
        $question_id,
3636
        $exercise_id,
3637
        $course_code,
3638
        $session_id,
3639
        $questionType = ''
3640
    ) {
3641
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3642
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3643
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3644
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3645
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3646
3647
        $question_id = intval($question_id);
3648
        $exercise_id = intval($exercise_id);
3649
        $courseId = api_get_course_int_id($course_code);
3650
        $session_id = intval($session_id);
3651
3652
        if (FILL_IN_BLANKS == $questionType) {
3653
            $listStudentsId = [];
3654
            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3655
                api_get_course_id(),
3656
                true
3657
            );
3658
            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3659
                $listStudentsId[] = $listStudentInfo['user_id'];
3660
            }
3661
3662
            $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
3663
                $exercise_id,
3664
                $question_id,
3665
                $listStudentsId,
3666
                '1970-01-01',
3667
                '3000-01-01'
3668
            );
3669
3670
            return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
3671
        }
3672
3673
        if (empty($session_id)) {
3674
            $courseCondition = "
3675
            INNER JOIN $courseUser cu
3676
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3677
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3678
        } else {
3679
            $courseCondition = "
3680
            INNER JOIN $courseUserSession cu
3681
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3682
            $courseConditionWhere = " AND cu.status = 0 ";
3683
        }
3684
3685
        $sql = "SELECT DISTINCT exe_user_id
3686
    		FROM $track_exercises e
3687
    		INNER JOIN $track_attempt a
3688
    		ON (
3689
    		    a.exe_id = e.exe_id AND
3690
    		    e.c_id = a.c_id AND
3691
    		    e.session_id  = a.session_id
3692
            )
3693
            INNER JOIN $courseTable c
3694
            ON (c.id = a.c_id)
3695
    		$courseCondition
3696
    		WHERE
3697
    		    exe_exo_id = $exercise_id AND
3698
                a.c_id = $courseId AND
3699
                e.session_id = $session_id AND
3700
                question_id = $question_id AND
3701
                answer <> '0' AND
3702
                e.status = ''
3703
                $courseConditionWhere
3704
            ";
3705
        $result = Database::query($sql);
3706
        $return = 0;
3707
        if ($result) {
3708
            $return = Database::num_rows($result);
3709
        }
3710
3711
        return $return;
3712
    }
3713
3714
    /**
3715
     * Get number of answers to hotspot questions.
3716
     *
3717
     * @param int    $answer_id
3718
     * @param int    $question_id
3719
     * @param int    $exercise_id
3720
     * @param string $courseId
3721
     * @param int    $session_id
3722
     *
3723
     * @return int
3724
     */
3725
    public static function get_number_students_answer_hotspot_count(
3726
        $answer_id,
3727
        $question_id,
3728
        $exercise_id,
3729
        $courseId,
3730
        $session_id
3731
    ) {
3732
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3733
        $track_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
3734
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3735
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3736
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3737
3738
        $question_id = (int) $question_id;
3739
        $answer_id = (int) $answer_id;
3740
        $exercise_id = (int) $exercise_id;
3741
        $courseId = (int) $courseId;
3742
        $session_id = (int) $session_id;
3743
3744
        if (empty($session_id)) {
3745
            $courseCondition = "
3746
            INNER JOIN $courseUser cu
3747
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3748
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3749
        } else {
3750
            $courseCondition = "
3751
            INNER JOIN $courseUserSession cu
3752
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3753
            $courseConditionWhere = ' AND cu.status = 0 ';
3754
        }
3755
3756
        $sql = "SELECT DISTINCT exe_user_id
3757
    		FROM $track_exercises e
3758
    		INNER JOIN $track_hotspot a
3759
    		ON (a.hotspot_exe_id = e.exe_id)
3760
    		INNER JOIN $courseTable c
3761
    		ON (a.c_id = c.id)
3762
    		$courseCondition
3763
    		WHERE
3764
    		    exe_exo_id              = $exercise_id AND
3765
                a.c_id 	= $courseId AND
3766
                e.session_id            = $session_id AND
3767
                hotspot_answer_id       = $answer_id AND
3768
                hotspot_question_id     = $question_id AND
3769
                hotspot_correct         =  1 AND
3770
                e.status                = ''
3771
                $courseConditionWhere
3772
            ";
3773
3774
        $result = Database::query($sql);
3775
        $return = 0;
3776
        if ($result) {
3777
            $return = Database::num_rows($result);
3778
        }
3779
3780
        return $return;
3781
    }
3782
3783
    /**
3784
     * @param int    $answer_id
3785
     * @param int    $question_id
3786
     * @param int    $exercise_id
3787
     * @param string $course_code
3788
     * @param int    $session_id
3789
     * @param string $question_type
3790
     * @param string $correct_answer
3791
     * @param string $current_answer
3792
     *
3793
     * @return int
3794
     */
3795
    public static function get_number_students_answer_count(
3796
        $answer_id,
3797
        $question_id,
3798
        $exercise_id,
3799
        $course_code,
3800
        $session_id,
3801
        $question_type = null,
3802
        $correct_answer = null,
3803
        $current_answer = null
3804
    ) {
3805
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3806
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3807
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3808
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3809
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3810
3811
        $question_id = (int) $question_id;
3812
        $answer_id = (int) $answer_id;
3813
        $exercise_id = (int) $exercise_id;
3814
        $courseId = api_get_course_int_id($course_code);
3815
        $session_id = (int) $session_id;
3816
3817
        switch ($question_type) {
3818
            case FILL_IN_BLANKS:
3819
                $answer_condition = '';
3820
                $select_condition = ' e.exe_id, answer ';
3821
                break;
3822
            case MATCHING:
3823
            case MATCHING_DRAGGABLE:
3824
            default:
3825
                $answer_condition = " answer = $answer_id AND ";
3826
                $select_condition = ' DISTINCT exe_user_id ';
3827
        }
3828
3829
        if (empty($session_id)) {
3830
            $courseCondition = "
3831
            INNER JOIN $courseUser cu
3832
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3833
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3834
        } else {
3835
            $courseCondition = "
3836
            INNER JOIN $courseUserSession cu
3837
            ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
3838
            $courseConditionWhere = ' AND cu.status = 0 ';
3839
        }
3840
3841
        $sql = "SELECT $select_condition
3842
    		FROM $track_exercises e
3843
    		INNER JOIN $track_attempt a
3844
    		ON (
3845
    		    a.exe_id = e.exe_id AND
3846
    		    e.c_id = a.c_id AND
3847
    		    e.session_id  = a.session_id
3848
            )
3849
            INNER JOIN $courseTable c
3850
            ON c.id = a.c_id
3851
    		$courseCondition
3852
    		WHERE
3853
    		    exe_exo_id = $exercise_id AND
3854
                a.c_id = $courseId AND
3855
                e.session_id = $session_id AND
3856
                $answer_condition
3857
                question_id = $question_id AND
3858
                e.status = ''
3859
                $courseConditionWhere
3860
            ";
3861
        $result = Database::query($sql);
3862
        $return = 0;
3863
        if ($result) {
3864
            $good_answers = 0;
3865
            switch ($question_type) {
3866
                case FILL_IN_BLANKS:
3867
                    while ($row = Database::fetch_array($result, 'ASSOC')) {
3868
                        $fill_blank = self::check_fill_in_blanks(
3869
                            $correct_answer,
3870
                            $row['answer'],
3871
                            $current_answer
3872
                        );
3873
                        if (isset($fill_blank[$current_answer]) && 1 == $fill_blank[$current_answer]) {
3874
                            $good_answers++;
3875
                        }
3876
                    }
3877
3878
                    return $good_answers;
3879
                    break;
3880
                case MATCHING:
3881
                case MATCHING_DRAGGABLE:
3882
                default:
3883
                    $return = Database::num_rows($result);
3884
            }
3885
        }
3886
3887
        return $return;
3888
    }
3889
3890
    /**
3891
     * @param array  $answer
3892
     * @param string $user_answer
3893
     *
3894
     * @return array
3895
     */
3896
    public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
3897
    {
3898
        // the question is encoded like this
3899
        // [A] B [C] D [E] F::10,10,10@1
3900
        // number 1 before the "@" means that is a switchable fill in blank question
3901
        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3902
        // means that is a normal fill blank question
3903
        // first we explode the "::"
3904
        $pre_array = explode('::', $answer);
3905
        // is switchable fill blank or not
3906
        $last = count($pre_array) - 1;
3907
        $is_set_switchable = explode('@', $pre_array[$last]);
3908
        $switchable_answer_set = false;
3909
        if (isset($is_set_switchable[1]) && 1 == $is_set_switchable[1]) {
3910
            $switchable_answer_set = true;
3911
        }
3912
        $answer = '';
3913
        for ($k = 0; $k < $last; $k++) {
3914
            $answer .= $pre_array[$k];
3915
        }
3916
        // splits weightings that are joined with a comma
3917
        $answerWeighting = explode(',', $is_set_switchable[0]);
3918
3919
        // we save the answer because it will be modified
3920
        //$temp = $answer;
3921
        $temp = $answer;
3922
3923
        $answer = '';
3924
        $j = 0;
3925
        //initialise answer tags
3926
        $user_tags = $correct_tags = $real_text = [];
3927
        // the loop will stop at the end of the text
3928
        while (1) {
3929
            // quits the loop if there are no more blanks (detect '[')
3930
            if (false === ($pos = api_strpos($temp, '['))) {
3931
                // adds the end of the text
3932
                $answer = $temp;
3933
                $real_text[] = $answer;
3934
                break; //no more "blanks", quit the loop
3935
            }
3936
            // adds the piece of text that is before the blank
3937
            //and ends with '[' into a general storage array
3938
            $real_text[] = api_substr($temp, 0, $pos + 1);
3939
            $answer .= api_substr($temp, 0, $pos + 1);
3940
            //take the string remaining (after the last "[" we found)
3941
            $temp = api_substr($temp, $pos + 1);
3942
            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3943
            if (false === ($pos = api_strpos($temp, ']'))) {
3944
                // adds the end of the text
3945
                $answer .= $temp;
3946
                break;
3947
            }
3948
3949
            $str = $user_answer;
3950
3951
            preg_match_all('#\[([^[]*)\]#', $str, $arr);
3952
            $str = str_replace('\r\n', '', $str);
3953
            $choices = $arr[1];
3954
            $choice = [];
3955
            $check = false;
3956
            $i = 0;
3957
            foreach ($choices as $item) {
3958
                if ($current_answer === $item) {
3959
                    $check = true;
3960
                }
3961
                if ($check) {
3962
                    $choice[] = $item;
3963
                    $i++;
3964
                }
3965
                if (3 == $i) {
3966
                    break;
3967
                }
3968
            }
3969
            $tmp = api_strrpos($choice[$j], ' / ');
3970
3971
            if (false !== $tmp) {
3972
                $choice[$j] = api_substr($choice[$j], 0, $tmp);
3973
            }
3974
3975
            $choice[$j] = trim($choice[$j]);
3976
3977
            //Needed to let characters ' and " to work as part of an answer
3978
            $choice[$j] = stripslashes($choice[$j]);
3979
3980
            $user_tags[] = api_strtolower($choice[$j]);
3981
            //put the contents of the [] answer tag into correct_tags[]
3982
            $correct_tags[] = api_strtolower(api_substr($temp, 0, $pos));
3983
            $j++;
3984
            $temp = api_substr($temp, $pos + 1);
3985
        }
3986
3987
        $answer = '';
3988
        $real_correct_tags = $correct_tags;
3989
        $chosen_list = [];
3990
        $good_answer = [];
3991
3992
        for ($i = 0; $i < count($real_correct_tags); $i++) {
3993
            if (!$switchable_answer_set) {
3994
                //needed to parse ' and " characters
3995
                $user_tags[$i] = stripslashes($user_tags[$i]);
3996
                if ($correct_tags[$i] == $user_tags[$i]) {
3997
                    $good_answer[$correct_tags[$i]] = 1;
3998
                } elseif (!empty($user_tags[$i])) {
3999
                    $good_answer[$correct_tags[$i]] = 0;
4000
                } else {
4001
                    $good_answer[$correct_tags[$i]] = 0;
4002
                }
4003
            } else {
4004
                // switchable fill in the blanks
4005
                if (in_array($user_tags[$i], $correct_tags)) {
4006
                    $correct_tags = array_diff($correct_tags, $chosen_list);
4007
                    $good_answer[$correct_tags[$i]] = 1;
4008
                } elseif (!empty($user_tags[$i])) {
4009
                    $good_answer[$correct_tags[$i]] = 0;
4010
                } else {
4011
                    $good_answer[$correct_tags[$i]] = 0;
4012
                }
4013
            }
4014
            // adds the correct word, followed by ] to close the blank
4015
            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
4016
            if (isset($real_text[$i + 1])) {
4017
                $answer .= $real_text[$i + 1];
4018
            }
4019
        }
4020
4021
        return $good_answer;
4022
    }
4023
4024
    /**
4025
     * @param int    $exercise_id
4026
     * @param string $course_code
4027
     * @param int    $session_id
4028
     *
4029
     * @return int
4030
     */
4031
    public static function get_number_students_finish_exercise(
4032
        $exercise_id,
4033
        $course_code,
4034
        $session_id
4035
    ) {
4036
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4037
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4038
4039
        $exercise_id = (int) $exercise_id;
4040
        $course_code = Database::escape_string($course_code);
4041
        $session_id = (int) $session_id;
4042
4043
        $sql = "SELECT DISTINCT exe_user_id
4044
                FROM $track_exercises e
4045
                INNER JOIN $track_attempt a
4046
                ON (a.exe_id = e.exe_id)
4047
                WHERE
4048
                    exe_exo_id 	 = $exercise_id AND
4049
                    course_code  = '$course_code' AND
4050
                    e.session_id = $session_id AND
4051
                    status = ''";
4052
        $result = Database::query($sql);
4053
        $return = 0;
4054
        if ($result) {
4055
            $return = Database::num_rows($result);
4056
        }
4057
4058
        return $return;
4059
    }
4060
4061
    /**
4062
     * Return an HTML select menu with the student groups.
4063
     *
4064
     * @param string $name     is the name and the id of the <select>
4065
     * @param string $default  default value for option
4066
     * @param string $onchange
4067
     *
4068
     * @return string the html code of the <select>
4069
     */
4070
    public static function displayGroupMenu($name, $default, $onchange = "")
4071
    {
4072
        // check the default value of option
4073
        $tabSelected = [$default => " selected='selected' "];
4074
        $res = "";
4075
        $res .= "<select name='$name' id='$name' onchange='".$onchange."' >";
4076
        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
4077
                'AllGroups'
4078
            )." --</option>";
4079
        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
4080
                'NotInAGroup'
4081
            )." -</option>";
4082
        $tabGroups = GroupManager::get_group_list();
4083
        $currentCatId = 0;
4084
        $countGroups = count($tabGroups);
4085
        for ($i = 0; $i < $countGroups; $i++) {
4086
            $tabCategory = GroupManager::get_category_from_group(
4087
                $tabGroups[$i]['iid']
4088
            );
4089
            if ($tabCategory["id"] != $currentCatId) {
4090
                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
4091
                $currentCatId = $tabCategory["id"];
4092
            }
4093
            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".
4094
                $tabGroups[$i]["id"]."'>".
4095
                $tabGroups[$i]["name"].
4096
                "</option>";
4097
        }
4098
        $res .= "</select>";
4099
4100
        return $res;
4101
    }
4102
4103
    /**
4104
     * @param int $exe_id
4105
     */
4106
    public static function create_chat_exercise_session($exe_id)
4107
    {
4108
        if (!isset($_SESSION['current_exercises'])) {
4109
            $_SESSION['current_exercises'] = [];
4110
        }
4111
        $_SESSION['current_exercises'][$exe_id] = true;
4112
    }
4113
4114
    /**
4115
     * @param int $exe_id
4116
     */
4117
    public static function delete_chat_exercise_session($exe_id)
4118
    {
4119
        if (isset($_SESSION['current_exercises'])) {
4120
            $_SESSION['current_exercises'][$exe_id] = false;
4121
        }
4122
    }
4123
4124
    /**
4125
     * Display the exercise results.
4126
     *
4127
     * @param Exercise $objExercise
4128
     * @param int      $exeId
4129
     * @param bool     $save_user_result save users results (true) or just show the results (false)
4130
     * @param string   $remainingMessage
4131
     */
4132
    public static function displayQuestionListByAttempt(
4133
        $objExercise,
4134
        $exeId,
4135
        $save_user_result = false,
4136
        $remainingMessage = ''
4137
    ) {
4138
        $origin = api_get_origin();
4139
        $courseId = api_get_course_int_id();
4140
        $courseCode = api_get_course_id();
4141
        $sessionId = api_get_session_id();
4142
4143
        // Getting attempt info
4144
        $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
4145
4146
        // Getting question list
4147
        $question_list = [];
4148
        $studentInfo = [];
4149
        if (!empty($exercise_stat_info['data_tracking'])) {
4150
            $studentInfo = api_get_user_info($exercise_stat_info['exe_user_id']);
4151
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
4152
        } else {
4153
            // Try getting the question list only if save result is off
4154
            if (false == $save_user_result) {
4155
                $question_list = $objExercise->get_validated_question_list();
4156
            }
4157
            if (in_array(
4158
                $objExercise->getFeedbackType(),
4159
                [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4160
            )) {
4161
                $question_list = $objExercise->get_validated_question_list();
4162
            }
4163
        }
4164
4165
        if ($objExercise->getResultAccess()) {
4166
            if (false === $objExercise->hasResultsAccess($exercise_stat_info)) {
4167
                echo Display::return_message(
4168
                    sprintf(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess())
4169
                );
4170
4171
                return false;
4172
            }
4173
4174
            if (!empty($objExercise->getResultAccess())) {
4175
                $url = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id;
4176
                echo $objExercise->returnTimeLeftDiv();
4177
                echo $objExercise->showSimpleTimeControl(
4178
                    $objExercise->getResultAccessTimeDiff($exercise_stat_info),
4179
                    $url
4180
                );
4181
            }
4182
        }
4183
4184
        $counter = 1;
4185
        $total_score = $total_weight = 0;
4186
        $exercise_content = null;
4187
4188
        $show_results = false;
4189
        $show_only_score = false;
4190
        if (in_array($objExercise->results_disabled,
4191
            [
4192
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4193
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
4194
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4195
            ]
4196
        )) {
4197
            $show_results = true;
4198
        }
4199
4200
        if (in_array(
4201
            $objExercise->results_disabled,
4202
            [
4203
                RESULT_DISABLE_SHOW_SCORE_ONLY,
4204
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
4205
                RESULT_DISABLE_RANKING,
4206
            ]
4207
        )
4208
        ) {
4209
            $show_only_score = true;
4210
        }
4211
4212
        // Not display expected answer, but score, and feedback
4213
        $show_all_but_expected_answer = false;
4214
        if (RESULT_DISABLE_SHOW_SCORE_ONLY == $objExercise->results_disabled &&
4215
            EXERCISE_FEEDBACK_TYPE_END == $objExercise->getFeedbackType()
4216
        ) {
4217
            $show_all_but_expected_answer = true;
4218
            $show_results = true;
4219
            $show_only_score = false;
4220
        }
4221
4222
        $showTotalScoreAndUserChoicesInLastAttempt = true;
4223
        $showTotalScore = true;
4224
        $showQuestionScore = true;
4225
4226
        if (in_array(
4227
            $objExercise->results_disabled,
4228
            [
4229
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
4230
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
4231
            ])
4232
        ) {
4233
            $show_only_score = true;
4234
            $show_results = true;
4235
            $numberAttempts = 0;
4236
            if ($objExercise->attempts > 0) {
4237
                $attempts = Event::getExerciseResultsByUser(
4238
                    api_get_user_id(),
4239
                    $objExercise->id,
4240
                    $courseId,
4241
                    $sessionId,
4242
                    $exercise_stat_info['orig_lp_id'],
4243
                    $exercise_stat_info['orig_lp_item_id'],
4244
                    'desc'
4245
                );
4246
                if ($attempts) {
4247
                    $numberAttempts = count($attempts);
4248
                }
4249
4250
                if ($save_user_result) {
4251
                    $numberAttempts++;
4252
                }
4253
                $showTotalScore = false;
4254
                $showTotalScoreAndUserChoicesInLastAttempt = false;
4255
                if ($numberAttempts >= $objExercise->attempts) {
4256
                    $showTotalScore = true;
4257
                    $show_results = true;
4258
                    $show_only_score = false;
4259
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
4260
                }
4261
            }
4262
4263
            if (RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK ==
4264
                $objExercise->results_disabled
4265
            ) {
4266
                $show_only_score = false;
4267
                $show_results = true;
4268
                $show_all_but_expected_answer = false;
4269
                $showTotalScore = false;
4270
                $showQuestionScore = false;
4271
                if ($numberAttempts >= $objExercise->attempts) {
4272
                    $showTotalScore = true;
4273
                    $showQuestionScore = true;
4274
                }
4275
            }
4276
        }
4277
4278
        if ('embeddable' !== $origin &&
4279
            !empty($exercise_stat_info['exe_user_id']) &&
4280
            !empty($studentInfo)
4281
        ) {
4282
            // Shows exercise header
4283
            echo $objExercise->showExerciseResultHeader(
4284
                $studentInfo,
4285
                $exercise_stat_info,
4286
                $save_user_result
4287
            );
4288
        }
4289
4290
        // Display text when test is finished #4074 and for LP #4227
4291
        $endOfMessage = $objExercise->getTextWhenFinished();
4292
        if (!empty($endOfMessage)) {
4293
            echo Display::div(
4294
                $endOfMessage,
4295
                ['id' => 'quiz_end_message']
4296
            );
4297
        }
4298
4299
        $question_list_answers = [];
4300
        $media_list = [];
4301
        $category_list = [];
4302
        $loadChoiceFromSession = false;
4303
        $fromDatabase = true;
4304
        $exerciseResult = null;
4305
        $exerciseResultCoordinates = null;
4306
        $delineationResults = null;
4307
4308
        if (in_array(
4309
            $objExercise->getFeedbackType(),
4310
            [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4311
        )) {
4312
            $loadChoiceFromSession = true;
4313
            $fromDatabase = false;
4314
            $exerciseResult = Session::read('exerciseResult');
4315
            $exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
4316
            $delineationResults = Session::read('hotspot_delineation_result');
4317
            $delineationResults = isset($delineationResults[$objExercise->id]) ? $delineationResults[$objExercise->id] : null;
4318
        }
4319
4320
        $countPendingQuestions = 0;
4321
        $result = [];
4322
        // Loop over all question to show results for each of them, one by one
4323
        if (!empty($question_list)) {
4324
            foreach ($question_list as $questionId) {
4325
                // Creates a temporary Question object
4326
                $objQuestionTmp = Question::read($questionId, $objExercise->course);
4327
                // This variable came from exercise_submit_modal.php
4328
                ob_start();
4329
                $choice = null;
4330
                $delineationChoice = null;
4331
                if ($loadChoiceFromSession) {
4332
                    $choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : null;
4333
                    $delineationChoice = isset($delineationResults[$questionId]) ? $delineationResults[$questionId] : null;
4334
                }
4335
4336
                // We're inside *one* question. Go through each possible answer for this question
4337
                $result = $objExercise->manage_answer(
4338
                    $exeId,
4339
                    $questionId,
4340
                    $choice,
4341
                    'exercise_result',
4342
                    $exerciseResultCoordinates,
4343
                    $save_user_result,
4344
                    $fromDatabase,
4345
                    $show_results,
4346
                    $objExercise->selectPropagateNeg(),
4347
                    $delineationChoice,
4348
                    $showTotalScoreAndUserChoicesInLastAttempt
4349
                );
4350
4351
                if (empty($result)) {
4352
                    continue;
4353
                }
4354
4355
                $total_score += $result['score'];
4356
                $total_weight += $result['weight'];
4357
4358
                $question_list_answers[] = [
4359
                    'question' => $result['open_question'],
4360
                    'answer' => $result['open_answer'],
4361
                    'answer_type' => $result['answer_type'],
4362
                    'generated_oral_file' => $result['generated_oral_file'],
4363
                ];
4364
4365
                $my_total_score = $result['score'];
4366
                $my_total_weight = $result['weight'];
4367
4368
                // Category report
4369
                $category_was_added_for_this_test = false;
4370
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
4371
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
4372
                        $category_list[$objQuestionTmp->category]['score'] = 0;
4373
                    }
4374
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
4375
                        $category_list[$objQuestionTmp->category]['total'] = 0;
4376
                    }
4377
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
4378
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
4379
                    $category_was_added_for_this_test = true;
4380
                }
4381
4382
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
4383
                    foreach ($objQuestionTmp->category_list as $category_id) {
4384
                        $category_list[$category_id]['score'] += $my_total_score;
4385
                        $category_list[$category_id]['total'] += $my_total_weight;
4386
                        $category_was_added_for_this_test = true;
4387
                    }
4388
                }
4389
4390
                // No category for this question!
4391
                if (false == $category_was_added_for_this_test) {
4392
                    if (!isset($category_list['none']['score'])) {
4393
                        $category_list['none']['score'] = 0;
4394
                    }
4395
                    if (!isset($category_list['none']['total'])) {
4396
                        $category_list['none']['total'] = 0;
4397
                    }
4398
4399
                    $category_list['none']['score'] += $my_total_score;
4400
                    $category_list['none']['total'] += $my_total_weight;
4401
                }
4402
4403
                if (0 == $objExercise->selectPropagateNeg() && $my_total_score < 0) {
4404
                    $my_total_score = 0;
4405
                }
4406
4407
                $comnt = null;
4408
                if ($show_results) {
4409
                    $comnt = Event::get_comments($exeId, $questionId);
4410
                    $teacherAudio = self::getOralFeedbackAudio(
4411
                        $exeId,
4412
                        $questionId,
4413
                        api_get_user_id()
4414
                    );
4415
4416
                    if (!empty($comnt) || $teacherAudio) {
4417
                        echo '<b>'.get_lang('Feedback').'</b>';
4418
                    }
4419
4420
                    if (!empty($comnt)) {
4421
                        echo self::getFeedbackText($comnt);
4422
                    }
4423
4424
                    if ($teacherAudio) {
4425
                        echo $teacherAudio;
4426
                    }
4427
                }
4428
4429
                $score = [];
4430
                if ($show_results) {
4431
                    $scorePassed = $my_total_score >= $my_total_weight;
4432
                    if (function_exists('bccomp')) {
4433
                        $compareResult = bccomp($my_total_score, $my_total_weight, 3);
4434
                        $scorePassed = 1 === $compareResult || 0 === $compareResult;
4435
                    }
4436
                    $score = [
4437
                        'result' => self::show_score(
4438
                            $my_total_score,
4439
                            $my_total_weight,
4440
                            false
4441
                        ),
4442
                        'pass' => $scorePassed,
4443
                        'score' => $my_total_score,
4444
                        'weight' => $my_total_weight,
4445
                        'comments' => $comnt,
4446
                        'user_answered' => $result['user_answered'],
4447
                    ];
4448
                }
4449
4450
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
4451
                    $reviewScore = [
4452
                        'score' => $my_total_score,
4453
                        'comments' => Event::get_comments($exeId, $questionId),
4454
                    ];
4455
                    $check = $objQuestionTmp->isQuestionWaitingReview($reviewScore);
4456
                    if (false === $check) {
4457
                        $countPendingQuestions++;
4458
                    }
4459
                }
4460
4461
                $contents = ob_get_clean();
4462
                $question_content = '';
4463
                if ($show_results) {
4464
                    $question_content = '<div class="question_row_answer">';
4465
                    if (false == $showQuestionScore) {
4466
                        $score = [];
4467
                    }
4468
4469
                    // Shows question title an description
4470
                    $question_content .= $objQuestionTmp->return_header(
4471
                        $objExercise,
4472
                        $counter,
4473
                        $score
4474
                    );
4475
                }
4476
                $counter++;
4477
                $question_content .= $contents;
4478
                if ($show_results) {
4479
                    $question_content .= '</div>';
4480
                }
4481
                if ($objExercise->showExpectedChoice()) {
4482
                    $exercise_content .= Display::div(
4483
                        Display::panel($question_content),
4484
                        ['class' => 'question-panel']
4485
                    );
4486
                } else {
4487
                    // $show_all_but_expected_answer should not happen at
4488
                    // the same time as $show_results
4489
                    if ($show_results && !$show_only_score) {
4490
                        $exercise_content .= Display::div(
4491
                            Display::panel($question_content),
4492
                            ['class' => 'question-panel']
4493
                        );
4494
                    }
4495
                }
4496
            } // end foreach() block that loops over all questions
4497
        }
4498
4499
        $totalScoreText = null;
4500
        $certificateBlock = '';
4501
        if (($show_results || $show_only_score) && $showTotalScore) {
4502
            if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $result['answer_type']) {
4503
                echo '<h1 style="text-align : center; margin : 20px 0;">'.get_lang('Your results').'</h1><br />';
4504
            }
4505
            $totalScoreText .= '<div class="question_row_score">';
4506
            if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $result['answer_type']) {
4507
                $totalScoreText .= self::getQuestionDiagnosisRibbon(
4508
                    $objExercise,
4509
                    $total_score,
4510
                    $total_weight,
4511
                    true
4512
                );
4513
            } else {
4514
                $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
4515
4516
                if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
4517
                    $formula = $pluginEvaluation->getFormulaForExercise($objExercise->selectId());
4518
4519
                    if (!empty($formula)) {
4520
                        $total_score = $pluginEvaluation->getResultWithFormula($exeId, $formula);
4521
                        $total_weight = $pluginEvaluation->getMaxScore();
4522
                    }
4523
                }
4524
4525
                $totalScoreText .= self::getTotalScoreRibbon(
4526
                    $objExercise,
4527
                    $total_score,
4528
                    $total_weight,
4529
                    true,
4530
                    $countPendingQuestions
4531
                );
4532
            }
4533
            $totalScoreText .= '</div>';
4534
4535
            if (!empty($studentInfo)) {
4536
                $certificateBlock = self::generateAndShowCertificateBlock(
4537
                    $total_score,
4538
                    $total_weight,
4539
                    $objExercise,
4540
                    $studentInfo['id'],
4541
                    $courseCode,
4542
                    $sessionId
4543
                );
4544
            }
4545
        }
4546
4547
        if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $result['answer_type']) {
4548
            $chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults(
4549
                $exeId,
4550
                $objExercise
4551
            );
4552
            echo $chartMultiAnswer;
4553
        }
4554
4555
        if (!empty($category_list) && ($show_results || $show_only_score)) {
4556
            // Adding total
4557
            $category_list['total'] = [
4558
                'score' => $total_score,
4559
                'total' => $total_weight,
4560
            ];
4561
            echo TestCategory::get_stats_table_by_attempt($objExercise->id, $category_list);
4562
        }
4563
4564
        if ($show_all_but_expected_answer) {
4565
            $exercise_content .= Display::return_message(get_lang('Note: This test has been setup to hide the expected answers.'));
4566
        }
4567
4568
        // Remove audio auto play from questions on results page - refs BT#7939
4569
        $exercise_content = preg_replace(
4570
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
4571
            '',
4572
            $exercise_content
4573
        );
4574
4575
        echo $totalScoreText;
4576
        echo $certificateBlock;
4577
4578
        // Ofaj change BT#11784
4579
        if (api_get_configuration_value('quiz_show_description_on_results_page') &&
4580
            !empty($objExercise->description)
4581
        ) {
4582
            echo Display::div($objExercise->description, ['class' => 'exercise_description']);
4583
        }
4584
4585
        echo $exercise_content;
4586
        if (!$show_only_score) {
4587
            echo $totalScoreText;
4588
        }
4589
4590
        if ($save_user_result) {
4591
            // Tracking of results
4592
            if ($exercise_stat_info) {
4593
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
4594
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
4595
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
4596
4597
                if (api_is_allowed_to_session_edit()) {
4598
                    Event::updateEventExercise(
4599
                        $exercise_stat_info['exe_id'],
4600
                        $objExercise->selectId(),
4601
                        $total_score,
4602
                        $total_weight,
4603
                        $sessionId,
4604
                        $learnpath_id,
4605
                        $learnpath_item_id,
4606
                        $learnpath_item_view_id,
4607
                        $exercise_stat_info['exe_duration'],
4608
                        $question_list
4609
                    );
4610
4611
                    $allowStats = api_get_configuration_value('allow_gradebook_stats');
4612
                    if ($allowStats) {
4613
                        $objExercise->generateStats(
4614
                            $objExercise->selectId(),
4615
                            api_get_course_info(),
4616
                            $sessionId
4617
                        );
4618
                    }
4619
                }
4620
            }
4621
4622
            // Send notification at the end
4623
            if (!api_is_allowed_to_edit(null, true) &&
4624
                !api_is_excluded_user_type()
4625
            ) {
4626
                $objExercise->send_mail_notification_for_exam(
4627
                    'end',
4628
                    $question_list_answers,
4629
                    $origin,
4630
                    $exeId,
4631
                    $total_score,
4632
                    $total_weight
4633
                );
4634
            }
4635
        }
4636
4637
        if (in_array(
4638
            $objExercise->selectResultsDisabled(),
4639
            [RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING]
4640
        )) {
4641
            echo Display::page_header(get_lang('Ranking'), null, 'h4');
4642
            echo self::displayResultsInRanking(
4643
                $objExercise->iId,
4644
                api_get_user_id(),
4645
                $courseId,
4646
                $sessionId
4647
            );
4648
        }
4649
4650
        if (!empty($remainingMessage)) {
4651
            echo Display::return_message($remainingMessage, 'normal', false);
4652
        }
4653
    }
4654
4655
    /**
4656
     * Display the ranking of results in a exercise.
4657
     *
4658
     * @param int $exerciseId
4659
     * @param int $currentUserId
4660
     * @param int $courseId
4661
     * @param int $sessionId
4662
     *
4663
     * @return string
4664
     */
4665
    public static function displayResultsInRanking($exerciseId, $currentUserId, $courseId, $sessionId = 0)
4666
    {
4667
        $data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId);
4668
4669
        $table = new HTML_Table(['class' => 'table table-hover table-bordered']);
4670
        $table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']);
4671
        $table->setHeaderContents(0, 1, get_lang('Username'));
4672
        $table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']);
4673
        $table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']);
4674
4675
        foreach ($data as $r => $item) {
4676
            if (!isset($item[1])) {
4677
                continue;
4678
            }
4679
            $selected = $item[1]->getId() == $currentUserId;
4680
4681
            foreach ($item as $c => $value) {
4682
                $table->setCellContents($r + 1, $c, $value);
4683
4684
                $attrClass = '';
4685
4686
                if (in_array($c, [0, 2])) {
4687
                    $attrClass = 'text-right';
4688
                } elseif (3 == $c) {
4689
                    $attrClass = 'text-center';
4690
                }
4691
4692
                if ($selected) {
4693
                    $attrClass .= ' warning';
4694
                }
4695
4696
                $table->setCellAttributes($r + 1, $c, ['class' => $attrClass]);
4697
            }
4698
        }
4699
4700
        return $table->toHtml();
4701
    }
4702
4703
    /**
4704
     * Get the ranking for results in a exercise.
4705
     * Function used internally by ExerciseLib::displayResultsInRanking.
4706
     *
4707
     * @param int $exerciseId
4708
     * @param int $courseId
4709
     * @param int $sessionId
4710
     *
4711
     * @return array
4712
     */
4713
    public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0)
4714
    {
4715
        $em = Database::getManager();
4716
4717
        $dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId';
4718
        $dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId');
4719
4720
        $result = $em
4721
            ->createQuery($dql)
4722
            ->setParameters(['id' => $exerciseId, 'cId' => $courseId])
4723
            ->getScalarResult();
4724
4725
        $data = [];
4726
4727
        foreach ($result as $item) {
4728
            $data[] = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId);
4729
        }
4730
4731
        usort(
4732
            $data,
4733
            function ($a, $b) {
4734
                if ($a['score'] != $b['score']) {
4735
                    return $a['score'] > $b['score'] ? -1 : 1;
4736
                }
4737
4738
                if ($a['exe_date'] != $b['exe_date']) {
4739
                    return $a['exe_date'] < $b['exe_date'] ? -1 : 1;
4740
                }
4741
4742
                return 0;
4743
            }
4744
        );
4745
4746
        // flags to display the same position in case of tie
4747
        $lastScore = $data[0]['score'];
4748
        $position = 1;
4749
        $data = array_map(
4750
            function ($item) use (&$lastScore, &$position) {
4751
                if ($item['score'] < $lastScore) {
4752
                    $position++;
4753
                }
4754
4755
                $lastScore = $item['score'];
4756
4757
                return [
4758
                    $position,
4759
                    api_get_user_entity($item['exe_user_id']),
4760
                    self::show_score($item['score'], $item['max_score'], true, true, true),
4761
                    api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT),
4762
                ];
4763
            },
4764
            $data
4765
        );
4766
4767
        return $data;
4768
    }
4769
4770
    /**
4771
     * Get a special ribbon on top of "degree of certainty" questions (
4772
     * variation from getTotalScoreRibbon() for other question types).
4773
     *
4774
     * @param Exercise $objExercise
4775
     * @param float    $score
4776
     * @param float    $weight
4777
     * @param bool     $checkPassPercentage
4778
     *
4779
     * @return string
4780
     */
4781
    public static function getQuestionDiagnosisRibbon($objExercise, $score, $weight, $checkPassPercentage = false)
4782
    {
4783
        $displayChartDegree = true;
4784
        $ribbon = $displayChartDegree ? '<div class="ribbon">' : '';
4785
4786
        if ($checkPassPercentage) {
4787
            $isSuccess = self::isSuccessExerciseResult(
4788
                $score, $weight, $objExercise->selectPassPercentage()
4789
            );
4790
            // Color the final test score if pass_percentage activated
4791
            $ribbonTotalSuccessOrError = '';
4792
            if (self::isPassPercentageEnabled($objExercise->selectPassPercentage())) {
4793
                if ($isSuccess) {
4794
                    $ribbonTotalSuccessOrError = ' ribbon-total-success';
4795
                } else {
4796
                    $ribbonTotalSuccessOrError = ' ribbon-total-error';
4797
                }
4798
            }
4799
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total '.$ribbonTotalSuccessOrError.'">' : '';
4800
        } else {
4801
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total">' : '';
4802
        }
4803
4804
        if ($displayChartDegree) {
4805
            $ribbon .= '<h3>'.get_lang('Score for the test').':&nbsp;';
4806
            $ribbon .= self::show_score($score, $weight, false, true);
4807
            $ribbon .= '</h3>';
4808
            $ribbon .= '</div>';
4809
        }
4810
4811
        if ($checkPassPercentage) {
4812
            $ribbon .= self::showSuccessMessage(
4813
                $score,
4814
                $weight,
4815
                $objExercise->selectPassPercentage()
4816
            );
4817
        }
4818
4819
        $ribbon .= $displayChartDegree ? '</div>' : '';
4820
4821
        return $ribbon;
4822
    }
4823
4824
    /**
4825
     * @param float $score
4826
     * @param float $weight
4827
     * @param bool  $checkPassPercentage
4828
     * @param int   $countPendingQuestions
4829
     *
4830
     * @return string
4831
     */
4832
    public static function getTotalScoreRibbon(
4833
        Exercise $objExercise,
4834
        $score,
4835
        $weight,
4836
        $checkPassPercentage = false,
4837
        $countPendingQuestions = 0
4838
    ) {
4839
        $hide = (int) $objExercise->getPageConfigurationAttribute('hide_total_score');
4840
        if (1 === $hide) {
4841
            return '';
4842
        }
4843
4844
        $passPercentage = $objExercise->selectPassPercentage();
4845
        $ribbon = '<div class="title-score">';
4846
        if ($checkPassPercentage) {
4847
            $isSuccess = self::isSuccessExerciseResult(
4848
                $score,
4849
                $weight,
4850
                $passPercentage
4851
            );
4852
            // Color the final test score if pass_percentage activated
4853
            $class = '';
4854
            if (self::isPassPercentageEnabled($passPercentage)) {
4855
                if ($isSuccess) {
4856
                    $class = ' ribbon-total-success';
4857
                } else {
4858
                    $class = ' ribbon-total-error';
4859
                }
4860
            }
4861
            $ribbon .= '<div class="total '.$class.'">';
4862
        } else {
4863
            $ribbon .= '<div class="total">';
4864
        }
4865
        $ribbon .= '<h3>'.get_lang('Score for the test').':&nbsp;';
4866
        $ribbon .= self::show_score($score, $weight, false, true);
4867
        $ribbon .= '</h3>';
4868
        $ribbon .= '</div>';
4869
        if ($checkPassPercentage) {
4870
            $ribbon .= self::showSuccessMessage(
4871
                $score,
4872
                $weight,
4873
                $passPercentage
4874
            );
4875
        }
4876
        $ribbon .= '</div>';
4877
4878
        if (!empty($countPendingQuestions)) {
4879
            $ribbon .= '<br />';
4880
            $ribbon .= Display::return_message(
4881
                sprintf(
4882
                    get_lang('Temporary score: %s open question(s) not corrected yet.'),
4883
                    $countPendingQuestions
4884
                ),
4885
                'warning'
4886
            );
4887
        }
4888
4889
        return $ribbon;
4890
    }
4891
4892
    /**
4893
     * @param int $countLetter
4894
     *
4895
     * @return mixed
4896
     */
4897
    public static function detectInputAppropriateClass($countLetter)
4898
    {
4899
        $limits = [
4900
            0 => 'input-mini',
4901
            10 => 'input-mini',
4902
            15 => 'input-medium',
4903
            20 => 'input-xlarge',
4904
            40 => 'input-xlarge',
4905
            60 => 'input-xxlarge',
4906
            100 => 'input-xxlarge',
4907
            200 => 'input-xxlarge',
4908
        ];
4909
4910
        foreach ($limits as $size => $item) {
4911
            if ($countLetter <= $size) {
4912
                return $item;
4913
            }
4914
        }
4915
4916
        return $limits[0];
4917
    }
4918
4919
    /**
4920
     * @param int    $senderId
4921
     * @param array  $course_info
4922
     * @param string $test
4923
     * @param string $url
4924
     *
4925
     * @return string
4926
     */
4927
    public static function getEmailNotification($senderId, $course_info, $test, $url)
4928
    {
4929
        $teacher_info = api_get_user_info($senderId);
4930
        $fromName = api_get_person_name(
4931
            $teacher_info['firstname'],
4932
            $teacher_info['lastname'],
4933
            null,
4934
            PERSON_NAME_EMAIL_ADDRESS
4935
        );
4936
4937
        $params = [
4938
            'course_title' => Security::remove_XSS($course_info['name']),
4939
            'test_title' => Security::remove_XSS($test),
4940
            'url' => $url,
4941
            'teacher_name' => $fromName,
4942
        ];
4943
4944
        return Container::getTwig()->render(
4945
            '@ChamiloCore/Mailer/Exercise/result_alert_body.html.twig',
4946
            $params
4947
        );
4948
    }
4949
4950
    /**
4951
     * @return string
4952
     */
4953
    public static function getNotCorrectedYetText()
4954
    {
4955
        return Display::return_message(get_lang('This answer has not yet been corrected. Meanwhile, your score for this question is set to 0, affecting the total score.'), 'warning');
4956
    }
4957
4958
    /**
4959
     * @param string $message
4960
     *
4961
     * @return string
4962
     */
4963
    public static function getFeedbackText($message)
4964
    {
4965
        return Display::return_message($message, 'warning', false);
4966
    }
4967
4968
    /**
4969
     * Get the recorder audio component for save a teacher audio feedback.
4970
     *
4971
     * @param int $attemptId
4972
     * @param int $questionId
4973
     * @param int $userId
4974
     *
4975
     * @return string
4976
     */
4977
    public static function getOralFeedbackForm($attemptId, $questionId, $userId)
4978
    {
4979
        $view = new Template('', false, false, false, false, false, false);
4980
        $view->assign('user_id', $userId);
4981
        $view->assign('question_id', $questionId);
4982
        $view->assign('directory', "/../exercises/teacher_audio/$attemptId/");
4983
        $view->assign('file_name', "{$questionId}_{$userId}");
4984
        $template = $view->get_template('exercise/oral_expression.tpl');
4985
4986
        return $view->fetch($template);
4987
    }
4988
4989
    /**
4990
     * Get the audio componen for a teacher audio feedback.
4991
     *
4992
     * @param int $attemptId
4993
     * @param int $questionId
4994
     * @param int $userId
4995
     *
4996
     * @return string
4997
     */
4998
    public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
4999
    {
5000
        return;
5001
        $courseInfo = api_get_course_info();
5002
        $sessionId = api_get_session_id();
5003
        $groupId = api_get_group_id();
5004
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
5005
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
5006
        $fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId);
5007
        $filePath = null;
5008
5009
        $relFilePath = "/exercises/teacher_audio/$attemptId/$fileName";
5010
5011
        if (file_exists($sysCourseDir.$relFilePath.'.ogg')) {
5012
            $filePath = $webCourseDir.$relFilePath.'.ogg';
5013
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) {
5014
            $filePath = $webCourseDir.$relFilePath.'.wav.wav';
5015
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) {
5016
            $filePath = $webCourseDir.$relFilePath.'.wav';
5017
        }
5018
5019
        if (!$filePath) {
5020
            return '';
5021
        }
5022
5023
        return Display::tag(
5024
            'audio',
5025
            null,
5026
            ['src' => $filePath]
5027
        );
5028
    }
5029
5030
    /**
5031
     * @return array
5032
     */
5033
    public static function getNotificationSettings()
5034
    {
5035
        $emailAlerts = [
5036
            2 => get_lang('SendEmailToTrainerWhenStudentStartQuiz'),
5037
            1 => get_lang('SendEmailToTrainerWhenStudentEndQuiz'), // default
5038
            3 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOpenQuestion'),
5039
            4 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOralQuestion'),
5040
        ];
5041
5042
        return $emailAlerts;
5043
    }
5044
5045
    /**
5046
     * Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
5047
     *
5048
     * @param int $exerciseId
5049
     * @param int $iconSize
5050
     *
5051
     * @return string
5052
     */
5053
    public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
5054
    {
5055
        $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
5056
        $actions = [];
5057
5058
        foreach ($additionalActions as $additionalAction) {
5059
            $actions[] = call_user_func(
5060
                $additionalAction,
5061
                $exerciseId,
5062
                $iconSize
5063
            );
5064
        }
5065
5066
        return implode(PHP_EOL, $actions);
5067
    }
5068
5069
    /**
5070
     * @param int $userId
5071
     * @param int $courseId
5072
     * @param int $sessionId
5073
     *
5074
     * @throws \Doctrine\ORM\Query\QueryException
5075
     *
5076
     * @return int
5077
     */
5078
    public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
5079
    {
5080
        $em = Database::getManager();
5081
5082
        $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
5083
5084
        $result = $em
5085
            ->createQuery('
5086
                SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
5087
                WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
5088
                    AND ea.tms > :time
5089
            ')
5090
            ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
5091
            ->getSingleScalarResult();
5092
5093
        return $result;
5094
    }
5095
5096
    /**
5097
     * @param int $userId
5098
     * @param int $numberOfQuestions
5099
     * @param int $courseId
5100
     * @param int $sessionId
5101
     *
5102
     * @throws \Doctrine\ORM\Query\QueryException
5103
     *
5104
     * @return bool
5105
     */
5106
    public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
5107
    {
5108
        $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
5109
5110
        if ($questionsLimitPerDay <= 0) {
5111
            return false;
5112
        }
5113
5114
        $midnightTime = ChamiloApi::getServerMidnightTime();
5115
5116
        $answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime(
5117
            $midnightTime,
5118
            $userId,
5119
            $courseId,
5120
            $sessionId
5121
        );
5122
5123
        return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
5124
    }
5125
5126
    /**
5127
     * Check if an exercise complies with the requirements to be embedded in the mobile app or a video.
5128
     * By making sure it is set on one question per page and it only contains unique-answer or multiple-answer questions
5129
     * or unique-answer image. And that the exam does not have immediate feedback.
5130
     *
5131
     * @param array $exercise Exercise info
5132
     *
5133
     * @throws \Doctrine\ORM\Query\QueryException
5134
     *
5135
     * @return bool
5136
     */
5137
    public static function isQuizEmbeddable(array $exercise)
5138
    {
5139
        $em = Database::getManager();
5140
5141
        if (ONE_PER_PAGE != $exercise['type'] ||
5142
            in_array($exercise['feedback_type'], [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])
5143
        ) {
5144
            return false;
5145
        }
5146
5147
        $countAll = $em
5148
            ->createQuery('SELECT COUNT(qq)
5149
                FROM ChamiloCourseBundle:CQuizQuestion qq
5150
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5151
                   WITH qq.iid = qrq.questionId
5152
                WHERE qrq.exerciceId = :id'
5153
            )
5154
            ->setParameter('id', $exercise['iid'])
5155
            ->getSingleScalarResult();
5156
5157
        $countOfAllowed = $em
5158
            ->createQuery('SELECT COUNT(qq)
5159
                FROM ChamiloCourseBundle:CQuizQuestion qq
5160
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5161
                   WITH qq.iid = qrq.questionId
5162
                WHERE qrq.exerciceId = :id AND qq.type IN (:types)'
5163
            )
5164
            ->setParameters(
5165
                [
5166
                    'id' => $exercise['iid'],
5167
                    'types' => [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE],
5168
                ]
5169
            )
5170
            ->getSingleScalarResult();
5171
5172
        return $countAll === $countOfAllowed;
5173
    }
5174
5175
    /**
5176
     * Generate a certificate linked to current quiz and.
5177
     * Return the HTML block with links to download and view the certificate.
5178
     *
5179
     * @param float  $totalScore
5180
     * @param float  $totalWeight
5181
     * @param int    $studentId
5182
     * @param string $courseCode
5183
     * @param int    $sessionId
5184
     *
5185
     * @return string
5186
     */
5187
    public static function generateAndShowCertificateBlock(
5188
        $totalScore,
5189
        $totalWeight,
5190
        Exercise $objExercise,
5191
        $studentId,
5192
        $courseCode,
5193
        $sessionId = 0
5194
    ) {
5195
        if (!api_get_configuration_value('quiz_generate_certificate_ending') ||
5196
            !self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage())
5197
        ) {
5198
            return '';
5199
        }
5200
5201
        /** @var Category $category */
5202
        $category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
5203
5204
        if (empty($category)) {
5205
            return '';
5206
        }
5207
5208
        /** @var Category $category */
5209
        $category = $category[0];
5210
        $categoryId = $category->get_id();
5211
        $link = LinkFactory::load(
5212
            null,
5213
            null,
5214
            $objExercise->selectId(),
5215
            null,
5216
            $courseCode,
5217
            $categoryId
5218
        );
5219
5220
        if (empty($link)) {
5221
            return '';
5222
        }
5223
5224
        $resourceDeletedMessage = $category->show_message_resource_delete($courseCode);
5225
5226
        if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) {
5227
            return '';
5228
        }
5229
5230
        $certificate = Category::generateUserCertificate($categoryId, $studentId);
5231
5232
        if (!is_array($certificate)) {
5233
            return '';
5234
        }
5235
5236
        return Category::getDownloadCertificateBlock($certificate);
5237
    }
5238
5239
    /**
5240
     * @param int $exeId      ID from track_e_exercises
5241
     * @param int $userId     User ID
5242
     * @param int $exerciseId Exercise ID
5243
     * @param int $courseId   Optional. Coure ID.
5244
     *
5245
     * @throws \Doctrine\ORM\ORMException
5246
     * @throws \Doctrine\ORM\OptimisticLockException
5247
     * @throws \Doctrine\ORM\TransactionRequiredException
5248
     *
5249
     * @return TrackEExercises|null
5250
     */
5251
    public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0)
5252
    {
5253
        if (empty($userId) || empty($exerciseId)) {
5254
            return null;
5255
        }
5256
5257
        $em = Database::getManager();
5258
        /** @var TrackEExercises $trackedExercise */
5259
        $trackedExercise = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId);
5260
5261
        if (empty($trackedExercise)) {
5262
            return null;
5263
        }
5264
5265
        if ($trackedExercise->getExeUserId() != $userId ||
5266
            $trackedExercise->getExeExoId() != $exerciseId
5267
        ) {
5268
            return null;
5269
        }
5270
5271
        $questionList = $trackedExercise->getDataTracking();
5272
5273
        if (empty($questionList)) {
5274
            return null;
5275
        }
5276
5277
        $questionList = explode(',', $questionList);
5278
5279
        $exercise = new Exercise($courseId);
5280
        $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : [];
5281
5282
        if (false === $exercise->read($exerciseId)) {
5283
            return null;
5284
        }
5285
5286
        $totalScore = 0;
5287
        $totalWeight = 0;
5288
5289
        $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
5290
5291
        $formula = 'true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)
5292
            ? $pluginEvaluation->getFormulaForExercise($exerciseId)
5293
            : 0;
5294
5295
        if (empty($formula)) {
5296
            foreach ($questionList as $questionId) {
5297
                $question = Question::read($questionId, $courseInfo);
5298
5299
                if (false === $question) {
5300
                    continue;
5301
                }
5302
5303
                $totalWeight += $question->selectWeighting();
5304
5305
                // We're inside *one* question. Go through each possible answer for this question
5306
                $result = $exercise->manage_answer(
5307
                    $exeId,
5308
                    $questionId,
5309
                    [],
5310
                    'exercise_result',
5311
                    [],
5312
                    false,
5313
                    true,
5314
                    false,
5315
                    $exercise->selectPropagateNeg(),
5316
                    [],
5317
                    [],
5318
                    true
5319
                );
5320
5321
                //  Adding the new score.
5322
                $totalScore += $result['score'];
5323
            }
5324
        } else {
5325
            $totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula);
5326
            $totalWeight = $pluginEvaluation->getMaxScore();
5327
        }
5328
5329
        $trackedExercise
5330
            ->setExeResult($totalScore)
5331
            ->setExeWeighting($totalWeight);
5332
5333
        $em->persist($trackedExercise);
5334
        $em->flush();
5335
5336
        return $trackedExercise;
5337
    }
5338
}
5339