Passed
Push — master ( 447a4c...2f567c )
by Julito
09:40
created

ExerciseLib::showSuccessMessage()   A

Complexity

Conditions 3

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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