Completed
Push — master ( 9b8b24...6e1754 )
by Julito
58:58
created

ExerciseLib::displayGroupMenu()   B

Complexity

Conditions 3

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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