Passed
Push — master ( 5db41b...b5271b )
by Julito
10:05
created

ExerciseLib::getNotCorrectedYetText()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
5214
        $sessionId = api_get_session_id();
5215
        $groupId = api_get_group_id();
5216
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
5217
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
5218
        $fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId);
5219
        $filePath = null;
5220
5221
        $relFilePath = "/exercises/teacher_audio/$attemptId/$fileName";
5222
5223
        if (file_exists($sysCourseDir.$relFilePath.'.ogg')) {
5224
            $filePath = $webCourseDir.$relFilePath.'.ogg';
5225
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) {
5226
            $filePath = $webCourseDir.$relFilePath.'.wav.wav';
5227
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) {
5228
            $filePath = $webCourseDir.$relFilePath.'.wav';
5229
        }
5230
5231
        if (!$filePath) {
5232
            return '';
5233
        }
5234
5235
        return Display::tag(
5236
            'audio',
5237
            null,
5238
            ['src' => $filePath]
5239
        );
5240
    }
5241
5242
    /**
5243
     * @return array
5244
     */
5245
    public static function getNotificationSettings()
5246
    {
5247
        $emailAlerts = [
5248
            2 => get_lang('SendEmailToTrainerWhenStudentStartQuiz'),
5249
            1 => get_lang('SendEmailToTrainerWhenStudentEndQuiz'), // default
5250
            3 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOpenQuestion'),
5251
            4 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOralQuestion'),
5252
        ];
5253
5254
        return $emailAlerts;
5255
    }
5256
5257
    /**
5258
     * Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
5259
     *
5260
     * @param int $exerciseId
5261
     * @param int $iconSize
5262
     *
5263
     * @return string
5264
     */
5265
    public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
5266
    {
5267
        $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
5268
        $actions = [];
5269
5270
        foreach ($additionalActions as $additionalAction) {
5271
            $actions[] = call_user_func(
5272
                $additionalAction,
5273
                $exerciseId,
5274
                $iconSize
5275
            );
5276
        }
5277
5278
        return implode(PHP_EOL, $actions);
5279
    }
5280
5281
    /**
5282
     * @param int $userId
5283
     * @param int $courseId
5284
     * @param int $sessionId
5285
     *
5286
     * @throws \Doctrine\ORM\Query\QueryException
5287
     *
5288
     * @return int
5289
     */
5290
    public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
5291
    {
5292
        $em = Database::getManager();
5293
5294
        $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
5295
5296
        $result = $em
5297
            ->createQuery('
5298
                SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
5299
                WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
5300
                    AND ea.tms > :time
5301
            ')
5302
            ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
5303
            ->getSingleScalarResult();
5304
5305
        return $result;
5306
    }
5307
5308
    /**
5309
     * @param int $userId
5310
     * @param int $numberOfQuestions
5311
     * @param int $courseId
5312
     * @param int $sessionId
5313
     *
5314
     * @throws \Doctrine\ORM\Query\QueryException
5315
     *
5316
     * @return bool
5317
     */
5318
    public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
5319
    {
5320
        $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
5321
5322
        if ($questionsLimitPerDay <= 0) {
5323
            return false;
5324
        }
5325
5326
        $midnightTime = ChamiloApi::getServerMidnightTime();
5327
5328
        $answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime(
5329
            $midnightTime,
5330
            $userId,
5331
            $courseId,
5332
            $sessionId
5333
        );
5334
5335
        return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
5336
    }
5337
5338
    /**
5339
     * Check if an exercise complies with the requirements to be embedded in the mobile app or a video.
5340
     * By making sure it is set on one question per page and it only contains unique-answer or multiple-answer questions
5341
     * or unique-answer image. And that the exam does not have immediate feedback.
5342
     *
5343
     * @return bool
5344
     */
5345
    public static function isQuizEmbeddable(CQuiz $exercise)
5346
    {
5347
        $em = Database::getManager();
5348
5349
        if (ONE_PER_PAGE != $exercise->getType() ||
5350
            in_array($exercise->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])
5351
        ) {
5352
            return false;
5353
        }
5354
5355
        $countAll = $em
5356
            ->createQuery('SELECT COUNT(qq)
5357
                FROM ChamiloCourseBundle:CQuizQuestion qq
5358
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5359
                   WITH qq.iid = qrq.question
5360
                WHERE qrq.quiz = :id'
5361
            )
5362
            ->setParameter('id', $exercise->getIid())
5363
            ->getSingleScalarResult();
5364
5365
        $countOfAllowed = $em
5366
            ->createQuery('SELECT COUNT(qq)
5367
                FROM ChamiloCourseBundle:CQuizQuestion qq
5368
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5369
                   WITH qq.iid = qrq.question
5370
                WHERE qrq.quiz = :id AND qq.type IN (:types)'
5371
            )
5372
            ->setParameters(
5373
                [
5374
                    'id' => $exercise->getIid(),
5375
                    'types' => [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE],
5376
                ]
5377
            )
5378
            ->getSingleScalarResult();
5379
5380
        return $countAll === $countOfAllowed;
5381
    }
5382
5383
    /**
5384
     * Generate a certificate linked to current quiz and.
5385
     * Return the HTML block with links to download and view the certificate.
5386
     *
5387
     * @param float  $totalScore
5388
     * @param float  $totalWeight
5389
     * @param int    $studentId
5390
     * @param string $courseCode
5391
     * @param int    $sessionId
5392
     *
5393
     * @return string
5394
     */
5395
    public static function generateAndShowCertificateBlock(
5396
        $totalScore,
5397
        $totalWeight,
5398
        Exercise $objExercise,
5399
        $studentId,
5400
        $courseCode,
5401
        $sessionId = 0
5402
    ) {
5403
        if (!api_get_configuration_value('quiz_generate_certificate_ending') ||
5404
            !self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage())
5405
        ) {
5406
            return '';
5407
        }
5408
5409
        /** @var Category $category */
5410
        $category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
5411
5412
        if (empty($category)) {
5413
            return '';
5414
        }
5415
5416
        /** @var Category $category */
5417
        $category = $category[0];
5418
        $categoryId = $category->get_id();
5419
        $link = LinkFactory::load(
5420
            null,
5421
            null,
5422
            $objExercise->getId(),
5423
            null,
5424
            $courseCode,
5425
            $categoryId
5426
        );
5427
5428
        if (empty($link)) {
5429
            return '';
5430
        }
5431
5432
        $resourceDeletedMessage = $category->show_message_resource_delete($courseCode);
5433
5434
        if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) {
5435
            return '';
5436
        }
5437
5438
        $certificate = Category::generateUserCertificate($categoryId, $studentId);
5439
5440
        if (!is_array($certificate)) {
5441
            return '';
5442
        }
5443
5444
        return Category::getDownloadCertificateBlock($certificate);
5445
    }
5446
5447
    /**
5448
     * @param int $exerciseId
5449
     */
5450
    public static function getExerciseTitleById($exerciseId)
5451
    {
5452
        $em = Database::getManager();
5453
5454
        return $em
5455
            ->createQuery('SELECT cq.title
5456
                FROM ChamiloCourseBundle:CQuiz cq
5457
                WHERE cq.iid = :iid'
5458
            )
5459
            ->setParameter('iid', $exerciseId)
5460
            ->getSingleScalarResult();
5461
    }
5462
5463
    /**
5464
     * @param int $exeId      ID from track_e_exercises
5465
     * @param int $userId     User ID
5466
     * @param int $exerciseId Exercise ID
5467
     * @param int $courseId   Optional. Coure ID.
5468
     *
5469
     * @return TrackEExercises|null
5470
     */
5471
    public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0)
5472
    {
5473
        if (empty($userId) || empty($exerciseId)) {
5474
            return null;
5475
        }
5476
5477
        $em = Database::getManager();
5478
        /** @var TrackEExercises $trackedExercise */
5479
        $trackedExercise = $em->getRepository(TrackEExercises::class)->find($exeId);
5480
5481
        if (empty($trackedExercise)) {
5482
            return null;
5483
        }
5484
5485
        if ($trackedExercise->getExeUserId() != $userId ||
5486
            $trackedExercise->getExeExoId() != $exerciseId
5487
        ) {
5488
            return null;
5489
        }
5490
5491
        $questionList = $trackedExercise->getDataTracking();
5492
5493
        if (empty($questionList)) {
5494
            return null;
5495
        }
5496
5497
        $questionList = explode(',', $questionList);
5498
5499
        $exercise = new Exercise($courseId);
5500
        $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : [];
5501
5502
        if (false === $exercise->read($exerciseId)) {
5503
            return null;
5504
        }
5505
5506
        $totalScore = 0;
5507
        $totalWeight = 0;
5508
5509
        $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
5510
5511
        $formula = 'true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)
5512
            ? $pluginEvaluation->getFormulaForExercise($exerciseId)
5513
            : 0;
5514
5515
        if (empty($formula)) {
5516
            foreach ($questionList as $questionId) {
5517
                $question = Question::read($questionId, $courseInfo);
5518
5519
                if (false === $question) {
5520
                    continue;
5521
                }
5522
5523
                $totalWeight += $question->selectWeighting();
5524
5525
                // We're inside *one* question. Go through each possible answer for this question
5526
                $result = $exercise->manage_answer(
5527
                    $exeId,
5528
                    $questionId,
5529
                    [],
5530
                    'exercise_result',
5531
                    [],
5532
                    false,
5533
                    true,
5534
                    false,
5535
                    $exercise->selectPropagateNeg(),
5536
                    [],
5537
                    [],
5538
                    true
5539
                );
5540
5541
                //  Adding the new score.
5542
                $totalScore += $result['score'];
5543
            }
5544
        } else {
5545
            $totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula);
5546
            $totalWeight = $pluginEvaluation->getMaxScore();
5547
        }
5548
5549
        $trackedExercise
5550
            ->setScore($totalScore)
5551
            ->setMaxScore($totalWeight);
5552
5553
        $em->persist($trackedExercise);
5554
        $em->flush();
5555
5556
        return $trackedExercise;
5557
    }
5558
5559
    public static function getTotalQuestionAnswered($courseId, $exerciseId, $questionId)
5560
    {
5561
        $courseId = (int) $courseId;
5562
        $exerciseId = (int) $exerciseId;
5563
        $questionId = (int) $questionId;
5564
5565
        $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
5566
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5567
5568
        $sql = "SELECT count(te.exe_id) total
5569
            FROM $attemptTable t
5570
            INNER JOIN $trackTable te
5571
            ON (te.c_id = t.c_id AND t.exe_id = te.exe_id)
5572
            WHERE
5573
                t.c_id = $courseId AND
5574
                exe_exo_id = $exerciseId AND
5575
                t.question_id = $questionId AND
5576
                status != 'incomplete'
5577
        ";
5578
        $queryTotal = Database::query($sql);
5579
        $totalRow = Database::fetch_array($queryTotal, 'ASSOC');
5580
        $total = 0;
5581
        if ($totalRow) {
5582
            $total = (int) $totalRow['total'];
5583
        }
5584
5585
        return $total;
5586
    }
5587
5588
    public static function getWrongQuestionResults($courseId, $exerciseId, $sessionId = 0, $limit = 10)
5589
    {
5590
        $courseId = (int) $courseId;
5591
        $exerciseId = (int) $exerciseId;
5592
        $limit = (int) $limit;
5593
5594
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
5595
        $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
5596
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5597
5598
        $sessionCondition = '';
5599
        if (!empty($sessionId)) {
5600
            $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
5601
        }
5602
5603
        $sql = "SELECT q.question, question_id, count(q.iid) count
5604
                FROM $attemptTable t
5605
                INNER JOIN $questionTable q
5606
                ON (q.c_id = t.c_id AND q.iid = t.question_id)
5607
                INNER JOIN $trackTable te
5608
                ON (te.c_id = q.c_id AND t.exe_id = te.exe_id)
5609
                WHERE
5610
                    t.c_id = $courseId AND
5611
                    t.marks != q.ponderation AND
5612
                    exe_exo_id = $exerciseId AND
5613
                    status != 'incomplete'
5614
                    $sessionCondition
5615
                GROUP BY q.iid
5616
                ORDER BY count DESC
5617
                LIMIT $limit
5618
        ";
5619
5620
        $result = Database::query($sql);
5621
5622
        return Database::store_result($result, 'ASSOC');
5623
    }
5624
5625
    public static function getExerciseResultsCount($type, $courseId, $exerciseId, $sessionId = 0)
5626
    {
5627
        $courseId = (int) $courseId;
5628
        $exerciseId = (int) $exerciseId;
5629
5630
        $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5631
5632
        $sessionCondition = '';
5633
        if (!empty($sessionId)) {
5634
            $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id');
5635
        }
5636
5637
        $selectCount = 'count(DISTINCT te.exe_id)';
5638
        $scoreCondition = '';
5639
        switch ($type) {
5640
            case 'correct_student':
5641
                $selectCount = 'count(DISTINCT te.exe_user_id)';
5642
                $scoreCondition = ' AND exe_result = exe_weighting ';
5643
                break;
5644
            case 'wrong_student':
5645
                $selectCount = 'count(DISTINCT te.exe_user_id)';
5646
                $scoreCondition = ' AND  exe_result != exe_weighting ';
5647
                break;
5648
            case 'correct':
5649
                $scoreCondition = ' AND exe_result = exe_weighting ';
5650
                break;
5651
            case 'wrong':
5652
                $scoreCondition = ' AND exe_result != exe_weighting ';
5653
                break;
5654
        }
5655
5656
        $sql = "SELECT $selectCount count
5657
                FROM $trackTable te
5658
                WHERE
5659
                    c_id = $courseId AND
5660
                    exe_exo_id = $exerciseId AND
5661
                    status != 'incomplete'
5662
                    $scoreCondition
5663
                    $sessionCondition
5664
        ";
5665
        $result = Database::query($sql);
5666
        $totalRow = Database::fetch_array($result, 'ASSOC');
5667
        $total = 0;
5668
        if ($totalRow) {
5669
            $total = (int) $totalRow['count'];
5670
        }
5671
5672
        return $total;
5673
    }
5674
5675
    public static function parseContent($content, $stats, $exercise, $trackInfo, $currentUserId = 0)
5676
    {
5677
        $wrongAnswersCount = $stats['failed_answers_count'];
5678
        $attemptDate = substr($trackInfo['exe_date'], 0, 10);
5679
5680
        $content = str_replace(
5681
            [
5682
                '((exercise_error_count))',
5683
                '((all_answers_html))',
5684
                '((exercise_title))',
5685
                '((exercise_attempt_date))',
5686
            ],
5687
            [
5688
                $wrongAnswersCount,
5689
                $stats['all_answers_html'],
5690
                $exercise->get_formated_title(),
5691
                $attemptDate,
5692
            ],
5693
            $content
5694
        );
5695
5696
        $currentUserId = empty($currentUserId) ? api_get_user_id() : (int) $currentUserId;
5697
5698
        $content = AnnouncementManager::parseContent(
5699
            $currentUserId,
5700
            $content,
5701
            api_get_course_id(),
5702
            api_get_session_id()
5703
        );
5704
5705
        return $content;
5706
    }
5707
5708
    public static function sendNotification(
5709
        $currentUserId,
5710
        $objExercise,
5711
        $exercise_stat_info,
5712
        $courseInfo,
5713
        $attemptCountToSend,
5714
        $stats
5715
    ) {
5716
        $notifications = api_get_configuration_value('exercise_finished_notification_settings');
5717
        if (empty($notifications)) {
5718
            return false;
5719
        }
5720
5721
        $studentId = $exercise_stat_info['exe_user_id'];
5722
        $exerciseExtraFieldValue = new ExtraFieldValue('exercise');
5723
        $wrongAnswersCount = $stats['failed_answers_count'];
5724
        $exercisePassed = $stats['exercise_passed'];
5725
        $countPendingQuestions = $stats['count_pending_questions'];
5726
5727
        // If there are no pending questions (Open questions).
5728
        if (0 === $countPendingQuestions) {
5729
            /*$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5730
                $objExercise->iId,
5731
                'signature_mandatory'
5732
            );
5733
5734
            if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) {
5735
                if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
5736
                    $signature = ExerciseSignaturePlugin::getSignature($studentId, $exercise_stat_info);
5737
                    if (false !== $signature) {
5738
                        //return false;
5739
                    }
5740
                }
5741
            }*/
5742
5743
            // Notifications.
5744
            $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5745
                $objExercise->iId,
5746
                'notifications'
5747
            );
5748
            $exerciseNotification = '';
5749
            if ($extraFieldData && isset($extraFieldData['value'])) {
5750
                $exerciseNotification = $extraFieldData['value'];
5751
            }
5752
5753
            $subject = sprintf(get_lang('WrongAttemptXInCourseX'), $attemptCountToSend, $courseInfo['title']);
5754
            if ($exercisePassed) {
5755
                $subject = sprintf(get_lang('ExerciseValidationInCourseX'), $courseInfo['title']);
5756
            }
5757
5758
            if ($exercisePassed) {
5759
                $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5760
                    $objExercise->iId,
5761
                    'MailSuccess'
5762
                );
5763
            } else {
5764
                $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5765
                    $objExercise->iId,
5766
                    'MailAttempt'.$attemptCountToSend
5767
                );
5768
            }
5769
5770
            // Blocking exercise.
5771
            $blockPercentageExtra = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5772
                $objExercise->iId,
5773
                'blocking_percentage'
5774
            );
5775
            $blockPercentage = false;
5776
            if ($blockPercentageExtra && isset($blockPercentageExtra['value']) && $blockPercentageExtra['value']) {
5777
                $blockPercentage = $blockPercentageExtra['value'];
5778
            }
5779
            if ($blockPercentage) {
5780
                $passBlock = $stats['total_percentage'] > $blockPercentage;
5781
                if (false === $passBlock) {
5782
                    $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5783
                        $objExercise->iId,
5784
                        'MailIsBlockByPercentage'
5785
                    );
5786
                }
5787
            }
5788
5789
            $extraFieldValueUser = new ExtraFieldValue('user');
5790
5791
            if ($extraFieldData && isset($extraFieldData['value'])) {
5792
                $content = $extraFieldData['value'];
5793
                $content = self::parseContent($content, $stats, $objExercise, $exercise_stat_info, $studentId);
5794
                if (false === $exercisePassed) {
5795
                    if (0 !== $wrongAnswersCount) {
5796
                        $content .= $stats['failed_answers_html'];
5797
                    }
5798
                }
5799
5800
                $sendMessage = true;
5801
                if (!empty($exerciseNotification)) {
5802
                    foreach ($notifications as $name => $notificationList) {
5803
                        if ($exerciseNotification !== $name) {
5804
                            continue;
5805
                        }
5806
                        foreach ($notificationList as $notificationName => $attemptData) {
5807
                            if ('student_check' === $notificationName) {
5808
                                $sendMsgIfInList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : '';
5809
                                if (!empty($sendMsgIfInList)) {
5810
                                    foreach ($sendMsgIfInList as $skipVariable => $skipValues) {
5811
                                        $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
5812
                                            $studentId,
5813
                                            $skipVariable
5814
                                        );
5815
5816
                                        if (empty($userExtraFieldValue)) {
5817
                                            $sendMessage = false;
5818
                                            break;
5819
                                        } else {
5820
                                            $sendMessage = false;
5821
                                            if (isset($userExtraFieldValue['value']) &&
5822
                                                in_array($userExtraFieldValue['value'], $skipValues)
5823
                                            ) {
5824
                                                $sendMessage = true;
5825
                                                break;
5826
                                            }
5827
                                        }
5828
                                    }
5829
                                }
5830
                                break;
5831
                            }
5832
                        }
5833
                    }
5834
                }
5835
5836
                // Send to student.
5837
                if ($sendMessage) {
5838
                    MessageManager::send_message($currentUserId, $subject, $content);
5839
                }
5840
            }
5841
5842
            if (!empty($exerciseNotification)) {
5843
                foreach ($notifications as $name => $notificationList) {
5844
                    if ($exerciseNotification !== $name) {
5845
                        continue;
5846
                    }
5847
                    foreach ($notificationList as $attemptData) {
5848
                        $skipNotification = false;
5849
                        $skipNotificationList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : [];
5850
                        if (!empty($skipNotificationList)) {
5851
                            foreach ($skipNotificationList as $skipVariable => $skipValues) {
5852
                                $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable(
5853
                                    $studentId,
5854
                                    $skipVariable
5855
                                );
5856
5857
                                if (empty($userExtraFieldValue)) {
5858
                                    $skipNotification = true;
5859
                                    break;
5860
                                } else {
5861
                                    if (isset($userExtraFieldValue['value'])) {
5862
                                        if (!in_array($userExtraFieldValue['value'], $skipValues)) {
5863
                                            $skipNotification = true;
5864
                                            break;
5865
                                        }
5866
                                    } else {
5867
                                        $skipNotification = true;
5868
                                        break;
5869
                                    }
5870
                                }
5871
                            }
5872
                        }
5873
5874
                        if ($skipNotification) {
5875
                            continue;
5876
                        }
5877
5878
                        $email = isset($attemptData['email']) ? $attemptData['email'] : '';
5879
                        $emailList = explode(',', $email);
5880
                        if (empty($emailList)) {
5881
                            continue;
5882
                        }
5883
                        $attempts = isset($attemptData['attempts']) ? $attemptData['attempts'] : [];
5884
                        foreach ($attempts as $attempt) {
5885
                            $sendMessage = false;
5886
                            if (isset($attempt['attempt']) && $attemptCountToSend !== (int) $attempt['attempt']) {
5887
                                continue;
5888
                            }
5889
5890
                            if (!isset($attempt['status'])) {
5891
                                continue;
5892
                            }
5893
5894
                            if ($blockPercentage && isset($attempt['is_block_by_percentage'])) {
5895
                                if ($attempt['is_block_by_percentage']) {
5896
                                    if ($passBlock) {
5897
                                        continue;
5898
                                    }
5899
                                } else {
5900
                                    if (false === $passBlock) {
5901
                                        continue;
5902
                                    }
5903
                                }
5904
                            }
5905
5906
                            switch ($attempt['status']) {
5907
                                case 'passed':
5908
                                    if ($exercisePassed) {
5909
                                        $sendMessage = true;
5910
                                    }
5911
                                    break;
5912
                                case 'failed':
5913
                                    if (false === $exercisePassed) {
5914
                                        $sendMessage = true;
5915
                                    }
5916
                                    break;
5917
                                case 'all':
5918
                                    $sendMessage = true;
5919
                                    break;
5920
                            }
5921
5922
                            if ($sendMessage) {
5923
                                $attachments = [];
5924
                                if (isset($attempt['add_pdf']) && $attempt['add_pdf']) {
5925
                                    // Get pdf content
5926
                                    $pdfExtraData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5927
                                        $objExercise->iId,
5928
                                        $attempt['add_pdf']
5929
                                    );
5930
5931
                                    if ($pdfExtraData && isset($pdfExtraData['value'])) {
5932
                                        $pdfContent = self::parseContent(
5933
                                            $pdfExtraData['value'],
5934
                                            $stats,
5935
                                            $objExercise,
5936
                                            $exercise_stat_info,
5937
                                            $studentId
5938
                                        );
5939
5940
                                        @$pdf = new PDF();
5941
                                        $filename = get_lang('Exercise');
5942
                                        $cssFile = api_get_path(SYS_CSS_PATH).'themes/chamilo/default.css';
5943
                                        $pdfPath = @$pdf->content_to_pdf(
5944
                                            "<html><body>$pdfContent</body></html>",
5945
                                            file_get_contents($cssFile),
5946
                                            $filename,
5947
                                            api_get_course_id(),
5948
                                            'F',
5949
                                            false,
5950
                                            null,
5951
                                            false,
5952
                                            true
5953
                                        );
5954
                                        $attachments[] = ['filename' => $filename, 'path' => $pdfPath];
5955
                                    }
5956
                                }
5957
5958
                                $content = isset($attempt['content_default']) ? $attempt['content_default'] : '';
5959
                                if (isset($attempt['content'])) {
5960
                                    $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable(
5961
                                        $objExercise->iId,
5962
                                        $attempt['content']
5963
                                    );
5964
                                    if ($extraFieldData && isset($extraFieldData['value']) && !empty($extraFieldData['value'])) {
5965
                                        $content = $extraFieldData['value'];
5966
                                    }
5967
                                }
5968
5969
                                if (!empty($content)) {
5970
                                    $content = self::parseContent(
5971
                                        $content,
5972
                                        $stats,
5973
                                        $objExercise,
5974
                                        $exercise_stat_info,
5975
                                        $studentId
5976
                                    );
5977
                                    foreach ($emailList as $email) {
5978
                                        if (empty($email)) {
5979
                                            continue;
5980
                                        }
5981
                                        api_mail_html(
5982
                                            null,
5983
                                            $email,
5984
                                            $subject,
5985
                                            $content,
5986
                                            null,
5987
                                            null,
5988
                                            [],
5989
                                            $attachments
5990
                                        );
5991
                                    }
5992
                                }
5993
5994
                                if (isset($attempt['post_actions'])) {
5995
                                    foreach ($attempt['post_actions'] as $action => $params) {
5996
                                        switch ($action) {
5997
                                            case 'subscribe_student_to_courses':
5998
                                                foreach ($params as $code) {
5999
                                                    CourseManager::subscribeUser($currentUserId, $code);
6000
                                                    break;
6001
                                                }
6002
                                                break;
6003
                                        }
6004
                                    }
6005
                                }
6006
                            }
6007
                        }
6008
                    }
6009
                }
6010
            }
6011
        }
6012
    }
6013
6014
    /**
6015
     * Delete an exercise attempt.
6016
     *
6017
     * Log the exe_id deleted with the exe_user_id related.
6018
     *
6019
     * @param int $exeId
6020
     */
6021
    public static function deleteExerciseAttempt($exeId)
6022
    {
6023
        $exeId = (int) $exeId;
6024
6025
        $trackExerciseInfo = self::get_exercise_track_exercise_info($exeId);
6026
6027
        if (empty($trackExerciseInfo)) {
6028
            return;
6029
        }
6030
6031
        $tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6032
        $tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
6033
6034
        Database::query("DELETE FROM $tblTrackExercises WHERE exe_id = $exeId");
6035
        Database::query("DELETE FROM $tblTrackAttempt WHERE exe_id = $exeId");
6036
6037
        Event::addEvent(
6038
            LOG_EXERCISE_ATTEMPT_DELETE,
6039
            LOG_EXERCISE_ATTEMPT,
6040
            $exeId,
6041
            api_get_utc_datetime()
6042
        );
6043
        Event::addEvent(
6044
            LOG_EXERCISE_ATTEMPT_DELETE,
6045
            LOG_EXERCISE_AND_USER_ID,
6046
            $exeId.'-'.$trackExerciseInfo['exe_user_id'],
6047
            api_get_utc_datetime()
6048
        );
6049
    }
6050
6051
    public static function scorePassed($score, $total)
6052
    {
6053
        $compareResult = bccomp($score, $total, 3);
6054
        $scorePassed = 1 === $compareResult || 0 === $compareResult;
6055
        if (false === $scorePassed) {
6056
            $epsilon = 0.00001;
6057
            if (abs($score - $total) < $epsilon) {
6058
                $scorePassed = true;
6059
            }
6060
        }
6061
6062
        return $scorePassed;
6063
    }
6064
}
6065