Passed
Push — master ( d0b226...f5358a )
by Julito
10:41
created

ExerciseLib::get_exam_results_data()   F

Complexity

Conditions 104

Size

Total Lines 810
Code Lines 515

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 104
eloc 515
nop 13
dl 0
loc 810
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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