Completed
Push — master ( 024a20...a0a31d )
by Julito
09:32
created

ExerciseLib::recalculateResult()   C

Complexity

Conditions 13

Size

Total Lines 86
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 47
c 0
b 0
f 0
nop 4
dl 0
loc 86
rs 6.6166

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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