Completed
Push — master ( 966b12...7d9ab3 )
by Julito
08:22
created

ExerciseLib::generateAndShowCertificateBlock()   B

Complexity

Conditions 9

Size

Total Lines 50
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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