Passed
Push — 1.11.x ( 997583...e0410b )
by Julito
14:54
created

ExerciseLib::get_number_students_answer_count()   C

Complexity

Conditions 12

Size

Total Lines 93
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 59
nop 8
dl 0
loc 93
rs 6.4678
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\TrackEExercises;
7
use ChamiloSession as Session;
8
9
/**
10
 * Class ExerciseLib
11
 * shows a question and its answers.
12
 *
13
 * @author Olivier Brouckaert <[email protected]> 2003-2004
14
 * @author Hubert Borderiou 2011-10-21
15
 * @author ivantcholakov2009-07-20
16
 * @author Julio Montoya
17
 */
18
class ExerciseLib
19
{
20
    /**
21
     * Shows a question.
22
     *
23
     * @param Exercise $exercise
24
     * @param int      $questionId     $questionId question id
25
     * @param bool     $only_questions if true only show the questions, no exercise title
26
     * @param bool     $origin         i.e = learnpath
27
     * @param string   $current_item   current item from the list of questions
28
     * @param bool     $show_title
29
     * @param bool     $freeze
30
     * @param array    $user_choice
31
     * @param bool     $show_comment
32
     * @param bool     $show_answers
33
     *
34
     * @throws \Exception
35
     *
36
     * @return bool|int
37
     */
38
    public static function showQuestion(
39
        $exercise,
40
        $questionId,
41
        $only_questions = false,
42
        $origin = false,
43
        $current_item = '',
44
        $show_title = true,
45
        $freeze = false,
46
        $user_choice = [],
47
        $show_comment = false,
48
        $show_answers = false,
49
        $show_icon = false
50
    ) {
51
        $course_id = $exercise->course_id;
52
        $exerciseId = $exercise->iId;
53
54
        if (empty($course_id)) {
55
            return '';
56
        }
57
        $course = $exercise->course;
58
59
        // Change false to true in the following line to enable answer hinting
60
        $debug_mark_answer = $show_answers;
61
        // Reads question information
62
        if (!$objQuestionTmp = Question::read($questionId, $course)) {
63
            // Question not found
64
            return false;
65
        }
66
67
        $questionRequireAuth = WhispeakAuthPlugin::questionRequireAuthentify($questionId);
68
69
        if ($exercise->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_END) {
70
            $show_comment = false;
71
        }
72
73
        $answerType = $objQuestionTmp->selectType();
74
        $pictureName = $objQuestionTmp->getPictureFilename();
75
        $s = '';
76
        if ($answerType != HOT_SPOT &&
77
            $answerType != HOT_SPOT_DELINEATION &&
78
            $answerType != ANNOTATION
79
        ) {
80
            // Question is not a hotspot
81
            if (!$only_questions) {
82
                $questionDescription = $objQuestionTmp->selectDescription();
83
                if ($show_title) {
84
                    if ($exercise->display_category_name) {
85
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
86
                    }
87
                    $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
88
                    if ($answerType == READING_COMPREHENSION) {
89
                        // In READING_COMPREHENSION, the title of the question
90
                        // contains the question itself, which can only be
91
                        // shown at the end of the given time, so hide for now
92
                        $titleToDisplay = Display::div(
93
                            $current_item.'. '.get_lang('ReadingComprehension'),
94
                            ['class' => 'question_title']
95
                        );
96
                    }
97
                    echo $titleToDisplay;
98
                }
99
100
                if ($questionRequireAuth) {
101
                    WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise);
102
103
                    return false;
104
                }
105
106
                if (!empty($questionDescription) && $answerType != READING_COMPREHENSION) {
107
                    echo Display::div(
108
                        $questionDescription,
109
                        ['class' => 'question_description']
110
                    );
111
                }
112
            }
113
114
            if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION]) && $freeze) {
115
                return '';
116
            }
117
118
            echo '<div class="question_options">';
119
            // construction of the Answer object (also gets all answers details)
120
            $objAnswerTmp = new Answer($questionId, $course_id, $exercise);
121
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
122
            $quizQuestionOptions = Question::readQuestionOption($questionId, $course_id);
123
124
            // For "matching" type here, we need something a little bit special
125
            // because the match between the suggestions and the answers cannot be
126
            // done easily (suggestions and answers are in the same table), so we
127
            // have to go through answers first (elems with "correct" value to 0).
128
            $select_items = [];
129
            //This will contain the number of answers on the left side. We call them
130
            // suggestions here, for the sake of comprehensions, while the ones
131
            // on the right side are called answers
132
            $num_suggestions = 0;
133
            switch ($answerType) {
134
                case MATCHING:
135
                case DRAGGABLE:
136
                case MATCHING_DRAGGABLE:
137
                    if ($answerType == DRAGGABLE) {
138
                        $isVertical = $objQuestionTmp->extra == 'v';
139
                        $s .= '
140
                            <div class="row">
141
                                <div class="col-md-12">
142
                                    <p class="small">'.get_lang('DraggableQuestionIntro').'</p>
143
                                    <ul class="exercise-draggable-answer list-unstyled '
144
                            .($isVertical ? '' : 'list-inline').'" id="question-'.$questionId.'" data-question="'
145
                            .$questionId.'">
146
                        ';
147
                    } else {
148
                        $s .= '<div id="drag'.$questionId.'_question" class="drag_question">
149
                               <table class="table table-hover table-striped data_table">';
150
                    }
151
152
                    // Iterate through answers.
153
                    $x = 1;
154
                    // Mark letters for each answer.
155
                    $letter = 'A';
156
                    $answer_matching = [];
157
                    $cpt1 = [];
158
                    for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
159
                        $answerCorrect = $objAnswerTmp->isCorrect($answerId);
160
                        $numAnswer = $objAnswerTmp->selectAutoId($answerId);
161
                        if ($answerCorrect == 0) {
162
                            // options (A, B, C, ...) that will be put into the list-box
163
                            // have the "correct" field set to 0 because they are answer
164
                            $cpt1[$x] = $letter;
165
                            $answer_matching[$x] = $objAnswerTmp->selectAnswerByAutoId($numAnswer);
166
                            $x++;
167
                            $letter++;
168
                        }
169
                    }
170
171
                    $i = 1;
172
                    $select_items[0]['id'] = 0;
173
                    $select_items[0]['letter'] = '--';
174
                    $select_items[0]['answer'] = '';
175
                    foreach ($answer_matching as $id => $value) {
176
                        $select_items[$i]['id'] = $value['id_auto'];
177
                        $select_items[$i]['letter'] = $cpt1[$id];
178
                        $select_items[$i]['answer'] = $value['answer'];
179
                        $i++;
180
                    }
181
182
                    $user_choice_array_position = [];
183
                    if (!empty($user_choice)) {
184
                        foreach ($user_choice as $item) {
185
                            $user_choice_array_position[$item['position']] = $item['answer'];
186
                        }
187
                    }
188
                    $num_suggestions = ($nbrAnswers - $x) + 1;
189
                    break;
190
                case FREE_ANSWER:
191
                    $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
192
                    $form = new FormValidator('free_choice_'.$questionId);
193
                    $config = [
194
                        'ToolbarSet' => 'TestFreeAnswer',
195
                        'id' => 'choice['.$questionId.']',
196
                    ];
197
                    $form->addHtmlEditor(
198
                        'choice['.$questionId.']',
199
                        null,
200
                        false,
201
                        false,
202
                        $config
203
                    );
204
                    $form->setDefaults(["choice[".$questionId."]" => $fck_content]);
205
                    $s .= $form->returnForm();
206
                    break;
207
                case ORAL_EXPRESSION:
208
                    // Add nanog
209
                    if (api_get_setting('enable_record_audio') === 'true') {
210
                        //@todo pass this as a parameter
211
                        global $exercise_stat_info;
212
                        if (!empty($exercise_stat_info)) {
213
                            $objQuestionTmp->initFile(
214
                                api_get_session_id(),
215
                                api_get_user_id(),
216
                                $exercise_stat_info['exe_exo_id'],
217
                                $exercise_stat_info['exe_id']
218
                            );
219
                        } else {
220
                            $objQuestionTmp->initFile(
221
                                api_get_session_id(),
222
                                api_get_user_id(),
223
                                $exerciseId,
224
                                'temp_exe'
225
                            );
226
                        }
227
228
                        echo $objQuestionTmp->returnRecorder();
229
                    }
230
231
                    $form = new FormValidator('free_choice_'.$questionId);
232
                    $config = ['ToolbarSet' => 'TestFreeAnswer'];
233
234
                    $form->addHtml('<div id="'.'hide_description_'.$questionId.'_options" style="display: none;">');
235
                    $form->addHtmlEditor(
236
                        "choice[$questionId]",
237
                        null,
238
                        false,
239
                        false,
240
                        $config
241
                    );
242
                    $form->addHtml('</div>');
243
                    $s .= $form->returnForm();
244
                    break;
245
            }
246
247
            // Now navigate through the possible answers, using the max number of
248
            // answers for the question as a limiter
249
            $lines_count = 1; // a counter for matching-type answers
250
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
251
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
252
            ) {
253
                $header = Display::tag('th', get_lang('Options'));
254
                foreach ($objQuestionTmp->options as $item) {
255
                    if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
256
                        if (in_array($item, $objQuestionTmp->options)) {
257
                            $header .= Display::tag('th', get_lang($item));
258
                        } else {
259
                            $header .= Display::tag('th', $item);
260
                        }
261
                    } else {
262
                        $header .= Display::tag('th', $item);
263
                    }
264
                }
265
                if ($show_comment) {
266
                    $header .= Display::tag('th', get_lang('Feedback'));
267
                }
268
                $s .= '<table class="table table-hover table-striped">';
269
                $s .= Display::tag(
270
                    'tr',
271
                    $header,
272
                    ['style' => 'text-align:left;']
273
                );
274
            } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
275
                $header = Display::tag('th', get_lang('Options'), ['width' => '50%']);
276
                echo "
277
                <script>
278
                    function RadioValidator(question_id, answer_id)
279
                    {
280
                        var ShowAlert = '';
281
                        var typeRadioB = '';
282
                        var AllFormElements = window.document.getElementById('exercise_form').elements;
283
284
                        for (i = 0; i < AllFormElements.length; i++) {
285
                            if (AllFormElements[i].type == 'radio') {
286
                                var ThisRadio = AllFormElements[i].name;
287
                                var ThisChecked = 'No';
288
                                var AllRadioOptions = document.getElementsByName(ThisRadio);
289
290
                                for (x = 0; x < AllRadioOptions.length; x++) {
291
                                     if (AllRadioOptions[x].checked && ThisChecked == 'No') {
292
                                         ThisChecked = 'Yes';
293
                                         break;
294
                                     }
295
                                }
296
297
                                var AlreadySearched = ShowAlert.indexOf(ThisRadio);
298
                                if (ThisChecked == 'No' && AlreadySearched == -1) {
299
                                    ShowAlert = ShowAlert + ThisRadio;
300
                                }
301
                            }
302
                        }
303
                        if (ShowAlert != '') {
304
305
                        } else {
306
                            $('.question-validate-btn').removeAttr('disabled');
307
                        }
308
                    }
309
310
                    function handleRadioRow(event, question_id, answer_id) {
311
                        var t = event.target;
312
                        if (t && t.tagName == 'INPUT')
313
                            return;
314
                        while (t && t.tagName != 'TD') {
315
                            t = t.parentElement;
316
                        }
317
                        var r = t.getElementsByTagName('INPUT')[0];
318
                        r.click();
319
                        RadioValidator(question_id, answer_id);
320
                    }
321
322
                    $(function() {
323
                        var ShowAlert = '';
324
                        var typeRadioB = '';
325
                        var question_id = $('input[name=question_id]').val();
326
                        var AllFormElements = window.document.getElementById('exercise_form').elements;
327
328
                        for (i = 0; i < AllFormElements.length; i++) {
329
                            if (AllFormElements[i].type == 'radio') {
330
                                var ThisRadio = AllFormElements[i].name;
331
                                var ThisChecked = 'No';
332
                                var AllRadioOptions = document.getElementsByName(ThisRadio);
333
334
                                for (x = 0; x < AllRadioOptions.length; x++) {
335
                                    if (AllRadioOptions[x].checked && ThisChecked == 'No') {
336
                                        ThisChecked = \"Yes\";
337
                                        break;
338
                                    }
339
                                }
340
341
                                var AlreadySearched = ShowAlert.indexOf(ThisRadio);
342
                                if (ThisChecked == 'No' && AlreadySearched == -1) {
343
                                    ShowAlert = ShowAlert + ThisRadio;
344
                                }
345
                            }
346
                        }
347
348
                        if (ShowAlert != '') {
349
                             $('.question-validate-btn').attr('disabled', 'disabled');
350
                        } else {
351
                            $('.question-validate-btn').removeAttr('disabled');
352
                        }
353
                    });
354
                </script>";
355
356
                foreach ($objQuestionTmp->optionsTitle as $item) {
357
                    if (in_array($item, $objQuestionTmp->optionsTitle)) {
358
                        $properties = [];
359
                        if ($item === 'Answers') {
360
                            $properties['colspan'] = 2;
361
                            $properties['style'] = 'background-color: #F56B2A; color: #ffffff;';
362
                        } elseif ($item == 'DegreeOfCertaintyThatMyAnswerIsCorrect') {
363
                            $properties['colspan'] = 6;
364
                            $properties['style'] = 'background-color: #330066; color: #ffffff;';
365
                        }
366
                        $header .= Display::tag('th', get_lang($item), $properties);
367
                    } else {
368
                        $header .= Display::tag('th', $item);
369
                    }
370
                }
371
372
                if ($show_comment) {
373
                    $header .= Display::tag('th', get_lang('Feedback'));
374
                }
375
376
                $s .= '<table class="table table-hover table-striped data_table">';
377
                $s .= Display::tag('tr', $header, ['style' => 'text-align:left;']);
378
379
                // ajout de la 2eme ligne d'entête pour true/falss et les pourcentages de certitude
380
                $header1 = Display::tag('th', '&nbsp;');
381
                $cpt1 = 0;
382
                foreach ($objQuestionTmp->options as $item) {
383
                    $colorBorder1 = $cpt1 == (count($objQuestionTmp->options) - 1)
384
                        ? '' : 'border-right: solid #FFFFFF 1px;';
385
                    if ($item === 'True' || $item === 'False') {
386
                        $header1 .= Display::tag(
387
                            'th',
388
                            get_lang($item),
389
                            ['style' => 'background-color: #F7C9B4; color: black;'.$colorBorder1]
390
                        );
391
                    } else {
392
                        $header1 .= Display::tag(
393
                            'th',
394
                            $item,
395
                            ['style' => 'background-color: #e6e6ff; color: black;padding:5px; '.$colorBorder1]
396
                        );
397
                    }
398
                    $cpt1++;
399
                }
400
                if ($show_comment) {
401
                    $header1 .= Display::tag('th', '&nbsp;');
402
                }
403
404
                $s .= Display::tag('tr', $header1);
405
406
                // add explanation
407
                $header2 = Display::tag('th', '&nbsp;');
408
                $descriptionList = [
409
                    get_lang('DegreeOfCertaintyIDeclareMyIgnorance'),
410
                    get_lang('DegreeOfCertaintyIAmVeryUnsure'),
411
                    get_lang('DegreeOfCertaintyIAmUnsure'),
412
                    get_lang('DegreeOfCertaintyIAmPrettySure'),
413
                    get_lang('DegreeOfCertaintyIAmSure'),
414
                    get_lang('DegreeOfCertaintyIAmVerySure'),
415
                ];
416
                $counter2 = 0;
417
                foreach ($objQuestionTmp->options as $item) {
418
                    if ($item === 'True' || $item === 'False') {
419
                        $header2 .= Display::tag('td',
420
                            '&nbsp;',
421
                            ['style' => 'background-color: #F7E1D7; color: black;border-right: solid #FFFFFF 1px;']);
422
                    } else {
423
                        $color_border2 = ($counter2 == (count($objQuestionTmp->options) - 1)) ?
424
                            '' : 'border-right: solid #FFFFFF 1px;font-size:11px;';
425
                        $header2 .= Display::tag(
426
                            'td',
427
                            nl2br($descriptionList[$counter2]),
428
                            ['style' => 'background-color: #EFEFFC; color: black; width: 110px; text-align:center;
429
                                vertical-align: top; padding:5px; '.$color_border2]);
430
                        $counter2++;
431
                    }
432
                }
433
                if ($show_comment) {
434
                    $header2 .= Display::tag('th', '&nbsp;');
435
                }
436
                $s .= Display::tag('tr', $header2);
437
            }
438
439
            if ($show_comment) {
440
                if (in_array(
441
                    $answerType,
442
                    [
443
                        MULTIPLE_ANSWER,
444
                        MULTIPLE_ANSWER_COMBINATION,
445
                        UNIQUE_ANSWER,
446
                        UNIQUE_ANSWER_IMAGE,
447
                        UNIQUE_ANSWER_NO_OPTION,
448
                        GLOBAL_MULTIPLE_ANSWER,
449
                    ]
450
                )) {
451
                    $header = Display::tag('th', get_lang('Options'));
452
                    if ($exercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END) {
453
                        $header .= Display::tag('th', get_lang('Feedback'));
454
                    }
455
                    $s .= '<table class="table table-hover table-striped">';
456
                    $s .= Display::tag(
457
                        'tr',
458
                        $header,
459
                        ['style' => 'text-align:left;']
460
                    );
461
                }
462
            }
463
464
            $matching_correct_answer = 0;
465
            $userChoiceList = [];
466
            if (!empty($user_choice)) {
467
                foreach ($user_choice as $item) {
468
                    $userChoiceList[] = $item['answer'];
469
                }
470
            }
471
472
            $hidingClass = '';
473
            if ($answerType == READING_COMPREHENSION) {
474
                $objQuestionTmp->setExerciseType($exercise->selectType());
475
                $objQuestionTmp->processText($objQuestionTmp->selectDescription());
476
                $hidingClass = 'hide-reading-answers';
477
                $s .= Display::div(
478
                    $objQuestionTmp->selectTitle(),
479
                    ['class' => 'question_title '.$hidingClass]
480
                );
481
            }
482
483
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
484
                $answer = $objAnswerTmp->selectAnswer($answerId);
485
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
486
                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
487
                $comment = $objAnswerTmp->selectComment($answerId);
488
                $attributes = [];
489
490
                switch ($answerType) {
491
                    case UNIQUE_ANSWER:
492
                    case UNIQUE_ANSWER_NO_OPTION:
493
                    case UNIQUE_ANSWER_IMAGE:
494
                    case READING_COMPREHENSION:
495
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
496
                        if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
497
                            $attributes = [
498
                                'id' => $input_id,
499
                                'checked' => 1,
500
                                'selected' => 1,
501
                            ];
502
                        } else {
503
                            $attributes = ['id' => $input_id];
504
                        }
505
506
                        if ($debug_mark_answer) {
507
                            if ($answerCorrect) {
508
                                $attributes['checked'] = 1;
509
                                $attributes['selected'] = 1;
510
                            }
511
                        }
512
513
                        if ($show_comment) {
514
                            $s .= '<tr><td>';
515
                        }
516
517
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
518
                            if ($show_comment) {
519
                                if (empty($comment)) {
520
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'"
521
                                            class="exercise-unique-answer-image" style="text-align: center">';
522
                                } else {
523
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'"
524
                                            class="exercise-unique-answer-image col-xs-6 col-sm-12"
525
                                            style="text-align: center">';
526
                                }
527
                            } else {
528
                                $s .= '<div id="answer'.$questionId.$numAnswer.'"
529
                                        class="exercise-unique-answer-image col-xs-6 col-md-3"
530
                                        style="text-align: center">';
531
                            }
532
                        }
533
534
                        if ($answerType != UNIQUE_ANSWER_IMAGE) {
535
                            $userStatus = STUDENT;
536
                            // Allows to do a remove_XSS in question of exersice with user status COURSEMANAGER
537
                            // see BT#18242
538
                            if (api_get_configuration_value('question_exercise_html_strict_filtering')) {
539
                                $userStatus = COURSEMANAGERLOWSECURITY;
540
                            }
541
                            $answer = Security::remove_XSS($answer, $userStatus);
542
                        }
543
                        $s .= Display::input(
544
                            'hidden',
545
                            'choice2['.$questionId.']',
546
                            '0'
547
                        );
548
549
                        $answer_input = null;
550
                        $attributes['class'] = 'checkradios';
551
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
552
                            $attributes['class'] = '';
553
                            $attributes['style'] = 'display: none;';
554
                            $answer = '<div class="thumbnail">'.$answer.'</div>';
555
                        }
556
557
                        $answer_input .= '<label class="radio '.$hidingClass.'">';
558
                        $answer_input .= Display::input(
559
                            'radio',
560
                            'choice['.$questionId.']',
561
                            $numAnswer,
562
                            $attributes
563
                        );
564
                        $answer_input .= $answer;
565
                        $answer_input .= '</label>';
566
567
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
568
                            $answer_input .= "</div>";
569
                        }
570
571
                        if ($show_comment) {
572
                            $s .= $answer_input;
573
                            $s .= '</td>';
574
                            $s .= '<td>';
575
                            $s .= $comment;
576
                            $s .= '</td>';
577
                            $s .= '</tr>';
578
                        } else {
579
                            $s .= $answer_input;
580
                        }
581
                        break;
582
                    case MULTIPLE_ANSWER:
583
                    case MULTIPLE_ANSWER_TRUE_FALSE:
584
                    case GLOBAL_MULTIPLE_ANSWER:
585
                    case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
586
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
587
                        $userStatus = STUDENT;
588
                        // Allows to do a remove_XSS in question of exersice with user status COURSEMANAGER
589
                        // see BT#18242
590
                        if (api_get_configuration_value('question_exercise_html_strict_filtering')) {
591
                            $userStatus = COURSEMANAGERLOWSECURITY;
592
                        }
593
                        $answer = Security::remove_XSS($answer, $userStatus);
594
595
                        if (in_array($numAnswer, $userChoiceList)) {
596
                            $attributes = [
597
                                'id' => $input_id,
598
                                'checked' => 1,
599
                                'selected' => 1,
600
                            ];
601
                        } else {
602
                            $attributes = ['id' => $input_id];
603
                        }
604
605
                        if ($debug_mark_answer) {
606
                            if ($answerCorrect) {
607
                                $attributes['checked'] = 1;
608
                                $attributes['selected'] = 1;
609
                            }
610
                        }
611
612
                        if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
613
                            $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
614
                            $attributes['class'] = 'checkradios';
615
                            $answer_input = '<label class="checkbox">';
616
                            $answer_input .= Display::input(
617
                                'checkbox',
618
                                'choice['.$questionId.']['.$numAnswer.']',
619
                                $numAnswer,
620
                                $attributes
621
                            );
622
                            $answer_input .= $answer;
623
                            $answer_input .= '</label>';
624
625
                            if ($show_comment) {
626
                                $s .= '<tr><td>';
627
                                $s .= $answer_input;
628
                                $s .= '</td>';
629
                                $s .= '<td>';
630
                                $s .= $comment;
631
                                $s .= '</td>';
632
                                $s .= '</tr>';
633
                            } else {
634
                                $s .= $answer_input;
635
                            }
636
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
637
                            $myChoice = [];
638
                            if (!empty($userChoiceList)) {
639
                                foreach ($userChoiceList as $item) {
640
                                    $item = explode(':', $item);
641
                                    if (!empty($item)) {
642
                                        $myChoice[$item[0]] = isset($item[1]) ? $item[1] : '';
643
                                    }
644
                                }
645
                            }
646
647
                            $s .= '<tr>';
648
                            $s .= Display::tag('td', $answer);
649
650
                            if (!empty($quizQuestionOptions)) {
651
                                foreach ($quizQuestionOptions as $id => $item) {
652
                                    if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) {
653
                                        $attributes = [
654
                                            'checked' => 1,
655
                                            'selected' => 1,
656
                                        ];
657
                                    } else {
658
                                        $attributes = [];
659
                                    }
660
661
                                    if ($debug_mark_answer) {
662
                                        if ($id == $answerCorrect) {
663
                                            $attributes['checked'] = 1;
664
                                            $attributes['selected'] = 1;
665
                                        }
666
                                    }
667
                                    $s .= Display::tag(
668
                                        'td',
669
                                        Display::input(
670
                                            'radio',
671
                                            'choice['.$questionId.']['.$numAnswer.']',
672
                                            $id,
673
                                            $attributes
674
                                        ),
675
                                        ['style' => '']
676
                                    );
677
                                }
678
                            }
679
680
                            if ($show_comment) {
681
                                $s .= '<td>';
682
                                $s .= $comment;
683
                                $s .= '</td>';
684
                            }
685
                            $s .= '</tr>';
686
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
687
                            $myChoice = [];
688
                            if (!empty($userChoiceList)) {
689
                                foreach ($userChoiceList as $item) {
690
                                    $item = explode(':', $item);
691
                                    $myChoice[$item[0]] = $item[1];
692
                                }
693
                            }
694
                            $myChoiceDegreeCertainty = [];
695
                            if (!empty($userChoiceList)) {
696
                                foreach ($userChoiceList as $item) {
697
                                    $item = explode(':', $item);
698
                                    $myChoiceDegreeCertainty[$item[0]] = $item[2];
699
                                }
700
                            }
701
                            $s .= '<tr>';
702
                            $s .= Display::tag('td', $answer);
703
704
                            if (!empty($quizQuestionOptions)) {
705
                                foreach ($quizQuestionOptions as $id => $item) {
706
                                    if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) {
707
                                        $attributes = ['checked' => 1, 'selected' => 1];
708
                                    } else {
709
                                        $attributes = [];
710
                                    }
711
                                    $attributes['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')';
712
713
                                    // radio button selection
714
                                    if (isset($myChoiceDegreeCertainty[$numAnswer]) &&
715
                                        $id == $myChoiceDegreeCertainty[$numAnswer]
716
                                    ) {
717
                                        $attributes1 = ['checked' => 1, 'selected' => 1];
718
                                    } else {
719
                                        $attributes1 = [];
720
                                    }
721
722
                                    $attributes1['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')';
723
724
                                    if ($debug_mark_answer) {
725
                                        if ($id == $answerCorrect) {
726
                                            $attributes['checked'] = 1;
727
                                            $attributes['selected'] = 1;
728
                                        }
729
                                    }
730
731
                                    if ($item['name'] == 'True' || $item['name'] == 'False') {
732
                                        $s .= Display::tag('td',
733
                                            Display::input('radio',
734
                                                'choice['.$questionId.']['.$numAnswer.']',
735
                                                $id,
736
                                                $attributes
737
                                            ),
738
                                            ['style' => 'text-align:center; background-color:#F7E1D7;',
739
                                                'onclick' => 'handleRadioRow(event, '.
740
                                                    $questionId.', '.
741
                                                    $numAnswer.')',
742
                                            ]
743
                                        );
744
                                    } else {
745
                                        $s .= Display::tag('td',
746
                                            Display::input('radio',
747
                                                'choiceDegreeCertainty['.$questionId.']['.$numAnswer.']',
748
                                                $id,
749
                                                $attributes1
750
                                            ),
751
                                            ['style' => 'text-align:center; background-color:#EFEFFC;',
752
                                                'onclick' => 'handleRadioRow(event, '.
753
                                                    $questionId.', '.
754
                                                    $numAnswer.')',
755
                                            ]
756
                                        );
757
                                    }
758
                                }
759
                            }
760
761
                            if ($show_comment) {
762
                                $s .= '<td>';
763
                                $s .= $comment;
764
                                $s .= '</td>';
765
                            }
766
                            $s .= '</tr>';
767
                        }
768
                        break;
769
                    case MULTIPLE_ANSWER_COMBINATION:
770
                        // multiple answers
771
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
772
773
                        if (in_array($numAnswer, $userChoiceList)) {
774
                            $attributes = [
775
                                'id' => $input_id,
776
                                'checked' => 1,
777
                                'selected' => 1,
778
                            ];
779
                        } else {
780
                            $attributes = ['id' => $input_id];
781
                        }
782
783
                        if ($debug_mark_answer) {
784
                            if ($answerCorrect) {
785
                                $attributes['checked'] = 1;
786
                                $attributes['selected'] = 1;
787
                            }
788
                        }
789
790
                        $userStatus = STUDENT;
791
                        // Allows to do a remove_XSS in question of exersice with user status COURSEMANAGER
792
                        // see BT#18242
793
                        if (api_get_configuration_value('question_exercise_html_strict_filtering')) {
794
                            $userStatus = COURSEMANAGERLOWSECURITY;
795
                        }
796
                        $answer = Security::remove_XSS($answer, $userStatus);
797
                        $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
798
                        $answer_input .= '<label class="checkbox">';
799
                        $answer_input .= Display::input(
800
                            'checkbox',
801
                            'choice['.$questionId.']['.$numAnswer.']',
802
                            1,
803
                            $attributes
804
                        );
805
                        $answer_input .= $answer;
806
                        $answer_input .= '</label>';
807
808
                        if ($show_comment) {
809
                            $s .= '<tr>';
810
                            $s .= '<td>';
811
                            $s .= $answer_input;
812
                            $s .= '</td>';
813
                            $s .= '<td>';
814
                            $s .= $comment;
815
                            $s .= '</td>';
816
                            $s .= '</tr>';
817
                        } else {
818
                            $s .= $answer_input;
819
                        }
820
                        break;
821
                    case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
822
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
823
                        $myChoice = [];
824
                        if (!empty($userChoiceList)) {
825
                            foreach ($userChoiceList as $item) {
826
                                $item = explode(':', $item);
827
                                if (isset($item[1]) && isset($item[0])) {
828
                                    $myChoice[$item[0]] = $item[1];
829
                                }
830
                            }
831
                        }
832
                        $userStatus = STUDENT;
833
                        // Allows to do a remove_XSS in question of exersice with user status COURSEMANAGER
834
                        // see BT#18242
835
                        if (api_get_configuration_value('question_exercise_html_strict_filtering')) {
836
                            $userStatus = COURSEMANAGERLOWSECURITY;
837
                        }
838
                        $answer = Security::remove_XSS($answer, $userStatus);
839
                        $s .= '<tr>';
840
                        $s .= Display::tag('td', $answer);
841
                        foreach ($objQuestionTmp->options as $key => $item) {
842
                            if (isset($myChoice[$numAnswer]) && $key == $myChoice[$numAnswer]) {
843
                                $attributes = [
844
                                    'checked' => 1,
845
                                    'selected' => 1,
846
                                ];
847
                            } else {
848
                                $attributes = [];
849
                            }
850
851
                            if ($debug_mark_answer) {
852
                                if ($key == $answerCorrect) {
853
                                    $attributes['checked'] = 1;
854
                                    $attributes['selected'] = 1;
855
                                }
856
                            }
857
                            $s .= Display::tag(
858
                                'td',
859
                                Display::input(
860
                                    'radio',
861
                                    'choice['.$questionId.']['.$numAnswer.']',
862
                                    $key,
863
                                    $attributes
864
                                )
865
                            );
866
                        }
867
868
                        if ($show_comment) {
869
                            $s .= '<td>';
870
                            $s .= $comment;
871
                            $s .= '</td>';
872
                        }
873
                        $s .= '</tr>';
874
                        break;
875
                    case FILL_IN_BLANKS:
876
                        // display the question, with field empty, for student to fill it,
877
                        // or filled to display the answer in the Question preview of the exercise/admin.php page
878
                        $displayForStudent = true;
879
                        $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
880
                        // Correct answers
881
                        $correctAnswerList = $listAnswerInfo['words'];
882
                        // Student's answer
883
                        $studentAnswerList = [];
884
                        if (isset($user_choice[0]['answer'])) {
885
                            $arrayStudentAnswer = FillBlanks::getAnswerInfo(
886
                                $user_choice[0]['answer'],
887
                                true
888
                            );
889
                            $studentAnswerList = $arrayStudentAnswer['student_answer'];
890
                        }
891
892
                        // If the question must be shown with the answer (in page exercise/admin.php)
893
                        // for teacher preview set the student-answer to the correct answer
894
                        if ($debug_mark_answer) {
895
                            $studentAnswerList = $correctAnswerList;
896
                            $displayForStudent = false;
897
                        }
898
899
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
900
                            $answer = '';
901
                            for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
902
                                // display the common word
903
                                $answer .= $listAnswerInfo['common_words'][$i];
904
                                // display the blank word
905
                                $correctItem = $listAnswerInfo['words'][$i];
906
                                if (isset($studentAnswerList[$i])) {
907
                                    // If student already started this test and answered this question,
908
                                    // fill the blank with his previous answers
909
                                    // may be "" if student viewed the question, but did not fill the blanks
910
                                    $correctItem = $studentAnswerList[$i];
911
                                }
912
                                $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
913
                                $answer .= FillBlanks::getFillTheBlankHtml(
914
                                    $current_item,
915
                                    $questionId,
916
                                    $correctItem,
917
                                    $attributes,
918
                                    $answer,
919
                                    $listAnswerInfo,
920
                                    $displayForStudent,
921
                                    $i
922
                                );
923
                            }
924
                            // display the last common word
925
                            $answer .= $listAnswerInfo['common_words'][$i];
926
                        } else {
927
                            // display empty [input] with the right width for student to fill it
928
                            $answer = '';
929
                            for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
930
                                // display the common words
931
                                $answer .= $listAnswerInfo['common_words'][$i];
932
                                // display the blank word
933
                                $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
934
                                $answer .= FillBlanks::getFillTheBlankHtml(
935
                                    $current_item,
936
                                    $questionId,
937
                                    '',
938
                                    $attributes,
939
                                    $answer,
940
                                    $listAnswerInfo,
941
                                    $displayForStudent,
942
                                    $i
943
                                );
944
                            }
945
                            // display the last common word
946
                            $answer .= $listAnswerInfo['common_words'][$i];
947
                        }
948
                        $s .= $answer;
949
                        break;
950
                    case CALCULATED_ANSWER:
951
                        /*
952
                         * In the CALCULATED_ANSWER test
953
                         * you mustn't have [ and ] in the textarea
954
                         * you mustn't have @@ in the textarea
955
                         * the text to find mustn't be empty or contains only spaces
956
                         * the text to find mustn't contains HTML tags
957
                         * the text to find mustn't contains char "
958
                         */
959
                        if (null !== $origin) {
960
                            global $exe_id;
961
                            $exe_id = (int) $exe_id;
962
                            $trackAttempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
963
                            $sql = "SELECT answer FROM $trackAttempts
964
                                    WHERE exe_id = $exe_id AND question_id= $questionId";
965
                            $rsLastAttempt = Database::query($sql);
966
                            $rowLastAttempt = Database::fetch_array($rsLastAttempt);
967
968
                            $answer = null;
969
                            if (isset($rowLastAttempt['answer'])) {
970
                                $answer = $rowLastAttempt['answer'];
971
                            }
972
973
                            if (empty($answer)) {
974
                                $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(
975
                                    1,
976
                                    $nbrAnswers
977
                                );
978
                                $answer = $objAnswerTmp->selectAnswer(
979
                                    $_SESSION['calculatedAnswerId'][$questionId]
980
                                );
981
                            }
982
                        }
983
984
                        [$answer] = explode('@@', $answer);
985
                        // $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
986
                        api_preg_match_all(
987
                            '/\[[^]]+\]/',
988
                            $answer,
989
                            $correctAnswerList
990
                        );
991
992
                        // get student answer to display it if student go back
993
                        // to previous calculated answer question in a test
994
                        if (isset($user_choice[0]['answer'])) {
995
                            api_preg_match_all(
996
                                '/\[[^]]+\]/',
997
                                $answer,
998
                                $studentAnswerList
999
                            );
1000
                            $studentAnswerListToClean = $studentAnswerList[0];
1001
                            $studentAnswerList = [];
1002
1003
                            $maxStudents = count($studentAnswerListToClean);
1004
                            for ($i = 0; $i < $maxStudents; $i++) {
1005
                                $answerCorrected = $studentAnswerListToClean[$i];
1006
                                $answerCorrected = api_preg_replace(
1007
                                    '| / <font color="green"><b>.*$|',
1008
                                    '',
1009
                                    $answerCorrected
1010
                                );
1011
                                $answerCorrected = api_preg_replace(
1012
                                    '/^\[/',
1013
                                    '',
1014
                                    $answerCorrected
1015
                                );
1016
                                $answerCorrected = api_preg_replace(
1017
                                    '|^<font color="red"><s>|',
1018
                                    '',
1019
                                    $answerCorrected
1020
                                );
1021
                                $answerCorrected = api_preg_replace(
1022
                                    '|</s></font>$|',
1023
                                    '',
1024
                                    $answerCorrected
1025
                                );
1026
                                $answerCorrected = '['.$answerCorrected.']';
1027
                                $studentAnswerList[] = $answerCorrected;
1028
                            }
1029
                        }
1030
1031
                        // If display preview of answer in test view for exemple,
1032
                        // set the student answer to the correct answers
1033
                        if ($debug_mark_answer) {
1034
                            // contain the rights answers surronded with brackets
1035
                            $studentAnswerList = $correctAnswerList[0];
1036
                        }
1037
1038
                        /*
1039
                        Split the response by bracket
1040
                        tabComments is an array with text surrounding the text to find
1041
                        we add a space before and after the answerQuestion to be sure to
1042
                        have a block of text before and after [xxx] patterns
1043
                        so we have n text to find ([xxx]) and n+1 block of texts before,
1044
                        between and after the text to find
1045
                        */
1046
                        $tabComments = api_preg_split(
1047
                            '/\[[^]]+\]/',
1048
                            ' '.$answer.' '
1049
                        );
1050
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
1051
                            $answer = '';
1052
                            $i = 0;
1053
                            foreach ($studentAnswerList as $studentItem) {
1054
                                // Remove surronding brackets
1055
                                $studentResponse = api_substr(
1056
                                    $studentItem,
1057
                                    1,
1058
                                    api_strlen($studentItem) - 2
1059
                                );
1060
                                $size = strlen($studentItem);
1061
                                $attributes['class'] = self::detectInputAppropriateClass($size);
1062
                                $answer .= $tabComments[$i].
1063
                                    Display::input(
1064
                                        'text',
1065
                                        "choice[$questionId][]",
1066
                                        $studentResponse,
1067
                                        $attributes
1068
                                    );
1069
                                $i++;
1070
                            }
1071
                            $answer .= $tabComments[$i];
1072
                        } else {
1073
                            // display exercise with empty input fields
1074
                            // every [xxx] are replaced with an empty input field
1075
                            foreach ($correctAnswerList[0] as $item) {
1076
                                $size = strlen($item);
1077
                                $attributes['class'] = self::detectInputAppropriateClass($size);
1078
                                if (EXERCISE_FEEDBACK_TYPE_POPUP == $exercise->getFeedbackType()) {
1079
                                    $attributes['id'] = "question_$questionId";
1080
                                    $attributes['class'] .= ' checkCalculatedQuestionOnEnter ';
1081
                                }
1082
1083
                                $answer = str_replace(
1084
                                    $item,
1085
                                    Display::input(
1086
                                        'text',
1087
                                        "choice[$questionId][]",
1088
                                        '',
1089
                                        $attributes
1090
                                    ),
1091
                                    $answer
1092
                                );
1093
                            }
1094
                        }
1095
                        if (null !== $origin) {
1096
                            $s = $answer;
1097
                            break;
1098
                        } else {
1099
                            $s .= $answer;
1100
                        }
1101
                        break;
1102
                    case MATCHING:
1103
                        // matching type, showing suggestions and answers
1104
                        // TODO: replace $answerId by $numAnswer
1105
                        if ($answerCorrect != 0) {
1106
                            // only show elements to be answered (not the contents of
1107
                            // the select boxes, who are correct = 0)
1108
                            $s .= '<tr><td width="45%" valign="top">';
1109
                            $parsed_answer = $answer;
1110
                            // Left part questions
1111
                            $s .= '<p class="indent">'.$lines_count.'.&nbsp;'.$parsed_answer.'</p></td>';
1112
                            // Middle part (matches selects)
1113
                            // Id of select is # question + # of option
1114
                            $s .= '<td width="10%" valign="top" align="center">
1115
                                <div class="select-matching">
1116
                                <select
1117
                                    id="choice_id_'.$current_item.'_'.$lines_count.'"
1118
                                    name="choice['.$questionId.']['.$numAnswer.']">';
1119
1120
                            // fills the list-box
1121
                            foreach ($select_items as $key => $val) {
1122
                                // set $debug_mark_answer to true at function start to
1123
                                // show the correct answer with a suffix '-x'
1124
                                $selected = '';
1125
                                if ($debug_mark_answer) {
1126
                                    if ($val['id'] == $answerCorrect) {
1127
                                        $selected = 'selected="selected"';
1128
                                    }
1129
                                }
1130
                                //$user_choice_array_position
1131
                                if (isset($user_choice_array_position[$numAnswer]) &&
1132
                                    $val['id'] == $user_choice_array_position[$numAnswer]
1133
                                ) {
1134
                                    $selected = 'selected="selected"';
1135
                                }
1136
                                $s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
1137
                            }
1138
1139
                            $s .= '</select></div></td><td width="5%" class="separate">&nbsp;</td>';
1140
                            $s .= '<td width="40%" valign="top" >';
1141
                            if (isset($select_items[$lines_count])) {
1142
                                $s .= '<div class="text-right">
1143
                                        <p class="indent">'.
1144
                                    $select_items[$lines_count]['letter'].'.&nbsp; '.
1145
                                    $select_items[$lines_count]['answer'].'
1146
                                        </p>
1147
                                        </div>';
1148
                            } else {
1149
                                $s .= '&nbsp;';
1150
                            }
1151
                            $s .= '</td>';
1152
                            $s .= '</tr>';
1153
                            $lines_count++;
1154
                            // If the left side of the "matching" has been completely
1155
                            // shown but the right side still has values to show...
1156
                            if (($lines_count - 1) == $num_suggestions) {
1157
                                // if it remains answers to shown at the right side
1158
                                while (isset($select_items[$lines_count])) {
1159
                                    $s .= '<tr>
1160
                                      <td colspan="2"></td>
1161
                                      <td valign="top">';
1162
                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.
1163
                                        $select_items[$lines_count]['answer'];
1164
                                    $s .= "</td>
1165
                                </tr>";
1166
                                    $lines_count++;
1167
                                }
1168
                            }
1169
                            $matching_correct_answer++;
1170
                        }
1171
                        break;
1172
                    case DRAGGABLE:
1173
                        if ($answerCorrect) {
1174
                            $windowId = $questionId.'_'.$lines_count;
1175
                            $s .= '<li class="touch-items" id="'.$windowId.'">';
1176
                            $s .= Display::div(
1177
                                $answer,
1178
                                [
1179
                                    'id' => "window_$windowId",
1180
                                    'class' => "window{$questionId}_question_draggable exercise-draggable-answer-option",
1181
                                ]
1182
                            );
1183
1184
                            $draggableSelectOptions = [];
1185
                            $selectedValue = 0;
1186
                            $selectedIndex = 0;
1187
                            if ($user_choice) {
1188
                                foreach ($user_choice as $userChoiceKey => $chosen) {
1189
                                    $userChoiceKey++;
1190
                                    if ($lines_count != $userChoiceKey) {
1191
                                        continue;
1192
                                    }
1193
                                    /*if ($answerCorrect != $chosen['answer']) {
1194
                                        continue;
1195
                                    }*/
1196
                                    $selectedValue = $chosen['answer'];
1197
                                }
1198
                            }
1199
                            foreach ($select_items as $key => $select_item) {
1200
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
1201
                            }
1202
1203
                            foreach ($draggableSelectOptions as $value => $text) {
1204
                                if ($value == $selectedValue) {
1205
                                    break;
1206
                                }
1207
                                $selectedIndex++;
1208
                            }
1209
1210
                            $s .= Display::select(
1211
                                "choice[$questionId][$numAnswer]",
1212
                                $draggableSelectOptions,
1213
                                $selectedValue,
1214
                                [
1215
                                    'id' => "window_{$windowId}_select",
1216
                                    'class' => 'select_option hidden',
1217
                                ],
1218
                                false
1219
                            );
1220
1221
                            if ($selectedValue && $selectedIndex) {
1222
                                $s .= "
1223
                                    <script>
1224
                                        $(function() {
1225
                                            DraggableAnswer.deleteItem(
1226
                                                $('#{$questionId}_$lines_count'),
1227
                                                $('#drop_{$questionId}_{$selectedIndex}')
1228
                                            );
1229
                                        });
1230
                                    </script>
1231
                                ";
1232
                            }
1233
1234
                            if (isset($select_items[$lines_count])) {
1235
                                $s .= Display::div(
1236
                                    Display::tag(
1237
                                        'b',
1238
                                        $select_items[$lines_count]['letter']
1239
                                    ).$select_items[$lines_count]['answer'],
1240
                                    [
1241
                                        'id' => "window_{$windowId}_answer",
1242
                                        'class' => 'hidden',
1243
                                    ]
1244
                                );
1245
                            } else {
1246
                                $s .= '&nbsp;';
1247
                            }
1248
1249
                            $lines_count++;
1250
                            if (($lines_count - 1) == $num_suggestions) {
1251
                                while (isset($select_items[$lines_count])) {
1252
                                    $s .= Display::tag('b', $select_items[$lines_count]['letter']);
1253
                                    $s .= $select_items[$lines_count]['answer'];
1254
                                    $lines_count++;
1255
                                }
1256
                            }
1257
1258
                            $matching_correct_answer++;
1259
                            $s .= '</li>';
1260
                        }
1261
                        break;
1262
                    case MATCHING_DRAGGABLE:
1263
                        if ($answerId == 1) {
1264
                            echo $objAnswerTmp->getJs();
1265
                        }
1266
                        if ($answerCorrect != 0) {
1267
                            $windowId = "{$questionId}_{$lines_count}";
1268
                            $s .= <<<HTML
1269
                            <tr>
1270
                                <td width="45%">
1271
                                    <div id="window_{$windowId}"
1272
                                        class="window window_left_question window{$questionId}_question">
1273
                                        <strong>$lines_count.</strong>
1274
                                        $answer
1275
                                    </div>
1276
                                </td>
1277
                                <td width="10%">
1278
HTML;
1279
1280
                            $draggableSelectOptions = [];
1281
                            $selectedValue = 0;
1282
                            $selectedIndex = 0;
1283
1284
                            if ($user_choice) {
1285
                                foreach ($user_choice as $chosen) {
1286
                                    if ($numAnswer == $chosen['position']) {
1287
                                        $selectedValue = $chosen['answer'];
1288
                                        break;
1289
                                    }
1290
                                }
1291
                            }
1292
1293
                            foreach ($select_items as $key => $selectItem) {
1294
                                $draggableSelectOptions[$selectItem['id']] = $selectItem['letter'];
1295
                            }
1296
1297
                            foreach ($draggableSelectOptions as $value => $text) {
1298
                                if ($value == $selectedValue) {
1299
                                    break;
1300
                                }
1301
                                $selectedIndex++;
1302
                            }
1303
1304
                            $s .= Display::select(
1305
                                "choice[$questionId][$numAnswer]",
1306
                                $draggableSelectOptions,
1307
                                $selectedValue,
1308
                                [
1309
                                    'id' => "window_{$windowId}_select",
1310
                                    'class' => 'hidden',
1311
                                ],
1312
                                false
1313
                            );
1314
1315
                            if (!empty($answerCorrect) && !empty($selectedValue)) {
1316
                                // Show connect if is not freeze (question preview)
1317
                                if (!$freeze) {
1318
                                    $s .= "
1319
                                        <script>
1320
                                            $(function() {
1321
                                                $(window).on('load', function() {
1322
                                                    jsPlumb.connect({
1323
                                                        source: 'window_$windowId',
1324
                                                        target: 'window_{$questionId}_{$selectedIndex}_answer',
1325
                                                        endpoint: ['Blank', {radius: 15}],
1326
                                                        anchors: ['RightMiddle', 'LeftMiddle'],
1327
                                                        paintStyle: {strokeStyle: '#8A8888', lineWidth: 8},
1328
                                                        connector: [
1329
                                                            MatchingDraggable.connectorType,
1330
                                                            {curvines: MatchingDraggable.curviness}
1331
                                                        ]
1332
                                                    });
1333
                                                });
1334
                                            });
1335
                                        </script>
1336
                                    ";
1337
                                }
1338
                            }
1339
1340
                            $s .= '</td><td width="45%">';
1341
                            if (isset($select_items[$lines_count])) {
1342
                                $s .= <<<HTML
1343
                                <div id="window_{$windowId}_answer" class="window window_right_question">
1344
                                    <strong>{$select_items[$lines_count]['letter']}.</strong>
1345
                                    {$select_items[$lines_count]['answer']}
1346
                                </div>
1347
HTML;
1348
                            } else {
1349
                                $s .= '&nbsp;';
1350
                            }
1351
1352
                            $s .= '</td></tr>';
1353
                            $lines_count++;
1354
                            if (($lines_count - 1) == $num_suggestions) {
1355
                                while (isset($select_items[$lines_count])) {
1356
                                    $s .= <<<HTML
1357
                                    <tr>
1358
                                        <td colspan="2"></td>
1359
                                        <td>
1360
                                            <strong>{$select_items[$lines_count]['letter']}</strong>
1361
                                            {$select_items[$lines_count]['answer']}
1362
                                        </td>
1363
                                    </tr>
1364
HTML;
1365
                                    $lines_count++;
1366
                                }
1367
                            }
1368
                            $matching_correct_answer++;
1369
                        }
1370
                        break;
1371
                }
1372
            }
1373
1374
            if ($show_comment) {
1375
                $s .= '</table>';
1376
            } elseif (in_array(
1377
                $answerType,
1378
                [
1379
                    MATCHING,
1380
                    MATCHING_DRAGGABLE,
1381
                    UNIQUE_ANSWER_NO_OPTION,
1382
                    MULTIPLE_ANSWER_TRUE_FALSE,
1383
                    MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE,
1384
                    MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY,
1385
                ]
1386
            )) {
1387
                $s .= '</table>';
1388
            }
1389
1390
            if ($answerType == DRAGGABLE) {
1391
                $isVertical = $objQuestionTmp->extra == 'v';
1392
                $s .= "
1393
                           </ul>
1394
                        </div><!-- .col-md-12 -->
1395
                    </div><!-- .row -->
1396
                ";
1397
                $counterAnswer = 1;
1398
                $s .= $isVertical ? '' : '<div class="row">';
1399
                for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1400
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
1401
                    $windowId = $questionId.'_'.$counterAnswer;
1402
                    if ($answerCorrect) {
1403
                        $s .= $isVertical ? '<div class="row">' : '';
1404
                        $s .= '
1405
                            <div class="'.($isVertical ? 'col-md-12' : 'col-xs-12 col-sm-4 col-md-3 col-lg-2').'">
1406
                                <div class="droppable-item">
1407
                                    <span class="number">'.$counterAnswer.'.</span>
1408
                                    <div id="drop_'.$windowId.'" class="droppable">
1409
                                    </div>
1410
                                 </div>
1411
                            </div>
1412
                        ';
1413
                        $s .= $isVertical ? '</div>' : '';
1414
                        $counterAnswer++;
1415
                    }
1416
                }
1417
1418
                $s .= $isVertical ? '' : '</div>'; // row
1419
//                $s .= '</div>';
1420
            }
1421
1422
            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
1423
                $s .= '</div>'; //drag_question
1424
            }
1425
1426
            $s .= '</div>'; //question_options row
1427
1428
            // destruction of the Answer object
1429
            unset($objAnswerTmp);
1430
            // destruction of the Question object
1431
            unset($objQuestionTmp);
1432
            if ('export' == $origin) {
1433
                return $s;
1434
            }
1435
            echo $s;
1436
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
1437
            global $exe_id;
1438
            // Question is a HOT_SPOT
1439
            // Checking document/images visibility
1440
            if (api_is_platform_admin() || api_is_course_admin()) {
1441
                $doc_id = $objQuestionTmp->getPictureId();
1442
                if (is_numeric($doc_id)) {
1443
                    $images_folder_visibility = api_get_item_visibility(
1444
                        $course,
1445
                        'document',
1446
                        $doc_id,
1447
                        api_get_session_id()
1448
                    );
1449
                    if (!$images_folder_visibility) {
1450
                        // Show only to the course/platform admin if the image is set to visibility = false
1451
                        echo Display::return_message(
1452
                            get_lang('ChangeTheVisibilityOfTheCurrentImage'),
1453
                            'warning'
1454
                        );
1455
                    }
1456
                }
1457
            }
1458
            $questionDescription = $objQuestionTmp->selectDescription();
1459
1460
            // Get the answers, make a list
1461
            $objAnswerTmp = new Answer($questionId, $course_id);
1462
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1463
1464
            // get answers of hotpost
1465
            $answers_hotspot = [];
1466
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1467
                $answers = $objAnswerTmp->selectAnswerByAutoId(
1468
                    $objAnswerTmp->selectAutoId($answerId)
1469
                );
1470
                $answers_hotspot[$answers['id']] = $objAnswerTmp->selectAnswer(
1471
                    $answerId
1472
                );
1473
            }
1474
1475
            $answerList = '';
1476
            $hotspotColor = 0;
1477
            if ($answerType != HOT_SPOT_DELINEATION) {
1478
                $answerList = '
1479
                    <div class="well well-sm">
1480
                        <h5 class="page-header">'.get_lang('HotspotZones').'</h5>
1481
                        <ol>
1482
                ';
1483
1484
                if (!empty($answers_hotspot)) {
1485
                    Session::write("hotspot_ordered$questionId", array_keys($answers_hotspot));
1486
                    foreach ($answers_hotspot as $value) {
1487
                        $answerList .= '<li>';
1488
                        if ($freeze) {
1489
                            $answerList .= '<span class="hotspot-color-'.$hotspotColor
1490
                                .' fa fa-square" aria-hidden="true"></span>'.PHP_EOL;
1491
                        }
1492
                        $answerList .= $value;
1493
                        $answerList .= '</li>';
1494
                        $hotspotColor++;
1495
                    }
1496
                }
1497
1498
                $answerList .= '
1499
                        </ol>
1500
                    </div>
1501
                ';
1502
            }
1503
1504
            if ($freeze) {
1505
                $relPath = api_get_path(WEB_CODE_PATH);
1506
                echo "
1507
                    <div class=\"row\">
1508
                        <div class=\"col-sm-9\">
1509
                            <div id=\"hotspot-preview-$questionId\"></div>
1510
                        </div>
1511
                        <div class=\"col-sm-3\">
1512
                            $answerList
1513
                        </div>
1514
                    </div>
1515
                    <script>
1516
                        new ".($answerType == HOT_SPOT ? "HotspotQuestion" : "DelineationQuestion")."({
1517
                            questionId: $questionId,
1518
                            exerciseId: {$exercise->id},
1519
                            exeId: 0,
1520
                            selector: '#hotspot-preview-$questionId',
1521
                            'for': 'preview',
1522
                            relPath: '$relPath'
1523
                        });
1524
                    </script>
1525
                ";
1526
1527
                return;
1528
            }
1529
1530
            if (!$only_questions) {
1531
                if ($show_title) {
1532
                    if ($exercise->display_category_name) {
1533
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1534
                    }
1535
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1536
                }
1537
1538
                if ($questionRequireAuth) {
1539
                    WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise);
1540
1541
                    return false;
1542
                }
1543
1544
                //@todo I need to the get the feedback type
1545
                echo <<<HOTSPOT
1546
                    <input type="hidden" name="hidden_hotspot_id" value="$questionId" />
1547
                    <div class="exercise_questions">
1548
                        $questionDescription
1549
                        <div class="row">
1550
HOTSPOT;
1551
            }
1552
1553
            $relPath = api_get_path(WEB_CODE_PATH);
1554
            $s .= "<div class=\"col-sm-8 col-md-9\">
1555
                   <div class=\"hotspot-image\"></div>
1556
                    <script>
1557
                        $(function() {
1558
                            new ".($answerType == HOT_SPOT_DELINEATION ? 'DelineationQuestion' : 'HotspotQuestion')."({
1559
                                questionId: $questionId,
1560
                                exerciseId: {$exercise->id},
1561
                                exeId: 0,
1562
                                selector: '#question_div_' + $questionId + ' .hotspot-image',
1563
                                'for': 'user',
1564
                                relPath: '$relPath'
1565
                            });
1566
                        });
1567
                    </script>
1568
                </div>
1569
                <div class=\"col-sm-4 col-md-3\">
1570
                    $answerList
1571
                </div>
1572
            ";
1573
1574
            echo <<<HOTSPOT
1575
                            $s
1576
                        </div>
1577
                    </div>
1578
HOTSPOT;
1579
        } elseif ($answerType == ANNOTATION) {
1580
            global $exe_id;
1581
            $relPath = api_get_path(WEB_CODE_PATH);
1582
            if (api_is_platform_admin() || api_is_course_admin()) {
1583
                $docId = DocumentManager::get_document_id($course, '/images/'.$pictureName);
1584
                if ($docId) {
1585
                    $images_folder_visibility = api_get_item_visibility(
1586
                        $course,
1587
                        'document',
1588
                        $docId,
1589
                        api_get_session_id()
1590
                    );
1591
1592
                    if (!$images_folder_visibility) {
1593
                        echo Display::return_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'), 'warning');
1594
                    }
1595
                }
1596
1597
                if ($freeze) {
1598
                    echo Display::img(
1599
                        api_get_path(WEB_COURSE_PATH).$course['path'].'/document/images/'.$pictureName,
1600
                        $objQuestionTmp->selectTitle(),
1601
                        ['width' => '600px']
1602
                    );
1603
1604
                    return 0;
1605
                }
1606
            }
1607
1608
            if (!$only_questions) {
1609
                if ($show_title) {
1610
                    if ($exercise->display_category_name) {
1611
                        TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1612
                    }
1613
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1614
                }
1615
1616
                if ($questionRequireAuth) {
1617
                    WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise);
1618
1619
                    return false;
1620
                }
1621
1622
                echo '
1623
                    <input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />
1624
                    <div class="exercise_questions">
1625
                        '.$objQuestionTmp->selectDescription().'
1626
                        <div class="row">
1627
                            <div class="col-sm-8 col-md-9">
1628
                                <div id="annotation-canvas-'.$questionId.'" class="annotation-canvas center-block">
1629
                                </div>
1630
                                <script>
1631
                                    AnnotationQuestion({
1632
                                        questionId: '.$questionId.',
1633
                                        exerciseId: '.$exerciseId.',
1634
                                        relPath: \''.$relPath.'\',
1635
                                        courseId: '.$course_id.',
1636
                                    });
1637
                                </script>
1638
                            </div>
1639
                            <div class="col-sm-4 col-md-3">
1640
                                <div class="well well-sm" id="annotation-toolbar-'.$questionId.'">
1641
                                    <div class="btn-toolbar">
1642
                                        <div class="btn-group" data-toggle="buttons">
1643
                                            <label class="btn btn-default active"
1644
                                                aria-label="'.get_lang('AddAnnotationPath').'">
1645
                                                <input
1646
                                                    type="radio" value="0"
1647
                                                    name="'.$questionId.'-options" autocomplete="off" checked>
1648
                                                <span class="fa fa-pencil" aria-hidden="true"></span>
1649
                                            </label>
1650
                                            <label class="btn btn-default"
1651
                                                aria-label="'.get_lang('AddAnnotationText').'">
1652
                                                <input
1653
                                                    type="radio" value="1"
1654
                                                    name="'.$questionId.'-options" autocomplete="off">
1655
                                                <span class="fa fa-font fa-fw" aria-hidden="true"></span>
1656
                                            </label>
1657
                                        </div>
1658
                                    </div>
1659
                                    <ul class="list-unstyled"></ul>
1660
                                </div>
1661
                            </div>
1662
                        </div>
1663
                    </div>
1664
                ';
1665
            }
1666
            $objAnswerTmp = new Answer($questionId);
1667
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1668
            unset($objAnswerTmp, $objQuestionTmp);
1669
        }
1670
1671
        return $nbrAnswers;
1672
    }
1673
1674
    /**
1675
     * Get an HTML string with the list of exercises where the given question
1676
     * is being used.
1677
     *
1678
     * @param int $questionId    The iid of the question being observed
1679
     * @param int $excludeTestId If defined, exclude this (current) test from the list of results
1680
     *
1681
     * @return string An HTML string containing a div and a table
1682
     */
1683
    public static function showTestsWhereQuestionIsUsed(int $questionId, int $excludeTestId = 0)
1684
    {
1685
        $questionId = (int) $questionId;
1686
        $sql = "SELECT qz.title quiz_title,
1687
                        c.title course_title,
1688
                        s.name session_name,
1689
                        qz.iid as quiz_id,
1690
                        qz.c_id,
1691
                        qz.session_id
1692
                FROM c_quiz qz,
1693
                    c_quiz_rel_question qq,
1694
                    course c,
1695
                    session s
1696
                WHERE qz.c_id = c.id AND
1697
                    (qz.session_id = s.id OR qz.session_id = 0) AND
1698
                    qq.exercice_id = qz.iid AND ";
1699
        if (!empty($excludeTestId)) {
1700
            $excludeTestId = (int) $excludeTestId;
1701
            $sql .= " qz.iid != $excludeTestId AND ";
1702
        }
1703
        $sql .= "     qq.question_id = $questionId
1704
                GROUP BY qq.iid";
1705
1706
        $result = [];
1707
        $html = "";
1708
1709
        $sqlResult = Database::query($sql);
1710
1711
        if (Database::num_rows($sqlResult) != 0) {
1712
            while ($row = Database::fetch_array($sqlResult, 'ASSOC')) {
1713
                $tmp = [];
1714
                $tmp[0] = $row['course_title'];
1715
                $tmp[1] = $row['session_name'];
1716
                $tmp[2] = $row['quiz_title'];
1717
                $courseDetails = api_get_course_info_by_id($row['c_id']);
1718
                $courseCode = $courseDetails['code'];
1719
                // Send do other test with r=1 to reset current test session variables
1720
                $urlToQuiz = api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq_params($courseCode, $row['session_id']).'&exerciseId='.$row['quiz_id'].'&r=1';
1721
                $tmp[3] = '<a href="'.$urlToQuiz.'">'.Display::return_icon('quiz.png', get_lang('Edit')).'</a>';
1722
                if ((int) $row['session_id'] == 0) {
1723
                    $tmp[1] = '-';
1724
                }
1725
1726
                $result[] = $tmp;
1727
            }
1728
1729
            $headers = [
1730
                get_lang('Course'),
1731
                get_lang('Session'),
1732
                get_lang('Quiz'),
1733
                get_lang('LinkToTestEdition'),
1734
            ];
1735
1736
            $title = Display::div(
1737
                get_lang('QuestionAlsoUsedInTheFollowingTests'),
1738
                [
1739
                    'class' => 'section-title',
1740
                    'style' => 'margin-top: 25px; border-bottom: none',
1741
                ]
1742
            );
1743
1744
            $html = $title.Display::table($headers, $result);
1745
        }
1746
1747
        echo $html;
1748
    }
1749
1750
    /**
1751
     * @param int $exeId
1752
     *
1753
     * @return array
1754
     */
1755
    public static function get_exercise_track_exercise_info($exeId)
1756
    {
1757
        $quizTable = Database::get_course_table(TABLE_QUIZ_TEST);
1758
        $trackExerciseTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1759
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1760
        $exeId = (int) $exeId;
1761
        $result = [];
1762
        if (!empty($exeId)) {
1763
            $sql = " SELECT q.*, tee.*
1764
                FROM $quizTable as q
1765
                INNER JOIN $trackExerciseTable as tee
1766
                ON q.id = tee.exe_exo_id
1767
                INNER JOIN $courseTable c
1768
                ON c.id = tee.c_id
1769
                WHERE tee.exe_id = $exeId
1770
                AND q.c_id = c.id";
1771
1772
            $sqlResult = Database::query($sql);
1773
            if (Database::num_rows($sqlResult)) {
1774
                $result = Database::fetch_array($sqlResult, 'ASSOC');
1775
                $result['duration_formatted'] = '';
1776
                if (!empty($result['exe_duration'])) {
1777
                    $time = api_format_time($result['exe_duration'], 'js');
1778
                    $result['duration_formatted'] = $time;
1779
                }
1780
            }
1781
        }
1782
1783
        return $result;
1784
    }
1785
1786
    /**
1787
     * Validates the time control key.
1788
     *
1789
     * @param int $exercise_id
1790
     * @param int $lp_id
1791
     * @param int $lp_item_id
1792
     *
1793
     * @return bool
1794
     */
1795
    public static function exercise_time_control_is_valid(
1796
        $exercise_id,
1797
        $lp_id = 0,
1798
        $lp_item_id = 0
1799
    ) {
1800
        $course_id = api_get_course_int_id();
1801
        $exercise_id = (int) $exercise_id;
1802
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1803
        $sql = "SELECT expired_time FROM $table
1804
                WHERE c_id = $course_id AND id = $exercise_id";
1805
        $result = Database::query($sql);
1806
        $row = Database::fetch_array($result, 'ASSOC');
1807
        if (!empty($row['expired_time'])) {
1808
            $current_expired_time_key = self::get_time_control_key(
1809
                $exercise_id,
1810
                $lp_id,
1811
                $lp_item_id
1812
            );
1813
            if (isset($_SESSION['expired_time'][$current_expired_time_key])) {
1814
                $current_time = time();
1815
                $expired_time = api_strtotime(
1816
                    $_SESSION['expired_time'][$current_expired_time_key],
1817
                    'UTC'
1818
                );
1819
                $total_time_allowed = $expired_time + 30;
1820
                if ($total_time_allowed < $current_time) {
1821
                    return false;
1822
                }
1823
1824
                return true;
1825
            }
1826
1827
            return false;
1828
        }
1829
1830
        return true;
1831
    }
1832
1833
    /**
1834
     * Deletes the time control token.
1835
     *
1836
     * @param int $exercise_id
1837
     * @param int $lp_id
1838
     * @param int $lp_item_id
1839
     */
1840
    public static function exercise_time_control_delete(
1841
        $exercise_id,
1842
        $lp_id = 0,
1843
        $lp_item_id = 0
1844
    ) {
1845
        $current_expired_time_key = self::get_time_control_key(
1846
            $exercise_id,
1847
            $lp_id,
1848
            $lp_item_id
1849
        );
1850
        unset($_SESSION['expired_time'][$current_expired_time_key]);
1851
    }
1852
1853
    /**
1854
     * Generates the time control key.
1855
     *
1856
     * @param int $exercise_id
1857
     * @param int $lp_id
1858
     * @param int $lp_item_id
1859
     *
1860
     * @return string
1861
     */
1862
    public static function get_time_control_key(
1863
        $exercise_id,
1864
        $lp_id = 0,
1865
        $lp_item_id = 0
1866
    ) {
1867
        $exercise_id = (int) $exercise_id;
1868
        $lp_id = (int) $lp_id;
1869
        $lp_item_id = (int) $lp_item_id;
1870
1871
        return
1872
            api_get_course_int_id().'_'.
1873
            api_get_session_id().'_'.
1874
            $exercise_id.'_'.
1875
            api_get_user_id().'_'.
1876
            $lp_id.'_'.
1877
            $lp_item_id;
1878
    }
1879
1880
    /**
1881
     * Get session time control.
1882
     *
1883
     * @param int $exercise_id
1884
     * @param int $lp_id
1885
     * @param int $lp_item_id
1886
     *
1887
     * @return int
1888
     */
1889
    public static function get_session_time_control_key(
1890
        $exercise_id,
1891
        $lp_id = 0,
1892
        $lp_item_id = 0
1893
    ) {
1894
        $return_value = 0;
1895
        $time_control_key = self::get_time_control_key(
1896
            $exercise_id,
1897
            $lp_id,
1898
            $lp_item_id
1899
        );
1900
        if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) {
1901
            $return_value = $_SESSION['expired_time'][$time_control_key];
1902
        }
1903
1904
        return $return_value;
1905
    }
1906
1907
    /**
1908
     * Gets count of exam results.
1909
     *
1910
     * @param int    $exerciseId
1911
     * @param array  $conditions
1912
     * @param string $courseCode
1913
     * @param bool   $showSession
1914
     * @param bool   $searchAllTeacherCourses
1915
     * @param int    $status
1916
     *
1917
     * @return array
1918
     */
1919
    public static function get_count_exam_results(
1920
        $exerciseId,
1921
        $conditions,
1922
        $courseCode = '',
1923
        $showSession = false,
1924
        $searchAllTeacherCourses = false,
1925
        $status = 0
1926
    ) {
1927
        return self::get_exam_results_data(
1928
            null,
1929
            null,
1930
            null,
1931
            null,
1932
            $exerciseId,
1933
            $conditions,
1934
            true,
1935
            $courseCode,
1936
            $showSession,
1937
            false,
1938
            [],
1939
            false,
1940
            false,
1941
            false,
1942
            $searchAllTeacherCourses,
1943
            $status
1944
        );
1945
    }
1946
1947
    /**
1948
     * @param string $path
1949
     *
1950
     * @return int
1951
     */
1952
    public static function get_count_exam_hotpotatoes_results($path)
1953
    {
1954
        return self::get_exam_results_hotpotatoes_data(
1955
            0,
1956
            0,
1957
            '',
1958
            '',
1959
            $path,
1960
            true,
1961
            ''
1962
        );
1963
    }
1964
1965
    /**
1966
     * @param int    $in_from
1967
     * @param int    $in_number_of_items
1968
     * @param int    $in_column
1969
     * @param int    $in_direction
1970
     * @param string $in_hotpot_path
1971
     * @param bool   $in_get_count
1972
     * @param null   $where_condition
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $where_condition is correct as it would always require null to be passed?
Loading history...
1973
     *
1974
     * @return array|int
1975
     */
1976
    public static function get_exam_results_hotpotatoes_data(
1977
        $in_from,
1978
        $in_number_of_items,
1979
        $in_column,
1980
        $in_direction,
1981
        $in_hotpot_path,
1982
        $in_get_count = false,
1983
        $where_condition = null
1984
    ) {
1985
        $courseId = api_get_course_int_id();
1986
        // by default in_column = 1 If parameters given, it is the name of the column witch is the bdd field name
1987
        if ($in_column == 1) {
1988
            $in_column = 'firstname';
1989
        }
1990
        $in_hotpot_path = Database::escape_string($in_hotpot_path);
1991
        $in_direction = Database::escape_string($in_direction);
1992
        $in_direction = !in_array(strtolower(trim($in_direction)), ['asc', 'desc']) ? 'asc' : $in_direction;
1993
        $in_column = Database::escape_string($in_column);
1994
        $in_number_of_items = (int) $in_number_of_items;
1995
        $in_from = (int) $in_from;
1996
1997
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(
1998
            TABLE_STATISTIC_TRACK_E_HOTPOTATOES
1999
        );
2000
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
2001
2002
        $sql = "SELECT *, thp.id AS thp_id
2003
                FROM $TBL_TRACK_HOTPOTATOES thp
2004
                JOIN $TBL_USER u
2005
                ON thp.exe_user_id = u.user_id
2006
                WHERE
2007
                    thp.c_id = $courseId AND
2008
                    exe_name LIKE '$in_hotpot_path%'";
2009
2010
        // just count how many answers
2011
        if ($in_get_count) {
2012
            $res = Database::query($sql);
2013
2014
            return Database::num_rows($res);
2015
        }
2016
        // get a number of sorted results
2017
        $sql .= " $where_condition
2018
            ORDER BY `$in_column` $in_direction
2019
            LIMIT $in_from, $in_number_of_items";
2020
2021
        $res = Database::query($sql);
2022
        $result = [];
2023
        $apiIsAllowedToEdit = api_is_allowed_to_edit();
2024
        $urlBase = api_get_path(WEB_CODE_PATH).
2025
            'exercise/hotpotatoes_exercise_report.php?action=delete&'.
2026
            api_get_cidreq().'&id=';
2027
        while ($data = Database::fetch_array($res)) {
2028
            $actions = null;
2029
2030
            if ($apiIsAllowedToEdit) {
2031
                $url = $urlBase.$data['thp_id'].'&path='.$data['exe_name'];
2032
                $actions = Display::url(
2033
                    Display::return_icon('delete.png', get_lang('Delete')),
2034
                    $url
2035
                );
2036
            }
2037
2038
            $result[] = [
2039
                'firstname' => $data['firstname'],
2040
                'lastname' => $data['lastname'],
2041
                'username' => $data['username'],
2042
                'group_name' => implode(
2043
                    '<br/>',
2044
                    GroupManager::get_user_group_name($data['user_id'])
2045
                ),
2046
                'exe_date' => $data['exe_date'],
2047
                'score' => $data['exe_result'].' / '.$data['exe_weighting'],
2048
                'actions' => $actions,
2049
            ];
2050
        }
2051
2052
        return $result;
2053
    }
2054
2055
    /**
2056
     * @param string $exercisePath
2057
     * @param int    $userId
2058
     * @param int    $courseId
2059
     * @param int    $sessionId
2060
     *
2061
     * @return array
2062
     */
2063
    public static function getLatestHotPotatoResult(
2064
        $exercisePath,
2065
        $userId,
2066
        $courseId,
2067
        $sessionId
2068
    ) {
2069
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
2070
        $exercisePath = Database::escape_string($exercisePath);
2071
        $userId = (int) $userId;
2072
        $courseId = (int) $courseId;
2073
2074
        $sql = "SELECT * FROM $table
2075
                WHERE
2076
                    c_id = $courseId AND
2077
                    exe_name LIKE '$exercisePath%' AND
2078
                    exe_user_id = $userId
2079
                ORDER BY id
2080
                LIMIT 1";
2081
        $result = Database::query($sql);
2082
        $attempt = [];
2083
        if (Database::num_rows($result)) {
2084
            $attempt = Database::fetch_array($result, 'ASSOC');
2085
        }
2086
2087
        return $attempt;
2088
    }
2089
2090
    /**
2091
     * Gets exercise results.
2092
     *
2093
     * @todo this function should be moved in a library  + no global calls
2094
     *
2095
     * @param int    $from
2096
     * @param int    $number_of_items
2097
     * @param int    $column
2098
     * @param string $direction
2099
     * @param int    $exercise_id
2100
     * @param null   $extra_where_conditions
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $extra_where_conditions is correct as it would always require null to be passed?
Loading history...
2101
     * @param bool   $get_count
2102
     * @param string $courseCode
2103
     * @param bool   $showSessionField
2104
     * @param bool   $showExerciseCategories
2105
     * @param array  $userExtraFieldsToAdd
2106
     * @param bool   $useCommaAsDecimalPoint
2107
     * @param bool   $roundValues
2108
     * @param bool   $getOnlyIds
2109
     *
2110
     * @return array
2111
     */
2112
    public static function get_exam_results_data(
2113
        $from,
2114
        $number_of_items,
2115
        $column,
2116
        $direction,
2117
        $exercise_id,
2118
        $extra_where_conditions = null,
2119
        $get_count = false,
2120
        $courseCode = null,
2121
        $showSessionField = false,
2122
        $showExerciseCategories = false,
2123
        $userExtraFieldsToAdd = [],
2124
        $useCommaAsDecimalPoint = false,
2125
        $roundValues = false,
2126
        $getOnlyIds = false,
2127
        $searchAllTeacherCourses = false,
2128
        $status = 0
2129
    ) {
2130
        //@todo replace all this globals
2131
        global $filter;
2132
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
2133
        $courseInfo = api_get_course_info($courseCode);
2134
        $documentPath = '';
2135
        $sessionId = api_get_session_id();
2136
        $courseId = 0;
2137
        if (!empty($courseInfo)) {
2138
            $courseId = $courseInfo['real_id'];
2139
            $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
2140
        }
2141
2142
        $is_allowedToEdit =
2143
            api_is_allowed_to_edit(null, true) ||
2144
            api_is_allowed_to_edit(true) ||
2145
            api_is_drh() ||
2146
            api_is_student_boss() ||
2147
            api_is_session_admin();
2148
2149
        $courseCondition = "c_id = $courseId";
2150
        $statusCondition = '';
2151
2152
        if (!empty($status)) {
2153
            switch ($status) {
2154
                case 2:
2155
                    // validated
2156
                    $statusCondition = ' AND revised = 1 ';
2157
                    break;
2158
                case 3:
2159
                    // not validated
2160
                    $statusCondition = ' AND revised = 0 ';
2161
                    break;
2162
            }
2163
        }
2164
2165
        if (false === $searchAllTeacherCourses) {
2166
            if (empty($courseInfo)) {
2167
                return [];
2168
            }
2169
        } else {
2170
            $courses = CourseManager::get_courses_list_by_user_id(api_get_user_id(), false, false, false);
2171
2172
            if (empty($courses)) {
2173
                return [];
2174
            }
2175
2176
            $courses = array_column($courses, 'real_id');
2177
            $is_allowedToEdit = true;
2178
            $courseCondition = "c_id IN ('".implode("', '", $courses)."') ";
2179
        }
2180
2181
        $exercise_id = (int) $exercise_id;
2182
2183
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
2184
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
2185
        $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
2186
        $TBL_GROUP = Database::get_course_table(TABLE_GROUP);
2187
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2188
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
2189
        $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
2190
2191
        $session_id_and = '';
2192
        $sessionCondition = '';
2193
        if (!$showSessionField) {
2194
            $session_id_and = " AND te.session_id = $sessionId ";
2195
            $sessionCondition = " AND ttte.session_id = $sessionId";
2196
        }
2197
2198
        if ($searchAllTeacherCourses) {
2199
            $session_id_and = " AND te.session_id = 0 ";
2200
            $sessionCondition = " AND ttte.session_id = 0";
2201
        }
2202
2203
        if (empty($sessionId) &&
2204
            api_get_configuration_value('show_exercise_session_attempts_in_base_course')
2205
        ) {
2206
            $session_id_and = '';
2207
            $sessionCondition = '';
2208
        }
2209
2210
        $exercise_where = '';
2211
        $exerciseFilter = '';
2212
        if (!empty($exercise_id)) {
2213
            $exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.' ';
2214
            $exerciseFilter = " AND exe_exo_id = $exercise_id ";
2215
        }
2216
2217
        $hotpotatoe_where = '';
2218
        if (!empty($_GET['path'])) {
2219
            $hotpotatoe_path = Database::escape_string($_GET['path']);
2220
            $hotpotatoe_where .= ' AND exe_name = "'.$hotpotatoe_path.'"  ';
2221
        }
2222
2223
        // sql for chamilo-type tests for teacher / tutor view
2224
        $sql_inner_join_tbl_track_exercices = "
2225
        (
2226
            SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised
2227
            FROM $TBL_TRACK_EXERCICES ttte
2228
            LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
2229
            ON (ttte.exe_id = tr.exe_id)
2230
            WHERE
2231
                $courseCondition
2232
                $exerciseFilter
2233
                $sessionCondition
2234
        )";
2235
2236
        if ($is_allowedToEdit) {
2237
            //@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries
2238
            // Hack in order to filter groups
2239
            $sql_inner_join_tbl_user = '';
2240
            if (strpos($extra_where_conditions, 'group_id')) {
2241
                $sql_inner_join_tbl_user = "
2242
                (
2243
                    SELECT
2244
                        u.user_id,
2245
                        firstname,
2246
                        lastname,
2247
                        official_code,
2248
                        email,
2249
                        username,
2250
                        g.name as group_name,
2251
                        g.id as group_id
2252
                    FROM $TBL_USER u
2253
                    INNER JOIN $TBL_GROUP_REL_USER gru
2254
                    ON (gru.user_id = u.user_id AND gru.c_id= $courseId )
2255
                    INNER JOIN $TBL_GROUP g
2256
                    ON (gru.group_id = g.id AND g.c_id= $courseId )
2257
                )";
2258
            }
2259
2260
            if (strpos($extra_where_conditions, 'group_all')) {
2261
                $extra_where_conditions = str_replace(
2262
                    "AND (  group_id = 'group_all'  )",
2263
                    '',
2264
                    $extra_where_conditions
2265
                );
2266
                $extra_where_conditions = str_replace(
2267
                    "AND group_id = 'group_all'",
2268
                    '',
2269
                    $extra_where_conditions
2270
                );
2271
                $extra_where_conditions = str_replace(
2272
                    "group_id = 'group_all' AND",
2273
                    '',
2274
                    $extra_where_conditions
2275
                );
2276
2277
                $sql_inner_join_tbl_user = "
2278
                (
2279
                    SELECT
2280
                        u.user_id,
2281
                        firstname,
2282
                        lastname,
2283
                        official_code,
2284
                        email,
2285
                        username,
2286
                        '' as group_name,
2287
                        '' as group_id
2288
                    FROM $TBL_USER u
2289
                )";
2290
                $sql_inner_join_tbl_user = null;
2291
            }
2292
2293
            if (strpos($extra_where_conditions, 'group_none')) {
2294
                $extra_where_conditions = str_replace(
2295
                    "AND (  group_id = 'group_none'  )",
2296
                    "AND (  group_id is null  )",
2297
                    $extra_where_conditions
2298
                );
2299
                $extra_where_conditions = str_replace(
2300
                    "AND group_id = 'group_none'",
2301
                    "AND (  group_id is null  )",
2302
                    $extra_where_conditions
2303
                );
2304
                $sql_inner_join_tbl_user = "
2305
            (
2306
                SELECT
2307
                    u.user_id,
2308
                    firstname,
2309
                    lastname,
2310
                    official_code,
2311
                    email,
2312
                    username,
2313
                    g.name as group_name,
2314
                    g.id as group_id
2315
                FROM $TBL_USER u
2316
                LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
2317
                ON ( gru.user_id = u.user_id AND gru.c_id = $courseId )
2318
                LEFT OUTER JOIN $TBL_GROUP g
2319
                ON (gru.group_id = g.id AND g.c_id = $courseId )
2320
            )";
2321
            }
2322
2323
            // All
2324
            $is_empty_sql_inner_join_tbl_user = false;
2325
            if (empty($sql_inner_join_tbl_user)) {
2326
                $is_empty_sql_inner_join_tbl_user = true;
2327
                $sql_inner_join_tbl_user = "
2328
            (
2329
                SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code
2330
                FROM $TBL_USER u
2331
                WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
2332
            )";
2333
            }
2334
2335
            $sqlFromOption = '';
2336
            $sqlWhereOption = '';
2337
            if (false === $searchAllTeacherCourses) {
2338
                $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
2339
                $sqlWhereOption = "  AND gru.c_id = $courseId AND gru.user_id = user.user_id ";
2340
            }
2341
2342
            $first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
2343
2344
            if ($get_count) {
2345
                $sql_select = 'SELECT count(te.exe_id) ';
2346
            } else {
2347
                $sql_select = "SELECT DISTINCT
2348
                    user_id,
2349
                    $first_and_last_name,
2350
                    official_code,
2351
                    ce.title,
2352
                    username,
2353
                    te.exe_result,
2354
                    te.exe_weighting,
2355
                    te.exe_date,
2356
                    te.exe_id,
2357
                    te.c_id,
2358
                    te.session_id,
2359
                    email as exemail,
2360
                    te.start_date,
2361
                    ce.expired_time,
2362
                    steps_counter,
2363
                    exe_user_id,
2364
                    te.exe_duration,
2365
                    te.status as completion_status,
2366
                    propagate_neg,
2367
                    revised,
2368
                    group_name,
2369
                    group_id,
2370
                    orig_lp_id,
2371
                    te.user_ip";
2372
            }
2373
2374
            $sql = " $sql_select
2375
                FROM $TBL_EXERCICES AS ce
2376
                INNER JOIN $sql_inner_join_tbl_track_exercices AS te
2377
                ON (te.exe_exo_id = ce.id)
2378
                INNER JOIN $sql_inner_join_tbl_user AS user
2379
                ON (user.user_id = exe_user_id)
2380
                WHERE
2381
                    te.$courseCondition
2382
                    $session_id_and AND
2383
                    ce.active <> -1 AND
2384
                    ce.$courseCondition
2385
                    $exercise_where
2386
                    $extra_where_conditions
2387
                    $statusCondition
2388
                ";
2389
2390
            // sql for hotpotatoes tests for teacher / tutor view
2391
            if ($get_count) {
2392
                $hpsql_select = ' SELECT count(username) ';
2393
            } else {
2394
                $hpsql_select = " SELECT
2395
                    $first_and_last_name ,
2396
                    username,
2397
                    official_code,
2398
                    tth.exe_name,
2399
                    tth.exe_result ,
2400
                    tth.exe_weighting,
2401
                    tth.exe_date";
2402
            }
2403
2404
            $hpsql = " $hpsql_select
2405
                FROM
2406
                    $TBL_TRACK_HOTPOTATOES tth,
2407
                    $TBL_USER user
2408
                    $sqlFromOption
2409
                WHERE
2410
                    user.user_id=tth.exe_user_id AND
2411
                    tth.$courseCondition
2412
                    $hotpotatoe_where
2413
                    $sqlWhereOption AND
2414
                     user.status NOT IN (".api_get_users_status_ignored_in_reports('string').")
2415
                ORDER BY tth.c_id ASC, tth.exe_date DESC ";
2416
        }
2417
2418
        if (empty($sql)) {
2419
            return false;
2420
        }
2421
2422
        if ($get_count) {
2423
            $resx = Database::query($sql);
2424
            $rowx = Database::fetch_row($resx, 'ASSOC');
2425
2426
            return $rowx[0];
2427
        }
2428
2429
        $teacher_id_list = [];
2430
        if (!empty($courseCode)) {
2431
            $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
2432
            if (!empty($teacher_list)) {
2433
                foreach ($teacher_list as $teacher) {
2434
                    $teacher_id_list[] = $teacher['user_id'];
2435
                }
2436
            }
2437
        }
2438
2439
        $scoreDisplay = new ScoreDisplay();
2440
        $decimalSeparator = '.';
2441
        $thousandSeparator = ',';
2442
2443
        if ($useCommaAsDecimalPoint) {
2444
            $decimalSeparator = ',';
2445
            $thousandSeparator = '';
2446
        }
2447
2448
        $listInfo = [];
2449
        // Simple exercises
2450
        if (empty($hotpotatoe_where)) {
2451
            $column = !empty($column) ? Database::escape_string($column) : null;
2452
            $from = (int) $from;
2453
            $number_of_items = (int) $number_of_items;
2454
            $direction = !in_array(strtolower(trim($direction)), ['asc', 'desc']) ? 'asc' : $direction;
2455
2456
            if (!empty($column)) {
2457
                $sql .= " ORDER BY `$column` $direction ";
2458
            }
2459
2460
            if (!$getOnlyIds) {
2461
                $sql .= " LIMIT $from, $number_of_items";
2462
            }
2463
2464
            $results = [];
2465
            $resx = Database::query($sql);
2466
            while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
2467
                $results[] = $rowx;
2468
            }
2469
2470
            $clean_group_list = [];
2471
            $lp_list = [];
2472
2473
            if (!empty($courseInfo)) {
2474
                $group_list = GroupManager::get_group_list(null, $courseInfo);
2475
                if (!empty($group_list)) {
2476
                    foreach ($group_list as $group) {
2477
                        $clean_group_list[$group['id']] = $group['name'];
2478
                    }
2479
                }
2480
2481
                $lp_list_obj = new LearnpathList(api_get_user_id());
2482
                $lp_list = $lp_list_obj->get_flat_list();
2483
                $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
2484
            }
2485
2486
            if (is_array($results)) {
2487
                $users_array_id = [];
2488
                $from_gradebook = false;
2489
                if (isset($_GET['gradebook']) && $_GET['gradebook'] === 'view') {
2490
                    $from_gradebook = true;
2491
                }
2492
                $sizeof = count($results);
2493
                $locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE);
2494
                $timeNow = strtotime(api_get_utc_datetime());
2495
                $courseItemList = [];
2496
                // Looping results
2497
                for ($i = 0; $i < $sizeof; $i++) {
2498
                    $attempt = $results[$i];
2499
                    $revised = $attempt['revised'];
2500
                    $attemptSessionId = (int) $attempt['session_id'];
2501
                    if (false === $searchAllTeacherCourses) {
2502
                        $courseItemInfo = api_get_course_info();
2503
                        $cidReq = api_get_cidreq(false).'&id_session='.$attemptSessionId;
2504
                    } else {
2505
                        if (isset($courseItemList[$attempt['c_id']])) {
2506
                            $courseItemInfo = $courseItemList[$attempt['c_id']];
2507
                        } else {
2508
                            $courseItemInfo = api_get_course_info_by_id($attempt['c_id']);
2509
                            $courseItemList[$attempt['c_id']] = $courseItemInfo;
2510
                        }
2511
                        $cidReq = 'cidReq='.$courseItemInfo['code'].'&id_session='.$attemptSessionId;
2512
                    }
2513
2514
                    if ('incomplete' === $attempt['completion_status']) {
2515
                        // If the exercise was incomplete, we need to determine
2516
                        // if it is still into the time allowed, or if its
2517
                        // allowed time has expired and it can be closed
2518
                        // (it's "unclosed")
2519
                        $minutes = $attempt['expired_time'];
2520
                        if ($minutes == 0) {
2521
                            // There's no time limit, so obviously the attempt
2522
                            // can still be "ongoing", but the teacher should
2523
                            // be able to choose to close it, so mark it as
2524
                            // "unclosed" instead of "ongoing"
2525
                            $revised = 2;
2526
                        } else {
2527
                            $allowedSeconds = $minutes * 60;
2528
                            $timeAttemptStarted = strtotime($attempt['start_date']);
2529
                            $secondsSinceStart = $timeNow - $timeAttemptStarted;
2530
                            $revised = 3; // mark as "ongoing"
2531
                            if ($secondsSinceStart > $allowedSeconds) {
2532
                                $revised = 2; // mark as "unclosed"
2533
                            }
2534
                        }
2535
                    }
2536
2537
                    if ($from_gradebook && ($is_allowedToEdit)) {
2538
                        if (in_array(
2539
                            $attempt['username'].$attempt['firstname'].$attempt['lastname'],
2540
                            $users_array_id
2541
                        )) {
2542
                            continue;
2543
                        }
2544
                        $users_array_id[] = $attempt['username'].$attempt['firstname'].$attempt['lastname'];
2545
                    }
2546
2547
                    $lp_obj = isset($attempt['orig_lp_id']) &&
2548
                        isset($lp_list[$attempt['orig_lp_id']]) ? $lp_list[$attempt['orig_lp_id']] : null;
2549
                    if (empty($lp_obj)) {
2550
                        // Try to get the old id (id instead of iid)
2551
                        $lpNewId = isset($attempt['orig_lp_id']) &&
2552
                        isset($oldIds[$attempt['orig_lp_id']]) ? $oldIds[$attempt['orig_lp_id']] : null;
2553
                        if ($lpNewId) {
2554
                            $lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
2555
                        }
2556
                    }
2557
                    $lp_name = null;
2558
                    if ($lp_obj) {
2559
                        $url = api_get_path(WEB_CODE_PATH).
2560
                            'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$attempt['orig_lp_id'];
2561
                        $lp_name = Display::url(
2562
                            $lp_obj['lp_name'],
2563
                            $url,
2564
                            ['target' => '_blank']
2565
                        );
2566
                    }
2567
2568
                    // Add all groups by user
2569
                    $group_name_list = '';
2570
                    if ($is_empty_sql_inner_join_tbl_user) {
2571
                        $group_list = GroupManager::get_group_ids(
2572
                            api_get_course_int_id(),
2573
                            $attempt['user_id']
2574
                        );
2575
2576
                        foreach ($group_list as $id) {
2577
                            if (isset($clean_group_list[$id])) {
2578
                                $group_name_list .= $clean_group_list[$id].'<br/>';
2579
                            }
2580
                        }
2581
                        $attempt['group_name'] = $group_name_list;
2582
                    }
2583
2584
                    $attempt['exe_duration'] = !empty($attempt['exe_duration']) ? round($attempt['exe_duration'] / 60) : 0;
2585
                    $id = $attempt['exe_id'];
2586
                    $dt = api_convert_and_format_date($attempt['exe_weighting']);
2587
2588
                    // we filter the results if we have the permission to
2589
                    $result_disabled = 0;
2590
                    if (isset($attempt['results_disabled'])) {
2591
                        $result_disabled = (int) $attempt['results_disabled'];
2592
                    }
2593
                    if ($result_disabled == 0) {
2594
                        $my_res = $attempt['exe_result'];
2595
                        $my_total = $attempt['exe_weighting'];
2596
                        $attempt['start_date'] = api_get_local_time($attempt['start_date']);
2597
                        $attempt['exe_date'] = api_get_local_time($attempt['exe_date']);
2598
2599
                        if (!$attempt['propagate_neg'] && $my_res < 0) {
2600
                            $my_res = 0;
2601
                        }
2602
2603
                        $score = self::show_score(
2604
                            $my_res,
2605
                            $my_total,
2606
                            true,
2607
                            true,
2608
                            false,
2609
                            false,
2610
                            $decimalSeparator,
2611
                            $thousandSeparator,
2612
                            $roundValues
2613
                        );
2614
2615
                        $actions = '<div class="pull-right">';
2616
                        if ($is_allowedToEdit) {
2617
                            if (isset($teacher_id_list)) {
2618
                                if (in_array(
2619
                                    $attempt['exe_user_id'],
2620
                                    $teacher_id_list
2621
                                )) {
2622
                                    $actions .= Display::return_icon('teacher.png', get_lang('Teacher'));
2623
                                }
2624
                            }
2625
                            $revisedLabel = '';
2626
                            switch ($revised) {
2627
                                case 0:
2628
                                    $actions .= "<a href='exercise_show.php?".$cidReq."&action=qualify&id=$id'>".
2629
                                        Display:: return_icon(
2630
                                            'quiz.png',
2631
                                            get_lang('Qualify')
2632
                                        );
2633
                                    $actions .= '</a>';
2634
                                    $revisedLabel = Display::label(
2635
                                        get_lang('NotValidated'),
2636
                                        'info'
2637
                                    );
2638
                                    break;
2639
                                case 1:
2640
                                    $actions .= "<a href='exercise_show.php?".$cidReq."&action=edit&id=$id'>".
2641
                                        Display:: return_icon(
2642
                                            'edit.png',
2643
                                            get_lang('Edit'),
2644
                                            [],
2645
                                            ICON_SIZE_SMALL
2646
                                        );
2647
                                    $actions .= '</a>';
2648
                                    $revisedLabel = Display::label(
2649
                                        get_lang('Validated'),
2650
                                        'success'
2651
                                    );
2652
                                    break;
2653
                                case 2: //finished but not marked as such
2654
                                    $actions .= '<a href="exercise_report.php?'
2655
                                        .$cidReq
2656
                                        .'&exerciseId='.$exercise_id
2657
                                        .'&a=close&id='.$id
2658
                                        .'">'.
2659
                                        Display:: return_icon(
2660
                                            'lock.png',
2661
                                            get_lang('MarkAttemptAsClosed'),
2662
                                            [],
2663
                                            ICON_SIZE_SMALL
2664
                                        );
2665
                                    $actions .= '</a>';
2666
                                    $revisedLabel = Display::label(
2667
                                        get_lang('Unclosed'),
2668
                                        'warning'
2669
                                    );
2670
                                    break;
2671
                                case 3: //still ongoing
2672
                                    $actions .= Display:: return_icon(
2673
                                        'clock.png',
2674
                                        get_lang('AttemptStillOngoingPleaseWait'),
2675
                                        [],
2676
                                        ICON_SIZE_SMALL
2677
                                    );
2678
                                    $actions .= '';
2679
                                    $revisedLabel = Display::label(
2680
                                        get_lang('Ongoing'),
2681
                                        'danger'
2682
                                    );
2683
                                    break;
2684
                            }
2685
2686
                            if ($filter == 2) {
2687
                                $actions .= ' <a href="exercise_history.php?'.$cidReq.'&exe_id='.$id.'">'.
2688
                                    Display:: return_icon(
2689
                                        'history.png',
2690
                                        get_lang('ViewHistoryChange')
2691
                                    ).'</a>';
2692
                            }
2693
2694
                            // Admin can always delete the attempt
2695
                            if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
2696
                                $ip = Tracking::get_ip_from_user_event(
2697
                                    $attempt['exe_user_id'],
2698
                                    api_get_utc_datetime(),
2699
                                    false
2700
                                );
2701
                                $actions .= '<a href="http://www.whatsmyip.org/ip-geo-location/?ip='.$ip.'" target="_blank">'
2702
                                    .Display::return_icon('info.png', $ip)
2703
                                    .'</a>';
2704
2705
                                $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'.
2706
                                    $cidReq.'&'.
2707
                                    http_build_query([
2708
                                        'id' => $id,
2709
                                        'exercise' => $exercise_id,
2710
                                        'user' => $attempt['exe_user_id'],
2711
                                    ]);
2712
                                $actions .= Display::url(
2713
                                    Display::return_icon('reload.png', get_lang('RecalculateResults')),
2714
                                    $recalculateUrl,
2715
                                    [
2716
                                        'data-exercise' => $exercise_id,
2717
                                        'data-user' => $attempt['exe_user_id'],
2718
                                        'data-id' => $id,
2719
                                        'class' => 'exercise-recalculate',
2720
                                    ]
2721
                                );
2722
2723
                                $filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0;
2724
                                $delete_link = '<a
2725
                                    href="exercise_report.php?'.$cidReq.'&filter_by_user='.$filterByUser.'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'"
2726
                                    onclick="javascript:if(!confirm(\''.sprintf(
2727
                                        addslashes(get_lang('DeleteAttempt')),
2728
                                        $attempt['username'],
2729
                                        $dt
2730
                                    ).'\')) return false;">';
2731
                                $delete_link .= Display::return_icon(
2732
                                        'delete.png',
2733
                                        addslashes(get_lang('Delete'))
2734
                                    ).'</a>';
2735
2736
                                if (api_is_drh() && !api_is_platform_admin()) {
2737
                                    $delete_link = null;
2738
                                }
2739
                                if (api_is_session_admin()) {
2740
                                    $delete_link = '';
2741
                                }
2742
                                if ($revised == 3) {
2743
                                    $delete_link = null;
2744
                                }
2745
                                $actions .= $delete_link;
2746
                            }
2747
                        } else {
2748
                            $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.$cidReq.'&id='.$attempt['exe_id'];
2749
                            $attempt_link = Display::url(
2750
                                get_lang('Show'),
2751
                                $attempt_url,
2752
                                [
2753
                                    'class' => 'ajax btn btn-default',
2754
                                    'data-title' => get_lang('Show'),
2755
                                ]
2756
                            );
2757
                            $actions .= $attempt_link;
2758
                        }
2759
                        $actions .= '</div>';
2760
2761
                        if (!empty($userExtraFieldsToAdd)) {
2762
                            foreach ($userExtraFieldsToAdd as $variable) {
2763
                                $extraFieldValue = new ExtraFieldValue('user');
2764
                                $values = $extraFieldValue->get_values_by_handler_and_field_variable(
2765
                                    $attempt['user_id'],
2766
                                    $variable
2767
                                );
2768
                                if (isset($values['value'])) {
2769
                                    $attempt[$variable] = $values['value'];
2770
                                }
2771
                            }
2772
                        }
2773
2774
                        $exeId = $attempt['exe_id'];
2775
                        $attempt['id'] = $exeId;
2776
                        $category_list = [];
2777
                        if ($is_allowedToEdit) {
2778
                            $sessionName = '';
2779
                            $sessionStartAccessDate = '';
2780
                            if (!empty($attemptSessionId)) {
2781
                                $sessionInfo = api_get_session_info($attemptSessionId);
2782
                                if (!empty($sessionInfo)) {
2783
                                    $sessionName = $sessionInfo['name'];
2784
                                    $sessionStartAccessDate = api_get_local_time($sessionInfo['access_start_date']);
2785
                                }
2786
                            }
2787
2788
                            $courseId = $courseItemInfo['real_id'];
2789
2790
                            if ($searchAllTeacherCourses) {
2791
                                $attempt['course'] = $courseItemInfo['title'];
2792
                                $attempt['exercise'] = $attempt['title'];
2793
                            }
2794
2795
                            $objExercise = new Exercise($courseId);
2796
                            if ($showExerciseCategories) {
2797
                                // Getting attempt info
2798
                                $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
2799
                                if (!empty($exercise_stat_info['data_tracking'])) {
2800
                                    $question_list = explode(',', $exercise_stat_info['data_tracking']);
2801
                                    if (!empty($question_list)) {
2802
                                        foreach ($question_list as $questionId) {
2803
                                            $objQuestionTmp = Question::read($questionId, $objExercise->course);
2804
                                            // We're inside *one* question.
2805
                                            // Go through each possible answer for this question.
2806
                                            $result = $objExercise->manage_answer(
2807
                                                $exeId,
2808
                                                $questionId,
2809
                                                null,
2810
                                                'exercise_result',
2811
                                                false,
2812
                                                false,
2813
                                                true,
2814
                                                false,
2815
                                                $objExercise->selectPropagateNeg(),
2816
                                                null,
2817
                                                true
2818
                                            );
2819
2820
                                            $my_total_score = $result['score'];
2821
                                            $my_total_weight = $result['weight'];
2822
2823
                                            // Category report
2824
                                            $category_was_added_for_this_test = false;
2825
                                            if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
2826
                                                if (!isset($category_list[$objQuestionTmp->category]['score'])) {
2827
                                                    $category_list[$objQuestionTmp->category]['score'] = 0;
2828
                                                }
2829
                                                if (!isset($category_list[$objQuestionTmp->category]['total'])) {
2830
                                                    $category_list[$objQuestionTmp->category]['total'] = 0;
2831
                                                }
2832
                                                $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
2833
                                                $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
2834
                                                $category_was_added_for_this_test = true;
2835
                                            }
2836
2837
                                            if (isset($objQuestionTmp->category_list) &&
2838
                                                !empty($objQuestionTmp->category_list)
2839
                                            ) {
2840
                                                foreach ($objQuestionTmp->category_list as $category_id) {
2841
                                                    $category_list[$category_id]['score'] += $my_total_score;
2842
                                                    $category_list[$category_id]['total'] += $my_total_weight;
2843
                                                    $category_was_added_for_this_test = true;
2844
                                                }
2845
                                            }
2846
2847
                                            // No category for this question!
2848
                                            if ($category_was_added_for_this_test == false) {
2849
                                                if (!isset($category_list['none']['score'])) {
2850
                                                    $category_list['none']['score'] = 0;
2851
                                                }
2852
                                                if (!isset($category_list['none']['total'])) {
2853
                                                    $category_list['none']['total'] = 0;
2854
                                                }
2855
2856
                                                $category_list['none']['score'] += $my_total_score;
2857
                                                $category_list['none']['total'] += $my_total_weight;
2858
                                            }
2859
                                        }
2860
                                    }
2861
                                }
2862
                            }
2863
2864
                            foreach ($category_list as $categoryId => $result) {
2865
                                $scoreToDisplay = self::show_score(
2866
                                    $result['score'],
2867
                                    $result['total'],
2868
                                    true,
2869
                                    true,
2870
                                    false,
2871
                                    false,
2872
                                    $decimalSeparator,
2873
                                    $thousandSeparator,
2874
                                    $roundValues
2875
                                );
2876
                                $attempt['category_'.$categoryId] = $scoreToDisplay;
2877
                                $attempt['category_'.$categoryId.'_score_percentage'] = self::show_score(
2878
                                    $result['score'],
2879
                                    $result['total'],
2880
                                    true,
2881
                                    true,
2882
                                    true,
2883
                                    true,
2884
                                    $decimalSeparator,
2885
                                    $thousandSeparator,
2886
                                    $roundValues
2887
                                );
2888
                                $attempt['category_'.$categoryId.'_only_score'] = $result['score'];
2889
                                $attempt['category_'.$categoryId.'_total'] = $result['total'];
2890
                            }
2891
                            $attempt['session'] = $sessionName;
2892
                            $attempt['session_access_start_date'] = $sessionStartAccessDate;
2893
                            $attempt['status'] = $revisedLabel;
2894
                            $attempt['score'] = $score;
2895
                            $attempt['score_percentage'] = self::show_score(
2896
                                $my_res,
2897
                                $my_total,
2898
                                true,
2899
                                true,
2900
                                true,
2901
                                true,
2902
                                $decimalSeparator,
2903
                                $thousandSeparator,
2904
                                $roundValues
2905
                            );
2906
2907
                            if ($roundValues) {
2908
                                $whole = floor($my_res); // 1
2909
                                $fraction = $my_res - $whole; // .25
2910
                                if ($fraction >= 0.5) {
2911
                                    $onlyScore = ceil($my_res);
2912
                                } else {
2913
                                    $onlyScore = round($my_res);
2914
                                }
2915
                            } else {
2916
                                $onlyScore = $scoreDisplay->format_score(
2917
                                    $my_res,
2918
                                    false,
2919
                                    $decimalSeparator,
2920
                                    $thousandSeparator
2921
                                );
2922
                            }
2923
2924
                            $attempt['only_score'] = $onlyScore;
2925
2926
                            if ($roundValues) {
2927
                                $whole = floor($my_total); // 1
2928
                                $fraction = $my_total - $whole; // .25
2929
                                if ($fraction >= 0.5) {
2930
                                    $onlyTotal = ceil($my_total);
2931
                                } else {
2932
                                    $onlyTotal = round($my_total);
2933
                                }
2934
                            } else {
2935
                                $onlyTotal = $scoreDisplay->format_score(
2936
                                    $my_total,
2937
                                    false,
2938
                                    $decimalSeparator,
2939
                                    $thousandSeparator
2940
                                );
2941
                            }
2942
                            $attempt['total'] = $onlyTotal;
2943
                            $attempt['lp'] = $lp_name;
2944
                            $attempt['actions'] = $actions;
2945
                            $listInfo[] = $attempt;
2946
                        } else {
2947
                            $attempt['status'] = $revisedLabel;
2948
                            $attempt['score'] = $score;
2949
                            $attempt['actions'] = $actions;
2950
                            $listInfo[] = $attempt;
2951
                        }
2952
                    }
2953
                }
2954
            }
2955
        } else {
2956
            $hpresults = [];
2957
            $res = Database::query($hpsql);
2958
            if ($res !== false) {
2959
                $i = 0;
2960
                while ($resA = Database::fetch_array($res, 'NUM')) {
2961
                    for ($j = 0; $j < 6; $j++) {
2962
                        $hpresults[$i][$j] = $resA[$j];
2963
                    }
2964
                    $i++;
2965
                }
2966
            }
2967
2968
            // Print HotPotatoes test results.
2969
            if (is_array($hpresults)) {
2970
                for ($i = 0; $i < count($hpresults); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2971
                    $hp_title = GetQuizName($hpresults[$i][3], $documentPath);
2972
                    if ($hp_title == '') {
2973
                        $hp_title = basename($hpresults[$i][3]);
2974
                    }
2975
2976
                    $hp_date = api_get_local_time(
2977
                        $hpresults[$i][6],
2978
                        null,
2979
                        date_default_timezone_get()
2980
                    );
2981
                    $hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2);
2982
                    $hp_result .= '% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')';
2983
2984
                    if ($is_allowedToEdit) {
2985
                        $listInfo[] = [
2986
                            $hpresults[$i][0],
2987
                            $hpresults[$i][1],
2988
                            $hpresults[$i][2],
2989
                            '',
2990
                            $hp_title,
2991
                            '-',
2992
                            $hp_date,
2993
                            $hp_result,
2994
                            '-',
2995
                        ];
2996
                    } else {
2997
                        $listInfo[] = [
2998
                            $hp_title,
2999
                            '-',
3000
                            $hp_date,
3001
                            $hp_result,
3002
                            '-',
3003
                        ];
3004
                    }
3005
                }
3006
            }
3007
        }
3008
3009
        return $listInfo;
3010
    }
3011
3012
    /**
3013
     * @param $score
3014
     * @param $weight
3015
     *
3016
     * @return array
3017
     */
3018
    public static function convertScoreToPlatformSetting($score, $weight)
3019
    {
3020
        $maxNote = api_get_setting('exercise_max_score');
3021
        $minNote = api_get_setting('exercise_min_score');
3022
3023
        if ($maxNote != '' && $minNote != '') {
3024
            if (!empty($weight) && (float) $weight !== (float) 0) {
3025
                $score = $minNote + ($maxNote - $minNote) * $score / $weight;
3026
            } else {
3027
                $score = $minNote;
3028
            }
3029
            $weight = $maxNote;
3030
        }
3031
3032
        return ['score' => $score, 'weight' => $weight];
3033
    }
3034
3035
    /**
3036
     * Converts the score with the exercise_max_note and exercise_min_score
3037
     * the platform settings + formats the results using the float_format function.
3038
     *
3039
     * @param float  $score
3040
     * @param float  $weight
3041
     * @param bool   $show_percentage       show percentage or not
3042
     * @param bool   $use_platform_settings use or not the platform settings
3043
     * @param bool   $show_only_percentage
3044
     * @param bool   $hidePercentageSign    hide "%" sign
3045
     * @param string $decimalSeparator
3046
     * @param string $thousandSeparator
3047
     * @param bool   $roundValues           This option rounds the float values into a int using ceil()
3048
     * @param bool   $removeEmptyDecimals
3049
     *
3050
     * @return string an html with the score modified
3051
     */
3052
    public static function show_score(
3053
        $score,
3054
        $weight,
3055
        $show_percentage = true,
3056
        $use_platform_settings = true,
3057
        $show_only_percentage = false,
3058
        $hidePercentageSign = false,
3059
        $decimalSeparator = '.',
3060
        $thousandSeparator = ',',
3061
        $roundValues = false,
3062
        $removeEmptyDecimals = false
3063
    ) {
3064
        if (is_null($score) && is_null($weight)) {
3065
            return '-';
3066
        }
3067
3068
        $decimalSeparator = empty($decimalSeparator) ? '.' : $decimalSeparator;
3069
        $thousandSeparator = empty($thousandSeparator) ? ',' : $thousandSeparator;
3070
3071
        if ($use_platform_settings) {
3072
            $result = self::convertScoreToPlatformSetting($score, $weight);
3073
            $score = $result['score'];
3074
            $weight = $result['weight'];
3075
        }
3076
3077
        $percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
3078
3079
        // Formats values
3080
        $percentage = float_format($percentage, 1);
3081
        $score = float_format($score, 1);
3082
        $weight = float_format($weight, 1);
3083
3084
        if ($roundValues) {
3085
            $whole = floor($percentage); // 1
3086
            $fraction = $percentage - $whole; // .25
3087
3088
            // Formats values
3089
            if ($fraction >= 0.5) {
3090
                $percentage = ceil($percentage);
3091
            } else {
3092
                $percentage = round($percentage);
3093
            }
3094
3095
            $whole = floor($score); // 1
3096
            $fraction = $score - $whole; // .25
3097
            if ($fraction >= 0.5) {
3098
                $score = ceil($score);
3099
            } else {
3100
                $score = round($score);
3101
            }
3102
3103
            $whole = floor($weight); // 1
3104
            $fraction = $weight - $whole; // .25
3105
            if ($fraction >= 0.5) {
3106
                $weight = ceil($weight);
3107
            } else {
3108
                $weight = round($weight);
3109
            }
3110
        } else {
3111
            // Formats values
3112
            $percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator);
3113
            $score = float_format($score, 1, $decimalSeparator, $thousandSeparator);
3114
            $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
3115
        }
3116
3117
        if ($show_percentage) {
3118
            $percentageSign = ' %';
3119
            if ($hidePercentageSign) {
3120
                $percentageSign = '';
3121
            }
3122
            $html = $percentage."$percentageSign ($score / $weight)";
3123
            if ($show_only_percentage) {
3124
                $html = $percentage.$percentageSign;
3125
            }
3126
        } else {
3127
            if ($removeEmptyDecimals) {
3128
                if (ScoreDisplay::hasEmptyDecimals($weight)) {
3129
                    $weight = round($weight);
3130
                }
3131
            }
3132
            $html = $score.' / '.$weight;
3133
        }
3134
3135
        // Over write score
3136
        $scoreBasedInModel = self::convertScoreToModel($percentage);
3137
        if (!empty($scoreBasedInModel)) {
3138
            $html = $scoreBasedInModel;
3139
        }
3140
3141
        // Ignore other formats and use the configuration['exercise_score_format'] value
3142
        // But also keep the round values settings.
3143
        $format = api_get_configuration_value('exercise_score_format');
3144
        if (!empty($format)) {
3145
            $html = ScoreDisplay::instance()->display_score([$score, $weight], $format);
3146
        }
3147
3148
        return Display::span($html, ['class' => 'score_exercise']);
3149
    }
3150
3151
    /**
3152
     * @param array $model
3153
     * @param float $percentage
3154
     *
3155
     * @return string
3156
     */
3157
    public static function getModelStyle($model, $percentage)
3158
    {
3159
        return '<span class="'.$model['css_class'].'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>';
3160
    }
3161
3162
    /**
3163
     * @param float $percentage value between 0 and 100
3164
     *
3165
     * @return string
3166
     */
3167
    public static function convertScoreToModel($percentage)
3168
    {
3169
        $model = self::getCourseScoreModel();
3170
        if (!empty($model)) {
3171
            $scoreWithGrade = [];
3172
            foreach ($model['score_list'] as $item) {
3173
                if ($percentage >= $item['min'] && $percentage <= $item['max']) {
3174
                    $scoreWithGrade = $item;
3175
                    break;
3176
                }
3177
            }
3178
3179
            if (!empty($scoreWithGrade)) {
3180
                return self::getModelStyle($scoreWithGrade, $percentage);
3181
            }
3182
        }
3183
3184
        return '';
3185
    }
3186
3187
    /**
3188
     * @return array
3189
     */
3190
    public static function getCourseScoreModel()
3191
    {
3192
        $modelList = self::getScoreModels();
3193
        if (empty($modelList)) {
3194
            return [];
3195
        }
3196
3197
        $courseInfo = api_get_course_info();
3198
        if (!empty($courseInfo)) {
3199
            $scoreModelId = api_get_course_setting('score_model_id');
3200
            if (-1 != $scoreModelId) {
3201
                $modelIdList = array_column($modelList['models'], 'id');
3202
                if (in_array($scoreModelId, $modelIdList)) {
3203
                    foreach ($modelList['models'] as $item) {
3204
                        if ($item['id'] == $scoreModelId) {
3205
                            return $item;
3206
                        }
3207
                    }
3208
                }
3209
            }
3210
        }
3211
3212
        return [];
3213
    }
3214
3215
    /**
3216
     * @return array
3217
     */
3218
    public static function getScoreModels()
3219
    {
3220
        return api_get_configuration_value('score_grade_model');
3221
    }
3222
3223
    /**
3224
     * @param float  $score
3225
     * @param float  $weight
3226
     * @param string $passPercentage
3227
     *
3228
     * @return bool
3229
     */
3230
    public static function isSuccessExerciseResult($score, $weight, $passPercentage)
3231
    {
3232
        $percentage = float_format(
3233
            ($score / (0 != $weight ? $weight : 1)) * 100,
3234
            1
3235
        );
3236
        if (isset($passPercentage) && !empty($passPercentage)) {
3237
            if ($percentage >= $passPercentage) {
3238
                return true;
3239
            }
3240
        }
3241
3242
        return false;
3243
    }
3244
3245
    /**
3246
     * @param string $name
3247
     * @param $weight
3248
     * @param $selected
3249
     *
3250
     * @return bool
3251
     */
3252
    public static function addScoreModelInput(
3253
        FormValidator $form,
3254
        $name,
3255
        $weight,
3256
        $selected
3257
    ) {
3258
        $model = self::getCourseScoreModel();
3259
        if (empty($model)) {
3260
            return false;
3261
        }
3262
3263
        /** @var HTML_QuickForm_select $element */
3264
        $element = $form->createElement(
3265
            'select',
3266
            $name,
3267
            get_lang('Qualification'),
3268
            [],
3269
            ['class' => 'exercise_mark_select']
3270
        );
3271
3272
        foreach ($model['score_list'] as $item) {
3273
            $i = api_number_format($item['score_to_qualify'] / 100 * $weight, 2);
3274
            $label = self::getModelStyle($item, $i);
3275
            $attributes = [
3276
                'class' => $item['css_class'],
3277
            ];
3278
            if ($selected == $i) {
3279
                $attributes['selected'] = 'selected';
3280
            }
3281
            $element->addOption($label, $i, $attributes);
3282
        }
3283
        $form->addElement($element);
3284
    }
3285
3286
    /**
3287
     * @return string
3288
     */
3289
    public static function getJsCode()
3290
    {
3291
        // Filling the scores with the right colors.
3292
        $models = self::getCourseScoreModel();
3293
        $cssListToString = '';
3294
        if (!empty($models)) {
3295
            $cssList = array_column($models['score_list'], 'css_class');
3296
            $cssListToString = implode(' ', $cssList);
3297
        }
3298
3299
        if (empty($cssListToString)) {
3300
            return '';
3301
        }
3302
        $js = <<<EOT
3303
3304
        function updateSelect(element) {
3305
            var spanTag = element.parent().find('span.filter-option');
3306
            var value = element.val();
3307
            var selectId = element.attr('id');
3308
            var optionClass = $('#' + selectId + ' option[value="'+value+'"]').attr('class');
3309
            spanTag.removeClass('$cssListToString');
3310
            spanTag.addClass(optionClass);
3311
        }
3312
3313
        $(function() {
3314
            // Loading values
3315
            $('.exercise_mark_select').on('loaded.bs.select', function() {
3316
                updateSelect($(this));
3317
            });
3318
            // On change
3319
            $('.exercise_mark_select').on('changed.bs.select', function() {
3320
                updateSelect($(this));
3321
            });
3322
        });
3323
EOT;
3324
3325
        return $js;
3326
    }
3327
3328
    /**
3329
     * @param float  $score
3330
     * @param float  $weight
3331
     * @param string $pass_percentage
3332
     *
3333
     * @return string
3334
     */
3335
    public static function showSuccessMessage($score, $weight, $pass_percentage)
3336
    {
3337
        $res = '';
3338
        if (self::isPassPercentageEnabled($pass_percentage)) {
3339
            $isSuccess = self::isSuccessExerciseResult(
3340
                $score,
3341
                $weight,
3342
                $pass_percentage
3343
            );
3344
3345
            if ($isSuccess) {
3346
                $html = get_lang('CongratulationsYouPassedTheTest');
3347
                $icon = Display::return_icon(
3348
                    'completed.png',
3349
                    get_lang('Correct'),
3350
                    [],
3351
                    ICON_SIZE_MEDIUM
3352
                );
3353
            } else {
3354
                $html = get_lang('YouDidNotReachTheMinimumScore');
3355
                $icon = Display::return_icon(
3356
                    'warning.png',
3357
                    get_lang('Wrong'),
3358
                    [],
3359
                    ICON_SIZE_MEDIUM
3360
                );
3361
            }
3362
            $html = Display::tag('h4', $html);
3363
            $html .= Display::tag(
3364
                'h5',
3365
                $icon,
3366
                ['style' => 'width:40px; padding:2px 10px 0px 0px']
3367
            );
3368
            $res = $html;
3369
        }
3370
3371
        return $res;
3372
    }
3373
3374
    /**
3375
     * Return true if pass_pourcentage activated (we use the pass pourcentage feature
3376
     * return false if pass_percentage = 0 (we don't use the pass pourcentage feature.
3377
     *
3378
     * @param $value
3379
     *
3380
     * @return bool
3381
     *              In this version, pass_percentage and show_success_message are disabled if
3382
     *              pass_percentage is set to 0
3383
     */
3384
    public static function isPassPercentageEnabled($value)
3385
    {
3386
        return $value > 0;
3387
    }
3388
3389
    /**
3390
     * Converts a numeric value in a percentage example 0.66666 to 66.67 %.
3391
     *
3392
     * @param $value
3393
     *
3394
     * @return float Converted number
3395
     */
3396
    public static function convert_to_percentage($value)
3397
    {
3398
        $return = '-';
3399
        if ($value != '') {
3400
            $return = float_format($value * 100, 1).' %';
3401
        }
3402
3403
        return $return;
3404
    }
3405
3406
    /**
3407
     * Getting all active exercises from a course from a session
3408
     * (if a session_id is provided we will show all the exercises in the course +
3409
     * all exercises in the session).
3410
     *
3411
     * @param array  $course_info
3412
     * @param int    $session_id
3413
     * @param bool   $check_publication_dates
3414
     * @param string $search                  Search exercise name
3415
     * @param bool   $search_all_sessions     Search exercises in all sessions
3416
     * @param   int 0 = only inactive exercises
0 ignored issues
show
Documentation Bug introduced by
The doc comment 0 at position 0 could not be parsed: Unknown type name '0' at position 0 in 0.
Loading history...
3417
     *                  1 = only active exercises,
3418
     *                  2 = all exercises
3419
     *                  3 = active <> -1
3420
     *
3421
     * @return array array with exercise data
3422
     */
3423
    public static function get_all_exercises(
3424
        $course_info = null,
3425
        $session_id = 0,
3426
        $check_publication_dates = false,
3427
        $search = '',
3428
        $search_all_sessions = false,
3429
        $active = 2
3430
    ) {
3431
        $course_id = api_get_course_int_id();
3432
3433
        if (!empty($course_info) && !empty($course_info['real_id'])) {
3434
            $course_id = $course_info['real_id'];
3435
        }
3436
3437
        if ($session_id == -1) {
3438
            $session_id = 0;
3439
        }
3440
3441
        $now = api_get_utc_datetime();
3442
        $timeConditions = '';
3443
        if ($check_publication_dates) {
3444
            // Start and end are set
3445
            $timeConditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' )  OR ";
3446
            // only start is set
3447
            $timeConditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
3448
            // only end is set
3449
            $timeConditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
3450
            // nothing is set
3451
            $timeConditions .= ' (start_time IS NULL AND end_time IS NULL)) ';
3452
        }
3453
3454
        $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
3455
        $needle = !empty($search) ? "%".$search."%" : '';
3456
3457
        // Show courses by active status
3458
        $active_sql = '';
3459
        if ($active == 3) {
3460
            $active_sql = ' active <> -1 AND';
3461
        } else {
3462
            if ($active != 2) {
3463
                $active_sql = sprintf(' active = %d AND', $active);
3464
            }
3465
        }
3466
3467
        if ($search_all_sessions == true) {
3468
            $conditions = [
3469
                'where' => [
3470
                    $active_sql.' c_id = ? '.$needle_where.$timeConditions => [
3471
                        $course_id,
3472
                        $needle,
3473
                    ],
3474
                ],
3475
                'order' => 'title',
3476
            ];
3477
        } else {
3478
            if (empty($session_id)) {
3479
                $conditions = [
3480
                    'where' => [
3481
                        $active_sql.' (session_id = 0 OR session_id IS NULL) AND c_id = ? '.$needle_where.$timeConditions => [
3482
                            $course_id,
3483
                            $needle,
3484
                        ],
3485
                    ],
3486
                    'order' => 'title',
3487
                ];
3488
            } else {
3489
                $conditions = [
3490
                    'where' => [
3491
                        $active_sql.' (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ? '.$needle_where.$timeConditions => [
3492
                            $session_id,
3493
                            $course_id,
3494
                            $needle,
3495
                        ],
3496
                    ],
3497
                    'order' => 'title',
3498
                ];
3499
            }
3500
        }
3501
3502
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3503
3504
        return Database::select('*', $table, $conditions);
3505
    }
3506
3507
    /**
3508
     * Getting all exercises (active only or all)
3509
     * from a course from a session
3510
     * (if a session_id is provided we will show all the exercises in the
3511
     * course + all exercises in the session).
3512
     *
3513
     * @param   array   course data
3514
     * @param   int     session id
3515
     * @param    int        course c_id
3516
     * @param bool $only_active_exercises
3517
     *
3518
     * @return array array with exercise data
3519
     *               modified by Hubert Borderiou
3520
     */
3521
    public static function get_all_exercises_for_course_id(
3522
        $course_info = null,
3523
        $session_id = 0,
3524
        $course_id = 0,
3525
        $only_active_exercises = true
3526
    ) {
3527
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3528
3529
        if ($only_active_exercises) {
3530
            // Only active exercises.
3531
            $sql_active_exercises = "active = 1 AND ";
3532
        } else {
3533
            // Not only active means visible and invisible NOT deleted (-2)
3534
            $sql_active_exercises = "active IN (1, 0) AND ";
3535
        }
3536
3537
        if ($session_id == -1) {
3538
            $session_id = 0;
3539
        }
3540
3541
        $params = [
3542
            $session_id,
3543
            $course_id,
3544
        ];
3545
3546
        if (empty($session_id)) {
3547
            $conditions = [
3548
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL) AND c_id = ?" => [$course_id]],
3549
                'order' => 'title',
3550
            ];
3551
        } else {
3552
            // All exercises
3553
            $conditions = [
3554
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id=?" => $params],
3555
                'order' => 'title',
3556
            ];
3557
        }
3558
3559
        return Database::select('*', $table, $conditions);
3560
    }
3561
3562
    /**
3563
     * Gets the position of the score based in a given score (result/weight)
3564
     * and the exe_id based in the user list
3565
     * (NO Exercises in LPs ).
3566
     *
3567
     * @param float  $my_score      user score to be compared *attention*
3568
     *                              $my_score = score/weight and not just the score
3569
     * @param int    $my_exe_id     exe id of the exercise
3570
     *                              (this is necessary because if 2 students have the same score the one
3571
     *                              with the minor exe_id will have a best position, just to be fair and FIFO)
3572
     * @param int    $exercise_id
3573
     * @param string $course_code
3574
     * @param int    $session_id
3575
     * @param array  $user_list
3576
     * @param bool   $return_string
3577
     *
3578
     * @return int the position of the user between his friends in a course
3579
     *             (or course within a session)
3580
     */
3581
    public static function get_exercise_result_ranking(
3582
        $my_score,
3583
        $my_exe_id,
3584
        $exercise_id,
3585
        $course_code,
3586
        $session_id = 0,
3587
        $user_list = [],
3588
        $return_string = true
3589
    ) {
3590
        //No score given we return
3591
        if (is_null($my_score)) {
3592
            return '-';
3593
        }
3594
        if (empty($user_list)) {
3595
            return '-';
3596
        }
3597
3598
        $best_attempts = [];
3599
        foreach ($user_list as $user_data) {
3600
            $user_id = $user_data['user_id'];
3601
            $best_attempts[$user_id] = self::get_best_attempt_by_user(
3602
                $user_id,
3603
                $exercise_id,
3604
                $course_code,
3605
                $session_id
3606
            );
3607
        }
3608
3609
        if (empty($best_attempts)) {
3610
            return 1;
3611
        } else {
3612
            $position = 1;
3613
            $my_ranking = [];
3614
            foreach ($best_attempts as $user_id => $result) {
3615
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3616
                    $my_ranking[$user_id] = $result['exe_result'] / $result['exe_weighting'];
3617
                } else {
3618
                    $my_ranking[$user_id] = 0;
3619
                }
3620
            }
3621
            //if (!empty($my_ranking)) {
3622
            asort($my_ranking);
3623
            $position = count($my_ranking);
3624
            if (!empty($my_ranking)) {
3625
                foreach ($my_ranking as $user_id => $ranking) {
3626
                    if ($my_score >= $ranking) {
3627
                        if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
3628
                            $exe_id = $best_attempts[$user_id]['exe_id'];
3629
                            if ($my_exe_id < $exe_id) {
3630
                                $position--;
3631
                            }
3632
                        } else {
3633
                            $position--;
3634
                        }
3635
                    }
3636
                }
3637
            }
3638
            //}
3639
            $return_value = [
3640
                'position' => $position,
3641
                'count' => count($my_ranking),
3642
            ];
3643
3644
            if ($return_string) {
3645
                if (!empty($position) && !empty($my_ranking)) {
3646
                    $return_value = $position.'/'.count($my_ranking);
3647
                } else {
3648
                    $return_value = '-';
3649
                }
3650
            }
3651
3652
            return $return_value;
3653
        }
3654
    }
3655
3656
    /**
3657
     * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
3658
     * (NO Exercises in LPs ) old functionality by attempt.
3659
     *
3660
     * @param   float   user score to be compared attention => score/weight
3661
     * @param   int     exe id of the exercise
3662
     * (this is necessary because if 2 students have the same score the one
3663
     * with the minor exe_id will have a best position, just to be fair and FIFO)
3664
     * @param   int     exercise id
3665
     * @param   string  course code
3666
     * @param   int     session id
3667
     * @param bool $return_string
3668
     *
3669
     * @return int the position of the user between his friends in a course (or course within a session)
3670
     */
3671
    public static function get_exercise_result_ranking_by_attempt(
3672
        $my_score,
3673
        $my_exe_id,
3674
        $exercise_id,
3675
        $courseId,
3676
        $session_id = 0,
3677
        $return_string = true
3678
    ) {
3679
        if (empty($session_id)) {
3680
            $session_id = 0;
3681
        }
3682
        if (is_null($my_score)) {
3683
            return '-';
3684
        }
3685
        $user_results = Event::get_all_exercise_results(
3686
            $exercise_id,
3687
            $courseId,
3688
            $session_id,
3689
            false
3690
        );
3691
        $position_data = [];
3692
        if (empty($user_results)) {
3693
            return 1;
3694
        } else {
3695
            $position = 1;
3696
            $my_ranking = [];
3697
            foreach ($user_results as $result) {
3698
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3699
                    $my_ranking[$result['exe_id']] = $result['exe_result'] / $result['exe_weighting'];
3700
                } else {
3701
                    $my_ranking[$result['exe_id']] = 0;
3702
                }
3703
            }
3704
            asort($my_ranking);
3705
            $position = count($my_ranking);
3706
            if (!empty($my_ranking)) {
3707
                foreach ($my_ranking as $exe_id => $ranking) {
3708
                    if ($my_score >= $ranking) {
3709
                        if ($my_score == $ranking) {
3710
                            if ($my_exe_id < $exe_id) {
3711
                                $position--;
3712
                            }
3713
                        } else {
3714
                            $position--;
3715
                        }
3716
                    }
3717
                }
3718
            }
3719
            $return_value = [
3720
                'position' => $position,
3721
                'count' => count($my_ranking),
3722
            ];
3723
3724
            if ($return_string) {
3725
                if (!empty($position) && !empty($my_ranking)) {
3726
                    return $position.'/'.count($my_ranking);
3727
                }
3728
            }
3729
3730
            return $return_value;
3731
        }
3732
    }
3733
3734
    /**
3735
     * Get the best attempt in a exercise (NO Exercises in LPs ).
3736
     *
3737
     * @param int $exercise_id
3738
     * @param int $courseId
3739
     * @param int $session_id
3740
     *
3741
     * @return array
3742
     */
3743
    public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id)
3744
    {
3745
        $user_results = Event::get_all_exercise_results(
3746
            $exercise_id,
3747
            $courseId,
3748
            $session_id,
3749
            false
3750
        );
3751
3752
        $best_score_data = [];
3753
        $best_score = 0;
3754
        if (!empty($user_results)) {
3755
            foreach ($user_results as $result) {
3756
                if (!empty($result['exe_weighting']) &&
3757
                    intval($result['exe_weighting']) != 0
3758
                ) {
3759
                    $score = $result['exe_result'] / $result['exe_weighting'];
3760
                    if ($score >= $best_score) {
3761
                        $best_score = $score;
3762
                        $best_score_data = $result;
3763
                    }
3764
                }
3765
            }
3766
        }
3767
3768
        return $best_score_data;
3769
    }
3770
3771
    /**
3772
     * Get the best score in a exercise (NO Exercises in LPs ).
3773
     *
3774
     * @param int $user_id
3775
     * @param int $exercise_id
3776
     * @param int $courseId
3777
     * @param int $session_id
3778
     *
3779
     * @return array
3780
     */
3781
    public static function get_best_attempt_by_user(
3782
        $user_id,
3783
        $exercise_id,
3784
        $courseId,
3785
        $session_id
3786
    ) {
3787
        $user_results = Event::get_all_exercise_results(
3788
            $exercise_id,
3789
            $courseId,
3790
            $session_id,
3791
            false,
3792
            $user_id
3793
        );
3794
        $best_score_data = [];
3795
        $best_score = 0;
3796
        if (!empty($user_results)) {
3797
            foreach ($user_results as $result) {
3798
                if (!empty($result['exe_weighting']) && (float) $result['exe_weighting'] != 0) {
3799
                    $score = $result['exe_result'] / $result['exe_weighting'];
3800
                    if ($score >= $best_score) {
3801
                        $best_score = $score;
3802
                        $best_score_data = $result;
3803
                    }
3804
                }
3805
            }
3806
        }
3807
3808
        return $best_score_data;
3809
    }
3810
3811
    /**
3812
     * Get average score (NO Exercises in LPs ).
3813
     *
3814
     * @param int $exerciseId
3815
     * @param int $courseId
3816
     * @param int $sessionId
3817
     *
3818
     * @return float Average score
3819
     */
3820
    public static function get_average_score($exerciseId, $courseId, $sessionId, $groupId = 0)
3821
    {
3822
        $user_results = Event::get_all_exercise_results(
3823
            $exerciseId,
3824
            $courseId,
3825
            $sessionId,
3826
            true,
3827
            null,
3828
            $groupId
3829
        );
3830
        $avg_score = 0;
3831
        if (!empty($user_results)) {
3832
            foreach ($user_results as $result) {
3833
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3834
                    $score = $result['exe_result'] / $result['exe_weighting'];
3835
                    $avg_score += $score;
3836
                }
3837
            }
3838
            $avg_score = float_format($avg_score / count($user_results), 1);
3839
        }
3840
3841
        return $avg_score;
3842
    }
3843
3844
    /**
3845
     * Get average quiz score by course (Only exercises not added in a LP).
3846
     *
3847
     * @param int $courseId
3848
     * @param int $sessionId
3849
     *
3850
     * @return float Average score
3851
     */
3852
    public static function get_average_score_by_course($courseId, $sessionId)
3853
    {
3854
        $user_results = Event::get_all_exercise_results_by_course(
3855
            $courseId,
3856
            $sessionId,
3857
            false
3858
        );
3859
        $avg_score = 0;
3860
        if (!empty($user_results)) {
3861
            foreach ($user_results as $result) {
3862
                if (!empty($result['exe_weighting']) && intval(
3863
                        $result['exe_weighting']
3864
                    ) != 0
3865
                ) {
3866
                    $score = $result['exe_result'] / $result['exe_weighting'];
3867
                    $avg_score += $score;
3868
                }
3869
            }
3870
            // We assume that all exe_weighting
3871
            $avg_score = $avg_score / count($user_results);
3872
        }
3873
3874
        return $avg_score;
3875
    }
3876
3877
    /**
3878
     * @param int $user_id
3879
     * @param int $courseId
3880
     * @param int $session_id
3881
     *
3882
     * @return float|int
3883
     */
3884
    public static function get_average_score_by_course_by_user(
3885
        $user_id,
3886
        $courseId,
3887
        $session_id
3888
    ) {
3889
        $user_results = Event::get_all_exercise_results_by_user(
3890
            $user_id,
3891
            $courseId,
3892
            $session_id
3893
        );
3894
        $avg_score = 0;
3895
        if (!empty($user_results)) {
3896
            foreach ($user_results as $result) {
3897
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3898
                    $score = $result['exe_result'] / $result['exe_weighting'];
3899
                    $avg_score += $score;
3900
                }
3901
            }
3902
            // We assume that all exe_weighting
3903
            $avg_score = ($avg_score / count($user_results));
3904
        }
3905
3906
        return $avg_score;
3907
    }
3908
3909
    /**
3910
     * Get average score by score (NO Exercises in LPs ).
3911
     *
3912
     * @param int $exercise_id
3913
     * @param int $courseId
3914
     * @param int $session_id
3915
     * @param int $user_count
3916
     *
3917
     * @return float Best average score
3918
     */
3919
    public static function get_best_average_score_by_exercise(
3920
        $exercise_id,
3921
        $courseId,
3922
        $session_id,
3923
        $user_count
3924
    ) {
3925
        $user_results = Event::get_best_exercise_results_by_user(
3926
            $exercise_id,
3927
            $courseId,
3928
            $session_id
3929
        );
3930
        $avg_score = 0;
3931
        if (!empty($user_results)) {
3932
            foreach ($user_results as $result) {
3933
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3934
                    $score = $result['exe_result'] / $result['exe_weighting'];
3935
                    $avg_score += $score;
3936
                }
3937
            }
3938
            // We asumme that all exe_weighting
3939
            if (!empty($user_count)) {
3940
                $avg_score = float_format($avg_score / $user_count, 1) * 100;
3941
            } else {
3942
                $avg_score = 0;
3943
            }
3944
        }
3945
3946
        return $avg_score;
3947
    }
3948
3949
    /**
3950
     * Get average score by score (NO Exercises in LPs ).
3951
     *
3952
     * @param int $exercise_id
3953
     * @param int $courseId
3954
     * @param int $session_id
3955
     *
3956
     * @return float Best average score
3957
     */
3958
    public static function getBestScoreByExercise(
3959
        $exercise_id,
3960
        $courseId,
3961
        $session_id
3962
    ) {
3963
        $user_results = Event::get_best_exercise_results_by_user(
3964
            $exercise_id,
3965
            $courseId,
3966
            $session_id
3967
        );
3968
        $avg_score = 0;
3969
        if (!empty($user_results)) {
3970
            foreach ($user_results as $result) {
3971
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3972
                    $score = $result['exe_result'] / $result['exe_weighting'];
3973
                    $avg_score += $score;
3974
                }
3975
            }
3976
        }
3977
3978
        return $avg_score;
3979
    }
3980
3981
    /**
3982
     * @param string $course_code
3983
     * @param int    $session_id
3984
     *
3985
     * @return array
3986
     */
3987
    public static function get_exercises_to_be_taken($course_code, $session_id)
3988
    {
3989
        $course_info = api_get_course_info($course_code);
3990
        $exercises = self::get_all_exercises($course_info, $session_id);
3991
        $result = [];
3992
        $now = time() + 15 * 24 * 60 * 60;
3993
        foreach ($exercises as $exercise_item) {
3994
            if (isset($exercise_item['end_time']) &&
3995
                !empty($exercise_item['end_time']) &&
3996
                api_strtotime($exercise_item['end_time'], 'UTC') < $now
3997
            ) {
3998
                $result[] = $exercise_item;
3999
            }
4000
        }
4001
4002
        return $result;
4003
    }
4004
4005
    /**
4006
     * Get student results (only in completed exercises) stats by question.
4007
     *
4008
     * @param int  $question_id
4009
     * @param int  $exercise_id
4010
     * @param int  $courseId
4011
     * @param int  $session_id
4012
     * @param bool $onlyStudent Filter only enrolled students
4013
     *
4014
     * @return array
4015
     */
4016
    public static function get_student_stats_by_question(
4017
        $question_id,
4018
        $exercise_id,
4019
        $courseId,
4020
        $session_id,
4021
        $onlyStudent = false
4022
    ) {
4023
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4024
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4025
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4026
4027
        $question_id = (int) $question_id;
4028
        $exercise_id = (int) $exercise_id;
4029
        $session_id = (int) $session_id;
4030
        $courseId = (int) $courseId;
4031
4032
        $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
4033
                FROM $track_exercises e ";
4034
        if ($onlyStudent) {
4035
            if (empty($session_id)) {
4036
                $courseCondition = "
4037
                    INNER JOIN $courseUser c
4038
                    ON (
4039
                        e.exe_user_id = c.user_id AND
4040
                        e.c_id = c.c_id AND
4041
                        c.status = ".STUDENT."
4042
                        AND relation_type <> 2
4043
                    )";
4044
            } else {
4045
                $sessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4046
                $courseCondition = "
4047
                    INNER JOIN $sessionRelCourse sc
4048
                    ON (
4049
                        e.exe_user_id = sc.user_id AND
4050
                        e.c_id = sc.c_id AND
4051
                        e.session_id = sc.session_id AND
4052
                        sc.status = 0
4053
                    ) ";
4054
            }
4055
            $sql .= $courseCondition;
4056
        }
4057
4058
        $sql .= "
4059
            INNER JOIN $track_attempt a
4060
    		ON (
4061
    		    a.exe_id = e.exe_id AND
4062
    		    e.c_id = a.c_id AND
4063
    		    e.session_id  = a.session_id
4064
            )
4065
    		WHERE
4066
    		    exe_exo_id 	= $exercise_id AND
4067
                a.c_id = $courseId AND
4068
                e.session_id = $session_id AND
4069
                question_id = $question_id AND
4070
                e.status = ''
4071
            LIMIT 1";
4072
        $result = Database::query($sql);
4073
        $return = [];
4074
        if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4075
            $return = Database::fetch_array($result, 'ASSOC');
4076
        }
4077
4078
        return $return;
4079
    }
4080
4081
    /**
4082
     * Get the correct answer count for a fill blanks question.
4083
     *
4084
     * @param int $question_id
4085
     * @param int $exercise_id
4086
     *
4087
     * @return array
4088
     */
4089
    public static function getNumberStudentsFillBlanksAnswerCount(
4090
        $question_id,
4091
        $exercise_id
4092
    ) {
4093
        $listStudentsId = [];
4094
        $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
4095
            api_get_course_id(),
4096
            true
4097
        );
4098
        foreach ($listAllStudentInfo as $i => $listStudentInfo) {
4099
            $listStudentsId[] = $listStudentInfo['user_id'];
4100
        }
4101
4102
        $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
4103
            $exercise_id,
4104
            $question_id,
4105
            $listStudentsId,
4106
            '1970-01-01',
4107
            '3000-01-01'
4108
        );
4109
4110
        $arrayCount = [];
4111
4112
        foreach ($listFillTheBlankResult as $resultCount) {
4113
            foreach ($resultCount as $index => $count) {
4114
                //this is only for declare the array index per answer
4115
                $arrayCount[$index] = 0;
4116
            }
4117
        }
4118
4119
        foreach ($listFillTheBlankResult as $resultCount) {
4120
            foreach ($resultCount as $index => $count) {
4121
                $count = ($count === 0) ? 1 : 0;
4122
                $arrayCount[$index] += $count;
4123
            }
4124
        }
4125
4126
        return $arrayCount;
4127
    }
4128
4129
    /**
4130
     * Get the number of questions with answers.
4131
     *
4132
     * @param int    $question_id
4133
     * @param int    $exercise_id
4134
     * @param string $course_code
4135
     * @param int    $session_id
4136
     * @param string $questionType
4137
     *
4138
     * @return int
4139
     */
4140
    public static function get_number_students_question_with_answer_count(
4141
        $question_id,
4142
        $exercise_id,
4143
        $course_code,
4144
        $session_id,
4145
        $questionType = ''
4146
    ) {
4147
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4148
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4149
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4150
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4151
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4152
4153
        $question_id = intval($question_id);
4154
        $exercise_id = intval($exercise_id);
4155
        $courseId = api_get_course_int_id($course_code);
4156
        $session_id = intval($session_id);
4157
4158
        if ($questionType == FILL_IN_BLANKS) {
4159
            $listStudentsId = [];
4160
            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
4161
                api_get_course_id(),
4162
                true
4163
            );
4164
            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
4165
                $listStudentsId[] = $listStudentInfo['user_id'];
4166
            }
4167
4168
            $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
4169
                $exercise_id,
4170
                $question_id,
4171
                $listStudentsId,
4172
                '1970-01-01',
4173
                '3000-01-01'
4174
            );
4175
4176
            return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
4177
        }
4178
4179
        if (empty($session_id)) {
4180
            $courseCondition = "
4181
            INNER JOIN $courseUser cu
4182
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
4183
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
4184
        } else {
4185
            $courseCondition = "
4186
            INNER JOIN $courseUserSession cu
4187
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
4188
            $courseConditionWhere = " AND cu.status = 0 ";
4189
        }
4190
4191
        $sql = "SELECT DISTINCT exe_user_id
4192
    		FROM $track_exercises e
4193
    		INNER JOIN $track_attempt a
4194
    		ON (
4195
    		    a.exe_id = e.exe_id AND
4196
    		    e.c_id = a.c_id AND
4197
    		    e.session_id  = a.session_id
4198
            )
4199
            INNER JOIN $courseTable c
4200
            ON (c.id = a.c_id)
4201
    		$courseCondition
4202
    		WHERE
4203
    		    exe_exo_id = $exercise_id AND
4204
                a.c_id = $courseId AND
4205
                e.session_id = $session_id AND
4206
                question_id = $question_id AND
4207
                answer <> '0' AND
4208
                e.status = ''
4209
                $courseConditionWhere
4210
            ";
4211
        $result = Database::query($sql);
4212
        $return = 0;
4213
        if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4214
            $return = Database::num_rows($result);
4215
        }
4216
4217
        return $return;
4218
    }
4219
4220
    /**
4221
     * Get number of answers to hotspot questions.
4222
     *
4223
     * @param int    $answer_id
4224
     * @param int    $question_id
4225
     * @param int    $exercise_id
4226
     * @param string $course_code
4227
     * @param int    $session_id
4228
     *
4229
     * @return int
4230
     */
4231
    public static function get_number_students_answer_hotspot_count(
4232
        $answer_id,
4233
        $question_id,
4234
        $exercise_id,
4235
        $course_code,
4236
        $session_id
4237
    ) {
4238
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4239
        $track_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4240
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4241
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4242
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4243
4244
        $question_id = (int) $question_id;
4245
        $answer_id = (int) $answer_id;
4246
        $exercise_id = (int) $exercise_id;
4247
        $course_code = Database::escape_string($course_code);
4248
        $session_id = (int) $session_id;
4249
4250
        if (empty($session_id)) {
4251
            $courseCondition = "
4252
            INNER JOIN $courseUser cu
4253
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
4254
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
4255
        } else {
4256
            $courseCondition = "
4257
            INNER JOIN $courseUserSession cu
4258
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
4259
            $courseConditionWhere = ' AND cu.status = 0 ';
4260
        }
4261
4262
        $sql = "SELECT DISTINCT exe_user_id
4263
    		FROM $track_exercises e
4264
    		INNER JOIN $track_hotspot a
4265
    		ON (a.hotspot_exe_id = e.exe_id)
4266
    		INNER JOIN $courseTable c
4267
    		ON (hotspot_course_code = c.code)
4268
    		$courseCondition
4269
    		WHERE
4270
    		    exe_exo_id              = $exercise_id AND
4271
                a.hotspot_course_code 	= '$course_code' AND
4272
                e.session_id            = $session_id AND
4273
                hotspot_answer_id       = $answer_id AND
4274
                hotspot_question_id     = $question_id AND
4275
                hotspot_correct         =  1 AND
4276
                e.status                = ''
4277
                $courseConditionWhere
4278
            ";
4279
4280
        $result = Database::query($sql);
4281
        $return = 0;
4282
        if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4283
            $return = Database::num_rows($result);
4284
        }
4285
4286
        return $return;
4287
    }
4288
4289
    /**
4290
     * @param int    $answer_id
4291
     * @param int    $question_id
4292
     * @param int    $exercise_id
4293
     * @param int    $courseId
4294
     * @param int    $session_id
4295
     * @param string $question_type
4296
     * @param string $correct_answer
4297
     * @param string $current_answer
4298
     *
4299
     * @return int
4300
     */
4301
    public static function get_number_students_answer_count(
4302
        $answer_id,
4303
        $question_id,
4304
        $exercise_id,
4305
        $courseId,
4306
        $session_id,
4307
        $question_type = null,
4308
        $correct_answer = null,
4309
        $current_answer = null
4310
    ) {
4311
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4312
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4313
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4314
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4315
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4316
4317
        $question_id = (int) $question_id;
4318
        $answer_id = (int) $answer_id;
4319
        $exercise_id = (int) $exercise_id;
4320
        $courseId = (int) $courseId;
4321
        $session_id = (int) $session_id;
4322
4323
        switch ($question_type) {
4324
            case FILL_IN_BLANKS:
4325
                $answer_condition = '';
4326
                $select_condition = ' e.exe_id, answer ';
4327
                break;
4328
            case MATCHING:
4329
            case MATCHING_DRAGGABLE:
4330
            default:
4331
                $answer_condition = " answer = $answer_id AND ";
4332
                $select_condition = ' DISTINCT exe_user_id ';
4333
        }
4334
4335
        if (empty($session_id)) {
4336
            $courseCondition = "
4337
            INNER JOIN $courseUser cu
4338
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
4339
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
4340
        } else {
4341
            $courseCondition = "
4342
            INNER JOIN $courseUserSession cu
4343
            ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
4344
            $courseConditionWhere = ' AND cu.status = 0 ';
4345
        }
4346
4347
        $sql = "SELECT $select_condition
4348
    		FROM $track_exercises e
4349
    		INNER JOIN $track_attempt a
4350
    		ON (
4351
    		    a.exe_id = e.exe_id AND
4352
    		    e.c_id = a.c_id AND
4353
    		    e.session_id  = a.session_id
4354
            )
4355
            INNER JOIN $courseTable c
4356
            ON c.id = a.c_id
4357
    		$courseCondition
4358
    		WHERE
4359
    		    exe_exo_id = $exercise_id AND
4360
                a.c_id = $courseId AND
4361
                e.session_id = $session_id AND
4362
                $answer_condition
4363
                question_id = $question_id AND
4364
                e.status = ''
4365
                $courseConditionWhere
4366
            ";
4367
        $result = Database::query($sql);
4368
        $return = 0;
4369
        if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4370
            $good_answers = 0;
4371
            switch ($question_type) {
4372
                case FILL_IN_BLANKS:
4373
                    while ($row = Database::fetch_array($result, 'ASSOC')) {
4374
                        $fill_blank = self::check_fill_in_blanks(
4375
                            $correct_answer,
4376
                            $row['answer'],
4377
                            $current_answer
4378
                        );
4379
                        if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) {
4380
                            $good_answers++;
4381
                        }
4382
                    }
4383
4384
                    return $good_answers;
4385
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
4386
                case MATCHING:
4387
                case MATCHING_DRAGGABLE:
4388
                default:
4389
                    $return = Database::num_rows($result);
4390
            }
4391
        }
4392
4393
        return $return;
4394
    }
4395
4396
    /**
4397
     * @param array  $answer
4398
     * @param string $user_answer
4399
     *
4400
     * @return array
4401
     */
4402
    public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
4403
    {
4404
        // the question is encoded like this
4405
        // [A] B [C] D [E] F::10,10,10@1
4406
        // number 1 before the "@" means that is a switchable fill in blank question
4407
        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
4408
        // means that is a normal fill blank question
4409
        // first we explode the "::"
4410
        $pre_array = explode('::', $answer);
4411
        // is switchable fill blank or not
4412
        $last = count($pre_array) - 1;
4413
        $is_set_switchable = explode('@', $pre_array[$last]);
4414
        $switchable_answer_set = false;
4415
        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
4416
            $switchable_answer_set = true;
4417
        }
4418
        $answer = '';
4419
        for ($k = 0; $k < $last; $k++) {
4420
            $answer .= $pre_array[$k];
4421
        }
4422
        // splits weightings that are joined with a comma
4423
        $answerWeighting = explode(',', $is_set_switchable[0]);
4424
4425
        // we save the answer because it will be modified
4426
        //$temp = $answer;
4427
        $temp = $answer;
4428
4429
        $answer = '';
4430
        $j = 0;
4431
        //initialise answer tags
4432
        $user_tags = $correct_tags = $real_text = [];
4433
        // the loop will stop at the end of the text
4434
        while (1) {
4435
            // quits the loop if there are no more blanks (detect '[')
4436
            if (($pos = api_strpos($temp, '[')) === false) {
4437
                // adds the end of the text
4438
                $answer = $temp;
4439
                $real_text[] = $answer;
4440
                break; //no more "blanks", quit the loop
4441
            }
4442
            // adds the piece of text that is before the blank
4443
            //and ends with '[' into a general storage array
4444
            $real_text[] = api_substr($temp, 0, $pos + 1);
4445
            $answer .= api_substr($temp, 0, $pos + 1);
4446
            //take the string remaining (after the last "[" we found)
4447
            $temp = api_substr($temp, $pos + 1);
4448
            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4449
            if (($pos = api_strpos($temp, ']')) === false) {
4450
                // adds the end of the text
4451
                $answer .= $temp;
4452
                break;
4453
            }
4454
4455
            $str = $user_answer;
4456
4457
            preg_match_all('#\[([^[]*)\]#', $str, $arr);
4458
            $str = str_replace('\r\n', '', $str);
4459
            $choices = $arr[1];
4460
            $choice = [];
4461
            $check = false;
4462
            $i = 0;
4463
            foreach ($choices as $item) {
4464
                if ($current_answer === $item) {
4465
                    $check = true;
4466
                }
4467
                if ($check) {
4468
                    $choice[] = $item;
4469
                    $i++;
4470
                }
4471
                if ($i == 3) {
4472
                    break;
4473
                }
4474
            }
4475
            $tmp = api_strrpos($choice[$j], ' / ');
4476
4477
            if ($tmp !== false) {
4478
                $choice[$j] = api_substr($choice[$j], 0, $tmp);
4479
            }
4480
4481
            $choice[$j] = trim($choice[$j]);
4482
4483
            //Needed to let characters ' and " to work as part of an answer
4484
            $choice[$j] = stripslashes($choice[$j]);
4485
4486
            $user_tags[] = api_strtolower($choice[$j]);
4487
            //put the contents of the [] answer tag into correct_tags[]
4488
            $correct_tags[] = api_strtolower(api_substr($temp, 0, $pos));
4489
            $j++;
4490
            $temp = api_substr($temp, $pos + 1);
4491
        }
4492
4493
        $answer = '';
4494
        $real_correct_tags = $correct_tags;
4495
        $chosen_list = [];
4496
        $good_answer = [];
4497
4498
        for ($i = 0; $i < count($real_correct_tags); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4499
            if (!$switchable_answer_set) {
4500
                //needed to parse ' and " characters
4501
                $user_tags[$i] = stripslashes($user_tags[$i]);
4502
                if ($correct_tags[$i] == $user_tags[$i]) {
4503
                    $good_answer[$correct_tags[$i]] = 1;
4504
                } elseif (!empty($user_tags[$i])) {
4505
                    $good_answer[$correct_tags[$i]] = 0;
4506
                } else {
4507
                    $good_answer[$correct_tags[$i]] = 0;
4508
                }
4509
            } else {
4510
                // switchable fill in the blanks
4511
                if (in_array($user_tags[$i], $correct_tags)) {
4512
                    $correct_tags = array_diff($correct_tags, $chosen_list);
4513
                    $good_answer[$correct_tags[$i]] = 1;
4514
                } elseif (!empty($user_tags[$i])) {
4515
                    $good_answer[$correct_tags[$i]] = 0;
4516
                } else {
4517
                    $good_answer[$correct_tags[$i]] = 0;
4518
                }
4519
            }
4520
            // adds the correct word, followed by ] to close the blank
4521
            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
4522
            if (isset($real_text[$i + 1])) {
4523
                $answer .= $real_text[$i + 1];
4524
            }
4525
        }
4526
4527
        return $good_answer;
4528
    }
4529
4530
    /**
4531
     * @param int    $exercise_id
4532
     * @param string $course_code
4533
     * @param int    $session_id
4534
     *
4535
     * @return int
4536
     */
4537
    public static function get_number_students_finish_exercise(
4538
        $exercise_id,
4539
        $course_code,
4540
        $session_id
4541
    ) {
4542
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4543
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4544
4545
        $exercise_id = (int) $exercise_id;
4546
        $course_code = Database::escape_string($course_code);
4547
        $session_id = (int) $session_id;
4548
4549
        $sql = "SELECT DISTINCT exe_user_id
4550
                FROM $track_exercises e
4551
                INNER JOIN $track_attempt a
4552
                ON (a.exe_id = e.exe_id)
4553
                WHERE
4554
                    exe_exo_id 	 = $exercise_id AND
4555
                    course_code  = '$course_code' AND
4556
                    e.session_id = $session_id AND
4557
                    status = ''";
4558
        $result = Database::query($sql);
4559
        $return = 0;
4560
        if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4561
            $return = Database::num_rows($result);
4562
        }
4563
4564
        return $return;
4565
    }
4566
4567
    /**
4568
     * Return an HTML select menu with the student groups.
4569
     *
4570
     * @param string $name     is the name and the id of the <select>
4571
     * @param string $default  default value for option
4572
     * @param string $onchange
4573
     *
4574
     * @return string the html code of the <select>
4575
     */
4576
    public static function displayGroupMenu($name, $default, $onchange = "")
4577
    {
4578
        // check the default value of option
4579
        $tabSelected = [$default => " selected='selected' "];
4580
        $res = "";
4581
        $res .= "<select name='$name' id='$name' onchange='".$onchange."' >";
4582
        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
4583
                'AllGroups'
4584
            )." --</option>";
4585
        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
4586
                'NotInAGroup'
4587
            )." -</option>";
4588
        $tabGroups = GroupManager::get_group_list();
4589
        $currentCatId = 0;
4590
        $countGroups = count($tabGroups);
4591
        for ($i = 0; $i < $countGroups; $i++) {
4592
            $tabCategory = GroupManager::get_category_from_group(
4593
                $tabGroups[$i]['iid']
4594
            );
4595
            if ($tabCategory["id"] != $currentCatId) {
4596
                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
4597
                $currentCatId = $tabCategory["id"];
4598
            }
4599
            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".
4600
                $tabGroups[$i]["id"]."'>".
4601
                $tabGroups[$i]["name"].
4602
                "</option>";
4603
        }
4604
        $res .= "</select>";
4605
4606
        return $res;
4607
    }
4608
4609
    /**
4610
     * @param int $exe_id
4611
     */
4612
    public static function create_chat_exercise_session($exe_id)
4613
    {
4614
        if (!isset($_SESSION['current_exercises'])) {
4615
            $_SESSION['current_exercises'] = [];
4616
        }
4617
        $_SESSION['current_exercises'][$exe_id] = true;
4618
    }
4619
4620
    /**
4621
     * @param int $exe_id
4622
     */
4623
    public static function delete_chat_exercise_session($exe_id)
4624
    {
4625
        if (isset($_SESSION['current_exercises'])) {
4626
            $_SESSION['current_exercises'][$exe_id] = false;
4627
        }
4628
    }
4629
4630
    /**
4631
     * Display the exercise results.
4632
     *
4633
     * @param Exercise $objExercise
4634
     * @param int      $exeId
4635
     * @param bool     $save_user_result save users results (true) or just show the results (false)
4636
     * @param string   $remainingMessage
4637
     * @param bool     $allowSignature
4638
     * @param bool     $allowExportPdf
4639
     * @param bool     $isExport
4640
     */
4641
    public static function displayQuestionListByAttempt(
4642
        $objExercise,
4643
        $exeId,
4644
        $save_user_result = false,
4645
        $remainingMessage = '',
4646
        $allowSignature = false,
4647
        $allowExportPdf = false,
4648
        $isExport = false
4649
    ) {
4650
        $origin = api_get_origin();
4651
        $courseId = api_get_course_int_id();
4652
        $courseCode = api_get_course_id();
4653
        $sessionId = api_get_session_id();
4654
4655
        // Getting attempt info
4656
        $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
4657
4658
        // Getting question list
4659
        $question_list = [];
4660
        $studentInfo = [];
4661
        if (!empty($exercise_stat_info['data_tracking'])) {
4662
            $studentInfo = api_get_user_info($exercise_stat_info['exe_user_id']);
4663
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
4664
        } else {
4665
            // Try getting the question list only if save result is off
4666
            if ($save_user_result == false) {
4667
                $question_list = $objExercise->get_validated_question_list();
4668
            }
4669
            if (in_array(
4670
                $objExercise->getFeedbackType(),
4671
                [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4672
            )) {
4673
                $question_list = $objExercise->get_validated_question_list();
4674
            }
4675
        }
4676
4677
        if ($objExercise->getResultAccess()) {
4678
            if ($objExercise->hasResultsAccess($exercise_stat_info) === false) {
4679
                echo Display::return_message(
4680
                    sprintf(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess())
4681
                );
4682
4683
                return false;
4684
            }
4685
4686
            if (!empty($objExercise->getResultAccess())) {
4687
                $url = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id;
4688
                echo $objExercise->returnTimeLeftDiv();
4689
                echo $objExercise->showSimpleTimeControl(
4690
                    $objExercise->getResultAccessTimeDiff($exercise_stat_info),
4691
                    $url
4692
                );
4693
            }
4694
        }
4695
4696
        $counter = 1;
4697
        $total_score = $total_weight = 0;
4698
        $exercise_content = null;
4699
        // Hide results
4700
        $show_results = false;
4701
        $show_only_score = false;
4702
        if (in_array($objExercise->results_disabled,
4703
            [
4704
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4705
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
4706
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4707
            ]
4708
        )) {
4709
            $show_results = true;
4710
        }
4711
4712
        if (in_array(
4713
            $objExercise->results_disabled,
4714
            [
4715
                RESULT_DISABLE_SHOW_SCORE_ONLY,
4716
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
4717
                RESULT_DISABLE_RANKING,
4718
            ]
4719
        )
4720
        ) {
4721
            $show_only_score = true;
4722
        }
4723
4724
        // Not display expected answer, but score, and feedback
4725
        $show_all_but_expected_answer = false;
4726
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
4727
            $objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END
4728
        ) {
4729
            $show_all_but_expected_answer = true;
4730
            $show_results = true;
4731
            $show_only_score = false;
4732
        }
4733
4734
        $showTotalScoreAndUserChoicesInLastAttempt = true;
4735
        $showTotalScore = true;
4736
        $showQuestionScore = true;
4737
        $attemptResult = [];
4738
4739
        if (in_array(
4740
            $objExercise->results_disabled,
4741
            [
4742
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
4743
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
4744
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
4745
            ])
4746
        ) {
4747
            $show_only_score = true;
4748
            $show_results = true;
4749
            $numberAttempts = 0;
4750
            if ($objExercise->attempts > 0) {
4751
                $attempts = Event::getExerciseResultsByUser(
4752
                    api_get_user_id(),
4753
                    $objExercise->id,
4754
                    $courseId,
4755
                    $sessionId,
4756
                    $exercise_stat_info['orig_lp_id'],
4757
                    $exercise_stat_info['orig_lp_item_id'],
4758
                    'desc'
4759
                );
4760
                if ($attempts) {
4761
                    $numberAttempts = count($attempts);
4762
                }
4763
4764
                if ($save_user_result) {
4765
                    $numberAttempts++;
4766
                }
4767
4768
                $showTotalScore = false;
4769
                if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
4770
                    $showTotalScore = true;
4771
                }
4772
                $showTotalScoreAndUserChoicesInLastAttempt = false;
4773
                if ($numberAttempts >= $objExercise->attempts) {
4774
                    $showTotalScore = true;
4775
                    $show_results = true;
4776
                    $show_only_score = false;
4777
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
4778
                }
4779
4780
                if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK) {
4781
                    $showTotalScore = true;
4782
                    $show_results = true;
4783
                    $show_only_score = false;
4784
                    $showTotalScoreAndUserChoicesInLastAttempt = false;
4785
                    if ($numberAttempts >= $objExercise->attempts) {
4786
                        $showTotalScoreAndUserChoicesInLastAttempt = true;
4787
                    }
4788
4789
                    // Check if the current attempt is the last.
4790
                    /*if (false === $save_user_result && !empty($attempts)) {
4791
                        $showTotalScoreAndUserChoicesInLastAttempt = false;
4792
                        $position = 1;
4793
                        foreach ($attempts as $attempt) {
4794
                            if ($exeId == $attempt['exe_id']) {
4795
                                break;
4796
                            }
4797
                            $position++;
4798
                        }
4799
4800
                        if ($position == $objExercise->attempts) {
4801
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4802
                        }
4803
                    }*/
4804
                }
4805
            }
4806
4807
            if ($objExercise->results_disabled ==
4808
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK
4809
            ) {
4810
                $show_only_score = false;
4811
                $show_results = true;
4812
                $show_all_but_expected_answer = false;
4813
                $showTotalScore = false;
4814
                $showQuestionScore = false;
4815
                if ($numberAttempts >= $objExercise->attempts) {
4816
                    $showTotalScore = true;
4817
                    $showQuestionScore = true;
4818
                }
4819
            }
4820
        }
4821
4822
        // When exporting to PDF hide feedback/comment/score show warning in hotspot.
4823
        if ($allowExportPdf && $isExport) {
4824
            $showTotalScore = false;
4825
            $showQuestionScore = false;
4826
            $objExercise->feedback_type = 2;
4827
            $objExercise->hideComment = true;
4828
            $objExercise->hideNoAnswer = true;
4829
            $objExercise->results_disabled = 0;
4830
            $objExercise->hideExpectedAnswer = true;
4831
            $show_results = true;
4832
        }
4833
4834
        if ('embeddable' !== $origin &&
4835
            !empty($exercise_stat_info['exe_user_id']) &&
4836
            !empty($studentInfo)
4837
        ) {
4838
            // Shows exercise header.
4839
            echo $objExercise->showExerciseResultHeader(
4840
                $studentInfo,
4841
                $exercise_stat_info,
4842
                $save_user_result,
4843
                $allowSignature,
4844
                $allowExportPdf
4845
            );
4846
        }
4847
4848
        // Display text when test is finished #4074 and for LP #4227
4849
        $endOfMessage = $objExercise->getTextWhenFinished();
4850
        if (!empty($endOfMessage)) {
4851
            echo Display::div(
4852
                $endOfMessage,
4853
                ['id' => 'quiz_end_message']
4854
            );
4855
        }
4856
4857
        $question_list_answers = [];
4858
        $category_list = [];
4859
        $loadChoiceFromSession = false;
4860
        $fromDatabase = true;
4861
        $exerciseResult = null;
4862
        $exerciseResultCoordinates = null;
4863
        $delineationResults = null;
4864
        if (true === $save_user_result && in_array(
4865
            $objExercise->getFeedbackType(),
4866
            [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4867
        )) {
4868
            $loadChoiceFromSession = true;
4869
            $fromDatabase = false;
4870
            $exerciseResult = Session::read('exerciseResult');
4871
            $exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
4872
            $delineationResults = Session::read('hotspot_delineation_result');
4873
            $delineationResults = isset($delineationResults[$objExercise->id]) ? $delineationResults[$objExercise->id] : null;
4874
        }
4875
4876
        $countPendingQuestions = 0;
4877
        $result = [];
4878
        // Loop over all question to show results for each of them, one by one
4879
        if (!empty($question_list)) {
4880
            foreach ($question_list as $questionId) {
4881
                // Creates a temporary Question object
4882
                $objQuestionTmp = Question::read($questionId, $objExercise->course);
4883
                // This variable came from exercise_submit_modal.php
4884
                ob_start();
4885
                $choice = null;
4886
                $delineationChoice = null;
4887
                if ($loadChoiceFromSession) {
4888
                    $choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : null;
4889
                    $delineationChoice = isset($delineationResults[$questionId]) ? $delineationResults[$questionId] : null;
4890
                }
4891
4892
                // We're inside *one* question. Go through each possible answer for this question
4893
                $result = $objExercise->manage_answer(
4894
                    $exeId,
4895
                    $questionId,
4896
                    $choice,
4897
                    'exercise_result',
4898
                    $exerciseResultCoordinates,
4899
                    $save_user_result,
4900
                    $fromDatabase,
4901
                    $show_results,
4902
                    $objExercise->selectPropagateNeg(),
4903
                    $delineationChoice,
4904
                    $showTotalScoreAndUserChoicesInLastAttempt
4905
                );
4906
4907
                if (empty($result)) {
4908
                    continue;
4909
                }
4910
4911
                $total_score += $result['score'];
4912
                $total_weight += $result['weight'];
4913
4914
                $question_list_answers[] = [
4915
                    'question' => $result['open_question'],
4916
                    'answer' => $result['open_answer'],
4917
                    'answer_type' => $result['answer_type'],
4918
                    'generated_oral_file' => $result['generated_oral_file'],
4919
                ];
4920
4921
                $my_total_score = $result['score'];
4922
                $my_total_weight = $result['weight'];
4923
                $scorePassed = self::scorePassed($my_total_score, $my_total_weight);
4924
4925
                // Category report
4926
                $category_was_added_for_this_test = false;
4927
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
4928
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
4929
                        $category_list[$objQuestionTmp->category]['score'] = 0;
4930
                    }
4931
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
4932
                        $category_list[$objQuestionTmp->category]['total'] = 0;
4933
                    }
4934
                    if (!isset($category_list[$objQuestionTmp->category]['total_questions'])) {
4935
                        $category_list[$objQuestionTmp->category]['total_questions'] = 0;
4936
                    }
4937
                    if (!isset($category_list[$objQuestionTmp->category]['passed'])) {
4938
                        $category_list[$objQuestionTmp->category]['passed'] = 0;
4939
                    }
4940
                    if (!isset($category_list[$objQuestionTmp->category]['wrong'])) {
4941
                        $category_list[$objQuestionTmp->category]['wrong'] = 0;
4942
                    }
4943
                    if (!isset($category_list[$objQuestionTmp->category]['no_answer'])) {
4944
                        $category_list[$objQuestionTmp->category]['no_answer'] = 0;
4945
                    }
4946
4947
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
4948
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
4949
                    if ($scorePassed) {
4950
                        // Only count passed if score is not empty
4951
                        if (!empty($my_total_score)) {
4952
                            $category_list[$objQuestionTmp->category]['passed']++;
4953
                        }
4954
                    } else {
4955
                        if ($result['user_answered']) {
4956
                            $category_list[$objQuestionTmp->category]['wrong']++;
4957
                        } else {
4958
                            $category_list[$objQuestionTmp->category]['no_answer']++;
4959
                        }
4960
                    }
4961
4962
                    $category_list[$objQuestionTmp->category]['total_questions']++;
4963
                    $category_was_added_for_this_test = true;
4964
                }
4965
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
4966
                    foreach ($objQuestionTmp->category_list as $category_id) {
4967
                        $category_list[$category_id]['score'] += $my_total_score;
4968
                        $category_list[$category_id]['total'] += $my_total_weight;
4969
                        $category_was_added_for_this_test = true;
4970
                    }
4971
                }
4972
4973
                // No category for this question!
4974
                if ($category_was_added_for_this_test == false) {
4975
                    if (!isset($category_list['none']['score'])) {
4976
                        $category_list['none']['score'] = 0;
4977
                    }
4978
                    if (!isset($category_list['none']['total'])) {
4979
                        $category_list['none']['total'] = 0;
4980
                    }
4981
4982
                    $category_list['none']['score'] += $my_total_score;
4983
                    $category_list['none']['total'] += $my_total_weight;
4984
                }
4985
4986
                if ($objExercise->selectPropagateNeg() == 0 && $my_total_score < 0) {
4987
                    $my_total_score = 0;
4988
                }
4989
4990
                $comnt = null;
4991
                if ($show_results) {
4992
                    $comnt = Event::get_comments($exeId, $questionId);
4993
                    $teacherAudio = self::getOralFeedbackAudio(
4994
                        $exeId,
4995
                        $questionId,
4996
                        api_get_user_id()
4997
                    );
4998
4999
                    if (!empty($comnt) || $teacherAudio) {
5000
                        echo '<b>'.get_lang('Feedback').'</b>';
5001
                    }
5002
5003
                    if (!empty($comnt)) {
5004
                        echo self::getFeedbackText($comnt);
5005
                    }
5006
5007
                    if ($teacherAudio) {
5008
                        echo $teacherAudio;
5009
                    }
5010
                }
5011
5012
                $calculatedScore = [
5013
                    'result' => self::show_score(
5014
                        $my_total_score,
5015
                        $my_total_weight,
5016
                        false
5017
                    ),
5018
                    'pass' => $scorePassed,
5019
                    'score' => $my_total_score,
5020
                    'weight' => $my_total_weight,
5021
                    'comments' => $comnt,
5022
                    'user_answered' => $result['user_answered'],
5023
                ];
5024
5025
                $score = [];
5026
                if ($show_results) {
5027
                    $score = $calculatedScore;
5028
                }
5029
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
5030
                    $reviewScore = [
5031
                        'score' => $my_total_score,
5032
                        'comments' => Event::get_comments($exeId, $questionId),
5033
                    ];
5034
                    $check = $objQuestionTmp->isQuestionWaitingReview($reviewScore);
5035
                    if (false === $check) {
5036
                        $countPendingQuestions++;
5037
                    }
5038
                }
5039
5040
                $contents = ob_get_clean();
5041
5042
                // Hide correct answers.
5043
                if ($scorePassed && false === $objExercise->disableHideCorrectAnsweredQuestions) {
5044
                    // Skip correct answers.
5045
                    $hide = (int) $objExercise->getPageConfigurationAttribute('hide_correct_answered_questions');
5046
                    if (1 === $hide) {
5047
                        continue;
5048
                    }
5049
                }
5050
5051
                $question_content = '';
5052
                if ($show_results) {
5053
                    $question_content = '<div class="question_row_answer">';
5054
                    if (false === $showQuestionScore) {
5055
                        $score = [];
5056
                    }
5057
5058
                    // Shows question title an description
5059
                    $question_content .= $objQuestionTmp->return_header(
5060
                        $objExercise,
5061
                        $counter,
5062
                        $score
5063
                    );
5064
                }
5065
                $counter++;
5066
                $question_content .= $contents;
5067
                if ($show_results) {
5068
                    $question_content .= '</div>';
5069
                }
5070
5071
                $calculatedScore['question_content'] = $question_content;
5072
                $attemptResult[] = $calculatedScore;
5073
5074
                if ($objExercise->showExpectedChoice()) {
5075
                    $exercise_content .= Display::div(
5076
                        Display::panel($question_content),
5077
                        ['class' => 'question-panel']
5078
                    );
5079
                } else {
5080
                    // $show_all_but_expected_answer should not happen at
5081
                    // the same time as $show_results
5082
                    if ($show_results && !$show_only_score) {
5083
                        $exercise_content .= Display::div(
5084
                            Display::panel($question_content),
5085
                            ['class' => 'question-panel']
5086
                        );
5087
                    }
5088
                }
5089
            }
5090
        }
5091
5092
        $totalScoreText = null;
5093
        $certificateBlock = '';
5094
        if (($show_results || $show_only_score) && $showTotalScore) {
5095
            if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5096
                echo '<h1 style="text-align : center; margin : 20px 0;">'.get_lang('YourResults').'</h1><br />';
5097
            }
5098
            $totalScoreText .= '<div class="question_row_score">';
5099
            if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5100
                $totalScoreText .= self::getQuestionDiagnosisRibbon(
5101
                    $objExercise,
5102
                    $total_score,
5103
                    $total_weight,
5104
                    true
5105
                );
5106
            } else {
5107
                $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
5108
                if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
5109
                    $formula = $pluginEvaluation->getFormulaForExercise($objExercise->selectId());
5110
5111
                    if (!empty($formula)) {
5112
                        $total_score = $pluginEvaluation->getResultWithFormula($exeId, $formula);
5113
                        $total_weight = $pluginEvaluation->getMaxScore();
5114
                    }
5115
                }
5116
5117
                $totalScoreText .= self::getTotalScoreRibbon(
5118
                    $objExercise,
5119
                    $total_score,
5120
                    $total_weight,
5121
                    true,
5122
                    $countPendingQuestions
5123
                );
5124
            }
5125
            $totalScoreText .= '</div>';
5126
5127
            if (!empty($studentInfo)) {
5128
                $certificateBlock = self::generateAndShowCertificateBlock(
5129
                    $total_score,
5130
                    $total_weight,
5131
                    $objExercise,
5132
                    $studentInfo['id'],
5133
                    $courseCode,
5134
                    $sessionId
5135
                );
5136
            }
5137
        }
5138
5139
        if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5140
            $chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults(
5141
                $exeId,
5142
                $objExercise
5143
            );
5144
            echo $chartMultiAnswer;
5145
        }
5146
5147
        if (!empty($category_list) &&
5148
            ($show_results || $show_only_score || RESULT_DISABLE_RADAR == $objExercise->results_disabled)
5149
        ) {
5150
            // Adding total
5151
            $category_list['total'] = [
5152
                'score' => $total_score,
5153
                'total' => $total_weight,
5154
            ];
5155
            echo TestCategory::get_stats_table_by_attempt($objExercise, $category_list);
5156
        }
5157
5158
        if ($show_all_but_expected_answer) {
5159
            $exercise_content .= Display::return_message(get_lang('ExerciseWithFeedbackWithoutCorrectionComment'));
5160
        }
5161
5162
        // Remove audio auto play from questions on results page - refs BT#7939
5163
        $exercise_content = preg_replace(
5164
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
5165
            '',
5166
            $exercise_content
5167
        );
5168
5169
        echo $totalScoreText;
5170
        echo $certificateBlock;
5171
5172
        // Ofaj change BT#11784
5173
        if (api_get_configuration_value('quiz_show_description_on_results_page') &&
5174
            !empty($objExercise->description)
5175
        ) {
5176
            echo Display::div($objExercise->description, ['class' => 'exercise_description']);
5177
        }
5178
5179
        echo $exercise_content;
5180
        if (!$show_only_score) {
5181
            echo $totalScoreText;
5182
        }
5183
5184
        if ($save_user_result) {
5185
            // Tracking of results
5186
            if ($exercise_stat_info) {
5187
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
5188
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
5189
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
5190
5191
                if (api_is_allowed_to_session_edit()) {
5192
                    Event::updateEventExercise(
5193
                        $exercise_stat_info['exe_id'],
5194
                        $objExercise->selectId(),
5195
                        $total_score,
5196
                        $total_weight,
5197
                        $sessionId,
5198
                        $learnpath_id,
5199
                        $learnpath_item_id,
5200
                        $learnpath_item_view_id,
5201
                        $exercise_stat_info['exe_duration'],
5202
                        $question_list
5203
                    );
5204
5205
                    $allowStats = api_get_configuration_value('allow_gradebook_stats');
5206
                    if ($allowStats) {
5207
                        $objExercise->generateStats(
5208
                            $objExercise->selectId(),
5209
                            api_get_course_info(),
5210
                            $sessionId
5211
                        );
5212
                    }
5213
                }
5214
            }
5215
5216
            // Send notification at the end
5217
            if (!api_is_allowed_to_edit(null, true) &&
5218
                !api_is_excluded_user_type()
5219
            ) {
5220
                $objExercise->send_mail_notification_for_exam(
5221
                    'end',
5222
                    $question_list_answers,
5223
                    $origin,
5224
                    $exeId,
5225
                    $total_score,
5226
                    $total_weight
5227
                );
5228
            }
5229
        }
5230
5231
        if (in_array(
5232
            $objExercise->selectResultsDisabled(),
5233
            [RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING]
5234
        )) {
5235
            echo Display::page_header(get_lang('Ranking'), null, 'h4');
5236
            echo self::displayResultsInRanking(
5237
                $objExercise,
5238
                api_get_user_id(),
5239
                $courseId,
5240
                $sessionId
5241
            );
5242
        }
5243
5244
        if (!empty($remainingMessage)) {
5245
            echo Display::return_message($remainingMessage, 'normal', false);
5246
        }
5247
5248
        $failedAnswersCount = 0;
5249
        $wrongQuestionHtml = '';
5250
        $all = '';
5251
        foreach ($attemptResult as $item) {
5252
            if (false === $item['pass']) {
5253
                $failedAnswersCount++;
5254
                $wrongQuestionHtml .= $item['question_content'].'<br />';
5255
            }
5256
            $all .= $item['question_content'].'<br />';
5257
        }
5258
5259
        $passed = self::isPassPercentageAttemptPassed(
5260
            $objExercise,
5261
            $total_score,
5262
            $total_weight
5263
        );
5264
5265
        $percentage = 0;
5266
        if (!empty($total_weight)) {
5267
            $percentage = ($total_score / $total_weight) * 100;
5268
        }
5269
5270
        return [
5271
            'category_list' => $category_list,
5272
            'attempts_result_list' => $attemptResult, // array of results
5273
            'exercise_passed' => $passed, // boolean
5274
            'total_answers_count' => count($attemptResult), // int
5275
            'failed_answers_count' => $failedAnswersCount, // int
5276
            'failed_answers_html' => $wrongQuestionHtml,
5277
            'all_answers_html' => $all,
5278
            'total_score' => $total_score,
5279
            'total_weight' => $total_weight,
5280
            'total_percentage' => $percentage,
5281
            'count_pending_questions' => $countPendingQuestions,
5282
        ];
5283
    }
5284
5285
    /**
5286
     * Display the ranking of results in a exercise.
5287
     *
5288
     * @param Exercise $exercise
5289
     * @param int      $currentUserId
5290
     * @param int      $courseId
5291
     * @param int      $sessionId
5292
     *
5293
     * @return string
5294
     */
5295
    public static function displayResultsInRanking($exercise, $currentUserId, $courseId, $sessionId = 0)
5296
    {
5297
        $exerciseId = $exercise->iId;
5298
        $data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId);
5299
5300
        $table = new HTML_Table(['class' => 'table table-hover table-striped table-bordered']);
5301
        $table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']);
5302
        $table->setHeaderContents(0, 1, get_lang('Username'));
5303
        $table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']);
5304
        $table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']);
5305
5306
        foreach ($data as $r => $item) {
5307
            if (!isset($item[1])) {
5308
                continue;
5309
            }
5310
            $selected = $item[1]->getId() == $currentUserId;
5311
5312
            foreach ($item as $c => $value) {
5313
                $table->setCellContents($r + 1, $c, $value);
5314
5315
                $attrClass = '';
5316
5317
                if (in_array($c, [0, 2])) {
5318
                    $attrClass = 'text-right';
5319
                } elseif (3 == $c) {
5320
                    $attrClass = 'text-center';
5321
                }
5322
5323
                if ($selected) {
5324
                    $attrClass .= ' warning';
5325
                }
5326
5327
                $table->setCellAttributes($r + 1, $c, ['class' => $attrClass]);
5328
            }
5329
        }
5330
5331
        return $table->toHtml();
5332
    }
5333
5334
    /**
5335
     * Get the ranking for results in a exercise.
5336
     * Function used internally by ExerciseLib::displayResultsInRanking.
5337
     *
5338
     * @param int $exerciseId
5339
     * @param int $courseId
5340
     * @param int $sessionId
5341
     *
5342
     * @return array
5343
     */
5344
    public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0)
5345
    {
5346
        $em = Database::getManager();
5347
5348
        $dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId';
5349
        $dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId');
5350
5351
        $result = $em
5352
            ->createQuery($dql)
5353
            ->setParameters(['id' => $exerciseId, 'cId' => $courseId])
5354
            ->getScalarResult();
5355
5356
        $data = [];
5357
        /** @var TrackEExercises $item */
5358
        foreach ($result as $item) {
5359
            $data[] = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId);
5360
        }
5361
5362
        usort(
5363
            $data,
5364
            function ($a, $b) {
5365
                if ($a['exe_result'] != $b['exe_result']) {
5366
                    return $a['exe_result'] > $b['exe_result'] ? -1 : 1;
5367
                }
5368
5369
                if ($a['exe_date'] != $b['exe_date']) {
5370
                    return $a['exe_date'] < $b['exe_date'] ? -1 : 1;
5371
                }
5372
5373
                return 0;
5374
            }
5375
        );
5376
5377
        // flags to display the same position in case of tie
5378
        $lastScore = $data[0]['exe_result'];
5379
        $position = 1;
5380
        $data = array_map(
5381
            function ($item) use (&$lastScore, &$position) {
5382
                if ($item['exe_result'] < $lastScore) {
5383
                    $position++;
5384
                }
5385
5386
                $lastScore = $item['exe_result'];
5387
5388
                return [
5389
                    $position,
5390
                    api_get_user_entity($item['exe_user_id']),
5391
                    self::show_score($item['exe_result'], $item['exe_weighting'], true, true, true),
5392
                    api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT),
5393
                ];
5394
            },
5395
            $data
5396
        );
5397
5398
        return $data;
5399
    }
5400
5401
    /**
5402
     * Get a special ribbon on top of "degree of certainty" questions (
5403
     * variation from getTotalScoreRibbon() for other question types).
5404
     *
5405
     * @param Exercise $objExercise
5406
     * @param float    $score
5407
     * @param float    $weight
5408
     * @param bool     $checkPassPercentage
5409
     *
5410
     * @return string
5411
     */
5412
    public static function getQuestionDiagnosisRibbon($objExercise, $score, $weight, $checkPassPercentage = false)
5413
    {
5414
        $displayChartDegree = true;
5415
        $ribbon = $displayChartDegree ? '<div class="ribbon">' : '';
5416
5417
        if ($checkPassPercentage) {
5418
            $passPercentage = $objExercise->selectPassPercentage();
5419
            $isSuccess = self::isSuccessExerciseResult($score, $weight, $passPercentage);
5420
            // Color the final test score if pass_percentage activated
5421
            $ribbonTotalSuccessOrError = '';
5422
            if (self::isPassPercentageEnabled($passPercentage)) {
5423
                if ($isSuccess) {
5424
                    $ribbonTotalSuccessOrError = ' ribbon-total-success';
5425
                } else {
5426
                    $ribbonTotalSuccessOrError = ' ribbon-total-error';
5427
                }
5428
            }
5429
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total '.$ribbonTotalSuccessOrError.'">' : '';
5430
        } else {
5431
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total">' : '';
5432
        }
5433
5434
        if ($displayChartDegree) {
5435
            $ribbon .= '<h3>'.get_lang('YourTotalScore').':&nbsp;';
5436
            $ribbon .= self::show_score($score, $weight, false, true);
5437
            $ribbon .= '</h3>';
5438
            $ribbon .= '</div>';
5439
        }
5440
5441
        if ($checkPassPercentage) {
5442
            $ribbon .= self::showSuccessMessage(
5443
                $score,
5444
                $weight,
5445
                $objExercise->selectPassPercentage()
5446
            );
5447
        }
5448
5449
        $ribbon .= $displayChartDegree ? '</div>' : '';
5450
5451
        return $ribbon;
5452
    }
5453
5454
    public static function isPassPercentageAttemptPassed($objExercise, $score, $weight)
5455
    {
5456
        $passPercentage = $objExercise->selectPassPercentage();
5457
5458
        return self::isSuccessExerciseResult($score, $weight, $passPercentage);
5459
    }
5460
5461
    /**
5462
     * @param float $score
5463
     * @param float $weight
5464
     * @param bool  $checkPassPercentage
5465
     * @param int   $countPendingQuestions
5466
     *
5467
     * @return string
5468
     */
5469
    public static function getTotalScoreRibbon(
5470
        Exercise $objExercise,
5471
        $score,
5472
        $weight,
5473
        $checkPassPercentage = false,
5474
        $countPendingQuestions = 0
5475
    ) {
5476
        $hide = (int) $objExercise->getPageConfigurationAttribute('hide_total_score');
5477
        if (1 === $hide) {
5478
            return '';
5479
        }
5480
5481
        $passPercentage = $objExercise->selectPassPercentage();
5482
        $ribbon = '<div class="title-score">';
5483
        if ($checkPassPercentage) {
5484
            $isSuccess = self::isSuccessExerciseResult(
5485
                $score,
5486
                $weight,
5487
                $passPercentage
5488
            );
5489
            // Color the final test score if pass_percentage activated
5490
            $class = '';
5491
            if (self::isPassPercentageEnabled($passPercentage)) {
5492
                if ($isSuccess) {
5493
                    $class = ' ribbon-total-success';
5494
                } else {
5495
                    $class = ' ribbon-total-error';
5496
                }
5497
            }
5498
            $ribbon .= '<div class="total '.$class.'">';
5499
        } else {
5500
            $ribbon .= '<div class="total">';
5501
        }
5502
        $ribbon .= '<h3>'.get_lang('YourTotalScore').':&nbsp;';
5503
        $ribbon .= self::show_score($score, $weight, false, true);
5504
        $ribbon .= '</h3>';
5505
        $ribbon .= '</div>';
5506
        if ($checkPassPercentage) {
5507
            $ribbon .= self::showSuccessMessage(
5508
                $score,
5509
                $weight,
5510
                $passPercentage
5511
            );
5512
        }
5513
        $ribbon .= '</div>';
5514
5515
        if (!empty($countPendingQuestions)) {
5516
            $ribbon .= '<br />';
5517
            $ribbon .= Display::return_message(
5518
                sprintf(
5519
                    get_lang('TempScoreXQuestionsNotCorrectedYet'),
5520
                    $countPendingQuestions
5521
                ),
5522
                'warning'
5523
            );
5524
        }
5525
5526
        return $ribbon;
5527
    }
5528
5529
    /**
5530
     * @param int $countLetter
5531
     *
5532
     * @return mixed
5533
     */
5534
    public static function detectInputAppropriateClass($countLetter)
5535
    {
5536
        $limits = [
5537
            0 => 'input-mini',
5538
            10 => 'input-mini',
5539
            15 => 'input-medium',
5540
            20 => 'input-xlarge',
5541
            40 => 'input-xlarge',
5542
            60 => 'input-xxlarge',
5543
            100 => 'input-xxlarge',
5544
            200 => 'input-xxlarge',
5545
        ];
5546
5547
        foreach ($limits as $size => $item) {
5548
            if ($countLetter <= $size) {
5549
                return $item;
5550
            }
5551
        }
5552
5553
        return $limits[0];
5554
    }
5555
5556
    /**
5557
     * @param int    $senderId
5558
     * @param array  $course_info
5559
     * @param string $test
5560
     * @param string $url
5561
     *
5562
     * @return string
5563
     */
5564
    public static function getEmailNotification($senderId, $course_info, $test, $url)
5565
    {
5566
        $teacher_info = api_get_user_info($senderId);
5567
        $from_name = api_get_person_name(
5568
            $teacher_info['firstname'],
5569
            $teacher_info['lastname'],
5570
            null,
5571
            PERSON_NAME_EMAIL_ADDRESS
5572
        );
5573
5574
        $view = new Template('', false, false, false, false, false, false);
5575
        $view->assign('course_title', Security::remove_XSS($course_info['name']));
5576
        $view->assign('test_title', Security::remove_XSS($test));
5577
        $view->assign('url', $url);
5578
        $view->assign('teacher_name', $from_name);
5579
        $template = $view->get_template('mail/exercise_result_alert_body.tpl');
5580
5581
        return $view->fetch($template);
5582
    }
5583
5584
    /**
5585
     * @return string
5586
     */
5587
    public static function getNotCorrectedYetText()
5588
    {
5589
        return Display::return_message(get_lang('notCorrectedYet'), 'warning');
5590
    }
5591
5592
    /**
5593
     * @param string $message
5594
     *
5595
     * @return string
5596
     */
5597
    public static function getFeedbackText($message)
5598
    {
5599
        return Display::return_message($message, 'warning', false);
5600
    }
5601
5602
    /**
5603
     * Get the recorder audio component for save a teacher audio feedback.
5604
     *
5605
     * @param Template $template
5606
     * @param int      $attemptId
5607
     * @param int      $questionId
5608
     * @param int      $userId
5609
     *
5610
     * @return string
5611
     */
5612
    public static function getOralFeedbackForm($template, $attemptId, $questionId, $userId)
5613
    {
5614
        $template->assign('user_id', $userId);
5615
        $template->assign('question_id', $questionId);
5616
        $template->assign('directory', "/../exercises/teacher_audio/$attemptId/");
5617
        $template->assign('file_name', "{$questionId}_{$userId}");
5618
5619
        return $template->fetch($template->get_template('exercise/oral_expression.tpl'));
5620
    }
5621
5622
    /**
5623
     * Get the audio componen for a teacher audio feedback.
5624
     *
5625
     * @param int $attemptId
5626
     * @param int $questionId
5627
     * @param int $userId
5628
     *
5629
     * @return string
5630
     */
5631
    public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
5632
    {
5633
        $courseInfo = api_get_course_info();
5634
        $sessionId = api_get_session_id();
5635
        $groupId = api_get_group_id();
5636
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
5637
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
5638
        $fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId);
5639
        $filePath = null;
5640
5641
        $relFilePath = "/exercises/teacher_audio/$attemptId/$fileName";
5642
5643
        if (file_exists($sysCourseDir.$relFilePath.'.ogg')) {
5644
            $filePath = $webCourseDir.$relFilePath.'.ogg';
5645
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) {
5646
            $filePath = $webCourseDir.$relFilePath.'.wav.wav';
5647
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) {
5648
            $filePath = $webCourseDir.$relFilePath.'.wav';
5649
        }
5650
5651
        if (!$filePath) {
5652
            return '';
5653
        }
5654
5655
        return Display::tag(
5656
            'audio',
5657
            null,
5658
            ['src' => $filePath]
5659
        );
5660
    }
5661
5662
    /**
5663
     * @return array
5664
     */
5665
    public static function getNotificationSettings()
5666
    {
5667
        $emailAlerts = [
5668
            2 => get_lang('SendEmailToTeacherWhenStudentStartQuiz'),
5669
            1 => get_lang('SendEmailToTeacherWhenStudentEndQuiz'), // default
5670
            3 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOpenQuestion'),
5671
            4 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOralQuestion'),
5672
        ];
5673
5674
        return $emailAlerts;
5675
    }
5676
5677
    /**
5678
     * Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
5679
     *
5680
     * @param int $exerciseId
5681
     * @param int $iconSize
5682
     *
5683
     * @return string
5684
     */
5685
    public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
5686
    {
5687
        $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
5688
        $actions = [];
5689
5690
        foreach ($additionalActions as $additionalAction) {
5691
            $actions[] = call_user_func(
5692
                $additionalAction,
5693
                $exerciseId,
5694
                $iconSize
5695
            );
5696
        }
5697
5698
        return implode(PHP_EOL, $actions);
5699
    }
5700
5701
    /**
5702
     * @param int $userId
5703
     * @param int $courseId
5704
     * @param int $sessionId
5705
     *
5706
     * @throws \Doctrine\ORM\Query\QueryException
5707
     *
5708
     * @return int
5709
     */
5710
    public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
5711
    {
5712
        $em = Database::getManager();
5713
5714
        $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
5715
5716
        $result = $em
5717
            ->createQuery('
5718
                SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
5719
                WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
5720
                    AND ea.tms > :time
5721
            ')
5722
            ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
5723
            ->getSingleScalarResult();
5724
5725
        return $result;
5726
    }
5727
5728
    /**
5729
     * @param int $userId
5730
     * @param int $numberOfQuestions
5731
     * @param int $courseId
5732
     * @param int $sessionId
5733
     *
5734
     * @throws \Doctrine\ORM\Query\QueryException
5735
     *
5736
     * @return bool
5737
     */
5738
    public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
5739
    {
5740
        $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
5741
5742
        if ($questionsLimitPerDay <= 0) {
5743
            return false;
5744
        }
5745
5746
        $midnightTime = ChamiloApi::getServerMidnightTime();
5747
5748
        $answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime(
5749
            $midnightTime,
5750
            $userId,
5751
            $courseId,
5752
            $sessionId
5753
        );
5754
5755
        return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
5756
    }
5757
5758
    /**
5759
     * Check if an exercise complies with the requirements to be embedded in the mobile app or a video.
5760
     * By making sure it is set on one question per page and it only contains unique-answer or multiple-answer questions
5761
     * or unique-answer image. And that the exam does not have immediate feedback.
5762
     *
5763
     * @param array $exercise Exercise info
5764
     *
5765
     * @throws \Doctrine\ORM\Query\QueryException
5766
     *
5767
     * @return bool
5768
     */
5769
    public static function isQuizEmbeddable(array $exercise)
5770
    {
5771
        $em = Database::getManager();
5772
5773
        if (ONE_PER_PAGE != $exercise['type'] ||
5774
            in_array($exercise['feedback_type'], [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])
5775
        ) {
5776
            return false;
5777
        }
5778
5779
        $countAll = $em
5780
            ->createQuery('SELECT COUNT(qq)
5781
                FROM ChamiloCourseBundle:CQuizQuestion qq
5782
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5783
                   WITH qq.iid = qrq.questionId
5784
                WHERE qrq.exerciceId = :id'
5785
            )
5786
            ->setParameter('id', $exercise['iid'])
5787
            ->getSingleScalarResult();
5788
5789
        $countOfAllowed = $em
5790
            ->createQuery('SELECT COUNT(qq)
5791
                FROM ChamiloCourseBundle:CQuizQuestion qq
5792
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5793
                   WITH qq.iid = qrq.questionId
5794
                WHERE qrq.exerciceId = :id AND qq.type IN (:types)'
5795
            )
5796
            ->setParameters(
5797
                [
5798
                    'id' => $exercise['iid'],
5799
                    'types' => [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE],
5800
                ]
5801
            )
5802
            ->getSingleScalarResult();
5803
5804
        return $countAll === $countOfAllowed;
5805
    }
5806
5807
    /**
5808
     * Generate a certificate linked to current quiz and.
5809
     * Return the HTML block with links to download and view the certificate.
5810
     *
5811
     * @param float  $totalScore
5812
     * @param float  $totalWeight
5813
     * @param int    $studentId
5814
     * @param string $courseCode
5815
     * @param int    $sessionId
5816
     *
5817
     * @return string
5818
     */
5819
    public static function generateAndShowCertificateBlock(
5820
        $totalScore,
5821
        $totalWeight,
5822
        Exercise $objExercise,
5823
        $studentId,
5824
        $courseCode,
5825
        $sessionId = 0
5826
    ) {
5827
        if (!api_get_configuration_value('quiz_generate_certificate_ending') ||
5828
            !self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage())
5829
        ) {
5830
            return '';
5831
        }
5832
5833
        /** @var Category $category */
5834
        $category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
5835
5836
        if (empty($category)) {
5837
            return '';
5838
        }
5839
5840
        /** @var Category $category */
5841
        $category = $category[0];
5842
        $categoryId = $category->get_id();
5843
        $link = LinkFactory::load(
5844
            null,
5845
            null,
5846
            $objExercise->selectId(),
5847
            null,
5848
            $courseCode,
5849
            $categoryId
5850
        );
5851
5852
        if (empty($link)) {
5853
            return '';
5854
        }
5855
5856
        $resourceDeletedMessage = $category->show_message_resource_delete($courseCode);
5857
5858
        if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) {
5859
            return '';
5860
        }
5861
5862
        $certificate = Category::generateUserCertificate($categoryId, $studentId);
5863
5864
        if (!is_array($certificate)) {
5865
            return '';
5866
        }
5867
5868
        return Category::getDownloadCertificateBlock($certificate);
5869
    }
5870
5871
    /**
5872
     * @param int $exerciseId
5873
     */
5874
    public static function getExerciseTitleById($exerciseId)
5875
    {
5876
        $em = Database::getManager();
5877
5878
        return $em
5879
            ->createQuery('SELECT cq.title
5880
                FROM ChamiloCourseBundle:CQuiz cq
5881
                WHERE cq.iid = :iid'
5882
            )
5883
            ->setParameter('iid', $exerciseId)
5884
            ->getSingleScalarResult();
5885
    }
5886
5887
    /**
5888
     * @param int $exeId      ID from track_e_exercises
5889
     * @param int $userId     User ID
5890
     * @param int $exerciseId Exercise ID
5891
     * @param int $courseId   Optional. Coure ID.
5892
     *
5893
     * @return TrackEExercises|null
5894
     */
5895
    public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0)
5896
    {
5897
        if (empty($userId) || empty($exerciseId)) {
5898
            return null;
5899
        }
5900
5901
        $em = Database::getManager();
5902
        /** @var TrackEExercises $trackedExercise */
5903
        $trackedExercise = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId);
5904
5905
        if (empty($trackedExercise)) {
5906
            return null;
5907
        }
5908
5909
        if ($trackedExercise->getExeUserId() != $userId ||
5910
            $trackedExercise->getExeExoId() != $exerciseId
5911
        ) {
5912
            return null;
5913
        }
5914
5915
        $questionList = $trackedExercise->getDataTracking();
5916
5917
        if (empty($questionList)) {
5918
            return null;
5919
        }
5920
5921
        $questionList = explode(',', $questionList);
5922
5923
        $exercise = new Exercise($courseId);
5924
        $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : [];
5925
5926
        if ($exercise->read($exerciseId) === false) {
5927
            return null;
5928
        }
5929
5930
        $totalScore = 0;
5931
        $totalWeight = 0;
5932
5933
        $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
5934
5935
        $formula = 'true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)
5936
            ? $pluginEvaluation->getFormulaForExercise($exerciseId)
5937
            : 0;
5938
5939
        if (empty($formula)) {
5940
            foreach ($questionList as $questionId) {
5941
                $question = Question::read($questionId, $courseInfo);
5942
5943
                if (false === $question) {
5944
                    continue;
5945
                }
5946
5947
                $totalWeight += $question->selectWeighting();
5948
5949
                // We're inside *one* question. Go through each possible answer for this question
5950
                $result = $exercise->manage_answer(
5951
                    $exeId,
5952
                    $questionId,
5953
                    [],
5954
                    'exercise_result',
5955
                    [],
5956
                    false,
5957
                    true,
5958
                    false,
5959
                    $exercise->selectPropagateNeg(),
5960
                    [],
5961
                    [],
5962
                    true
5963
                );
5964
5965
                //  Adding the new score.
5966
                $totalScore += $result['score'];
5967
            }
5968
        } else {
5969
            $totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula);
5970
            $totalWeight = $pluginEvaluation->getMaxScore();
5971
        }
5972
5973
        $trackedExercise
5974
            ->setExeResult($totalScore)
5975
            ->setExeWeighting($totalWeight);
5976
5977
        $em->persist($trackedExercise);
5978
        $em->flush();
5979
5980
        return $trackedExercise;
5981
    }
5982
5983
    public static function getTotalQuestionAnswered($courseId, $exerciseId, $questionId, $sessionId = 0, $groups = [], $users = [])
5984
    {
5985
        $courseId = (int) $courseId;
5986
        $exerciseId = (int) $exerciseId;
5987
        $questionId = (int) $questionId;
5988
        $sessionId = (int) $sessionId;
5989
5990
        $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
5991
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5992
5993
        $userCondition = '';
5994
        $allUsers = [];
5995
        if (!empty($groups)) {
5996
            foreach ($groups as $groupId) {
5997
                $groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId);
5998
                if (!empty($groupUsers)) {
5999
                    $allUsers = array_merge($allUsers, $groupUsers);
6000
                }
6001
            }
6002
        }
6003
6004
        if (!empty($users)) {
6005
            $allUsers = array_merge($allUsers, $users);
6006
        }
6007
6008
        if (!empty($allUsers)) {
6009
            $allUsers = array_map('intval', $allUsers);
6010
            $usersToString = implode("', '", $allUsers);
6011
            $userCondition = " AND user_id IN ('$usersToString') ";
6012
        }
6013
6014
        $sessionCondition = '';
6015
        if (!empty($sessionId)) {
6016
            $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
6017
        }
6018
6019
        $sql = "SELECT count(te.exe_id) total
6020
                FROM $attemptTable t
6021
                INNER JOIN $trackTable te
6022
                ON (te.c_id = t.c_id AND t.exe_id = te.exe_id)
6023
                WHERE
6024
                    t.c_id = $courseId AND
6025
                    exe_exo_id = $exerciseId AND
6026
                    t.question_id = $questionId AND
6027
                    status != 'incomplete'
6028
                    $sessionCondition
6029
                    $userCondition
6030
        ";
6031
        $queryTotal = Database::query($sql);
6032
        $totalRow = Database::fetch_array($queryTotal, 'ASSOC');
6033
        $total = 0;
6034
        if ($totalRow) {
6035
            $total = (int) $totalRow['total'];
6036
        }
6037
6038
        return $total;
6039
    }
6040
6041
    public static function getWrongQuestionResults($courseId, $exerciseId, $sessionId = 0, $groups = [], $users = [], $limit = 10)
6042
    {
6043
        $courseId = (int) $courseId;
6044
        $exerciseId = (int) $exerciseId;
6045
        $limit = (int) $limit;
6046
6047
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
6048
        $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
6049
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6050
6051
        $sessionCondition = '';
6052
        if (!empty($sessionId)) {
6053
            $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
6054
        }
6055
6056
        $userCondition = '';
6057
        $allUsers = [];
6058
        if (!empty($groups)) {
6059
            foreach ($groups as $groupId) {
6060
                $groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId);
6061
                if (!empty($groupUsers)) {
6062
                    $allUsers = array_merge($allUsers, $groupUsers);
6063
                }
6064
            }
6065
        }
6066
6067
        if (!empty($users)) {
6068
            $allUsers = array_merge($allUsers, $users);
6069
        }
6070
6071
        if (!empty($allUsers)) {
6072
            $allUsers = array_map('intval', $allUsers);
6073
            $usersToString = implode("', '", $allUsers);
6074
            $userCondition .= " AND user_id IN ('$usersToString') ";
6075
        }
6076
6077
        $sql = "SELECT q.question, question_id, count(q.iid) count
6078
                FROM $attemptTable t
6079
                INNER JOIN $questionTable q
6080
                ON (q.c_id = t.c_id AND q.id = t.question_id)
6081
                INNER JOIN $trackTable te
6082
                ON (te.c_id = q.c_id AND t.exe_id = te.exe_id)
6083
                WHERE
6084
                    t.c_id = $courseId AND
6085
                    t.marks != q.ponderation AND
6086
                    exe_exo_id = $exerciseId AND
6087
                    status != 'incomplete'
6088
                    $sessionCondition
6089
                    $userCondition
6090
                GROUP BY q.iid
6091
                ORDER BY count DESC
6092
                LIMIT $limit
6093
        ";
6094
6095
        $result = Database::query($sql);
6096
6097
        return Database::store_result($result, 'ASSOC');
6098
    }
6099
6100
    public static function getExerciseResultsCount($type, $courseId, Exercise $exercise, $sessionId = 0)
6101
    {
6102
        $courseId = (int) $courseId;
6103
        $exerciseId = (int) $exercise->iId;
6104
6105
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6106
6107
        $sessionCondition = '';
6108
        if (!empty($sessionId)) {
6109
            $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
6110
        }
6111
6112
        $passPercentage = $exercise->selectPassPercentage();
6113
        $minPercentage = 100;
6114
        if (!empty($passPercentage)) {
6115
            $minPercentage = $passPercentage;
6116
        }
6117
6118
        $selectCount = 'count(DISTINCT te.exe_id)';
6119
        $scoreCondition = '';
6120
        switch ($type) {
6121
            case 'correct_student':
6122
                $selectCount = 'count(DISTINCT te.exe_user_id)';
6123
                $scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage ";
6124
                break;
6125
            case 'wrong_student':
6126
                $selectCount = 'count(DISTINCT te.exe_user_id)';
6127
                $scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage ";
6128
                break;
6129
            case 'correct':
6130
                $scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage ";
6131
                break;
6132
            case 'wrong':
6133
                $scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage ";
6134
                break;
6135
        }
6136
6137
        $sql = "SELECT $selectCount count
6138
                FROM $trackTable te
6139
                WHERE
6140
                    c_id = $courseId AND
6141
                    exe_exo_id = $exerciseId AND
6142
                    status != 'incomplete'
6143
                    $scoreCondition
6144
                    $sessionCondition
6145
        ";
6146
        $result = Database::query($sql);
6147
        $totalRow = Database::fetch_array($result, 'ASSOC');
6148
        $total = 0;
6149
        if ($totalRow) {
6150
            $total = (int) $totalRow['count'];
6151
        }
6152
6153
        return $total;
6154
    }
6155
6156
    public static function parseContent($content, $stats, Exercise $exercise, $trackInfo, $currentUserId = 0)
6157
    {
6158
        $wrongAnswersCount = $stats['failed_answers_count'];
6159
        $attemptDate = substr($trackInfo['exe_date'], 0, 10);
6160
        $exeId = $trackInfo['exe_id'];
6161
        $resultsStudentUrl = api_get_path(WEB_CODE_PATH).
6162
            'exercise/result.php?id='.$exeId.'&'.api_get_cidreq();
6163
        $resultsTeacherUrl = api_get_path(WEB_CODE_PATH).
6164
            'exercise/exercise_show.php?action=edit&id='.$exeId.'&'.api_get_cidreq(true, true, 'teacher');
6165
6166
        $content = str_replace(
6167
            [
6168
                '((exercise_error_count))',
6169
                '((all_answers_html))',
6170
                '((all_answers_teacher_html))',
6171
                '((exercise_title))',
6172
                '((exercise_attempt_date))',
6173
                '((link_to_test_result_page_student))',
6174
                '((link_to_test_result_page_teacher))',
6175
            ],
6176
            [
6177
                $wrongAnswersCount,
6178
                $stats['all_answers_html'],
6179
                $stats['all_answers_teacher_html'],
6180
                $exercise->get_formated_title(),
6181
                $attemptDate,
6182
                $resultsStudentUrl,
6183
                $resultsTeacherUrl,
6184
            ],
6185
            $content
6186
        );
6187
6188
        $currentUserId = empty($currentUserId) ? api_get_user_id() : (int) $currentUserId;
6189
6190
        $content = AnnouncementManager::parseContent(
6191
            $currentUserId,
6192
            $content,
6193
            api_get_course_id(),
6194
            api_get_session_id()
6195
        );
6196
6197
        return $content;
6198
    }
6199
6200
    public static function sendNotification(
6201
        $currentUserId,
6202
        $objExercise,
6203
        $exercise_stat_info,
6204
        $courseInfo,
6205
        $attemptCountToSend,
6206
        $stats,
6207
        $statsTeacher
6208
    ) {
6209
        $notifications = api_get_configuration_value('exercise_finished_notification_settings');
6210
        if (empty($notifications)) {
6211
            return false;
6212
        }
6213
6214
        $studentId = $exercise_stat_info['exe_user_id'];
6215
        $exerciseExtraFieldValue = new ExtraFieldValue('exercise');
6216
        $wrongAnswersCount = $stats['failed_answers_count'];
6217
        $exercisePassed = $stats['exercise_passed'];
6218
        $countPendingQuestions = $stats['count_pending_questions'];
6219
        $stats['all_answers_teacher_html'] = $statsTeacher['all_answers_html'];
6220
6221
        // If there are no pending questions (Open questions).
6222
        if (0 === $countPendingQuestions) {
6223
            /*$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6224
                $objExercise->iId,
6225
                'signature_mandatory'
6226
            );
6227
6228
            if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) {
6229
                if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
6230
                    $signature = ExerciseSignaturePlugin::getSignature($studentId, $exercise_stat_info);
6231
                    if (false !== $signature) {
6232
                        //return false;
6233
                    }
6234
                }
6235
            }*/
6236
6237
            // Notifications.
6238
            $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6239
                $objExercise->iId,
6240
                'notifications'
6241
            );
6242
            $exerciseNotification = '';
6243
            if ($extraFieldData && isset($extraFieldData['value'])) {
6244
                $exerciseNotification = $extraFieldData['value'];
6245
            }
6246
6247
            $subject = sprintf(get_lang('WrongAttemptXInCourseX'), $attemptCountToSend, $courseInfo['title']);
6248
            if ($exercisePassed) {
6249
                $subject = sprintf(get_lang('ExerciseValidationInCourseX'), $courseInfo['title']);
6250
            }
6251
6252
            if ($exercisePassed) {
6253
                $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6254
                    $objExercise->iId,
6255
                    'MailSuccess'
6256
                );
6257
            } else {
6258
                $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6259
                    $objExercise->iId,
6260
                    'MailAttempt'.$attemptCountToSend
6261
                );
6262
            }
6263
6264
            // Blocking exercise.
6265
            $blockPercentageExtra = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6266
                $objExercise->iId,
6267
                'blocking_percentage'
6268
            );
6269
            $blockPercentage = false;
6270
            if ($blockPercentageExtra && isset($blockPercentageExtra['value']) && $blockPercentageExtra['value']) {
6271
                $blockPercentage = $blockPercentageExtra['value'];
6272
            }
6273
            if ($blockPercentage) {
6274
                $passBlock = $stats['total_percentage'] > $blockPercentage;
6275
                if (false === $passBlock) {
6276
                    $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6277
                        $objExercise->iId,
6278
                        'MailIsBlockByPercentage'
6279
                    );
6280
                }
6281
            }
6282
6283
            $extraFieldValueUser = new ExtraFieldValue('user');
6284
6285
            if ($extraFieldData && isset($extraFieldData['value'])) {
6286
                $content = $extraFieldData['value'];
6287
                $content = self::parseContent($content, $stats, $objExercise, $exercise_stat_info, $studentId);
6288
                //if (false === $exercisePassed) {
6289
                if (0 !== $wrongAnswersCount) {
6290
                    $content .= $stats['failed_answers_html'];
6291
                }
6292
6293
                $sendMessage = true;
6294
                if (!empty($exerciseNotification)) {
6295
                    foreach ($notifications as $name => $notificationList) {
6296
                        if ($exerciseNotification !== $name) {
6297
                            continue;
6298
                        }
6299
                        foreach ($notificationList as $notificationName => $attemptData) {
6300
                            if ('student_check' === $notificationName) {
6301
                                $sendMsgIfInList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : '';
6302
                                if (!empty($sendMsgIfInList)) {
6303
                                    foreach ($sendMsgIfInList as $skipVariable => $skipValues) {
6304
                                        $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
6305
                                            $studentId,
6306
                                            $skipVariable
6307
                                        );
6308
6309
                                        if (empty($userExtraFieldValue)) {
6310
                                            $sendMessage = false;
6311
                                            break;
6312
                                        } else {
6313
                                            $sendMessage = false;
6314
                                            if (isset($userExtraFieldValue['value']) &&
6315
                                                in_array($userExtraFieldValue['value'], $skipValues)
6316
                                            ) {
6317
                                                $sendMessage = true;
6318
                                                break;
6319
                                            }
6320
                                        }
6321
                                    }
6322
                                }
6323
                                break;
6324
                            }
6325
                        }
6326
                    }
6327
                }
6328
6329
                // Send to student.
6330
                if ($sendMessage) {
6331
                    MessageManager::send_message($currentUserId, $subject, $content);
6332
                }
6333
            }
6334
6335
            if (!empty($exerciseNotification)) {
6336
                foreach ($notifications as $name => $notificationList) {
6337
                    if ($exerciseNotification !== $name) {
6338
                        continue;
6339
                    }
6340
                    foreach ($notificationList as $attemptData) {
6341
                        $skipNotification = false;
6342
                        $skipNotificationList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : [];
6343
                        if (!empty($skipNotificationList)) {
6344
                            foreach ($skipNotificationList as $skipVariable => $skipValues) {
6345
                                $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
6346
                                    $studentId,
6347
                                    $skipVariable
6348
                                );
6349
6350
                                if (empty($userExtraFieldValue)) {
6351
                                    $skipNotification = true;
6352
                                    break;
6353
                                } else {
6354
                                    if (isset($userExtraFieldValue['value'])) {
6355
                                        if (!in_array($userExtraFieldValue['value'], $skipValues)) {
6356
                                            $skipNotification = true;
6357
                                            break;
6358
                                        }
6359
                                    } else {
6360
                                        $skipNotification = true;
6361
                                        break;
6362
                                    }
6363
                                }
6364
                            }
6365
                        }
6366
6367
                        if ($skipNotification) {
6368
                            continue;
6369
                        }
6370
6371
                        $email = isset($attemptData['email']) ? $attemptData['email'] : '';
6372
                        $emailList = explode(',', $email);
6373
                        if (empty($emailList)) {
6374
                            continue;
6375
                        }
6376
                        $attempts = isset($attemptData['attempts']) ? $attemptData['attempts'] : [];
6377
                        foreach ($attempts as $attempt) {
6378
                            $sendMessage = false;
6379
                            if (isset($attempt['attempt']) && $attemptCountToSend !== (int) $attempt['attempt']) {
6380
                                continue;
6381
                            }
6382
6383
                            if (!isset($attempt['status'])) {
6384
                                continue;
6385
                            }
6386
6387
                            if ($blockPercentage && isset($attempt['is_block_by_percentage'])) {
6388
                                if ($attempt['is_block_by_percentage']) {
6389
                                    if ($passBlock) {
6390
                                        continue;
6391
                                    }
6392
                                } else {
6393
                                    if (false === $passBlock) {
6394
                                        continue;
6395
                                    }
6396
                                }
6397
                            }
6398
6399
                            switch ($attempt['status']) {
6400
                                case 'passed':
6401
                                    if ($exercisePassed) {
6402
                                        $sendMessage = true;
6403
                                    }
6404
                                    break;
6405
                                case 'failed':
6406
                                    if (false === $exercisePassed) {
6407
                                        $sendMessage = true;
6408
                                    }
6409
                                    break;
6410
                                case 'all':
6411
                                    $sendMessage = true;
6412
                                    break;
6413
                            }
6414
6415
                            if ($sendMessage) {
6416
                                $attachments = [];
6417
                                if (isset($attempt['add_pdf']) && $attempt['add_pdf']) {
6418
                                    // Get pdf content
6419
                                    $pdfExtraData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6420
                                        $objExercise->iId,
6421
                                        $attempt['add_pdf']
6422
                                    );
6423
6424
                                    if ($pdfExtraData && isset($pdfExtraData['value'])) {
6425
                                        $pdfContent = self::parseContent(
6426
                                            $pdfExtraData['value'],
6427
                                            $stats,
6428
                                            $objExercise,
6429
                                            $exercise_stat_info,
6430
                                            $studentId
6431
                                        );
6432
6433
                                        @$pdf = new PDF();
6434
                                        $filename = get_lang('Exercise');
6435
                                        $cssFile = api_get_path(SYS_CSS_PATH).'themes/chamilo/default.css';
6436
                                        $pdfPath = @$pdf->content_to_pdf(
6437
                                            "<html><body>$pdfContent</body></html>",
6438
                                            file_get_contents($cssFile),
6439
                                            $filename,
6440
                                            api_get_course_id(),
6441
                                            'F',
6442
                                            false,
6443
                                            null,
6444
                                            false,
6445
                                            true
6446
                                        );
6447
                                        $attachments[] = ['filename' => $filename, 'path' => $pdfPath];
6448
                                    }
6449
                                }
6450
6451
                                $content = isset($attempt['content_default']) ? $attempt['content_default'] : '';
6452
                                if (isset($attempt['content'])) {
6453
                                    $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
6454
                                        $objExercise->iId,
6455
                                        $attempt['content']
6456
                                    );
6457
                                    if ($extraFieldData && isset($extraFieldData['value']) && !empty($extraFieldData['value'])) {
6458
                                        $content = $extraFieldData['value'];
6459
                                    }
6460
                                }
6461
6462
                                if (!empty($content)) {
6463
                                    $content = self::parseContent(
6464
                                        $content,
6465
                                        $stats,
6466
                                        $objExercise,
6467
                                        $exercise_stat_info,
6468
                                        $studentId
6469
                                    );
6470
                                    foreach ($emailList as $email) {
6471
                                        if (empty($email)) {
6472
                                            continue;
6473
                                        }
6474
                                        api_mail_html(
6475
                                            null,
6476
                                            $email,
6477
                                            $subject,
6478
                                            $content,
6479
                                            null,
6480
                                            null,
6481
                                            [],
6482
                                            $attachments
6483
                                        );
6484
                                    }
6485
                                }
6486
6487
                                if (isset($attempt['post_actions'])) {
6488
                                    foreach ($attempt['post_actions'] as $action => $params) {
6489
                                        switch ($action) {
6490
                                            case 'subscribe_student_to_courses':
6491
                                                foreach ($params as $code) {
6492
                                                    CourseManager::subscribeUser($currentUserId, $code);
6493
                                                    break;
6494
                                                }
6495
                                                break;
6496
                                        }
6497
                                    }
6498
                                }
6499
                            }
6500
                        }
6501
                    }
6502
                }
6503
            }
6504
        }
6505
    }
6506
6507
    /**
6508
     * Delete an exercise attempt.
6509
     *
6510
     * Log the exe_id deleted with the exe_user_id related.
6511
     *
6512
     * @param int $exeId
6513
     */
6514
    public static function deleteExerciseAttempt($exeId)
6515
    {
6516
        $exeId = (int) $exeId;
6517
6518
        $trackExerciseInfo = self::get_exercise_track_exercise_info($exeId);
6519
6520
        if (empty($trackExerciseInfo)) {
6521
            return;
6522
        }
6523
6524
        $tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6525
        $tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
6526
6527
        Database::query("DELETE FROM $tblTrackExercises WHERE exe_id = $exeId");
6528
        Database::query("DELETE FROM $tblTrackAttempt WHERE exe_id = $exeId");
6529
6530
        Event::addEvent(
6531
            LOG_EXERCISE_ATTEMPT_DELETE,
6532
            LOG_EXERCISE_ATTEMPT,
6533
            $exeId,
6534
            api_get_utc_datetime()
6535
        );
6536
        Event::addEvent(
6537
            LOG_EXERCISE_ATTEMPT_DELETE,
6538
            LOG_EXERCISE_AND_USER_ID,
6539
            $exeId.'-'.$trackExerciseInfo['exe_user_id'],
6540
            api_get_utc_datetime()
6541
        );
6542
    }
6543
6544
    public static function scorePassed($score, $total)
6545
    {
6546
        $compareResult = bccomp($score, $total, 3);
6547
        $scorePassed = 1 === $compareResult || 0 === $compareResult;
6548
        if (false === $scorePassed) {
6549
            $epsilon = 0.00001;
6550
            if (abs($score - $total) < $epsilon) {
6551
                $scorePassed = true;
6552
            }
6553
        }
6554
6555
        return $scorePassed;
6556
    }
6557
}
6558