Completed
Push — master ( 255b9b...7e1a98 )
by Julito
09:17
created

ExerciseLib::exerciseResultsInRanking()   B

Complexity

Conditions 7

Size

Total Lines 59
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 33
c 1
b 0
f 0
nop 3
dl 0
loc 59
rs 8.4586

How to fix   Long Method   

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2590
                    $hp_title = GetQuizName($hpresults[$i][3], $documentPath);
2591
                    if ($hp_title == '') {
2592
                        $hp_title = basename($hpresults[$i][3]);
2593
                    }
2594
2595
                    $hp_date = api_get_local_time(
2596
                        $hpresults[$i][6],
2597
                        null,
2598
                        date_default_timezone_get()
2599
                    );
2600
                    $hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2);
2601
                    $hp_result .= '% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')';
2602
2603
                    if ($is_allowedToEdit) {
2604
                        $listInfo[] = [
2605
                            $hpresults[$i][0],
2606
                            $hpresults[$i][1],
2607
                            $hpresults[$i][2],
2608
                            '',
2609
                            $hp_title,
2610
                            '-',
2611
                            $hp_date,
2612
                            $hp_result,
2613
                            '-',
2614
                        ];
2615
                    } else {
2616
                        $listInfo[] = [
2617
                            $hp_title,
2618
                            '-',
2619
                            $hp_date,
2620
                            $hp_result,
2621
                            '-',
2622
                        ];
2623
                    }
2624
                }
2625
            }
2626
        }
2627
2628
        return $listInfo;
2629
    }
2630
2631
    /**
2632
     * @param $score
2633
     * @param $weight
2634
     *
2635
     * @return array
2636
     */
2637
    public static function convertScoreToPlatformSetting($score, $weight)
2638
    {
2639
        $maxNote = api_get_setting('exercise_max_score');
2640
        $minNote = api_get_setting('exercise_min_score');
2641
2642
        if ($maxNote != '' && $minNote != '') {
2643
            if (!empty($weight) && intval($weight) != 0) {
2644
                $score = $minNote + ($maxNote - $minNote) * $score / $weight;
2645
            } else {
2646
                $score = $minNote;
2647
            }
2648
            $weight = $maxNote;
2649
        }
2650
2651
        return ['score' => $score, 'weight' => $weight];
2652
    }
2653
2654
    /**
2655
     * Converts the score with the exercise_max_note and exercise_min_score
2656
     * the platform settings + formats the results using the float_format function.
2657
     *
2658
     * @param float  $score
2659
     * @param float  $weight
2660
     * @param bool   $show_percentage       show percentage or not
2661
     * @param bool   $use_platform_settings use or not the platform settings
2662
     * @param bool   $show_only_percentage
2663
     * @param bool   $hidePercentageSign    hide "%" sign
2664
     * @param string $decimalSeparator
2665
     * @param string $thousandSeparator
2666
     * @param bool   $roundValues           This option rounds the float values into a int using ceil()
2667
     *
2668
     * @return string an html with the score modified
2669
     */
2670
    public static function show_score(
2671
        $score,
2672
        $weight,
2673
        $show_percentage = true,
2674
        $use_platform_settings = true,
2675
        $show_only_percentage = false,
2676
        $hidePercentageSign = false,
2677
        $decimalSeparator = '.',
2678
        $thousandSeparator = ',',
2679
        $roundValues = false
2680
    ) {
2681
        if (is_null($score) && is_null($weight)) {
2682
            return '-';
2683
        }
2684
2685
        if ($use_platform_settings) {
2686
            $result = self::convertScoreToPlatformSetting($score, $weight);
2687
            $score = $result['score'];
2688
            $weight = $result['weight'];
2689
        }
2690
2691
        $percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
2692
        // Formats values
2693
        $percentage = float_format($percentage, 1);
2694
        $score = float_format($score, 1);
2695
        $weight = float_format($weight, 1);
2696
        if ($roundValues) {
2697
            $whole = floor($percentage); // 1
2698
            $fraction = $percentage - $whole; // .25
2699
2700
            // Formats values
2701
            if ($fraction >= 0.5) {
2702
                $percentage = ceil($percentage);
2703
            } else {
2704
                $percentage = round($percentage);
2705
            }
2706
2707
            $whole = floor($score); // 1
2708
            $fraction = $score - $whole; // .25
2709
            if ($fraction >= 0.5) {
2710
                $score = ceil($score);
2711
            } else {
2712
                $score = round($score);
2713
            }
2714
2715
            $whole = floor($weight); // 1
2716
            $fraction = $weight - $whole; // .25
2717
            if ($fraction >= 0.5) {
2718
                $weight = ceil($weight);
2719
            } else {
2720
                $weight = round($weight);
2721
            }
2722
        } else {
2723
            // Formats values
2724
            $percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator);
2725
            $score = float_format($score, 1, $decimalSeparator, $thousandSeparator);
2726
            $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
2727
        }
2728
2729
        if ($show_percentage) {
2730
            $percentageSign = '%';
2731
            if ($hidePercentageSign) {
2732
                $percentageSign = '';
2733
            }
2734
            $html = $percentage."$percentageSign ($score / $weight)";
2735
            if ($show_only_percentage) {
2736
                $html = $percentage.$percentageSign;
2737
            }
2738
        } else {
2739
            $html = $score.' / '.$weight;
2740
        }
2741
2742
        // Over write score
2743
        $scoreBasedInModel = self::convertScoreToModel($percentage);
2744
        if (!empty($scoreBasedInModel)) {
2745
            $html = $scoreBasedInModel;
2746
        }
2747
2748
        // Ignore other formats and use the configuratio['exercise_score_format'] value
2749
        // But also keep the round values settings.
2750
        $format = api_get_configuration_value('exercise_score_format');
2751
        if (!empty($format)) {
2752
            $html = ScoreDisplay::instance()->display_score([$score, $weight], $format);
2753
        }
2754
2755
        $html = Display::span($html, ['class' => 'score_exercise']);
2756
2757
        return $html;
2758
    }
2759
2760
    /**
2761
     * @param array $model
2762
     * @param float $percentage
2763
     *
2764
     * @return string
2765
     */
2766
    public static function getModelStyle($model, $percentage)
2767
    {
2768
        $modelWithStyle = '<span class="'.$model['css_class'].'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>';
2769
2770
        return $modelWithStyle;
2771
    }
2772
2773
    /**
2774
     * @param float $percentage value between 0 and 100
2775
     *
2776
     * @return string
2777
     */
2778
    public static function convertScoreToModel($percentage)
2779
    {
2780
        $model = self::getCourseScoreModel();
2781
        if (!empty($model)) {
2782
            $scoreWithGrade = [];
2783
            foreach ($model['score_list'] as $item) {
2784
                if ($percentage >= $item['min'] && $percentage <= $item['max']) {
2785
                    $scoreWithGrade = $item;
2786
                    break;
2787
                }
2788
            }
2789
2790
            if (!empty($scoreWithGrade)) {
2791
                return self::getModelStyle($scoreWithGrade, $percentage);
2792
            }
2793
        }
2794
2795
        return '';
2796
    }
2797
2798
    /**
2799
     * @return array
2800
     */
2801
    public static function getCourseScoreModel()
2802
    {
2803
        $modelList = self::getScoreModels();
2804
        if (empty($modelList)) {
2805
            return [];
2806
        }
2807
2808
        $courseInfo = api_get_course_info();
2809
        if (!empty($courseInfo)) {
2810
            $scoreModelId = api_get_course_setting('score_model_id');
2811
            if ($scoreModelId != -1) {
2812
                $modelIdList = array_column($modelList['models'], 'id');
2813
                if (in_array($scoreModelId, $modelIdList)) {
2814
                    foreach ($modelList['models'] as $item) {
2815
                        if ($item['id'] == $scoreModelId) {
2816
                            return $item;
2817
                        }
2818
                    }
2819
                }
2820
            }
2821
        }
2822
2823
        return [];
2824
    }
2825
2826
    /**
2827
     * @return array
2828
     */
2829
    public static function getScoreModels()
2830
    {
2831
        return api_get_configuration_value('score_grade_model');
2832
    }
2833
2834
    /**
2835
     * @param float  $score
2836
     * @param float  $weight
2837
     * @param string $pass_percentage
2838
     *
2839
     * @return bool
2840
     */
2841
    public static function isSuccessExerciseResult($score, $weight, $pass_percentage)
2842
    {
2843
        $percentage = float_format(
2844
            ($score / ($weight != 0 ? $weight : 1)) * 100,
2845
            1
2846
        );
2847
        if (isset($pass_percentage) && !empty($pass_percentage)) {
2848
            if ($percentage >= $pass_percentage) {
2849
                return true;
2850
            }
2851
        }
2852
2853
        return false;
2854
    }
2855
2856
    /**
2857
     * @param string $name
2858
     * @param $weight
2859
     * @param $selected
2860
     *
2861
     * @return bool
2862
     */
2863
    public static function addScoreModelInput(
2864
        FormValidator $form,
2865
        $name,
2866
        $weight,
2867
        $selected
2868
    ) {
2869
        $model = self::getCourseScoreModel();
2870
        if (empty($model)) {
2871
            return false;
2872
        }
2873
2874
        /** @var HTML_QuickForm_select $element */
2875
        $element = $form->createElement(
2876
            'select',
2877
            $name,
2878
            get_lang('Score'),
2879
            [],
2880
            ['class' => 'exercise_mark_select']
2881
        );
2882
2883
        foreach ($model['score_list'] as $item) {
2884
            $i = api_number_format($item['score_to_qualify'] / 100 * $weight, 2);
2885
            $label = self::getModelStyle($item, $i);
2886
            $attributes = [
2887
                'class' => $item['css_class'],
2888
            ];
2889
            if ($selected == $i) {
2890
                $attributes['selected'] = 'selected';
2891
            }
2892
            $element->addOption($label, $i, $attributes);
2893
        }
2894
        $form->addElement($element);
2895
    }
2896
2897
    /**
2898
     * @return string
2899
     */
2900
    public static function getJsCode()
2901
    {
2902
        // Filling the scores with the right colors.
2903
        $models = self::getCourseScoreModel();
2904
        $cssListToString = '';
2905
        if (!empty($models)) {
2906
            $cssList = array_column($models['score_list'], 'css_class');
2907
            $cssListToString = implode(' ', $cssList);
2908
        }
2909
2910
        if (empty($cssListToString)) {
2911
            return '';
2912
        }
2913
        $js = <<<EOT
2914
        
2915
        function updateSelect(element) {
2916
            var spanTag = element.parent().find('span.filter-option');
2917
            var value = element.val();
2918
            var selectId = element.attr('id');
2919
            var optionClass = $('#' + selectId + ' option[value="'+value+'"]').attr('class');
2920
            spanTag.removeClass('$cssListToString');
2921
            spanTag.addClass(optionClass);
2922
        }
2923
        
2924
        $(function() {
2925
            // Loading values
2926
            $('.exercise_mark_select').on('loaded.bs.select', function() {
2927
                updateSelect($(this));
2928
            });
2929
            // On change
2930
            $('.exercise_mark_select').on('changed.bs.select', function() {
2931
                updateSelect($(this));
2932
            });
2933
        });
2934
EOT;
2935
2936
        return $js;
2937
    }
2938
2939
    /**
2940
     * @param float  $score
2941
     * @param float  $weight
2942
     * @param string $pass_percentage
2943
     *
2944
     * @return string
2945
     */
2946
    public static function showSuccessMessage($score, $weight, $pass_percentage)
2947
    {
2948
        $res = '';
2949
        if (self::isPassPercentageEnabled($pass_percentage)) {
2950
            $isSuccess = self::isSuccessExerciseResult(
2951
                $score,
2952
                $weight,
2953
                $pass_percentage
2954
            );
2955
2956
            if ($isSuccess) {
2957
                $html = get_lang('Congratulations you passed the test!');
2958
                $icon = Display::return_icon(
2959
                    'completed.png',
2960
                    get_lang('Correct'),
2961
                    [],
2962
                    ICON_SIZE_MEDIUM
2963
                );
2964
            } else {
2965
                $html = get_lang('You didn\'t reach the minimum score');
2966
                $icon = Display::return_icon(
2967
                    'warning.png',
2968
                    get_lang('Wrong'),
2969
                    [],
2970
                    ICON_SIZE_MEDIUM
2971
                );
2972
            }
2973
            $html = Display::tag('h4', $html);
2974
            $html .= Display::tag(
2975
                'h5',
2976
                $icon,
2977
                ['style' => 'width:40px; padding:2px 10px 0px 0px']
2978
            );
2979
            $res = $html;
2980
        }
2981
2982
        return $res;
2983
    }
2984
2985
    /**
2986
     * Return true if pass_pourcentage activated (we use the pass pourcentage feature
2987
     * return false if pass_percentage = 0 (we don't use the pass pourcentage feature.
2988
     *
2989
     * @param $value
2990
     *
2991
     * @return bool
2992
     *              In this version, pass_percentage and show_success_message are disabled if
2993
     *              pass_percentage is set to 0
2994
     */
2995
    public static function isPassPercentageEnabled($value)
2996
    {
2997
        return $value > 0;
2998
    }
2999
3000
    /**
3001
     * Converts a numeric value in a percentage example 0.66666 to 66.67 %.
3002
     *
3003
     * @param $value
3004
     *
3005
     * @return float Converted number
3006
     */
3007
    public static function convert_to_percentage($value)
3008
    {
3009
        $return = '-';
3010
        if ($value != '') {
3011
            $return = float_format($value * 100, 1).' %';
3012
        }
3013
3014
        return $return;
3015
    }
3016
3017
    /**
3018
     * Getting all active exercises from a course from a session
3019
     * (if a session_id is provided we will show all the exercises in the course +
3020
     * all exercises in the session).
3021
     *
3022
     * @param array  $course_info
3023
     * @param int    $session_id
3024
     * @param bool   $check_publication_dates
3025
     * @param string $search                  Search exercise name
3026
     * @param bool   $search_all_sessions     Search exercises in all sessions
3027
     * @param   int 0 = only inactive exercises
0 ignored issues
show
Documentation Bug introduced by
The doc comment 0 at position 0 could not be parsed: Unknown type name '0' at position 0 in 0.
Loading history...
3028
     *                  1 = only active exercises,
3029
     *                  2 = all exercises
3030
     *                  3 = active <> -1
3031
     *
3032
     * @return array array with exercise data
3033
     */
3034
    public static function get_all_exercises(
3035
        $course_info = null,
3036
        $session_id = 0,
3037
        $check_publication_dates = false,
3038
        $search = '',
3039
        $search_all_sessions = false,
3040
        $active = 2
3041
    ) {
3042
        $course_id = api_get_course_int_id();
3043
3044
        if (!empty($course_info) && !empty($course_info['real_id'])) {
3045
            $course_id = $course_info['real_id'];
3046
        }
3047
3048
        if ($session_id == -1) {
3049
            $session_id = 0;
3050
        }
3051
3052
        $now = api_get_utc_datetime();
3053
        $timeConditions = '';
3054
        if ($check_publication_dates) {
3055
            // Start and end are set
3056
            $timeConditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' )  OR ";
3057
            // only start is set
3058
            $timeConditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
3059
            // only end is set
3060
            $timeConditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
3061
            // nothing is set
3062
            $timeConditions .= ' (start_time IS NULL AND end_time IS NULL)) ';
3063
        }
3064
3065
        $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
3066
        $needle = !empty($search) ? "%".$search."%" : '';
3067
3068
        // Show courses by active status
3069
        $active_sql = '';
3070
        if ($active == 3) {
3071
            $active_sql = ' active <> -1 AND';
3072
        } else {
3073
            if ($active != 2) {
3074
                $active_sql = sprintf(' active = %d AND', $active);
3075
            }
3076
        }
3077
3078
        if ($search_all_sessions == true) {
3079
            $conditions = [
3080
                'where' => [
3081
                    $active_sql.' c_id = ? '.$needle_where.$timeConditions => [
3082
                        $course_id,
3083
                        $needle,
3084
                    ],
3085
                ],
3086
                'order' => 'title',
3087
            ];
3088
        } else {
3089
            if (empty($session_id)) {
3090
                $conditions = [
3091
                    'where' => [
3092
                        $active_sql.' (session_id = 0 OR session_id IS NULL) AND c_id = ? '.$needle_where.$timeConditions => [
3093
                            $course_id,
3094
                            $needle,
3095
                        ],
3096
                    ],
3097
                    'order' => 'title',
3098
                ];
3099
            } else {
3100
                $conditions = [
3101
                    'where' => [
3102
                        $active_sql.' (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ? '.$needle_where.$timeConditions => [
3103
                            $session_id,
3104
                            $course_id,
3105
                            $needle,
3106
                        ],
3107
                    ],
3108
                    'order' => 'title',
3109
                ];
3110
            }
3111
        }
3112
3113
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3114
3115
        return Database::select('*', $table, $conditions);
3116
    }
3117
3118
    /**
3119
     * Getting all exercises (active only or all)
3120
     * from a course from a session
3121
     * (if a session_id is provided we will show all the exercises in the
3122
     * course + all exercises in the session).
3123
     *
3124
     * @param   array   course data
3125
     * @param   int     session id
3126
     * @param    int        course c_id
3127
     * @param bool $only_active_exercises
3128
     *
3129
     * @return array array with exercise data
3130
     *               modified by Hubert Borderiou
3131
     */
3132
    public static function get_all_exercises_for_course_id(
3133
        $course_info = null,
3134
        $session_id = 0,
3135
        $course_id = 0,
3136
        $only_active_exercises = true
3137
    ) {
3138
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
3139
3140
        if ($only_active_exercises) {
3141
            // Only active exercises.
3142
            $sql_active_exercises = "active = 1 AND ";
3143
        } else {
3144
            // Not only active means visible and invisible NOT deleted (-2)
3145
            $sql_active_exercises = "active IN (1, 0) AND ";
3146
        }
3147
3148
        if ($session_id == -1) {
3149
            $session_id = 0;
3150
        }
3151
3152
        $params = [
3153
            $session_id,
3154
            $course_id,
3155
        ];
3156
3157
        if (empty($session_id)) {
3158
            $conditions = [
3159
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL) AND c_id = ?" => [$course_id]],
3160
                'order' => 'title',
3161
            ];
3162
        } else {
3163
            // All exercises
3164
            $conditions = [
3165
                'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id=?" => $params],
3166
                'order' => 'title',
3167
            ];
3168
        }
3169
3170
        return Database::select('*', $table, $conditions);
3171
    }
3172
3173
    /**
3174
     * Gets the position of the score based in a given score (result/weight)
3175
     * and the exe_id based in the user list
3176
     * (NO Exercises in LPs ).
3177
     *
3178
     * @param float  $my_score      user score to be compared *attention*
3179
     *                              $my_score = score/weight and not just the score
3180
     * @param int    $my_exe_id     exe id of the exercise
3181
     *                              (this is necessary because if 2 students have the same score the one
3182
     *                              with the minor exe_id will have a best position, just to be fair and FIFO)
3183
     * @param int    $exercise_id
3184
     * @param string $course_code
3185
     * @param int    $session_id
3186
     * @param array  $user_list
3187
     * @param bool   $return_string
3188
     *
3189
     * @return int the position of the user between his friends in a course
3190
     *             (or course within a session)
3191
     */
3192
    public static function get_exercise_result_ranking(
3193
        $my_score,
3194
        $my_exe_id,
3195
        $exercise_id,
3196
        $course_code,
3197
        $session_id = 0,
3198
        $user_list = [],
3199
        $return_string = true
3200
    ) {
3201
        //No score given we return
3202
        if (is_null($my_score)) {
3203
            return '-';
3204
        }
3205
        if (empty($user_list)) {
3206
            return '-';
3207
        }
3208
3209
        $best_attempts = [];
3210
        foreach ($user_list as $user_data) {
3211
            $user_id = $user_data['user_id'];
3212
            $best_attempts[$user_id] = self::get_best_attempt_by_user(
3213
                $user_id,
3214
                $exercise_id,
3215
                $course_code,
3216
                $session_id
3217
            );
3218
        }
3219
3220
        if (empty($best_attempts)) {
3221
            return 1;
3222
        } else {
3223
            $position = 1;
3224
            $my_ranking = [];
3225
            foreach ($best_attempts as $user_id => $result) {
3226
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3227
                    $my_ranking[$user_id] = $result['score'] / $result['max_score'];
3228
                } else {
3229
                    $my_ranking[$user_id] = 0;
3230
                }
3231
            }
3232
            //if (!empty($my_ranking)) {
3233
            asort($my_ranking);
3234
            $position = count($my_ranking);
3235
            if (!empty($my_ranking)) {
3236
                foreach ($my_ranking as $user_id => $ranking) {
3237
                    if ($my_score >= $ranking) {
3238
                        if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
3239
                            $exe_id = $best_attempts[$user_id]['exe_id'];
3240
                            if ($my_exe_id < $exe_id) {
3241
                                $position--;
3242
                            }
3243
                        } else {
3244
                            $position--;
3245
                        }
3246
                    }
3247
                }
3248
            }
3249
            //}
3250
            $return_value = [
3251
                'position' => $position,
3252
                'count' => count($my_ranking),
3253
            ];
3254
3255
            if ($return_string) {
3256
                if (!empty($position) && !empty($my_ranking)) {
3257
                    $return_value = $position.'/'.count($my_ranking);
3258
                } else {
3259
                    $return_value = '-';
3260
                }
3261
            }
3262
3263
            return $return_value;
3264
        }
3265
    }
3266
3267
    /**
3268
     * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
3269
     * (NO Exercises in LPs ) old functionality by attempt.
3270
     *
3271
     * @param   float   user score to be compared attention => score/weight
3272
     * @param   int     exe id of the exercise
3273
     * (this is necessary because if 2 students have the same score the one
3274
     * with the minor exe_id will have a best position, just to be fair and FIFO)
3275
     * @param   int     exercise id
3276
     * @param   string  course code
3277
     * @param   int     session id
3278
     * @param bool $return_string
3279
     *
3280
     * @return int the position of the user between his friends in a course (or course within a session)
3281
     */
3282
    public static function get_exercise_result_ranking_by_attempt(
3283
        $my_score,
3284
        $my_exe_id,
3285
        $exercise_id,
3286
        $courseId,
3287
        $session_id = 0,
3288
        $return_string = true
3289
    ) {
3290
        if (empty($session_id)) {
3291
            $session_id = 0;
3292
        }
3293
        if (is_null($my_score)) {
3294
            return '-';
3295
        }
3296
        $user_results = Event::get_all_exercise_results(
3297
            $exercise_id,
3298
            $courseId,
3299
            $session_id,
3300
            false
3301
        );
3302
        $position_data = [];
3303
        if (empty($user_results)) {
3304
            return 1;
3305
        } else {
3306
            $position = 1;
3307
            $my_ranking = [];
3308
            foreach ($user_results as $result) {
3309
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3310
                    $my_ranking[$result['exe_id']] = $result['score'] / $result['max_score'];
3311
                } else {
3312
                    $my_ranking[$result['exe_id']] = 0;
3313
                }
3314
            }
3315
            asort($my_ranking);
3316
            $position = count($my_ranking);
3317
            if (!empty($my_ranking)) {
3318
                foreach ($my_ranking as $exe_id => $ranking) {
3319
                    if ($my_score >= $ranking) {
3320
                        if ($my_score == $ranking) {
3321
                            if ($my_exe_id < $exe_id) {
3322
                                $position--;
3323
                            }
3324
                        } else {
3325
                            $position--;
3326
                        }
3327
                    }
3328
                }
3329
            }
3330
            $return_value = [
3331
                'position' => $position,
3332
                'count' => count($my_ranking),
3333
            ];
3334
3335
            if ($return_string) {
3336
                if (!empty($position) && !empty($my_ranking)) {
3337
                    return $position.'/'.count($my_ranking);
3338
                }
3339
            }
3340
3341
            return $return_value;
3342
        }
3343
    }
3344
3345
    /**
3346
     * Get the best attempt in a exercise (NO Exercises in LPs ).
3347
     *
3348
     * @param int $exercise_id
3349
     * @param int $courseId
3350
     * @param int $session_id
3351
     *
3352
     * @return array
3353
     */
3354
    public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id)
3355
    {
3356
        $user_results = Event::get_all_exercise_results(
3357
            $exercise_id,
3358
            $courseId,
3359
            $session_id,
3360
            false
3361
        );
3362
3363
        $best_score_data = [];
3364
        $best_score = 0;
3365
        if (!empty($user_results)) {
3366
            foreach ($user_results as $result) {
3367
                if (!empty($result['max_score']) &&
3368
                    intval($result['max_score']) != 0
3369
                ) {
3370
                    $score = $result['score'] / $result['max_score'];
3371
                    if ($score >= $best_score) {
3372
                        $best_score = $score;
3373
                        $best_score_data = $result;
3374
                    }
3375
                }
3376
            }
3377
        }
3378
3379
        return $best_score_data;
3380
    }
3381
3382
    /**
3383
     * Get the best score in a exercise (NO Exercises in LPs ).
3384
     *
3385
     * @param int $user_id
3386
     * @param int $exercise_id
3387
     * @param int $courseId
3388
     * @param int $session_id
3389
     *
3390
     * @return array
3391
     */
3392
    public static function get_best_attempt_by_user(
3393
        $user_id,
3394
        $exercise_id,
3395
        $courseId,
3396
        $session_id
3397
    ) {
3398
        $user_results = Event::get_all_exercise_results(
3399
            $exercise_id,
3400
            $courseId,
3401
            $session_id,
3402
            false,
3403
            $user_id
3404
        );
3405
        $best_score_data = [];
3406
        $best_score = 0;
3407
        if (!empty($user_results)) {
3408
            foreach ($user_results as $result) {
3409
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3410
                    $score = $result['score'] / $result['max_score'];
3411
                    if ($score >= $best_score) {
3412
                        $best_score = $score;
3413
                        $best_score_data = $result;
3414
                    }
3415
                }
3416
            }
3417
        }
3418
3419
        return $best_score_data;
3420
    }
3421
3422
    /**
3423
     * Get average score (NO Exercises in LPs ).
3424
     *
3425
     * @param    int    exercise id
3426
     * @param int $courseId
3427
     * @param    int    session id
3428
     *
3429
     * @return float Average score
3430
     */
3431
    public static function get_average_score($exercise_id, $courseId, $session_id)
3432
    {
3433
        $user_results = Event::get_all_exercise_results(
3434
            $exercise_id,
3435
            $courseId,
3436
            $session_id
3437
        );
3438
        $avg_score = 0;
3439
        if (!empty($user_results)) {
3440
            foreach ($user_results as $result) {
3441
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3442
                    $score = $result['score'] / $result['max_score'];
3443
                    $avg_score += $score;
3444
                }
3445
            }
3446
            $avg_score = float_format($avg_score / count($user_results), 1);
3447
        }
3448
3449
        return $avg_score;
3450
    }
3451
3452
    /**
3453
     * Get average score by score (NO Exercises in LPs ).
3454
     *
3455
     * @param int $courseId
3456
     * @param    int    session id
3457
     *
3458
     * @return float Average score
3459
     */
3460
    public static function get_average_score_by_course($courseId, $session_id)
3461
    {
3462
        $user_results = Event::get_all_exercise_results_by_course(
3463
            $courseId,
3464
            $session_id,
3465
            false
3466
        );
3467
        $avg_score = 0;
3468
        if (!empty($user_results)) {
3469
            foreach ($user_results as $result) {
3470
                if (!empty($result['max_score']) && intval(
3471
                        $result['max_score']
3472
                    ) != 0
3473
                ) {
3474
                    $score = $result['score'] / $result['max_score'];
3475
                    $avg_score += $score;
3476
                }
3477
            }
3478
            // We assume that all max_score
3479
            $avg_score = $avg_score / count($user_results);
3480
        }
3481
3482
        return $avg_score;
3483
    }
3484
3485
    /**
3486
     * @param int $user_id
3487
     * @param int $courseId
3488
     * @param int $session_id
3489
     *
3490
     * @return float|int
3491
     */
3492
    public static function get_average_score_by_course_by_user(
3493
        $user_id,
3494
        $courseId,
3495
        $session_id
3496
    ) {
3497
        $user_results = Event::get_all_exercise_results_by_user(
3498
            $user_id,
3499
            $courseId,
3500
            $session_id
3501
        );
3502
        $avg_score = 0;
3503
        if (!empty($user_results)) {
3504
            foreach ($user_results as $result) {
3505
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3506
                    $score = $result['score'] / $result['max_score'];
3507
                    $avg_score += $score;
3508
                }
3509
            }
3510
            // We assume that all max_score
3511
            $avg_score = ($avg_score / count($user_results));
3512
        }
3513
3514
        return $avg_score;
3515
    }
3516
3517
    /**
3518
     * Get average score by score (NO Exercises in LPs ).
3519
     *
3520
     * @param int $exercise_id
3521
     * @param int $courseId
3522
     * @param int $session_id
3523
     * @param int $user_count
3524
     *
3525
     * @return float Best average score
3526
     */
3527
    public static function get_best_average_score_by_exercise(
3528
        $exercise_id,
3529
        $courseId,
3530
        $session_id,
3531
        $user_count
3532
    ) {
3533
        $user_results = Event::get_best_exercise_results_by_user(
3534
            $exercise_id,
3535
            $courseId,
3536
            $session_id
3537
        );
3538
        $avg_score = 0;
3539
        if (!empty($user_results)) {
3540
            foreach ($user_results as $result) {
3541
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3542
                    $score = $result['score'] / $result['max_score'];
3543
                    $avg_score += $score;
3544
                }
3545
            }
3546
            // We asumme that all max_score
3547
            if (!empty($user_count)) {
3548
                $avg_score = float_format($avg_score / $user_count, 1) * 100;
3549
            } else {
3550
                $avg_score = 0;
3551
            }
3552
        }
3553
3554
        return $avg_score;
3555
    }
3556
3557
    /**
3558
     * Get average score by score (NO Exercises in LPs ).
3559
     *
3560
     * @param int $exercise_id
3561
     * @param int $courseId
3562
     * @param int $session_id
3563
     *
3564
     * @return float Best average score
3565
     */
3566
    public static function getBestScoreByExercise(
3567
        $exercise_id,
3568
        $courseId,
3569
        $session_id
3570
    ) {
3571
        $user_results = Event::get_best_exercise_results_by_user(
3572
            $exercise_id,
3573
            $courseId,
3574
            $session_id
3575
        );
3576
        $avg_score = 0;
3577
        if (!empty($user_results)) {
3578
            foreach ($user_results as $result) {
3579
                if (!empty($result['max_score']) && intval($result['max_score']) != 0) {
3580
                    $score = $result['score'] / $result['max_score'];
3581
                    $avg_score += $score;
3582
                }
3583
            }
3584
        }
3585
3586
        return $avg_score;
3587
    }
3588
3589
    /**
3590
     * @param string $course_code
3591
     * @param int    $session_id
3592
     *
3593
     * @return array
3594
     */
3595
    public static function get_exercises_to_be_taken($course_code, $session_id)
3596
    {
3597
        $course_info = api_get_course_info($course_code);
3598
        $exercises = self::get_all_exercises($course_info, $session_id);
3599
        $result = [];
3600
        $now = time() + 15 * 24 * 60 * 60;
3601
        foreach ($exercises as $exercise_item) {
3602
            if (isset($exercise_item['end_time']) &&
3603
                !empty($exercise_item['end_time']) &&
3604
                api_strtotime($exercise_item['end_time'], 'UTC') < $now
3605
            ) {
3606
                $result[] = $exercise_item;
3607
            }
3608
        }
3609
3610
        return $result;
3611
    }
3612
3613
    /**
3614
     * Get student results (only in completed exercises) stats by question.
3615
     *
3616
     * @param int    $question_id
3617
     * @param int    $exercise_id
3618
     * @param string $course_code
3619
     * @param int    $session_id
3620
     *
3621
     * @return array
3622
     */
3623
    public static function get_student_stats_by_question(
3624
        $question_id,
3625
        $exercise_id,
3626
        $course_code,
3627
        $session_id
3628
    ) {
3629
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3630
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3631
3632
        $question_id = (int) $question_id;
3633
        $exercise_id = (int) $exercise_id;
3634
        $course_code = Database::escape_string($course_code);
3635
        $session_id = (int) $session_id;
3636
        $courseId = api_get_course_int_id($course_code);
3637
3638
        $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
3639
    		FROM $track_exercises e
3640
    		INNER JOIN $track_attempt a
3641
    		ON (
3642
    		    a.exe_id = e.exe_id AND
3643
    		    e.c_id = a.c_id AND
3644
    		    e.session_id  = a.session_id
3645
            )
3646
    		WHERE
3647
    		    exe_exo_id 	= $exercise_id AND
3648
                a.c_id = $courseId AND
3649
                e.session_id = $session_id AND
3650
                question_id = $question_id AND
3651
                status = ''
3652
            LIMIT 1";
3653
        $result = Database::query($sql);
3654
        $return = [];
3655
        if ($result) {
3656
            $return = Database::fetch_array($result, 'ASSOC');
3657
        }
3658
3659
        return $return;
3660
    }
3661
3662
    /**
3663
     * Get the correct answer count for a fill blanks question.
3664
     *
3665
     * @param int $question_id
3666
     * @param int $exercise_id
3667
     *
3668
     * @return array
3669
     */
3670
    public static function getNumberStudentsFillBlanksAnswerCount(
3671
        $question_id,
3672
        $exercise_id
3673
    ) {
3674
        $listStudentsId = [];
3675
        $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3676
            api_get_course_id(),
3677
            true
3678
        );
3679
        foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3680
            $listStudentsId[] = $listStudentInfo['user_id'];
3681
        }
3682
3683
        $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
3684
            $exercise_id,
3685
            $question_id,
3686
            $listStudentsId,
3687
            '1970-01-01',
3688
            '3000-01-01'
3689
        );
3690
3691
        $arrayCount = [];
3692
3693
        foreach ($listFillTheBlankResult as $resultCount) {
3694
            foreach ($resultCount as $index => $count) {
3695
                //this is only for declare the array index per answer
3696
                $arrayCount[$index] = 0;
3697
            }
3698
        }
3699
3700
        foreach ($listFillTheBlankResult as $resultCount) {
3701
            foreach ($resultCount as $index => $count) {
3702
                $count = ($count === 0) ? 1 : 0;
3703
                $arrayCount[$index] += $count;
3704
            }
3705
        }
3706
3707
        return $arrayCount;
3708
    }
3709
3710
    /**
3711
     * Get the number of questions with answers.
3712
     *
3713
     * @param int    $question_id
3714
     * @param int    $exercise_id
3715
     * @param string $course_code
3716
     * @param int    $session_id
3717
     * @param string $questionType
3718
     *
3719
     * @return int
3720
     */
3721
    public static function get_number_students_question_with_answer_count(
3722
        $question_id,
3723
        $exercise_id,
3724
        $course_code,
3725
        $session_id,
3726
        $questionType = ''
3727
    ) {
3728
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3729
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3730
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3731
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3732
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3733
3734
        $question_id = intval($question_id);
3735
        $exercise_id = intval($exercise_id);
3736
        $courseId = api_get_course_int_id($course_code);
3737
        $session_id = intval($session_id);
3738
3739
        if ($questionType == FILL_IN_BLANKS) {
3740
            $listStudentsId = [];
3741
            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3742
                api_get_course_id(),
3743
                true
3744
            );
3745
            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3746
                $listStudentsId[] = $listStudentInfo['user_id'];
3747
            }
3748
3749
            $listFillTheBlankResult = FillBlanks::getFillTheBlankResult(
3750
                $exercise_id,
3751
                $question_id,
3752
                $listStudentsId,
3753
                '1970-01-01',
3754
                '3000-01-01'
3755
            );
3756
3757
            return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
3758
        }
3759
3760
        if (empty($session_id)) {
3761
            $courseCondition = "
3762
            INNER JOIN $courseUser cu
3763
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3764
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3765
        } else {
3766
            $courseCondition = "
3767
            INNER JOIN $courseUserSession cu
3768
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3769
            $courseConditionWhere = " AND cu.status = 0 ";
3770
        }
3771
3772
        $sql = "SELECT DISTINCT exe_user_id
3773
    		FROM $track_exercises e
3774
    		INNER JOIN $track_attempt a
3775
    		ON (
3776
    		    a.exe_id = e.exe_id AND
3777
    		    e.c_id = a.c_id AND
3778
    		    e.session_id  = a.session_id
3779
            )
3780
            INNER JOIN $courseTable c
3781
            ON (c.id = a.c_id)
3782
    		$courseCondition
3783
    		WHERE
3784
    		    exe_exo_id = $exercise_id AND
3785
                a.c_id = $courseId AND
3786
                e.session_id = $session_id AND
3787
                question_id = $question_id AND
3788
                answer <> '0' AND
3789
                e.status = ''
3790
                $courseConditionWhere
3791
            ";
3792
        $result = Database::query($sql);
3793
        $return = 0;
3794
        if ($result) {
3795
            $return = Database::num_rows($result);
3796
        }
3797
3798
        return $return;
3799
    }
3800
3801
    /**
3802
     * Get number of answers to hotspot questions.
3803
     *
3804
     * @param int    $answer_id
3805
     * @param int    $question_id
3806
     * @param int    $exercise_id
3807
     * @param string $courseId
3808
     * @param int    $session_id
3809
     *
3810
     * @return int
3811
     */
3812
    public static function get_number_students_answer_hotspot_count(
3813
        $answer_id,
3814
        $question_id,
3815
        $exercise_id,
3816
        $courseId,
3817
        $session_id
3818
    ) {
3819
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3820
        $track_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
3821
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3822
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3823
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3824
3825
        $question_id = (int) $question_id;
3826
        $answer_id = (int) $answer_id;
3827
        $exercise_id = (int) $exercise_id;
3828
        $courseId = (int) $courseId;
3829
        $session_id = (int) $session_id;
3830
3831
        if (empty($session_id)) {
3832
            $courseCondition = "
3833
            INNER JOIN $courseUser cu
3834
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3835
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3836
        } else {
3837
            $courseCondition = "
3838
            INNER JOIN $courseUserSession cu
3839
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3840
            $courseConditionWhere = ' AND cu.status = 0 ';
3841
        }
3842
3843
        $sql = "SELECT DISTINCT exe_user_id
3844
    		FROM $track_exercises e
3845
    		INNER JOIN $track_hotspot a
3846
    		ON (a.hotspot_exe_id = e.exe_id)
3847
    		INNER JOIN $courseTable c
3848
    		ON (a.c_id = c.id)
3849
    		$courseCondition
3850
    		WHERE
3851
    		    exe_exo_id              = $exercise_id AND
3852
                a.c_id 	= $courseId AND
3853
                e.session_id            = $session_id AND
3854
                hotspot_answer_id       = $answer_id AND
3855
                hotspot_question_id     = $question_id AND
3856
                hotspot_correct         =  1 AND
3857
                e.status                = ''
3858
                $courseConditionWhere
3859
            ";
3860
3861
        $result = Database::query($sql);
3862
        $return = 0;
3863
        if ($result) {
3864
            $return = Database::num_rows($result);
3865
        }
3866
3867
        return $return;
3868
    }
3869
3870
    /**
3871
     * @param int    $answer_id
3872
     * @param int    $question_id
3873
     * @param int    $exercise_id
3874
     * @param string $course_code
3875
     * @param int    $session_id
3876
     * @param string $question_type
3877
     * @param string $correct_answer
3878
     * @param string $current_answer
3879
     *
3880
     * @return int
3881
     */
3882
    public static function get_number_students_answer_count(
3883
        $answer_id,
3884
        $question_id,
3885
        $exercise_id,
3886
        $course_code,
3887
        $session_id,
3888
        $question_type = null,
3889
        $correct_answer = null,
3890
        $current_answer = null
3891
    ) {
3892
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3893
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3894
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3895
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3896
        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3897
3898
        $question_id = (int) $question_id;
3899
        $answer_id = (int) $answer_id;
3900
        $exercise_id = (int) $exercise_id;
3901
        $courseId = api_get_course_int_id($course_code);
3902
        $session_id = (int) $session_id;
3903
3904
        switch ($question_type) {
3905
            case FILL_IN_BLANKS:
3906
                $answer_condition = '';
3907
                $select_condition = ' e.exe_id, answer ';
3908
                break;
3909
            case MATCHING:
3910
            case MATCHING_DRAGGABLE:
3911
            default:
3912
                $answer_condition = " answer = $answer_id AND ";
3913
                $select_condition = ' DISTINCT exe_user_id ';
3914
        }
3915
3916
        if (empty($session_id)) {
3917
            $courseCondition = "
3918
            INNER JOIN $courseUser cu
3919
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3920
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3921
        } else {
3922
            $courseCondition = "
3923
            INNER JOIN $courseUserSession cu
3924
            ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
3925
            $courseConditionWhere = ' AND cu.status = 0 ';
3926
        }
3927
3928
        $sql = "SELECT $select_condition
3929
    		FROM $track_exercises e
3930
    		INNER JOIN $track_attempt a
3931
    		ON (
3932
    		    a.exe_id = e.exe_id AND
3933
    		    e.c_id = a.c_id AND
3934
    		    e.session_id  = a.session_id
3935
            )
3936
            INNER JOIN $courseTable c
3937
            ON c.id = a.c_id
3938
    		$courseCondition
3939
    		WHERE
3940
    		    exe_exo_id = $exercise_id AND
3941
                a.c_id = $courseId AND
3942
                e.session_id = $session_id AND
3943
                $answer_condition
3944
                question_id = $question_id AND
3945
                e.status = ''
3946
                $courseConditionWhere
3947
            ";
3948
        $result = Database::query($sql);
3949
        $return = 0;
3950
        if ($result) {
3951
            $good_answers = 0;
3952
            switch ($question_type) {
3953
                case FILL_IN_BLANKS:
3954
                    while ($row = Database::fetch_array($result, 'ASSOC')) {
3955
                        $fill_blank = self::check_fill_in_blanks(
3956
                            $correct_answer,
3957
                            $row['answer'],
3958
                            $current_answer
3959
                        );
3960
                        if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) {
3961
                            $good_answers++;
3962
                        }
3963
                    }
3964
3965
                    return $good_answers;
3966
                    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...
3967
                case MATCHING:
3968
                case MATCHING_DRAGGABLE:
3969
                default:
3970
                    $return = Database::num_rows($result);
3971
            }
3972
        }
3973
3974
        return $return;
3975
    }
3976
3977
    /**
3978
     * @param array  $answer
3979
     * @param string $user_answer
3980
     *
3981
     * @return array
3982
     */
3983
    public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
3984
    {
3985
        // the question is encoded like this
3986
        // [A] B [C] D [E] F::10,10,10@1
3987
        // number 1 before the "@" means that is a switchable fill in blank question
3988
        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3989
        // means that is a normal fill blank question
3990
        // first we explode the "::"
3991
        $pre_array = explode('::', $answer);
3992
        // is switchable fill blank or not
3993
        $last = count($pre_array) - 1;
3994
        $is_set_switchable = explode('@', $pre_array[$last]);
3995
        $switchable_answer_set = false;
3996
        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3997
            $switchable_answer_set = true;
3998
        }
3999
        $answer = '';
4000
        for ($k = 0; $k < $last; $k++) {
4001
            $answer .= $pre_array[$k];
4002
        }
4003
        // splits weightings that are joined with a comma
4004
        $answerWeighting = explode(',', $is_set_switchable[0]);
4005
4006
        // we save the answer because it will be modified
4007
        //$temp = $answer;
4008
        $temp = $answer;
4009
4010
        $answer = '';
4011
        $j = 0;
4012
        //initialise answer tags
4013
        $user_tags = $correct_tags = $real_text = [];
4014
        // the loop will stop at the end of the text
4015
        while (1) {
4016
            // quits the loop if there are no more blanks (detect '[')
4017
            if (($pos = api_strpos($temp, '[')) === false) {
4018
                // adds the end of the text
4019
                $answer = $temp;
4020
                $real_text[] = $answer;
4021
                break; //no more "blanks", quit the loop
4022
            }
4023
            // adds the piece of text that is before the blank
4024
            //and ends with '[' into a general storage array
4025
            $real_text[] = api_substr($temp, 0, $pos + 1);
4026
            $answer .= api_substr($temp, 0, $pos + 1);
4027
            //take the string remaining (after the last "[" we found)
4028
            $temp = api_substr($temp, $pos + 1);
4029
            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4030
            if (($pos = api_strpos($temp, ']')) === false) {
4031
                // adds the end of the text
4032
                $answer .= $temp;
4033
                break;
4034
            }
4035
4036
            $str = $user_answer;
4037
4038
            preg_match_all('#\[([^[]*)\]#', $str, $arr);
4039
            $str = str_replace('\r\n', '', $str);
4040
            $choices = $arr[1];
4041
            $choice = [];
4042
            $check = false;
4043
            $i = 0;
4044
            foreach ($choices as $item) {
4045
                if ($current_answer === $item) {
4046
                    $check = true;
4047
                }
4048
                if ($check) {
4049
                    $choice[] = $item;
4050
                    $i++;
4051
                }
4052
                if ($i == 3) {
4053
                    break;
4054
                }
4055
            }
4056
            $tmp = api_strrpos($choice[$j], ' / ');
4057
4058
            if ($tmp !== false) {
4059
                $choice[$j] = api_substr($choice[$j], 0, $tmp);
4060
            }
4061
4062
            $choice[$j] = trim($choice[$j]);
4063
4064
            //Needed to let characters ' and " to work as part of an answer
4065
            $choice[$j] = stripslashes($choice[$j]);
4066
4067
            $user_tags[] = api_strtolower($choice[$j]);
4068
            //put the contents of the [] answer tag into correct_tags[]
4069
            $correct_tags[] = api_strtolower(api_substr($temp, 0, $pos));
4070
            $j++;
4071
            $temp = api_substr($temp, $pos + 1);
4072
        }
4073
4074
        $answer = '';
4075
        $real_correct_tags = $correct_tags;
4076
        $chosen_list = [];
4077
        $good_answer = [];
4078
4079
        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...
4080
            if (!$switchable_answer_set) {
4081
                //needed to parse ' and " characters
4082
                $user_tags[$i] = stripslashes($user_tags[$i]);
4083
                if ($correct_tags[$i] == $user_tags[$i]) {
4084
                    $good_answer[$correct_tags[$i]] = 1;
4085
                } elseif (!empty($user_tags[$i])) {
4086
                    $good_answer[$correct_tags[$i]] = 0;
4087
                } else {
4088
                    $good_answer[$correct_tags[$i]] = 0;
4089
                }
4090
            } else {
4091
                // switchable fill in the blanks
4092
                if (in_array($user_tags[$i], $correct_tags)) {
4093
                    $correct_tags = array_diff($correct_tags, $chosen_list);
4094
                    $good_answer[$correct_tags[$i]] = 1;
4095
                } elseif (!empty($user_tags[$i])) {
4096
                    $good_answer[$correct_tags[$i]] = 0;
4097
                } else {
4098
                    $good_answer[$correct_tags[$i]] = 0;
4099
                }
4100
            }
4101
            // adds the correct word, followed by ] to close the blank
4102
            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
4103
            if (isset($real_text[$i + 1])) {
4104
                $answer .= $real_text[$i + 1];
4105
            }
4106
        }
4107
4108
        return $good_answer;
4109
    }
4110
4111
    /**
4112
     * @param int    $exercise_id
4113
     * @param string $course_code
4114
     * @param int    $session_id
4115
     *
4116
     * @return int
4117
     */
4118
    public static function get_number_students_finish_exercise(
4119
        $exercise_id,
4120
        $course_code,
4121
        $session_id
4122
    ) {
4123
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
4124
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
4125
4126
        $exercise_id = (int) $exercise_id;
4127
        $course_code = Database::escape_string($course_code);
4128
        $session_id = (int) $session_id;
4129
4130
        $sql = "SELECT DISTINCT exe_user_id
4131
                FROM $track_exercises e
4132
                INNER JOIN $track_attempt a
4133
                ON (a.exe_id = e.exe_id)
4134
                WHERE
4135
                    exe_exo_id 	 = $exercise_id AND
4136
                    course_code  = '$course_code' AND
4137
                    e.session_id = $session_id AND
4138
                    status = ''";
4139
        $result = Database::query($sql);
4140
        $return = 0;
4141
        if ($result) {
4142
            $return = Database::num_rows($result);
4143
        }
4144
4145
        return $return;
4146
    }
4147
4148
    /**
4149
     * Return an HTML select menu with the student groups.
4150
     *
4151
     * @param string $name     is the name and the id of the <select>
4152
     * @param string $default  default value for option
4153
     * @param string $onchange
4154
     *
4155
     * @return string the html code of the <select>
4156
     */
4157
    public static function displayGroupMenu($name, $default, $onchange = "")
4158
    {
4159
        // check the default value of option
4160
        $tabSelected = [$default => " selected='selected' "];
4161
        $res = "";
4162
        $res .= "<select name='$name' id='$name' onchange='".$onchange."' >";
4163
        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
4164
                'AllGroups'
4165
            )." --</option>";
4166
        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
4167
                'NotInAGroup'
4168
            )." -</option>";
4169
        $tabGroups = GroupManager::get_group_list();
4170
        $currentCatId = 0;
4171
        $countGroups = count($tabGroups);
4172
        for ($i = 0; $i < $countGroups; $i++) {
4173
            $tabCategory = GroupManager::get_category_from_group(
4174
                $tabGroups[$i]['iid']
4175
            );
4176
            if ($tabCategory["id"] != $currentCatId) {
4177
                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
4178
                $currentCatId = $tabCategory["id"];
4179
            }
4180
            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".
4181
                $tabGroups[$i]["id"]."'>".
4182
                $tabGroups[$i]["name"].
4183
                "</option>";
4184
        }
4185
        $res .= "</select>";
4186
4187
        return $res;
4188
    }
4189
4190
    /**
4191
     * @param int $exe_id
4192
     */
4193
    public static function create_chat_exercise_session($exe_id)
4194
    {
4195
        if (!isset($_SESSION['current_exercises'])) {
4196
            $_SESSION['current_exercises'] = [];
4197
        }
4198
        $_SESSION['current_exercises'][$exe_id] = true;
4199
    }
4200
4201
    /**
4202
     * @param int $exe_id
4203
     */
4204
    public static function delete_chat_exercise_session($exe_id)
4205
    {
4206
        if (isset($_SESSION['current_exercises'])) {
4207
            $_SESSION['current_exercises'][$exe_id] = false;
4208
        }
4209
    }
4210
4211
    /**
4212
     * Display the exercise results.
4213
     *
4214
     * @param Exercise $objExercise
4215
     * @param int      $exeId
4216
     * @param bool     $save_user_result save users results (true) or just show the results (false)
4217
     * @param string   $remainingMessage
4218
     */
4219
    public static function displayQuestionListByAttempt(
4220
        $objExercise,
4221
        $exeId,
4222
        $save_user_result = false,
4223
        $remainingMessage = ''
4224
    ) {
4225
        $origin = api_get_origin();
4226
        $courseCode = api_get_course_id();
4227
        $sessionId = api_get_session_id();
4228
4229
        // Getting attempt info
4230
        $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
4231
4232
        // Getting question list
4233
        $question_list = [];
4234
        $studentInfo = [];
4235
        if (!empty($exercise_stat_info['data_tracking'])) {
4236
            $studentInfo = api_get_user_info($exercise_stat_info['exe_user_id']);
4237
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
4238
        } else {
4239
            // Try getting the question list only if save result is off
4240
            if ($save_user_result == false) {
4241
                $question_list = $objExercise->get_validated_question_list();
4242
            }
4243
            if (in_array(
4244
                $objExercise->getFeedbackType(),
4245
                [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4246
            )) {
4247
                $question_list = $objExercise->get_validated_question_list();
4248
            }
4249
        }
4250
4251
        if ($objExercise->getResultAccess()) {
4252
            if ($objExercise->hasResultsAccess($exercise_stat_info) === false) {
4253
                echo Display::return_message(
4254
                    sprintf(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess())
4255
                );
4256
4257
                return false;
4258
            }
4259
4260
            if (!empty($objExercise->getResultAccess())) {
4261
                $url = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id;
4262
                echo $objExercise->returnTimeLeftDiv();
4263
                echo $objExercise->showSimpleTimeControl(
4264
                    $objExercise->getResultAccessTimeDiff($exercise_stat_info),
4265
                    $url
4266
                );
4267
            }
4268
        }
4269
4270
        $counter = 1;
4271
        $total_score = $total_weight = 0;
4272
        $exercise_content = null;
4273
4274
        // Hide results
4275
        $show_results = false;
4276
        $show_only_score = false;
4277
        if (in_array($objExercise->results_disabled,
4278
            [
4279
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4280
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
4281
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4282
            ]
4283
        )) {
4284
            $show_results = true;
4285
        }
4286
4287
        if (in_array(
4288
            $objExercise->results_disabled,
4289
            [
4290
                RESULT_DISABLE_SHOW_SCORE_ONLY,
4291
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
4292
                RESULT_DISABLE_RANKING,
4293
            ]
4294
        )
4295
        ) {
4296
            $show_only_score = true;
4297
        }
4298
4299
        // Not display expected answer, but score, and feedback
4300
        $show_all_but_expected_answer = false;
4301
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
4302
            $objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END
4303
        ) {
4304
            $show_all_but_expected_answer = true;
4305
            $show_results = true;
4306
            $show_only_score = false;
4307
        }
4308
4309
        $showTotalScoreAndUserChoicesInLastAttempt = true;
4310
        $showTotalScore = true;
4311
        $showQuestionScore = true;
4312
4313
        if (in_array(
4314
            $objExercise->results_disabled,
4315
            [
4316
                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
4317
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
4318
            ])
4319
        ) {
4320
            $show_only_score = true;
4321
            $show_results = true;
4322
            $numberAttempts = 0;
4323
            if ($objExercise->attempts > 0) {
4324
                $attempts = Event::getExerciseResultsByUser(
4325
                    api_get_user_id(),
4326
                    $objExercise->id,
4327
                    api_get_course_int_id(),
4328
                    api_get_session_id(),
4329
                    $exercise_stat_info['orig_lp_id'],
4330
                    $exercise_stat_info['orig_lp_item_id'],
4331
                    'desc'
4332
                );
4333
                if ($attempts) {
4334
                    $numberAttempts = count($attempts);
4335
                }
4336
4337
                if ($save_user_result) {
4338
                    $numberAttempts++;
4339
                }
4340
                $showTotalScore = false;
4341
                $showTotalScoreAndUserChoicesInLastAttempt = false;
4342
                if ($numberAttempts >= $objExercise->attempts) {
4343
                    $showTotalScore = true;
4344
                    $show_results = true;
4345
                    $show_only_score = false;
4346
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
4347
                }
4348
            }
4349
4350
            if ($objExercise->results_disabled ==
4351
                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK
4352
            ) {
4353
                $show_only_score = false;
4354
                $show_results = true;
4355
                $show_all_but_expected_answer = false;
4356
                $showTotalScore = false;
4357
                $showQuestionScore = false;
4358
                if ($numberAttempts >= $objExercise->attempts) {
4359
                    $showTotalScore = true;
4360
                    $showQuestionScore = true;
4361
                }
4362
            }
4363
        }
4364
4365
        if (($show_results || $show_only_score) && $origin !== 'embeddable') {
4366
            if (isset($exercise_stat_info['exe_user_id'])) {
4367
                if (!empty($studentInfo)) {
4368
                    // Shows exercise header
4369
                    echo $objExercise->showExerciseResultHeader(
4370
                        $studentInfo,
4371
                        $exercise_stat_info
4372
                    );
4373
                }
4374
            }
4375
        }
4376
4377
        // Display text when test is finished #4074 and for LP #4227
4378
        $endOfMessage = $objExercise->getTextWhenFinished();
4379
        if (!empty($endOfMessage)) {
4380
            echo Display::return_message($endOfMessage, 'normal', false);
4381
            echo "<div class='clear'>&nbsp;</div>";
4382
        }
4383
4384
        $question_list_answers = [];
4385
        $media_list = [];
4386
        $category_list = [];
4387
        $loadChoiceFromSession = false;
4388
        $fromDatabase = true;
4389
        $exerciseResult = null;
4390
        $exerciseResultCoordinates = null;
4391
        $delineationResults = null;
4392
4393
        if (in_array(
4394
            $objExercise->getFeedbackType(),
4395
            [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
4396
        )) {
4397
            $loadChoiceFromSession = true;
4398
            $fromDatabase = false;
4399
            $exerciseResult = Session::read('exerciseResult');
4400
            $exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
4401
            $delineationResults = Session::read('hotspot_delineation_result');
4402
            $delineationResults = isset($delineationResults[$objExercise->id]) ? $delineationResults[$objExercise->id] : null;
4403
        }
4404
4405
        $countPendingQuestions = 0;
4406
        $result = [];
4407
        // Loop over all question to show results for each of them, one by one
4408
        if (!empty($question_list)) {
4409
            foreach ($question_list as $questionId) {
4410
                // Creates a temporary Question object
4411
                $objQuestionTmp = Question::read($questionId, $objExercise->course);
4412
                // This variable came from exercise_submit_modal.php
4413
                ob_start();
4414
                $choice = null;
4415
                $delineationChoice = null;
4416
                if ($loadChoiceFromSession) {
4417
                    $choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : null;
4418
                    $delineationChoice = isset($delineationResults[$questionId]) ? $delineationResults[$questionId] : null;
4419
                }
4420
4421
                // We're inside *one* question. Go through each possible answer for this question
4422
                $result = $objExercise->manage_answer(
4423
                    $exeId,
4424
                    $questionId,
4425
                    $choice,
4426
                    'exercise_result',
4427
                    $exerciseResultCoordinates,
4428
                    $save_user_result,
4429
                    $fromDatabase,
4430
                    $show_results,
4431
                    $objExercise->selectPropagateNeg(),
4432
                    $delineationChoice,
4433
                    $showTotalScoreAndUserChoicesInLastAttempt
4434
                );
4435
4436
                if (empty($result)) {
4437
                    continue;
4438
                }
4439
4440
                $total_score += $result['score'];
4441
                $total_weight += $result['weight'];
4442
4443
                $question_list_answers[] = [
4444
                    'question' => $result['open_question'],
4445
                    'answer' => $result['open_answer'],
4446
                    'answer_type' => $result['answer_type'],
4447
                    'generated_oral_file' => $result['generated_oral_file'],
4448
                ];
4449
4450
                $my_total_score = $result['score'];
4451
                $my_total_weight = $result['weight'];
4452
4453
                // Category report
4454
                $category_was_added_for_this_test = false;
4455
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
4456
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
4457
                        $category_list[$objQuestionTmp->category]['score'] = 0;
4458
                    }
4459
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
4460
                        $category_list[$objQuestionTmp->category]['total'] = 0;
4461
                    }
4462
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
4463
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
4464
                    $category_was_added_for_this_test = true;
4465
                }
4466
4467
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
4468
                    foreach ($objQuestionTmp->category_list as $category_id) {
4469
                        $category_list[$category_id]['score'] += $my_total_score;
4470
                        $category_list[$category_id]['total'] += $my_total_weight;
4471
                        $category_was_added_for_this_test = true;
4472
                    }
4473
                }
4474
4475
                // No category for this question!
4476
                if ($category_was_added_for_this_test == false) {
4477
                    if (!isset($category_list['none']['score'])) {
4478
                        $category_list['none']['score'] = 0;
4479
                    }
4480
                    if (!isset($category_list['none']['total'])) {
4481
                        $category_list['none']['total'] = 0;
4482
                    }
4483
4484
                    $category_list['none']['score'] += $my_total_score;
4485
                    $category_list['none']['total'] += $my_total_weight;
4486
                }
4487
4488
                if ($objExercise->selectPropagateNeg() == 0 && $my_total_score < 0) {
4489
                    $my_total_score = 0;
4490
                }
4491
4492
                $comnt = null;
4493
                if ($show_results) {
4494
                    $comnt = Event::get_comments($exeId, $questionId);
4495
                    $teacherAudio = self::getOralFeedbackAudio(
4496
                        $exeId,
4497
                        $questionId,
4498
                        api_get_user_id()
4499
                    );
4500
4501
                    if (!empty($comnt) || $teacherAudio) {
4502
                        echo '<b>'.get_lang('Feedback').'</b>';
4503
                    }
4504
4505
                    if (!empty($comnt)) {
4506
                        echo self::getFeedbackText($comnt);
4507
                    }
4508
4509
                    if ($teacherAudio) {
4510
                        echo $teacherAudio;
4511
                    }
4512
                }
4513
4514
                $score = [];
4515
                if ($show_results) {
4516
                    $scorePassed = $my_total_score >= $my_total_weight;
4517
                    if (function_exists('bccomp')) {
4518
                        $compareResult = bccomp($my_total_score, $my_total_weight, 3);
4519
                        $scorePassed = $compareResult === 1 || $compareResult === 0;
4520
                    }
4521
                    $score = [
4522
                        'result' => self::show_score(
4523
                            $my_total_score,
4524
                            $my_total_weight,
4525
                            false
4526
                        ),
4527
                        'pass' => $scorePassed,
4528
                        'score' => $my_total_score,
4529
                        'weight' => $my_total_weight,
4530
                        'comments' => $comnt,
4531
                        'user_answered' => $result['user_answered'],
4532
                    ];
4533
                }
4534
4535
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
4536
                    $reviewScore = [
4537
                        'score' => $my_total_score,
4538
                        'comments' => Event::get_comments($exeId, $questionId),
4539
                    ];
4540
                    $check = $objQuestionTmp->isQuestionWaitingReview($reviewScore);
4541
                    if ($check === false) {
4542
                        $countPendingQuestions++;
4543
                    }
4544
                }
4545
4546
                $contents = ob_get_clean();
4547
                $question_content = '';
4548
                if ($show_results) {
4549
                    $question_content = '<div class="question_row_answer">';
4550
                    if ($showQuestionScore == false) {
4551
                        $score = [];
4552
                    }
4553
4554
                    // Shows question title an description
4555
                    $question_content .= $objQuestionTmp->return_header(
4556
                        $objExercise,
4557
                        $counter,
4558
                        $score
4559
                    );
4560
                }
4561
                $counter++;
4562
                $question_content .= $contents;
4563
                if ($show_results) {
4564
                    $question_content .= '</div>';
4565
                }
4566
                if ($objExercise->showExpectedChoice()) {
4567
                    $exercise_content .= Display::div(
4568
                        Display::panel($question_content),
4569
                        ['class' => 'question-panel']
4570
                    );
4571
                } else {
4572
                    // $show_all_but_expected_answer should not happen at
4573
                    // the same time as $show_results
4574
                    if ($show_results && !$show_only_score) {
4575
                        $exercise_content .= Display::div(
4576
                            Display::panel($question_content),
4577
                            ['class' => 'question-panel']
4578
                        );
4579
                    }
4580
                }
4581
            } // end foreach() block that loops over all questions
4582
        }
4583
4584
        $totalScoreText = null;
4585
        $certificateBlock = '';
4586
        if (($show_results || $show_only_score) && $showTotalScore) {
4587
            if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4588
                echo '<h1 style="text-align : center; margin : 20px 0;">'.get_lang('Your results').'</h1><br />';
4589
            }
4590
            $totalScoreText .= '<div class="question_row_score">';
4591
            if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4592
                $totalScoreText .= self::getQuestionDiagnosisRibbon(
4593
                    $objExercise,
4594
                    $total_score,
4595
                    $total_weight,
4596
                    true
4597
                );
4598
            } else {
4599
                $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
4600
4601
                if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
4602
                    $formula = $pluginEvaluation->getFormulaForExercise($objExercise->selectId());
4603
4604
                    if (!empty($formula)) {
4605
                        $total_score = $pluginEvaluation->getResultWithFormula($exeId, $formula);
4606
                        $total_weight = $pluginEvaluation->getMaxScore();
4607
                    }
4608
                }
4609
4610
                $totalScoreText .= self::getTotalScoreRibbon(
4611
                    $objExercise,
4612
                    $total_score,
4613
                    $total_weight,
4614
                    true,
4615
                    $countPendingQuestions
4616
                );
4617
            }
4618
            $totalScoreText .= '</div>';
4619
4620
            if (!empty($studentInfo)) {
4621
                $certificateBlock = self::generateAndShowCertificateBlock(
4622
                    $total_score,
4623
                    $total_weight,
4624
                    $objExercise,
4625
                    $studentInfo['id'],
4626
                    $courseCode,
4627
                    $sessionId
4628
                );
4629
            }
4630
        }
4631
4632
        if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4633
            $chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults(
4634
                $exeId,
4635
                $objExercise
4636
            );
4637
            echo $chartMultiAnswer;
4638
        }
4639
4640
        if (!empty($category_list) && ($show_results || $show_only_score)) {
4641
            // Adding total
4642
            $category_list['total'] = [
4643
                'score' => $total_score,
4644
                'total' => $total_weight,
4645
            ];
4646
            echo TestCategory::get_stats_table_by_attempt(
4647
                $objExercise->id,
4648
                $category_list
4649
            );
4650
        }
4651
4652
        if ($show_all_but_expected_answer) {
4653
            $exercise_content .= Display::return_message(get_lang('Note: This test has been setup to hide the expected answers.'));
4654
        }
4655
4656
        // Remove audio auto play from questions on results page - refs BT#7939
4657
        $exercise_content = preg_replace(
4658
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
4659
            '',
4660
            $exercise_content
4661
        );
4662
4663
        echo $totalScoreText;
4664
        echo $certificateBlock;
4665
4666
        // Ofaj change BT#11784
4667
        if (api_get_configuration_value('quiz_show_description_on_results_page') &&
4668
            !empty($objExercise->description)
4669
        ) {
4670
            echo Display::div($objExercise->description, ['class' => 'exercise_description']);
4671
        }
4672
4673
        echo $exercise_content;
4674
4675
        if (!$show_only_score) {
4676
            echo $totalScoreText;
4677
        }
4678
4679
        if ($save_user_result) {
4680
            // Tracking of results
4681
            if ($exercise_stat_info) {
4682
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
4683
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
4684
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
4685
4686
                if (api_is_allowed_to_session_edit()) {
4687
                    Event::updateEventExercise(
4688
                        $exercise_stat_info['exe_id'],
4689
                        $objExercise->selectId(),
4690
                        $total_score,
4691
                        $total_weight,
4692
                        api_get_session_id(),
4693
                        $learnpath_id,
4694
                        $learnpath_item_id,
4695
                        $learnpath_item_view_id,
4696
                        $exercise_stat_info['exe_duration'],
4697
                        $question_list
4698
                    );
4699
4700
                    $allowStats = api_get_configuration_value('allow_gradebook_stats');
4701
                    if ($allowStats) {
4702
                        $objExercise->generateStats(
4703
                            $objExercise->selectId(),
4704
                            api_get_course_info(),
4705
                            api_get_session_id()
4706
                        );
4707
                    }
4708
                }
4709
            }
4710
4711
            // Send notification at the end
4712
            if (!api_is_allowed_to_edit(null, true) &&
4713
                !api_is_excluded_user_type()
4714
            ) {
4715
                $objExercise->send_mail_notification_for_exam(
4716
                    'end',
4717
                    $question_list_answers,
4718
                    $origin,
4719
                    $exeId,
4720
                    $total_score,
4721
                    $total_weight
4722
                );
4723
            }
4724
        }
4725
4726
        if (in_array(
4727
            $objExercise->selectResultsDisabled(),
4728
            [RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING]
4729
        )) {
4730
            echo Display::page_header(get_lang('Ranking'), null, 'h4');
4731
            echo self::displayResultsInRanking(
4732
                $objExercise->iId,
4733
                api_get_user_id(),
4734
                api_get_course_int_id(),
4735
                api_get_session_id()
4736
            );
4737
        }
4738
4739
        if (!empty($remainingMessage)) {
4740
            echo Display::return_message($remainingMessage, 'normal', false);
4741
        }
4742
    }
4743
4744
    /**
4745
     * Display the ranking of results in a exercise.
4746
     *
4747
     * @param int $exerciseId
4748
     * @param int $currentUserId
4749
     * @param int $courseId
4750
     * @param int $sessionId
4751
     *
4752
     * @return string
4753
     */
4754
    public static function displayResultsInRanking($exerciseId, $currentUserId, $courseId, $sessionId = 0)
4755
    {
4756
        $data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId);
4757
4758
        $table = new HTML_Table(['class' => 'table table-hover table-bordered']);
4759
        $table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']);
4760
        $table->setHeaderContents(0, 1, get_lang('Username'));
4761
        $table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']);
4762
        $table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']);
4763
4764
        foreach ($data as $r => $item) {
4765
            if (!isset($item[1])) {
4766
                continue;
4767
            }
4768
            $selected = $item[1]->getId() == $currentUserId;
4769
4770
            foreach ($item as $c => $value) {
4771
                $table->setCellContents($r + 1, $c, $value);
4772
4773
                $attrClass = '';
4774
4775
                if (in_array($c, [0, 2])) {
4776
                    $attrClass = 'text-right';
4777
                } elseif (3 == $c) {
4778
                    $attrClass = 'text-center';
4779
                }
4780
4781
                if ($selected) {
4782
                    $attrClass .= ' warning';
4783
                }
4784
4785
                $table->setCellAttributes($r + 1, $c, ['class' => $attrClass]);
4786
            }
4787
        }
4788
4789
        return $table->toHtml();
4790
    }
4791
4792
    /**
4793
     * Get the ranking for results in a exercise.
4794
     * Function used internally by ExerciseLib::displayResultsInRanking.
4795
     *
4796
     * @param int $exerciseId
4797
     * @param int $courseId
4798
     * @param int $sessionId
4799
     *
4800
     * @return array
4801
     */
4802
    public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0)
4803
    {
4804
        $em = Database::getManager();
4805
4806
        $dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId';
4807
        $dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId');
4808
4809
        $result = $em
4810
            ->createQuery($dql)
4811
            ->setParameters(['id' => $exerciseId, 'cId' => $courseId])
4812
            ->getScalarResult();
4813
4814
        $data = [];
4815
4816
        /** @var TrackEExercises $item */
4817
        foreach ($result as $item) {
4818
            $bestAttemp = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId = 0);
4819
4820
            $data[] = $bestAttemp;
4821
        }
4822
4823
        usort(
4824
            $data,
4825
            function ($a, $b) {
4826
                if ($a['score'] != $b['score']) {
4827
                    return $a['score'] > $b['score'] ? -1 : 1;
4828
                }
4829
4830
                if ($a['exe_date'] != $b['exe_date']) {
4831
                    return $a['exe_date'] < $b['exe_date'] ? -1 : 1;
4832
                }
4833
4834
                return 0;
4835
            }
4836
        );
4837
4838
        // flags to display the same position in case of tie
4839
        $lastScore = $data[0]['score'];
4840
        $position = 1;
4841
4842
        $data = array_map(
4843
            function ($item) use (&$lastScore, &$position) {
4844
                if ($item['score'] < $lastScore) {
4845
                    $position++;
4846
                }
4847
4848
                $lastScore = $item['score'];
4849
4850
                return [
4851
                    $position,
4852
                    api_get_user_entity($item['exe_user_id']),
4853
                    self::show_score($item['score'], $item['max_score'], true, true, true),
4854
                    api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT),
4855
                ];
4856
            },
4857
            $data
4858
        );
4859
4860
        return $data;
4861
    }
4862
4863
    /**
4864
     * Get a special ribbon on top of "degree of certainty" questions (
4865
     * variation from getTotalScoreRibbon() for other question types).
4866
     *
4867
     * @param Exercise $objExercise
4868
     * @param float    $score
4869
     * @param float    $weight
4870
     * @param bool     $checkPassPercentage
4871
     *
4872
     * @return string
4873
     */
4874
    public static function getQuestionDiagnosisRibbon($objExercise, $score, $weight, $checkPassPercentage = false)
4875
    {
4876
        $displayChartDegree = true;
4877
        $ribbon = $displayChartDegree ? '<div class="ribbon">' : '';
4878
4879
        if ($checkPassPercentage) {
4880
            $isSuccess = self::isSuccessExerciseResult(
4881
                $score, $weight, $objExercise->selectPassPercentage()
4882
            );
4883
            // Color the final test score if pass_percentage activated
4884
            $ribbonTotalSuccessOrError = '';
4885
            if (self::isPassPercentageEnabled($objExercise->selectPassPercentage())) {
4886
                if ($isSuccess) {
4887
                    $ribbonTotalSuccessOrError = ' ribbon-total-success';
4888
                } else {
4889
                    $ribbonTotalSuccessOrError = ' ribbon-total-error';
4890
                }
4891
            }
4892
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total '.$ribbonTotalSuccessOrError.'">' : '';
4893
        } else {
4894
            $ribbon .= $displayChartDegree ? '<div class="rib rib-total">' : '';
4895
        }
4896
4897
        if ($displayChartDegree) {
4898
            $ribbon .= '<h3>'.get_lang('Score for the test').':&nbsp;';
4899
            $ribbon .= self::show_score($score, $weight, false, true);
4900
            $ribbon .= '</h3>';
4901
            $ribbon .= '</div>';
4902
        }
4903
4904
        if ($checkPassPercentage) {
4905
            $ribbon .= self::showSuccessMessage(
4906
                $score,
4907
                $weight,
4908
                $objExercise->selectPassPercentage()
4909
            );
4910
        }
4911
4912
        $ribbon .= $displayChartDegree ? '</div>' : '';
4913
4914
        return $ribbon;
4915
    }
4916
4917
    /**
4918
     * @param float $score
4919
     * @param float $weight
4920
     * @param bool  $checkPassPercentage
4921
     * @param int   $countPendingQuestions
4922
     *
4923
     * @return string
4924
     */
4925
    public static function getTotalScoreRibbon(
4926
        Exercise $objExercise,
4927
        $score,
4928
        $weight,
4929
        $checkPassPercentage = false,
4930
        $countPendingQuestions = 0
4931
    ) {
4932
        $hide = (int) $objExercise->getPageConfigurationAttribute('hide_total_score');
4933
        if ($hide === 1) {
4934
            return '';
4935
        }
4936
4937
        $passPercentage = $objExercise->selectPassPercentage();
4938
        $ribbon = '<div class="title-score">';
4939
        if ($checkPassPercentage) {
4940
            $isSuccess = self::isSuccessExerciseResult(
4941
                $score,
4942
                $weight,
4943
                $passPercentage
4944
            );
4945
            // Color the final test score if pass_percentage activated
4946
            $class = '';
4947
            if (self::isPassPercentageEnabled($passPercentage)) {
4948
                if ($isSuccess) {
4949
                    $class = ' ribbon-total-success';
4950
                } else {
4951
                    $class = ' ribbon-total-error';
4952
                }
4953
            }
4954
            $ribbon .= '<div class="total '.$class.'">';
4955
        } else {
4956
            $ribbon .= '<div class="total">';
4957
        }
4958
        $ribbon .= '<h3>'.get_lang('Score for the test').':&nbsp;';
4959
        $ribbon .= self::show_score($score, $weight, false, true);
4960
        $ribbon .= '</h3>';
4961
        $ribbon .= '</div>';
4962
        if ($checkPassPercentage) {
4963
            $ribbon .= self::showSuccessMessage(
4964
                $score,
4965
                $weight,
4966
                $passPercentage
4967
            );
4968
        }
4969
        $ribbon .= '</div>';
4970
4971
        if (!empty($countPendingQuestions)) {
4972
            $ribbon .= '<br />';
4973
            $ribbon .= Display::return_message(
4974
                sprintf(
4975
                    get_lang('Temporary score: %s open question(s) not corrected yet.'),
4976
                    $countPendingQuestions
4977
                ),
4978
                'warning'
4979
            );
4980
        }
4981
4982
        return $ribbon;
4983
    }
4984
4985
    /**
4986
     * @param int $countLetter
4987
     *
4988
     * @return mixed
4989
     */
4990
    public static function detectInputAppropriateClass($countLetter)
4991
    {
4992
        $limits = [
4993
            0 => 'input-mini',
4994
            10 => 'input-mini',
4995
            15 => 'input-medium',
4996
            20 => 'input-xlarge',
4997
            40 => 'input-xlarge',
4998
            60 => 'input-xxlarge',
4999
            100 => 'input-xxlarge',
5000
            200 => 'input-xxlarge',
5001
        ];
5002
5003
        foreach ($limits as $size => $item) {
5004
            if ($countLetter <= $size) {
5005
                return $item;
5006
            }
5007
        }
5008
5009
        return $limits[0];
5010
    }
5011
5012
    /**
5013
     * @param int    $senderId
5014
     * @param array  $course_info
5015
     * @param string $test
5016
     * @param string $url
5017
     *
5018
     * @return string
5019
     */
5020
    public static function getEmailNotification($senderId, $course_info, $test, $url)
5021
    {
5022
        $teacher_info = api_get_user_info($senderId);
5023
        $fromName = api_get_person_name(
5024
            $teacher_info['firstname'],
5025
            $teacher_info['lastname'],
5026
            null,
5027
            PERSON_NAME_EMAIL_ADDRESS
5028
        );
5029
5030
        $params = [
5031
            'course_title' => Security::remove_XSS($course_info['name']),
5032
            'test_title' => Security::remove_XSS($test),
5033
            'url' => $url,
5034
            'teacher_name' => $fromName,
5035
        ];
5036
5037
        return Container::getTwig()->render(
5038
            '@ChamiloTheme/Mailer/Exercise/result_alert_body.html.twig',
5039
            $params
5040
        );
5041
    }
5042
5043
    /**
5044
     * @return string
5045
     */
5046
    public static function getNotCorrectedYetText()
5047
    {
5048
        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');
5049
    }
5050
5051
    /**
5052
     * @param string $message
5053
     *
5054
     * @return string
5055
     */
5056
    public static function getFeedbackText($message)
5057
    {
5058
        return Display::return_message($message, 'warning', false);
5059
    }
5060
5061
    /**
5062
     * Get the recorder audio component for save a teacher audio feedback.
5063
     *
5064
     * @param int $attemptId
5065
     * @param int $questionId
5066
     * @param int $userId
5067
     *
5068
     * @return string
5069
     */
5070
    public static function getOralFeedbackForm($attemptId, $questionId, $userId)
5071
    {
5072
        $view = new Template('', false, false, false, false, false, false);
5073
        $view->assign('user_id', $userId);
5074
        $view->assign('question_id', $questionId);
5075
        $view->assign('directory', "/../exercises/teacher_audio/$attemptId/");
5076
        $view->assign('file_name', "{$questionId}_{$userId}");
5077
        $template = $view->get_template('exercise/oral_expression.tpl');
5078
5079
        return $view->fetch($template);
5080
    }
5081
5082
    /**
5083
     * Get the audio componen for a teacher audio feedback.
5084
     *
5085
     * @param int $attemptId
5086
     * @param int $questionId
5087
     * @param int $userId
5088
     *
5089
     * @return string
5090
     */
5091
    public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
5092
    {
5093
        $courseInfo = api_get_course_info();
5094
        $sessionId = api_get_session_id();
5095
        $groupId = api_get_group_id();
5096
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
5097
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
5098
        $fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId);
5099
        $filePath = null;
5100
5101
        $relFilePath = "/exercises/teacher_audio/$attemptId/$fileName";
5102
5103
        if (file_exists($sysCourseDir.$relFilePath.'.ogg')) {
5104
            $filePath = $webCourseDir.$relFilePath.'.ogg';
5105
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) {
5106
            $filePath = $webCourseDir.$relFilePath.'.wav.wav';
5107
        } elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) {
5108
            $filePath = $webCourseDir.$relFilePath.'.wav';
5109
        }
5110
5111
        if (!$filePath) {
5112
            return '';
5113
        }
5114
5115
        return Display::tag(
5116
            'audio',
5117
            null,
5118
            ['src' => $filePath]
5119
        );
5120
    }
5121
5122
    /**
5123
     * @return array
5124
     */
5125
    public static function getNotificationSettings()
5126
    {
5127
        $emailAlerts = [
5128
            2 => get_lang('SendEmailToTrainerWhenStudentStartQuiz'),
5129
            1 => get_lang('SendEmailToTrainerWhenStudentEndQuiz'), // default
5130
            3 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOpenQuestion'),
5131
            4 => get_lang('SendEmailToTrainerWhenStudentEndQuizOnlyIfOralQuestion'),
5132
        ];
5133
5134
        return $emailAlerts;
5135
    }
5136
5137
    /**
5138
     * Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
5139
     *
5140
     * @param int $exerciseId
5141
     * @param int $iconSize
5142
     *
5143
     * @return string
5144
     */
5145
    public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
5146
    {
5147
        $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
5148
        $actions = [];
5149
5150
        foreach ($additionalActions as $additionalAction) {
5151
            $actions[] = call_user_func(
5152
                $additionalAction,
5153
                $exerciseId,
5154
                $iconSize
5155
            );
5156
        }
5157
5158
        return implode(PHP_EOL, $actions);
5159
    }
5160
5161
    /**
5162
     * @param int $userId
5163
     * @param int $courseId
5164
     * @param int $sessionId
5165
     *
5166
     * @throws \Doctrine\ORM\Query\QueryException
5167
     *
5168
     * @return int
5169
     */
5170
    public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
5171
    {
5172
        $em = Database::getManager();
5173
5174
        $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
5175
5176
        $result = $em
5177
            ->createQuery('
5178
                SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
5179
                WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
5180
                    AND ea.tms > :time
5181
            ')
5182
            ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
5183
            ->getSingleScalarResult();
5184
5185
        return $result;
5186
    }
5187
5188
    /**
5189
     * @param int $userId
5190
     * @param int $numberOfQuestions
5191
     * @param int $courseId
5192
     * @param int $sessionId
5193
     *
5194
     * @throws \Doctrine\ORM\Query\QueryException
5195
     *
5196
     * @return bool
5197
     */
5198
    public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
5199
    {
5200
        $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
5201
5202
        if ($questionsLimitPerDay <= 0) {
5203
            return false;
5204
        }
5205
5206
        $midnightTime = ChamiloApi::getServerMidnightTime();
5207
5208
        $answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime(
5209
            $midnightTime,
5210
            $userId,
5211
            $courseId,
5212
            $sessionId
5213
        );
5214
5215
        return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
5216
    }
5217
5218
    /**
5219
     * Check if an exercise complies with the requirements to be embedded in the mobile app or a video.
5220
     * By making sure it is set on one question per page and it only contains unique-answer or multiple-answer questions
5221
     * or unique-answer image. And that the exam does not have immediate feedback.
5222
     *
5223
     * @param array $exercise Exercise info
5224
     *
5225
     * @throws \Doctrine\ORM\Query\QueryException
5226
     *
5227
     * @return bool
5228
     */
5229
    public static function isQuizEmbeddable(array $exercise)
5230
    {
5231
        $em = Database::getManager();
5232
5233
        if (ONE_PER_PAGE != $exercise['type'] ||
5234
            in_array($exercise['feedback_type'], [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])
5235
        ) {
5236
            return false;
5237
        }
5238
5239
        $countAll = $em
5240
            ->createQuery('SELECT COUNT(qq)
5241
                FROM ChamiloCourseBundle:CQuizQuestion qq
5242
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5243
                   WITH qq.iid = qrq.questionId
5244
                WHERE qrq.exerciceId = :id'
5245
            )
5246
            ->setParameter('id', $exercise['iid'])
5247
            ->getSingleScalarResult();
5248
5249
        $countOfAllowed = $em
5250
            ->createQuery('SELECT COUNT(qq)
5251
                FROM ChamiloCourseBundle:CQuizQuestion qq
5252
                INNER JOIN ChamiloCourseBundle:CQuizRelQuestion qrq
5253
                   WITH qq.iid = qrq.questionId
5254
                WHERE qrq.exerciceId = :id AND qq.type IN (:types)'
5255
            )
5256
            ->setParameters(
5257
                [
5258
                    'id' => $exercise['iid'],
5259
                    'types' => [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE],
5260
                ]
5261
            )
5262
            ->getSingleScalarResult();
5263
5264
        return $countAll === $countOfAllowed;
5265
    }
5266
5267
    /**
5268
     * Generate a certificate linked to current quiz and.
5269
     * Return the HTML block with links to download and view the certificate.
5270
     *
5271
     * @param float  $totalScore
5272
     * @param float  $totalWeight
5273
     * @param int    $studentId
5274
     * @param string $courseCode
5275
     * @param int    $sessionId
5276
     *
5277
     * @return string
5278
     */
5279
    public static function generateAndShowCertificateBlock(
5280
        $totalScore,
5281
        $totalWeight,
5282
        Exercise $objExercise,
5283
        $studentId,
5284
        $courseCode,
5285
        $sessionId = 0
5286
    ) {
5287
        if (!api_get_configuration_value('quiz_generate_certificate_ending') ||
5288
            !self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage())
5289
        ) {
5290
            return '';
5291
        }
5292
5293
        /** @var Category $category */
5294
        $category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
5295
5296
        if (empty($category)) {
5297
            return '';
5298
        }
5299
5300
        /** @var Category $category */
5301
        $category = $category[0];
5302
        $categoryId = $category->get_id();
5303
        $link = LinkFactory::load(
5304
            null,
5305
            null,
5306
            $objExercise->selectId(),
5307
            null,
5308
            $courseCode,
5309
            $categoryId
5310
        );
5311
5312
        if (empty($link)) {
5313
            return '';
5314
        }
5315
5316
        $resourceDeletedMessage = $category->show_message_resource_delete($courseCode);
5317
5318
        if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) {
5319
            return '';
5320
        }
5321
5322
        $certificate = Category::generateUserCertificate($categoryId, $studentId);
5323
5324
        if (!is_array($certificate)) {
5325
            return '';
5326
        }
5327
5328
        return Category::getDownloadCertificateBlock($certificate);
5329
    }
5330
}
5331