Passed
Push — master ( 49c89f...65060b )
by Julito
10:01
created

ExerciseLib::convertScoreToPlatformSetting()   A

Complexity

Conditions 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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