Passed
Push — 1.11.x ( b29e7a...3419db )
by Julito
14:38 queued 11s
created

ExerciseLib::getTotalScoreRibbon()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 58
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 37
nc 17
nop 5
dl 0
loc 58
rs 8.3946
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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