Completed
Push — master ( 7bef58...5c053f )
by Julito
25:30
created

ExerciseLib::displayGroupMenu()   B

Complexity

Conditions 3

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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