Test Setup Failed
Push — master ( c7183e...00c715 )
by Julito
30:43
created

ExerciseLib::getQuestionRibbon()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 10
rs 9.4285
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 int $questionId $questionId question id
20
     * @param bool $only_questions if true only show the questions, no exercise title
21
     * @param bool $origin i.e = learnpath
22
     * @param string $current_item current item from the list of questions
23
     * @param bool $show_title
24
     * @param bool $freeze
25
     * @param array $user_choice
26
     * @param bool $show_comment
27
     * @param null $exercise_feedback
28
     * @param bool $show_answers
29
     * @return bool|int
30
     */
31
    public static function showQuestion(
32
        $questionId,
33
        $only_questions = false,
34
        $origin = false,
35
        $current_item = '',
36
        $show_title = true,
37
        $freeze = false,
38
        $user_choice = array(),
39
        $show_comment = false,
40
        $exercise_feedback = null,
41
        $show_answers = false
42
    ) {
43
        $course_id = api_get_course_int_id();
44
        $course = api_get_course_info_by_id($course_id);
45
        // Change false to true in the following line to enable answer hinting
46
        $debug_mark_answer = $show_answers;
47
48
        // Reads question information
49
        if (!$objQuestionTmp = Question::read($questionId)) {
50
            // Question not found
51
            return false;
52
        }
53
54
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
55
            $show_comment = false;
56
        }
57
58
        $answerType = $objQuestionTmp->selectType();
59
        $pictureName = $objQuestionTmp->getPictureFilename();
60
        $s = '';
61
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);
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 = array();
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 = array();
136
                $cpt1 = array();
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, $exe_id;
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 = array(
214
                    'ToolbarSet' => 'TestFreeAnswer'
215
                );
216
                $form->addHtmlEditor(
217
                    "choice[".$questionId."]",
218
                    null,
219
                    false,
220
                    false,
221
                    $config
222
                );
223
                $s .= $form->returnForm();
224
            }
225
226
            // Now navigate through the possible answers, using the max number of
227
            // answers for the question as a limiter
228
            $lines_count = 1; // a counter for matching-type answers
229
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
230
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
231
            ) {
232
                $header = Display::tag('th', get_lang('Options'));
233
                foreach ($objQuestionTmp->options as $item) {
234
                    if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
235
                        if (in_array($item, $objQuestionTmp->options)) {
0 ignored issues
show
Bug introduced by
The property options does not seem to exist in Question.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
236
                            $header .= Display::tag('th', get_lang($item));
237
                        } else {
238
                            $header .= Display::tag('th', $item);
239
                        }
240
                    } else {
241
                        $header .= Display::tag('th', $item);
242
                    }
243
                }
244
                if ($show_comment) {
245
                    $header .= Display::tag('th', get_lang('Feedback'));
246
                }
247
                $s .= '<table class="table table-hover table-striped">';
248
                $s .= Display::tag(
249
                    'tr',
250
                    $header,
251
                    array('style' => 'text-align:left;')
252
                );
253
            }
254
255
            if ($show_comment) {
256
                if (in_array(
257
                    $answerType,
258
                    array(
259
                        MULTIPLE_ANSWER,
260
                        MULTIPLE_ANSWER_COMBINATION,
261
                        UNIQUE_ANSWER,
262
                        UNIQUE_ANSWER_IMAGE,
263
                        UNIQUE_ANSWER_NO_OPTION,
264
                        GLOBAL_MULTIPLE_ANSWER,
265
                    )
266
                )) {
267
                    $header = Display::tag('th', get_lang('Options'));
268
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
269
                        $header .= Display::tag('th', get_lang('Feedback'));
270
                    }
271
                    $s .= '<table class="table table-hover table-striped">';
272
                    $s .= Display::tag(
273
                        'tr',
274
                        $header,
275
                        array('style' => 'text-align:left;')
276
                    );
277
                }
278
            }
279
280
            $matching_correct_answer = 0;
281
            $user_choice_array = array();
282
            if (!empty($user_choice)) {
283
                foreach ($user_choice as $item) {
284
                    $user_choice_array[] = $item['answer'];
285
                }
286
            }
287
288
            $hidingClass = '';
289
            if ($answerType == READING_COMPREHENSION) {
290
                $objQuestionTmp->processText(
291
                    $objQuestionTmp->selectDescription()
292
                );
293
                $hidingClass = 'hide-reading-answers';
294
            }
295
            if ($answerType == READING_COMPREHENSION) {
296
                $s .= Display::div(
297
                    $objQuestionTmp->selectTitle(),
298
                    ['class' => 'question_title '.$hidingClass]
299
                );
300
            }
301
302
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
303
                $answer = $objAnswerTmp->selectAnswer($answerId);
304
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
305
                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
306
                $comment = $objAnswerTmp->selectComment($answerId);
307
                $attributes = array();
308
309
                switch ($answerType) {
310
                    case UNIQUE_ANSWER:
311
                        //no break
312
                    case UNIQUE_ANSWER_NO_OPTION:
313
                        //no break
314
                    case UNIQUE_ANSWER_IMAGE:
315
                        //no break
316
                    case READING_COMPREHENSION:
317
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
318
                        if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
319
                            $attributes = array(
320
                                'id' => $input_id,
321
                                'checked' => 1,
322
                                'selected' => 1
323
                            );
324
                        } else {
325
                            $attributes = array('id' => $input_id);
326
                        }
327
328
                        if ($debug_mark_answer) {
329
                            if ($answerCorrect) {
330
                                $attributes['checked'] = 1;
331
                                $attributes['selected'] = 1;
332
                            }
333
                        }
334
335
                        if ($show_comment) {
336
                            $s .= '<tr><td>';
337
                        }
338
339
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
340
                            if ($show_comment) {
341
                                if (empty($comment)) {
342
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
343
                                        . 'class="exercise-unique-answer-image" style="text-align: center">';
344
                                } else {
345
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
346
                                        . 'class="exercise-unique-answer-image col-xs-6 col-sm-12" style="text-align: center">';
347
                                }
348
                            } else {
349
                                $s .= '<div id="answer'.$questionId.$numAnswer.'" '
350
                                    . 'class="exercise-unique-answer-image col-xs-6 col-md-3" style="text-align: center">';
351
                            }
352
                        }
353
354
                        $answer = Security::remove_XSS($answer, STUDENT);
355
                        $s .= Display::input(
356
                            'hidden',
357
                            'choice2['.$questionId.']',
358
                            '0'
359
                        );
360
361
                        $answer_input = null;
362
363
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
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
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
541
                        $my_choice = array();
542
                        if (!empty($user_choice_array)) {
543
                            foreach ($user_choice_array as $item) {
544
                                $item = explode(':', $item);
545
                                if (isset($item[1]) && isset($item[0])) {
546
                                    $my_choice[$item[0]] = $item[1];
547
                                }
548
                            }
549
                        }
550
                        $answer = Security::remove_XSS($answer, STUDENT);
551
                        $s .= '<tr>';
552
                        $s .= Display::tag('td', $answer);
553
                        foreach ($objQuestionTmp->options as $key => $item) {
554
                            if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
555
                                $attributes = array(
556
                                    'checked' => 1,
557
                                    'selected' => 1
558
                                );
559
                            } else {
560
                                $attributes = array();
561
                            }
562
563
                            if ($debug_mark_answer) {
564
                                if ($key == $answerCorrect) {
565
                                    $attributes['checked'] = 1;
566
                                    $attributes['selected'] = 1;
567
                                }
568
                            }
569
                            $s .= Display::tag(
570
                                'td',
571
                                Display::input(
572
                                    'radio',
573
                                    'choice['.$questionId.']['.$numAnswer.']',
574
                                    $key,
575
                                    $attributes
576
                                )
577
                            );
578
                        }
579
580
                        if ($show_comment) {
581
                            $s .= '<td>';
582
                            $s .= $comment;
583
                            $s .= '</td>';
584
                        }
585
                        $s .= '</tr>';
586
                        break;
587
                    case FILL_IN_BLANKS:
588
                        // display the question, with field empty, for student to fill it,
589
                        // or filled to display the answer in the Question preview of the exercise/admin.php page
590
                        $displayForStudent = true;
591
                        $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
592
593
                        list($answer) = explode('::', $answer);
0 ignored issues
show
Unused Code introduced by
The assignment to $answer is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
594
                        // Correct answers
595
                        $correctAnswerList = $listAnswerInfo['tabwords'];
596
597
                        // Student's answer
598
                        $studentAnswerList = array();
599
                        if (isset($user_choice[0]['answer'])) {
600
                            $arrayStudentAnswer = FillBlanks::getAnswerInfo($user_choice[0]['answer'], true);
601
                            $studentAnswerList = $arrayStudentAnswer['studentanswer'];
602
                        }
603
604
                        // If the question must be shown with the answer (in page exercise/admin.php) for teacher preview
605
                        // set the student-answer to the correct answer
606
                        if ($debug_mark_answer) {
607
                            $studentAnswerList = $correctAnswerList;
608
                            $displayForStudent = false;
609
                        }
610
611
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
612
                            $answer = '';
613
                            for ($i = 0; $i < count($listAnswerInfo['commonwords']) - 1; $i++) {
614
                                // display the common word
615
                                $answer .= $listAnswerInfo['commonwords'][$i];
616
                                // display the blank word
617
                                $correctItem = $listAnswerInfo['tabwords'][$i];
618
                                if (isset($studentAnswerList[$i])) {
619
                                    // If student already started this test and answered this question,
620
                                    // fill the blank with his previous answers
621
                                    // may be "" if student viewed the question, but did not fill the blanks
622
                                    $correctItem = $studentAnswerList[$i];
623
                                }
624
                                $attributes['style'] = "width:".$listAnswerInfo['tabinputsize'][$i]."px";
625
                                $answer .= FillBlanks::getFillTheBlankHtml(
626
                                    $current_item,
627
                                    $questionId,
628
                                    $correctItem,
629
                                    $attributes,
630
                                    $answer,
631
                                    $listAnswerInfo,
632
                                    $displayForStudent,
633
                                    $i
634
                                );
635
                            }
636
                            // display the last common word
637
                            $answer .= $listAnswerInfo['commonwords'][$i];
638
                        } else {
639
                            // display empty [input] with the right width for student to fill it
640
                            $answer = '';
641
                            for ($i = 0; $i < count($listAnswerInfo['commonwords']) - 1; $i++) {
642
                                // display the common words
643
                                $answer .= $listAnswerInfo['commonwords'][$i];
644
                                // display the blank word
645
                                $attributes["style"] = "width:".$listAnswerInfo['tabinputsize'][$i]."px";
646
                                $answer .= FillBlanks::getFillTheBlankHtml(
647
                                    $current_item,
648
                                    $questionId,
649
                                    '',
650
                                    $attributes,
651
                                    $answer,
652
                                    $listAnswerInfo,
653
                                    $displayForStudent,
654
                                    $i
655
                                );
656
                            }
657
                            // display the last common word
658
                            $answer .= $listAnswerInfo['commonwords'][$i];
659
                        }
660
                        $s .= $answer;
661
                        break;
662
                    case CALCULATED_ANSWER:
663
                        /*
664
                         * In the CALCULATED_ANSWER test
665
                         * you mustn't have [ and ] in the textarea
666
                         * you mustn't have @@ in the textarea
667
                         * the text to find mustn't be empty or contains only spaces
668
                         * the text to find mustn't contains HTML tags
669
                         * the text to find mustn't contains char "
670
                         */
671
                        if ($origin !== null) {
672
                            global $exe_id;
673
                            $trackAttempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
674
                            $sql = 'SELECT answer FROM '.$trackAttempts.'
675
                                    WHERE exe_id=' . $exe_id.' AND question_id='.$questionId;
676
                            $rsLastAttempt = Database::query($sql);
677
                            $rowLastAttempt = Database::fetch_array($rsLastAttempt);
678
                            $answer = $rowLastAttempt['answer'];
679
                            if (empty($answer)) {
680
                                $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(
681
                                    1,
682
                                    $nbrAnswers
683
                                );
684
                                $answer = $objAnswerTmp->selectAnswer(
685
                                    $_SESSION['calculatedAnswerId'][$questionId]
686
                                );
687
                            }
688
                        }
689
690
                        list($answer) = explode('@@', $answer);
691
                        // $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
692
                        api_preg_match_all(
693
                            '/\[[^]]+\]/',
694
                            $answer,
695
                            $correctAnswerList
696
                        );
697
698
                        // get student answer to display it if student go back to previous calculated answer question in a test
699
                        if (isset($user_choice[0]['answer'])) {
700
                            api_preg_match_all(
701
                                '/\[[^]]+\]/',
702
                                $answer,
703
                                $studentAnswerList
704
                            );
705
                            $studentAnswerListTobecleaned = $studentAnswerList[0];
706
                            $studentAnswerList = array();
707
708
                            for ($i = 0; $i < count(
709
                                $studentAnswerListTobecleaned
710
                            ); $i++) {
711
                                $answerCorrected = $studentAnswerListTobecleaned[$i];
712
                                $answerCorrected = api_preg_replace(
713
                                    '| / <font color="green"><b>.*$|',
714
                                    '',
715
                                    $answerCorrected
716
                                );
717
                                $answerCorrected = api_preg_replace(
718
                                    '/^\[/',
719
                                    '',
720
                                    $answerCorrected
721
                                );
722
                                $answerCorrected = api_preg_replace(
723
                                    '|^<font color="red"><s>|',
724
                                    '',
725
                                    $answerCorrected
726
                                );
727
                                $answerCorrected = api_preg_replace(
728
                                    '|</s></font>$|',
729
                                    '',
730
                                    $answerCorrected
731
                                );
732
                                $answerCorrected = '['.$answerCorrected.']';
733
                                $studentAnswerList[] = $answerCorrected;
734
                            }
735
                        }
736
737
                        // If display preview of answer in test view for exemple, set the student answer to the correct answers
738
                        if ($debug_mark_answer) {
739
                            // contain the rights answers surronded with brackets
740
                            $studentAnswerList = $correctAnswerList[0];
741
                        }
742
743
                        /*
744
                        Split the response by bracket
745
                        tabComments is an array with text surrounding the text to find
746
                        we add a space before and after the answerQuestion to be sure to
747
                        have a block of text before and after [xxx] patterns
748
                        so we have n text to find ([xxx]) and n+1 block of texts before,
749
                        between and after the text to find
750
                        */
751
                        $tabComments = api_preg_split(
752
                            '/\[[^]]+\]/',
753
                            ' '.$answer.' '
754
                        );
755
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
756
                            $answer = "";
757
                            $i = 0;
758
                            foreach ($studentAnswerList as $studentItem) {
759
                                // remove surronding brackets
760
                                $studentResponse = api_substr(
761
                                    $studentItem,
762
                                    1,
763
                                    api_strlen($studentItem) - 2
764
                                );
765
                                $size = strlen($studentItem);
766
                                $attributes['class'] = self::detectInputAppropriateClass(
767
                                    $size
768
                                );
769
770
                                $answer .= $tabComments[$i].
771
                                    Display::input(
772
                                        'text',
773
                                        "choice[$questionId][]",
774
                                        $studentResponse,
775
                                        $attributes
776
                                    );
777
                                $i++;
778
                            }
779
                            $answer .= $tabComments[$i];
780
                        } else {
781
                            // display exercise with empty input fields
782
                            // every [xxx] are replaced with an empty input field
783
                            foreach ($correctAnswerList[0] as $item) {
784
                                $size = strlen($item);
785
                                $attributes['class'] = self::detectInputAppropriateClass(
786
                                    $size
787
                                );
788
                                $answer = str_replace(
789
                                    $item,
790
                                    Display::input(
791
                                        'text',
792
                                        "choice[$questionId][]",
793
                                        '',
794
                                        $attributes
795
                                    ),
796
                                    $answer
797
                                );
798
                            }
799
                        }
800
                        if ($origin !== null) {
801
                            $s = $answer;
802
                            break;
803
                        } else {
804
                            $s .= $answer;
805
                        }
806
                        break;
807
                    case MATCHING:
808
                        // matching type, showing suggestions and answers
809
                        // TODO: replace $answerId by $numAnswer
810
                        if ($answerCorrect != 0) {
811
                            // only show elements to be answered (not the contents of
812
                            // the select boxes, who are correct = 0)
813
                            $s .= '<tr><td width="45%" valign="top">';
814
                            $parsed_answer = $answer;
815
                            // Left part questions
816
                            $s .= '<p class="indent">'.$lines_count.'.&nbsp;'.$parsed_answer.'</p></td>';
817
                            // Middle part (matches selects)
818
                            // Id of select is # question + # of option
819
                            $s .= '<td width="10%" valign="top" align="center">
820
                                <div class="select-matching">
821
                                <select id="choice_id_'.$current_item.'_'.$lines_count.'" name="choice['.$questionId.']['.$numAnswer.']">';
822
823
                            // fills the list-box
824
                            foreach ($select_items as $key => $val) {
825
                                // set $debug_mark_answer to true at function start to
826
                                // show the correct answer with a suffix '-x'
827
                                $selected = '';
828
                                if ($debug_mark_answer) {
829
                                    if ($val['id'] == $answerCorrect) {
830
                                        $selected = 'selected="selected"';
831
                                    }
832
                                }
833
                                //$user_choice_array_position
834
                                if (isset($user_choice_array_position[$numAnswer]) && $val['id'] == $user_choice_array_position[$numAnswer]) {
835
                                    $selected = 'selected="selected"';
836
                                }
837
                                $s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
838
839
                            }  // end foreach()
840
841
                            $s .= '</select></div></td><td width="5%" class="separate">&nbsp;</td>';
842
                            $s .= '<td width="40%" valign="top" >';
843
                            if (isset($select_items[$lines_count])) {
844
                                $s .= '<div class="text-right"><p class="indent">'.$select_items[$lines_count]['letter'].'.&nbsp; '.$select_items[$lines_count]['answer'].'</p></div>';
845
                            } else {
846
                                $s .= '&nbsp;';
847
                            }
848
                            $s .= '</td>';
849
                            $s .= '</tr>';
850
                            $lines_count++;
851
                            //if the left side of the "matching" has been completely
852
                            // shown but the right side still has values to show...
853
                            if (($lines_count - 1) == $num_suggestions) {
854
                                // if it remains answers to shown at the right side
855
                                while (isset($select_items[$lines_count])) {
856
                                    $s .= '<tr>
857
                                      <td colspan="2"></td>
858
                                      <td valign="top">';
859
                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'];
860
                                    $s .= "</td>
861
                                </tr>";
862
                                    $lines_count++;
863
                                }    // end while()
864
                            }  // end if()
865
                            $matching_correct_answer++;
866
                        }
867
                        break;
868
                    case DRAGGABLE:
869
                        if ($answerCorrect) {
870
                            $parsed_answer = $answer;
871
                            /*$lines_count = '';
872
                            $data = $objAnswerTmp->getAnswerByAutoId($numAnswer);
873
                            $data = $objAnswerTmp->getAnswerByAutoId($data['correct']);
874
                            $lines_count = $data['answer'];*/
875
                            $windowId = $questionId.'_'.$lines_count;
876
                            $s .= '<li class="touch-items" id="'.$windowId.'">';
877
                            $s .= Display::div(
878
                                $parsed_answer,
879
                                [
880
                                    'id' => "window_$windowId",
881
                                    'class' => "window{$questionId}_question_draggable exercise-draggable-answer-option"
882
                                ]
883
                            );
884
885
                            $draggableSelectOptions = [];
886
                            $selectedValue = 0;
887
                            $selectedIndex = 0;
888
889 View Code Duplication
                            if ($user_choice) {
890
                                foreach ($user_choice as $chosen) {
891
                                    if ($answerCorrect != $chosen['answer']) {
892
                                        continue;
893
                                    }
894
895
                                    $selectedValue = $chosen['answer'];
896
                                }
897
                            }
898
899
                            foreach ($select_items as $key => $select_item) {
900
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
901
                            }
902
903
                            foreach ($draggableSelectOptions as $value => $text) {
904
                                if ($value == $selectedValue) {
905
                                    break;
906
                                }
907
                                $selectedIndex++;
908
                            }
909
910
                            $s .= Display::select(
911
                                "choice[$questionId][$numAnswer]",
912
                                $draggableSelectOptions,
913
                                $selectedValue,
914
                                [
915
                                    'id' => "window_{$windowId}_select",
916
                                    'class' => 'select_option hidden',
917
                                ],
918
                                false
919
                            );
920
921
                            if ($selectedValue && $selectedIndex) {
922
                                $s .= "
923
                                    <script>
924
                                        $(function() {
925
                                            DraggableAnswer.deleteItem(
926
                                                $('#{$questionId}_$lines_count'),
927
                                                $('#drop_{$questionId}_{$selectedIndex}')
928
                                            );
929
                                        });
930
                                    </script>
931
                                ";
932
                            }
933
934
                            if (isset($select_items[$lines_count])) {
935
                                $s .= Display::div(
936
                                    Display::tag(
937
                                        'b',
938
                                        $select_items[$lines_count]['letter']
939
                                    ).$select_items[$lines_count]['answer'],
940
                                    [
941
                                        'id' => "window_{$windowId}_answer",
942
                                        'class' => 'hidden'
943
                                    ]
944
                                );
945
                            } else {
946
                                $s .= '&nbsp;';
947
                            }
948
949
                            $lines_count++;
950
951
                            if (($lines_count - 1) == $num_suggestions) {
952
                                while (isset($select_items[$lines_count])) {
953
                                    $s .= Display::tag('b', $select_items[$lines_count]['letter']);
954
                                    $s .= $select_items[$lines_count]['answer'];
955
                                    $lines_count++;
956
                                }
957
                            }
958
959
                            $matching_correct_answer++;
960
961
                            $s .= '</li>';
962
                        }
963
                        break;
964
                    case MATCHING_DRAGGABLE:
965
                        if ($answerId == 1) {
966
                            echo $objAnswerTmp->getJs();
967
                        }
968
969
                        if ($answerCorrect != 0) {
970
                            $parsed_answer = $answer;
971
                            $windowId = "{$questionId}_{$lines_count}";
972
973
                            $s .= <<<HTML
974
                            <tr>
975
                                <td widht="45%">
976
                                    <div id="window_{$windowId}" class="window window_left_question window{$questionId}_question">
977
                                        <strong>$lines_count.</strong> $parsed_answer
978
                                    </div>
979
                                </td>
980
                                <td width="10%">
981
HTML;
982
983
                            $draggableSelectOptions = [];
984
                            $selectedValue = 0;
985
                            $selectedIndex = 0;
986
987 View Code Duplication
                            if ($user_choice) {
988
                                foreach ($user_choice as $chosen) {
989
                                    if ($answerCorrect != $chosen['answer']) {
990
                                        continue;
991
                                    }
992
                                    $selectedValue = $chosen['answer'];
993
                                }
994
                            }
995
996
                            foreach ($select_items as $key => $select_item) {
997
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
998
                            }
999
1000
                            foreach ($draggableSelectOptions as $value => $text) {
1001
                                if ($value == $selectedValue) {
1002
                                    break;
1003
                                }
1004
                                $selectedIndex++;
1005
                            }
1006
1007
                            $s .= Display::select(
1008
                                "choice[$questionId][$numAnswer]",
1009
                                $draggableSelectOptions,
1010
                                $selectedValue,
1011
                                [
1012
                                    'id' => "window_{$windowId}_select",
1013
                                    'class' => 'hidden'
1014
                                ],
1015
                                false
1016
                            );
1017
1018
                            if (!empty($answerCorrect) && !empty($selectedValue)) {
1019
                                // Show connect if is not freeze (question preview)
1020
                                if (!$freeze) {
1021
                                    $s .= "
1022
                                        <script>
1023
                                            $(document).on('ready', function () {
1024
                                                jsPlumb.ready(function() {
1025
                                                    jsPlumb.connect({
1026
                                                        source: 'window_$windowId',
1027
                                                        target: 'window_{$questionId}_{$selectedIndex}_answer',
1028
                                                        endpoint: ['Blank', {radius: 15}],
1029
                                                        anchors: ['RightMiddle', 'LeftMiddle'],
1030
                                                        paintStyle: {strokeStyle: '#8A8888', lineWidth: 8},
1031
                                                        connector: [
1032
                                                            MatchingDraggable.connectorType,
1033
                                                            {curvines: MatchingDraggable.curviness}
1034
                                                        ]
1035
                                                    });
1036
                                                });
1037
                                            });
1038
                                        </script>
1039
                                    ";
1040
                                }
1041
                            }
1042
1043
                            $s .= <<<HTML
1044
                            </td>
1045
                            <td width="45%">
1046
HTML;
1047
1048 View Code Duplication
                            if (isset($select_items[$lines_count])) {
1049
                                $s .= <<<HTML
1050
                                <div id="window_{$windowId}_answer" class="window window_right_question">
1051
                                    <strong>{$select_items[$lines_count]['letter']}.</strong> {$select_items[$lines_count]['answer']}
1052
                                </div>
1053
HTML;
1054
                            } else {
1055
                                $s .= '&nbsp;';
1056
                            }
1057
1058
                            $s .= '</td></tr>';
1059
                            $lines_count++;
1060 View Code Duplication
                            if (($lines_count - 1) == $num_suggestions) {
1061
                                while (isset($select_items[$lines_count])) {
1062
                                    $s .= <<<HTML
1063
                                    <tr>
1064
                                        <td colspan="2"></td>
1065
                                        <td>
1066
                                            <strong>{$select_items[$lines_count]['letter']}</strong>
1067
                                            {$select_items[$lines_count]['answer']}
1068
                                        </td>
1069
                                    </tr>
1070
HTML;
1071
                                    $lines_count++;
1072
                                }
1073
                            }
1074
                            $matching_correct_answer++;
1075
                        }
1076
                        break;
1077
                }
1078
            }    // end for()
1079
1080
            if ($show_comment) {
1081
                $s .= '</table>';
1082
            } elseif (in_array(
1083
                $answerType,
1084
                [
1085
                    MATCHING,
1086
                    MATCHING_DRAGGABLE,
1087
                    UNIQUE_ANSWER_NO_OPTION,
1088
                    MULTIPLE_ANSWER_TRUE_FALSE,
1089
                    MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE,
1090
                ]
1091
            )) {
1092
                $s .= '</table>';
1093
            }
1094
1095
            if ($answerType == DRAGGABLE) {
1096
                $isVertical = $objQuestionTmp->extra == 'v';
1097
1098
                $s .= "</ul>";
1099
                $s .= "</div>"; //clearfix
1100
                $counterAnswer = 1;
1101
                $s .= $isVertical ? '' : '<div class="row">';
1102
1103
                for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1104
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
1105
                    $windowId = $questionId.'_'.$counterAnswer;
1106
                    if ($answerCorrect) {
1107
                        $s .= $isVertical ? '<div class="row">' : '';
1108
                        $s .= '
1109
                            <div class="'.($isVertical ? 'col-md-12' : 'col-xs-12 col-sm-4 col-md-3 col-lg-2').'">
1110
                                <div id="drop_'.$windowId.'" class="droppable">&nbsp;</div>
1111
                            </div>
1112
                        ';
1113
                        $s .= $isVertical ? '</div>' : '';
1114
1115
                        $counterAnswer++;
1116
                    }
1117
                }
1118
1119
                $s .= $isVertical ? '' : '</div>'; // row
1120
                $s .= '</div>'; // col-md-12 ui-widget ui-helper-clearfix
1121
            }
1122
1123
            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
1124
                $s .= '</div>'; //drag_question
1125
            }
1126
1127
            $s .= '</div>'; //question_options row
1128
1129
            // destruction of the Answer object
1130
            unset($objAnswerTmp);
1131
1132
            // destruction of the Question object
1133
            unset($objQuestionTmp);
1134
1135
            if ($origin == 'export') {
1136
                return $s;
1137
            }
1138
1139
            echo $s;
1140
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
1141
            global $exerciseId, $exe_id;
1142
            // Question is a HOT_SPOT
1143
            //checking document/images visibility
1144
            if (api_is_platform_admin() || api_is_course_admin()) {
1145
                $doc_id = $objQuestionTmp->getPictureId();
1146 View Code Duplication
                if (is_numeric($doc_id)) {
1147
                    $images_folder_visibility = api_get_item_visibility(
1148
                        $course,
1149
                        'document',
1150
                        $doc_id,
1151
                        api_get_session_id()
1152
                    );
1153
                    if (!$images_folder_visibility) {
1154
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
1155
                        echo Display::return_message(
1156
                            get_lang('ChangeTheVisibilityOfTheCurrentImage'),
1157
                            'warning'
1158
                        );
1159
                    }
1160
                }
1161
            }
1162
            $questionName = $objQuestionTmp->selectTitle();
1163
            $questionDescription = $objQuestionTmp->selectDescription();
1164
1165
            // Get the answers, make a list
1166
            $objAnswerTmp = new Answer($questionId);
1167
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1168
1169
            // get answers of hotpost
1170
            $answers_hotspot = array();
1171
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
1172
                $answers = $objAnswerTmp->selectAnswerByAutoId(
1173
                    $objAnswerTmp->selectAutoId($answerId)
1174
                );
1175
                $answers_hotspot[$answers['id']] = $objAnswerTmp->selectAnswer(
1176
                    $answerId
1177
                );
1178
            }
1179
1180
            $answerList = '';
1181
            $hotspotColor = 0;
1182
            if ($answerType != HOT_SPOT_DELINEATION) {
1183
                $answerList = '
1184
                    <div class="well well-sm">
1185
                        <h5 class="page-header">' . get_lang('HotspotZones').'</h5>
1186
                        <ol>
1187
                ';
1188
1189
                if (!empty($answers_hotspot)) {
1190
                    Session::write("hotspot_ordered$questionId", array_keys($answers_hotspot));
1191
                    foreach ($answers_hotspot as $value) {
1192
                        $answerList .= "<li>";
1193
                        if ($freeze) {
1194
                            $answerList .= '<span class="hotspot-color-'.$hotspotColor
1195
                                .' fa fa-square" aria-hidden="true"></span>'.PHP_EOL;
1196
                        }
1197
                        $answerList .= $value;
1198
                        $answerList .= "</li>";
1199
                        $hotspotColor++;
1200
                    }
1201
                }
1202
1203
                $answerList .= '
1204
                        </ul>
1205
                    </div>
1206
                ';
1207
1208
                if ($freeze) {
1209
                    $relPath = api_get_path(WEB_CODE_PATH);
1210
                    echo "
1211
                        <div class=\"row\">
1212
                            <div class=\"col-sm-9\">
1213
                                <div id=\"hotspot-preview-$questionId\"></div>                                
1214
                            </div>
1215
                            <div class=\"col-sm-3\">
1216
                                $answerList
1217
                            </div>
1218
                        </div>
1219
                        <script>
1220
                                new ".($answerType == HOT_SPOT ? "HotspotQuestion" : "DelineationQuestion")."({
1221
                                    questionId: $questionId,
1222
                                    exerciseId: $exerciseId,
1223
                                    selector: '#hotspot-preview-$questionId',
1224
                                    for: 'preview',
1225
                                    relPath: '$relPath'
1226
                                });
1227
                        </script>
1228
                    ";
1229
                    return;
1230
                }
1231
            }
1232
1233
            if (!$only_questions) {
1234
                if ($show_title) {
1235
                    TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1236
1237
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1238
                }
1239
                //@todo I need to the get the feedback type
1240
                echo <<<HOTSPOT
1241
                    <input type="hidden" name="hidden_hotspot_id" value="$questionId" />
1242
                    <div class="exercise_questions">
1243
                        $questionDescription
1244
                        <div class="row">
1245
HOTSPOT;
1246
            }
1247
1248
            $relPath = api_get_path(WEB_CODE_PATH);
1249
            $s .= "
1250
                            <div class=\"col-sm-8 col-md-9\">
1251
                                <div class=\"hotspot-image\"></div>
1252
                                <script>
1253
                                    $(document).on('ready', function () {
1254
                                        new " . ($answerType == HOT_SPOT_DELINEATION ? 'DelineationQuestion' : 'HotspotQuestion')."({
1255
                                            questionId: $questionId,
1256
                                            exerciseId: $exe_id,
1257
                                            selector: '#question_div_' + $questionId + ' .hotspot-image',
1258
                                            for: 'user',
1259
                                            relPath: '$relPath'
1260
                                        });
1261
                                    });
1262
                                </script>
1263
                            </div>
1264
                            <div class=\"col-sm-4 col-md-3\">
1265
                                $answerList
1266
                            </div>
1267
            ";
1268
1269
            echo <<<HOTSPOT
1270
                            $s
1271
                        </div>
1272
                    </div>
1273
HOTSPOT;
1274
        } elseif ($answerType == ANNOTATION) {
1275
            global $exe_id;
1276
            $relPath = api_get_path(WEB_CODE_PATH);
1277
            if (api_is_platform_admin() || api_is_course_admin()) {
1278
                $docId = DocumentManager::get_document_id($course, '/images/'.$pictureName);
1279 View Code Duplication
                if ($docId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $docId of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1280
                    $images_folder_visibility = api_get_item_visibility(
1281
                        $course,
1282
                        'document',
1283
                        $docId,
1284
                        api_get_session_id()
1285
                    );
1286
1287
                    if (!$images_folder_visibility) {
1288
                        echo Display::return_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'), 'warning');
1289
                    }
1290
                }
1291
1292
                if ($freeze) {
1293
                    echo Display::img(
1294
                        api_get_path(WEB_COURSE_PATH).$course['path'].'/document/images/'.$pictureName,
1295
                        $objQuestionTmp->selectTitle(),
1296
                        ['width' => '600px']
1297
                    );
1298
1299
                    return 0;
1300
                }
1301
            }
1302
1303
            if (!$only_questions) {
1304
                if ($show_title) {
1305
                    TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1306
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1307
                }
1308
                echo '
1309
                    <input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />
1310
                    <div class="exercise_questions">
1311
                        '.$objQuestionTmp->selectDescription().'
1312
                        <div class="row">
1313
                            <div class="col-sm-8 col-md-9">
1314
                                <div id="annotation-canvas-'.$questionId.'" class="annotation-canvas center-block">
1315
                                </div>
1316
                                <script>
1317
                                    AnnotationQuestion({
1318
                                        questionId: '.$questionId.',
1319
                                        exerciseId: '.$exe_id.',
1320
                                        relPath: \''.$relPath.'\'
1321
                                    });
1322
                                </script>
1323
                            </div>
1324
                            <div class="col-sm-4 col-md-3">
1325
                                <div class="well well-sm" id="annotation-toolbar-'.$questionId.'">
1326
                                    <div class="btn-toolbar">
1327
                                        <div class="btn-group" data-toggle="buttons">
1328
                                            <label class="btn btn-default active"
1329
                                                aria-label="'.get_lang('AddAnnotationPath').'">
1330
                                                <input type="radio" value="0" name="'.$questionId.'-options" autocomplete="off" checked>
1331
                                                <span class="fa fa-pencil" aria-hidden="true"></span>
1332
                                            </label>
1333
                                            <label class="btn btn-default"
1334
                                                aria-label="'.get_lang('AddAnnotationText').'">
1335
                                                <input type="radio" value="1" name="'.$questionId.'-options" autocomplete="off">
1336
                                                <span class="fa fa-font fa-fw" aria-hidden="true"></span>
1337
                                            </label>
1338
                                        </div>
1339
                                    </div>
1340
                                    <ul class="list-unstyled"></ul>
1341
                                </div>
1342
                            </div>
1343
                        </div>
1344
                    </div>
1345
                ';
1346
            }
1347
1348
            $objAnswerTmp = new Answer($questionId);
1349
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1350
1351
            unset($objAnswerTmp, $objQuestionTmp);
1352
1353
        }
1354
        return $nbrAnswers;
1355
    }
1356
1357
    /**
1358
     * @param int $exe_id
1359
     * @return array
1360
     */
1361
    public static function get_exercise_track_exercise_info($exe_id)
1362
    {
1363
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1364
        $TBL_TRACK_EXERCICES = Database::get_main_table(
1365
            TABLE_STATISTIC_TRACK_E_EXERCISES
1366
        );
1367
        $TBL_COURSE = Database::get_main_table(TABLE_MAIN_COURSE);
1368
        $exe_id = intval($exe_id);
1369
        $result = array();
1370
        if (!empty($exe_id)) {
1371
            $sql = " SELECT q.*, tee.*
1372
                FROM $TBL_EXERCICES as q
1373
                INNER JOIN $TBL_TRACK_EXERCICES as tee
1374
                ON q.id = tee.exe_exo_id
1375
                INNER JOIN $TBL_COURSE c
1376
                ON c.id = tee.c_id
1377
                WHERE tee.exe_id = $exe_id
1378
                AND q.c_id = c.id";
1379
1380
            $res_fb_type = Database::query($sql);
1381
            $result = Database::fetch_array($res_fb_type, 'ASSOC');
1382
        }
1383
1384
        return $result;
1385
    }
1386
1387
    /**
1388
     * Validates the time control key
1389
     * @param int $exercise_id
1390
     * @param int $lp_id
1391
     * @param int $lp_item_id
1392
     * @return bool
1393
     */
1394
    public static function exercise_time_control_is_valid(
1395
        $exercise_id,
1396
        $lp_id = 0,
1397
        $lp_item_id = 0
1398
    ) {
1399
        $course_id = api_get_course_int_id();
1400
        $exercise_id = intval($exercise_id);
1401
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1402
        $sql = "SELECT expired_time FROM $TBL_EXERCICES
1403
            WHERE c_id = $course_id AND id = $exercise_id";
1404
        $result = Database::query($sql);
1405
        $row = Database::fetch_array($result, 'ASSOC');
1406
        if (!empty($row['expired_time'])) {
1407
            $current_expired_time_key = self::get_time_control_key(
1408
                $exercise_id,
1409
                $lp_id,
1410
                $lp_item_id
1411
            );
1412
            if (isset($_SESSION['expired_time'][$current_expired_time_key])) {
1413
                $current_time = time();
1414
                $expired_time = api_strtotime(
1415
                    $_SESSION['expired_time'][$current_expired_time_key],
1416
                    'UTC'
1417
                );
1418
                $total_time_allowed = $expired_time + 30;
1419
                if ($total_time_allowed < $current_time) {
1420
                    return false;
1421
                }
1422
                return true;
1423
            } else {
1424
                return false;
1425
            }
1426
        } else {
1427
            return true;
1428
        }
1429
    }
1430
1431
    /**
1432
     * Deletes the time control token
1433
     *
1434
     * @param int $exercise_id
1435
     * @param int $lp_id
1436
     * @param int $lp_item_id
1437
     */
1438
    public static function exercise_time_control_delete(
1439
        $exercise_id,
1440
        $lp_id = 0,
1441
        $lp_item_id = 0
1442
    ) {
1443
        $current_expired_time_key = self::get_time_control_key(
1444
            $exercise_id,
1445
            $lp_id,
1446
            $lp_item_id
1447
        );
1448
        unset($_SESSION['expired_time'][$current_expired_time_key]);
1449
    }
1450
1451
    /**
1452
     * Generates the time control key
1453
     */
1454
    public static function get_time_control_key(
1455
        $exercise_id,
1456
        $lp_id = 0,
1457
        $lp_item_id = 0
1458
    ) {
1459
        $exercise_id = intval($exercise_id);
1460
        $lp_id = intval($lp_id);
1461
        $lp_item_id = intval($lp_item_id);
1462
        return
1463
            api_get_course_int_id().'_'.
1464
            api_get_session_id().'_'.
1465
            $exercise_id.'_'.
1466
            api_get_user_id().'_'.
1467
            $lp_id.'_'.
1468
            $lp_item_id;
1469
    }
1470
1471
    /**
1472
     * Get session time control
1473
     *
1474
     * @param int $exercise_id
1475
     * @param int $lp_id
1476
     * @param int $lp_item_id
1477
     * @return int
1478
     */
1479
    public static function get_session_time_control_key(
1480
        $exercise_id,
1481
        $lp_id = 0,
1482
        $lp_item_id = 0
1483
    ) {
1484
        $return_value = 0;
1485
        $time_control_key = self::get_time_control_key(
1486
            $exercise_id,
1487
            $lp_id,
1488
            $lp_item_id
1489
        );
1490
        if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) {
1491
            $return_value = $_SESSION['expired_time'][$time_control_key];
1492
        }
1493
        return $return_value;
1494
    }
1495
1496
    /**
1497
     * Gets count of exam results
1498
     * @todo this function should be moved in a library  + no global calls
1499
     */
1500
    public static function get_count_exam_results($exercise_id, $extra_where_conditions)
1501
    {
1502
        $count = self::get_exam_results_data(
1503
            null,
1504
            null,
1505
            null,
1506
            null,
1507
            $exercise_id,
1508
            $extra_where_conditions,
1509
            true
1510
        );
1511
        return $count;
1512
    }
1513
1514
    /**
1515
     * @param string $in_hotpot_path
1516
     * @return int
1517
     */
1518
    public static function get_count_exam_hotpotatoes_results($in_hotpot_path)
1519
    {
1520
        return self::get_exam_results_hotpotatoes_data(
1521
            0,
1522
            0,
1523
            '',
1524
            '',
1525
            $in_hotpot_path,
1526
            true,
1527
            ''
1528
        );
1529
    }
1530
1531
    /**
1532
     * @param int $in_from
1533
     * @param int $in_number_of_items
1534
     * @param int $in_column
1535
     * @param int $in_direction
1536
     * @param string $in_hotpot_path
1537
     * @param bool $in_get_count
1538
     * @param null $where_condition
1539
     * @return array|int
1540
     */
1541
    public static function get_exam_results_hotpotatoes_data(
1542
        $in_from,
1543
        $in_number_of_items,
1544
        $in_column,
1545
        $in_direction,
1546
        $in_hotpot_path,
1547
        $in_get_count = false,
1548
        $where_condition = null
1549
    ) {
1550
        $courseId = api_get_course_int_id();
1551
        // by default in_column = 1 If parameters given, it is the name of the column witch is the bdd field name
1552
        if ($in_column == 1) {
1553
            $in_column = 'firstname';
1554
        }
1555
        $in_hotpot_path = Database::escape_string($in_hotpot_path);
1556
        $in_direction = Database::escape_string($in_direction);
1557
        $in_column = Database::escape_string($in_column);
1558
        $in_number_of_items = intval($in_number_of_items);
1559
        $in_from = intval($in_from);
1560
1561
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(
1562
            TABLE_STATISTIC_TRACK_E_HOTPOTATOES
1563
        );
1564
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
1565
1566
        $sql = "SELECT * FROM $TBL_TRACK_HOTPOTATOES thp
1567
            JOIN $TBL_USER u ON thp.exe_user_id = u.user_id
1568
            WHERE thp.c_id = $courseId AND exe_name LIKE '$in_hotpot_path%'";
1569
1570
        // just count how many answers
1571
        if ($in_get_count) {
1572
            $res = Database::query($sql);
1573
            return Database::num_rows($res);
1574
        }
1575
        // get a number of sorted results
1576
        $sql .= " $where_condition
1577
            ORDER BY $in_column $in_direction
1578
            LIMIT $in_from, $in_number_of_items";
1579
1580
        $res = Database::query($sql);
1581
        $result = array();
1582
        $apiIsAllowedToEdit = api_is_allowed_to_edit();
1583
        $urlBase = api_get_path(WEB_CODE_PATH).
1584
            'exercise/hotpotatoes_exercise_report.php?action=delete&'.
1585
            api_get_cidreq().'&id=';
1586
        while ($data = Database::fetch_array($res)) {
1587
            $actions = null;
1588
1589
            if ($apiIsAllowedToEdit) {
1590
                $url = $urlBase.$data['id'].'&path='.$data['exe_name'];
1591
                $actions = Display::url(
1592
                    Display::return_icon('delete.png', get_lang('Delete')),
1593
                    $url
1594
                );
1595
            }
1596
1597
            $result[] = array(
1598
                'firstname' => $data['firstname'],
1599
                'lastname' => $data['lastname'],
1600
                'username' => $data['username'],
1601
                'group_name' => implode(
1602
                    "<br/>",
1603
                    GroupManager::get_user_group_name($data['user_id'])
1604
                ),
1605
                'exe_date' => $data['exe_date'],
1606
                'score' => $data['exe_result'].' / '.$data['exe_weighting'],
1607
                'actions' => $actions,
1608
            );
1609
        }
1610
1611
        return $result;
1612
    }
1613
1614
    /**
1615
     * @param string $exercisePath
1616
     * @param int $userId
1617
     * @param int $courseId
1618
     * @param int $sessionId
1619
     *
1620
     * @return array
1621
     */
1622 View Code Duplication
    public static function getLatestHotPotatoResult(
1623
        $exercisePath,
1624
        $userId,
1625
        $courseId,
1626
        $sessionId
1627
    ) {
1628
        $table = Database::get_main_table(
1629
            TABLE_STATISTIC_TRACK_E_HOTPOTATOES
1630
        );
1631
        $exercisePath = Database::escape_string($exercisePath);
1632
        $userId = intval($userId);
1633
1634
        $sql = "SELECT * FROM $table
1635
            WHERE
1636
                c_id = $courseId AND
1637
                exe_name LIKE '$exercisePath%' AND
1638
                exe_user_id = $userId
1639
            ORDER BY id
1640
            LIMIT 1";
1641
        $result = Database::query($sql);
1642
        $attempt = array();
1643
        if (Database::num_rows($result)) {
1644
            $attempt = Database::fetch_array($result, 'ASSOC');
1645
        }
1646
        return $attempt;
1647
    }
1648
1649
    /**
1650
     * Gets the exam'data results
1651
     * @todo this function should be moved in a library  + no global calls
1652
     * @param int $from
1653
     * @param int $number_of_items
1654
     * @param int $column
1655
     * @param string $direction
1656
     * @param int $exercise_id
1657
     * @param null $extra_where_conditions
1658
     * @param bool $get_count
1659
     * @param string $courseCode
1660
     * @return array
1661
     */
1662
    public static function get_exam_results_data(
1663
        $from,
1664
        $number_of_items,
1665
        $column,
1666
        $direction,
1667
        $exercise_id,
1668
        $extra_where_conditions = null,
1669
        $get_count = false,
1670
        $courseCode = null
1671
    ) {
1672
        //@todo replace all this globals
1673
        global $documentPath, $filter;
1674
1675
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
1676
        $courseInfo = api_get_course_info($courseCode);
1677
        $course_id = $courseInfo['real_id'];
1678
        $sessionId = api_get_session_id();
1679
        $is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_allowed_to_edit(true) || api_is_drh() || api_is_student_boss();
1680
1681
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
1682
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1683
        $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
1684
        $TBL_GROUP = Database::get_course_table(TABLE_GROUP);
1685
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1686
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
1687
        $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1688
1689
        $session_id_and = ' AND te.session_id = '.$sessionId.' ';
1690
        $exercise_id = intval($exercise_id);
1691
1692
        $exercise_where = '';
1693
        if (!empty($exercise_id)) {
1694
            $exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.'  ';
1695
        }
1696
1697
        $hotpotatoe_where = '';
1698
        if (!empty($_GET['path'])) {
1699
            $hotpotatoe_path = Database::escape_string($_GET['path']);
1700
            $hotpotatoe_where .= ' AND exe_name = "'.$hotpotatoe_path.'"  ';
1701
        }
1702
1703
        // sql for chamilo-type tests for teacher / tutor view
1704
        $sql_inner_join_tbl_track_exercices = "
1705
        (
1706
            SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised
1707
            FROM $TBL_TRACK_EXERCICES ttte LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
1708
            ON (ttte.exe_id = tr.exe_id)
1709
            WHERE
1710
                c_id = $course_id AND
1711
                exe_exo_id = $exercise_id AND
1712
                ttte.session_id = ".$sessionId."
1713
        )";
1714
1715
        if ($is_allowedToEdit) {
1716
            //@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries
1717
            // Hack in order to filter groups
1718
            $sql_inner_join_tbl_user = '';
1719
            if (strpos($extra_where_conditions, 'group_id')) {
1720
                $sql_inner_join_tbl_user = "
1721
                (
1722
                    SELECT
1723
                        u.user_id,
1724
                        firstname,
1725
                        lastname,
1726
                        official_code,
1727
                        email,
1728
                        username,
1729
                        g.name as group_name,
1730
                        g.id as group_id
1731
                    FROM $TBL_USER u
1732
                    INNER JOIN $TBL_GROUP_REL_USER gru
1733
                    ON (gru.user_id = u.user_id AND gru.c_id=".$course_id.")
1734
                    INNER JOIN $TBL_GROUP g
1735
                    ON (gru.group_id = g.id AND g.c_id=".$course_id.")
1736
                )";
1737
            }
1738
1739
            if (strpos($extra_where_conditions, 'group_all')) {
1740
                $extra_where_conditions = str_replace(
1741
                    "AND (  group_id = 'group_all'  )",
1742
                    '',
1743
                    $extra_where_conditions
1744
                );
1745
                $extra_where_conditions = str_replace(
1746
                    "AND group_id = 'group_all'",
1747
                    '',
1748
                    $extra_where_conditions
1749
                );
1750
                $extra_where_conditions = str_replace(
1751
                    "group_id = 'group_all' AND",
1752
                    '',
1753
                    $extra_where_conditions
1754
                );
1755
1756
                $sql_inner_join_tbl_user = "
1757
                (
1758
                    SELECT
1759
                        u.user_id,
1760
                        firstname,
1761
                        lastname,
1762
                        official_code,
1763
                        email,
1764
                        username,
1765
                        '' as group_name,
1766
                        '' as group_id
1767
                    FROM $TBL_USER u
1768
                )";
1769
                $sql_inner_join_tbl_user = null;
1770
            }
1771
1772
            if (strpos($extra_where_conditions, 'group_none')) {
1773
                $extra_where_conditions = str_replace(
1774
                    "AND (  group_id = 'group_none'  )",
1775
                    "AND (  group_id is null  )",
1776
                    $extra_where_conditions
1777
                );
1778
                $extra_where_conditions = str_replace(
1779
                    "AND group_id = 'group_none'",
1780
                    "AND (  group_id is null  )",
1781
                    $extra_where_conditions
1782
                );
1783
                $sql_inner_join_tbl_user = "
1784
            (
1785
                SELECT
1786
                    u.user_id,
1787
                    firstname,
1788
                    lastname,
1789
                    official_code,
1790
                    email,
1791
                    username,
1792
                    g.name as group_name,
1793
                    g.id as group_id
1794
                FROM $TBL_USER u
1795
                LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
1796
                ON ( gru.user_id = u.user_id AND gru.c_id=".$course_id." )
1797
                LEFT OUTER JOIN $TBL_GROUP g
1798
                ON (gru.group_id = g.id AND g.c_id = ".$course_id.")
1799
            )";
1800
            }
1801
1802
            // All
1803
            $is_empty_sql_inner_join_tbl_user = false;
1804
            if (empty($sql_inner_join_tbl_user)) {
1805
                $is_empty_sql_inner_join_tbl_user = true;
1806
                $sql_inner_join_tbl_user = "
1807
            (
1808
                SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code
1809
                FROM $TBL_USER u
1810
                WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
1811
            )";
1812
            }
1813
1814
            $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
1815
            $sqlWhereOption = "  AND gru.c_id = ".$course_id." AND gru.user_id = user.user_id ";
1816
            $first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
1817
1818
            if ($get_count) {
1819
                $sql_select = "SELECT count(te.exe_id) ";
1820
            } else {
1821
                $sql_select = "SELECT DISTINCT
1822
                    user_id,
1823
                    $first_and_last_name,
1824
                    official_code,
1825
                    ce.title,
1826
                    username,
1827
                    te.exe_result,
1828
                    te.exe_weighting,
1829
                    te.exe_date,
1830
                    te.exe_id,
1831
                    email as exemail,
1832
                    te.start_date,
1833
                    ce.expired_time,
1834
                    steps_counter,
1835
                    exe_user_id,
1836
                    te.exe_duration,
1837
                    te.status as completion_status,
1838
                    propagate_neg,
1839
                    revised,
1840
                    group_name,
1841
                    group_id,
1842
                    orig_lp_id,
1843
                    te.user_ip";
1844
            }
1845
1846
            $sql = " $sql_select
1847
                FROM $TBL_EXERCICES AS ce
1848
                INNER JOIN $sql_inner_join_tbl_track_exercices AS te
1849
                ON (te.exe_exo_id = ce.id)
1850
                INNER JOIN $sql_inner_join_tbl_user AS user
1851
                ON (user.user_id = exe_user_id)
1852
                WHERE
1853
                    te.c_id = ".$course_id." $session_id_and AND
1854
                    ce.active <>-1 AND 
1855
                    ce.c_id = ".$course_id."
1856
                    $exercise_where
1857
                    $extra_where_conditions
1858
                ";
1859
1860
            // sql for hotpotatoes tests for teacher / tutor view
1861
            if ($get_count) {
1862
                $hpsql_select = "SELECT count(username)";
1863
            } else {
1864
                $hpsql_select = "SELECT
1865
                    $first_and_last_name ,
1866
                    username,
1867
                    official_code,
1868
                    tth.exe_name,
1869
                    tth.exe_result ,
1870
                    tth.exe_weighting,
1871
                    tth.exe_date";
1872
            }
1873
1874
            $hpsql = " $hpsql_select
1875
                FROM
1876
                    $TBL_TRACK_HOTPOTATOES tth,
1877
                    $TBL_USER user
1878
                    $sqlFromOption
1879
                WHERE
1880
                    user.user_id=tth.exe_user_id
1881
                    AND tth.c_id = ".$course_id."
1882
                    $hotpotatoe_where
1883
                    $sqlWhereOption
1884
                    AND user.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
1885
                ORDER BY
1886
                    tth.c_id ASC,
1887
                    tth.exe_date DESC";
1888
        }
1889
1890
        if ($get_count) {
1891
            $resx = Database::query($sql);
1892
            $rowx = Database::fetch_row($resx, 'ASSOC');
1893
1894
            return $rowx[0];
1895
        }
1896
1897
        $teacher_list = CourseManager::get_teacher_list_from_course_code(
1898
            $courseCode
1899
        );
1900
        $teacher_id_list = array();
1901
        if (!empty($teacher_list)) {
1902
            foreach ($teacher_list as $teacher) {
1903
                $teacher_id_list[] = $teacher['user_id'];
1904
            }
1905
        }
1906
1907
        $list_info = array();
1908
1909
        // Simple exercises
1910
        if (empty($hotpotatoe_where)) {
1911
            $column = !empty($column) ? Database::escape_string($column) : null;
1912
            $from = intval($from);
1913
            $number_of_items = intval($number_of_items);
1914
1915
            if (!empty($column)) {
1916
                $sql .= " ORDER BY $column $direction ";
1917
            }
1918
            $sql .= " LIMIT $from, $number_of_items";
1919
1920
            $results = array();
1921
            $resx = Database::query($sql);
1922
            while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
1923
                $results[] = $rowx;
1924
            }
1925
1926
            $group_list = GroupManager::get_group_list(null, $courseInfo);
1927
            $clean_group_list = array();
1928
1929
            if (!empty($group_list)) {
1930
                foreach ($group_list as $group) {
1931
                    $clean_group_list[$group['id']] = $group['name'];
1932
                }
1933
            }
1934
1935
            $lp_list_obj = new LearnpathList(api_get_user_id());
1936
            $lp_list = $lp_list_obj->get_flat_list();
1937
1938
            $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
1939
1940
            if (is_array($results)) {
1941
                $users_array_id = array();
1942
                $from_gradebook = false;
1943
                if (isset($_GET['gradebook']) && $_GET['gradebook'] == 'view') {
1944
                    $from_gradebook = true;
1945
                }
1946
                $sizeof = count($results);
1947
                $user_list_id = array();
1948
                $locked = api_resource_is_locked_by_gradebook(
1949
                    $exercise_id,
1950
                    LINK_EXERCISE
1951
                );
1952
1953
                $timeNow = strtotime(api_get_utc_datetime());
1954
                // Looping results
1955
                for ($i = 0; $i < $sizeof; $i++) {
1956
                    $revised = $results[$i]['revised'];
1957
                    if ($results[$i]['completion_status'] == 'incomplete') {
1958
                        // If the exercise was incomplete, we need to determine
1959
                        // if it is still into the time allowed, or if its
1960
                        // allowed time has expired and it can be closed
1961
                        // (it's "unclosed")
1962
                        $minutes = $results[$i]['expired_time'];
1963
                        if ($minutes == 0) {
1964
                            // There's no time limit, so obviously the attempt
1965
                            // can still be "ongoing", but the teacher should
1966
                            // be able to choose to close it, so mark it as
1967
                            // "unclosed" instead of "ongoing"
1968
                            $revised = 2;
1969
                        } else {
1970
                            $allowedSeconds = $minutes * 60;
1971
                            $timeAttemptStarted = strtotime($results[$i]['start_date']);
1972
                            $secondsSinceStart = $timeNow - $timeAttemptStarted;
1973
                            if ($secondsSinceStart > $allowedSeconds) {
1974
                                $revised = 2; // mark as "unclosed"
1975
                            } else {
1976
                                $revised = 3; // mark as "ongoing"
1977
                            }
1978
                        }
1979
                    }
1980
1981
                    if ($from_gradebook && ($is_allowedToEdit)) {
1982
                        if (in_array(
1983
                            $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'],
1984
                            $users_array_id
1985
                        )) {
1986
                            continue;
1987
                        }
1988
                        $users_array_id[] = $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'];
1989
                    }
1990
1991
                    $lp_obj = isset($results[$i]['orig_lp_id']) && isset($lp_list[$results[$i]['orig_lp_id']]) ? $lp_list[$results[$i]['orig_lp_id']] : null;
1992
                    if (empty($lp_obj)) {
1993
                        // Try to get the old id (id instead of iid)
1994
                        $lpNewId = isset($results[$i]['orig_lp_id']) && isset($oldIds[$results[$i]['orig_lp_id']]) ? $oldIds[$results[$i]['orig_lp_id']] : null;
1995
                        if ($lpNewId) {
1996
                            $lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
1997
                        }
1998
                    }
1999
                    $lp_name = null;
2000
                    if ($lp_obj) {
2001
                        $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$results[$i]['orig_lp_id'];
2002
                        $lp_name = Display::url(
2003
                            $lp_obj['lp_name'],
2004
                            $url,
2005
                            array('target' => '_blank')
2006
                        );
2007
                    }
2008
2009
                    // Add all groups by user
2010
                    $group_name_list = null;
2011
                    if ($is_empty_sql_inner_join_tbl_user) {
2012
                        $group_list = GroupManager::get_group_ids(
2013
                            api_get_course_int_id(),
2014
                            $results[$i]['user_id']
2015
                        );
2016
2017
                        foreach ($group_list as $id) {
2018
                            $group_name_list .= $clean_group_list[$id].'<br/>';
2019
                        }
2020
                        $results[$i]['group_name'] = $group_name_list;
2021
                    }
2022
2023
                    $results[$i]['exe_duration'] = !empty($results[$i]['exe_duration']) ? round($results[$i]['exe_duration'] / 60) : 0;
2024
2025
                    $user_list_id[] = $results[$i]['exe_user_id'];
2026
                    $id = $results[$i]['exe_id'];
2027
                    $dt = api_convert_and_format_date($results[$i]['exe_weighting']);
2028
2029
                    // we filter the results if we have the permission to
2030
                    if (isset($results[$i]['results_disabled'])) {
2031
                        $result_disabled = intval(
2032
                            $results[$i]['results_disabled']
2033
                        );
2034
                    } else {
2035
                        $result_disabled = 0;
2036
                    }
2037
2038
                    if ($result_disabled == 0) {
2039
                        $my_res = $results[$i]['exe_result'];
2040
                        $my_total = $results[$i]['exe_weighting'];
2041
2042
                        $results[$i]['start_date'] = api_get_local_time(
2043
                            $results[$i]['start_date']
2044
                        );
2045
                        $results[$i]['exe_date'] = api_get_local_time(
2046
                            $results[$i]['exe_date']
2047
                        );
2048
2049
                        if (!$results[$i]['propagate_neg'] && $my_res < 0) {
2050
                            $my_res = 0;
2051
                        }
2052
2053
                        $score = self::show_score($my_res, $my_total);
2054
2055
                        $actions = '<div class="pull-right">';
2056
                        if ($is_allowedToEdit) {
2057
                            if (isset($teacher_id_list)) {
2058
                                if (in_array(
2059
                                    $results[$i]['exe_user_id'],
2060
                                    $teacher_id_list
2061
                                )) {
2062
                                    $actions .= Display::return_icon(
2063
                                        'teacher.png',
2064
                                        get_lang('Teacher')
2065
                                    );
2066
                                }
2067
                            }
2068
                            $revisedLabel = '';
2069
                            switch ($revised) {
2070
                                case 0:
2071
                                    $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=qualify&id=$id'>".
2072
                                        Display:: return_icon(
2073
                                            'quiz.png',
2074
                                            get_lang('Qualify')
2075
                                        );
2076
                                    $actions .= '</a>';
2077
                                    $revisedLabel = Display::label(
2078
                                        get_lang('NotValidated'),
2079
                                        'info'
2080
                                    );
2081
                                    break;
2082 View Code Duplication
                                case 1:
2083
                                    $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=edit&id=$id'>".
2084
                                        Display:: return_icon(
2085
                                            'edit.png',
2086
                                            get_lang('Edit'),
2087
                                            array(),
2088
                                            ICON_SIZE_SMALL
2089
                                        );
2090
                                    $actions .= '</a>';
2091
                                    $revisedLabel = Display::label(
2092
                                        get_lang('Validated'),
2093
                                        'success'
2094
                                    );
2095
                                    break;
2096
                                case 2: //finished but not marked as such
2097
                                    $actions .= '<a href="exercise_report.php?'
2098
                                        . api_get_cidreq()
2099
                                        . '&exerciseId='
2100
                                        . $exercise_id
2101
                                        . '&a=close&id='
2102
                                        . $id
2103
                                        . '">'.
2104
                                        Display:: return_icon(
2105
                                            'lock.png',
2106
                                            get_lang('MarkAttemptAsClosed'),
2107
                                            array(),
2108
                                            ICON_SIZE_SMALL
2109
                                        );
2110
                                    $actions .= '</a>';
2111
                                    $revisedLabel = Display::label(
2112
                                        get_lang('Unclosed'),
2113
                                        'warning'
2114
                                    );
2115
                                    break;
2116 View Code Duplication
                                case 3: //still ongoing
2117
                                    $actions .= "".
2118
                                        Display:: return_icon(
2119
                                            'clock.png',
2120
                                            get_lang('AttemptStillOngoingPleaseWait'),
2121
                                            array(),
2122
                                            ICON_SIZE_SMALL
2123
                                        );
2124
                                    $actions .= '';
2125
                                    $revisedLabel = Display::label(
2126
                                        get_lang('Ongoing'),
2127
                                        'danger'
2128
                                    );
2129
                                    break;
2130
                            }
2131
2132
                            if ($filter == 2) {
2133
                                $actions .= ' <a href="exercise_history.php?'.api_get_cidreq().'&exe_id='.$id.'">'.
2134
                                    Display:: return_icon(
2135
                                        'history.png',
2136
                                        get_lang('ViewHistoryChange')
2137
                                    ).'</a>';
2138
                            }
2139
2140
                            //Admin can always delete the attempt
2141
                            if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
2142
                                $ip = TrackingUserLog::get_ip_from_user_event(
2143
                                    $results[$i]['exe_user_id'],
2144
                                    api_get_utc_datetime(),
2145
                                    false
2146
                                );
2147
                                $actions .= '<a href="http://www.whatsmyip.org/ip-geo-location/?ip='.$ip.'" target="_blank">'
2148
                                . Display::return_icon('info.png', $ip)
2149
                                .'</a>';
2150
2151
2152
                                $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'.
2153
                                    api_get_cidreq().'&'.
2154
                                    http_build_query([
2155
                                        'id' => $id,
2156
                                        'exercise' => $exercise_id,
2157
                                        'user' => $results[$i]['exe_user_id']
2158
                                    ]);
2159
                                $actions .= Display::url(
2160
                                    Display::return_icon('reload.png', get_lang('RecalculateResults')),
2161
                                    $recalculateUrl,
2162
                                    [
2163
                                        'data-exercise' => $exercise_id,
2164
                                        'data-user' => $results[$i]['exe_user_id'],
2165
                                        'data-id' => $id,
2166
                                        'class' => 'exercise-recalculate'
2167
                                    ]
2168
                                );
2169
2170
                                $delete_link = '<a href="exercise_report.php?'.api_get_cidreq().'&filter_by_user='.intval($_GET['filter_by_user']).'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'"
2171
                                onclick="javascript:if(!confirm(\'' . sprintf(
2172
                                        get_lang('DeleteAttempt'),
2173
                                        $results[$i]['username'],
2174
                                        $dt
2175
                                    ).'\')) return false;">'.Display:: return_icon(
2176
                                        'delete.png',
2177
                                        get_lang('Delete')
2178
                                    ).'</a>';
2179
                                $delete_link = utf8_encode($delete_link);
2180
2181
                                if (api_is_drh() && !api_is_platform_admin()) {
2182
                                    $delete_link = null;
2183
                                }
2184
                                if ($revised == 3) {
2185
                                    $delete_link = null;
2186
                                }
2187
                                $actions .= $delete_link;
2188
                            }
2189
2190
                        } else {
2191
                            $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.api_get_cidreq().'&id='.$results[$i]['exe_id'].'&id_session='.$sessionId;
2192
                            $attempt_link = Display::url(
2193
                                get_lang('Show'),
2194
                                $attempt_url,
2195
                                [
2196
                                    'class' => 'ajax btn btn-default',
2197
                                    'data-title' => get_lang('Show')
2198
                                ]
2199
                            );
2200
                            $actions .= $attempt_link;
2201
                        }
2202
                        $actions .= '</div>';
2203
2204
                        $results[$i]['id'] = $results[$i]['exe_id'];
2205
2206
                        if ($is_allowedToEdit) {
2207
                            $results[$i]['status'] = $revisedLabel;
2208
                            $results[$i]['score'] = $score;
2209
                            $results[$i]['lp'] = $lp_name;
2210
                            $results[$i]['actions'] = $actions;
2211
                            $list_info[] = $results[$i];
2212
                        } else {
2213
                            $results[$i]['status'] = $revisedLabel;
2214
                            $results[$i]['score'] = $score;
2215
                            $results[$i]['actions'] = $actions;
2216
                            $list_info[] = $results[$i];
2217
                        }
2218
                    }
2219
                }
2220
            }
2221
        } else {
2222
            $hpresults = StatsUtils::getManyResultsXCol($hpsql, 6);
2223
            // Print HotPotatoes test results.
2224
            if (is_array($hpresults)) {
2225
                for ($i = 0; $i < sizeof($hpresults); $i++) {
2226
                    $hp_title = GetQuizName($hpresults[$i][3], $documentPath);
2227
                    if ($hp_title == '') {
2228
                        $hp_title = basename($hpresults[$i][3]);
2229
                    }
2230
2231
                    $hp_date = api_get_local_time(
2232
                        $hpresults[$i][6],
2233
                        null,
2234
                        date_default_timezone_get()
2235
                    );
2236
                    $hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2)
2237
                        .'% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')';
2238
                    if ($is_allowedToEdit) {
2239
                        $list_info[] = array(
2240
                            $hpresults[$i][0],
2241
                            $hpresults[$i][1],
2242
                            $hpresults[$i][2],
2243
                            '',
2244
                            $hp_title,
2245
                            '-',
2246
                            $hp_date,
2247
                            $hp_result,
2248
                            '-'
2249
                        );
2250
                    } else {
2251
                        $list_info[] = array(
2252
                            $hp_title,
2253
                            '-',
2254
                            $hp_date,
2255
                            $hp_result,
2256
                            '-'
2257
                        );
2258
                    }
2259
                }
2260
            }
2261
        }
2262
2263
        return $list_info;
2264
    }
2265
2266
    /**
2267
     * Converts the score with the exercise_max_note and exercise_min_score
2268
     * the platform settings + formats the results using the float_format function
2269
     *
2270
     * @param float $score
2271
     * @param float $weight
2272
     * @param bool $show_percentage show percentage or not
2273
     * @param bool $use_platform_settings use or not the platform settings
2274
     * @param bool $show_only_percentage
2275
     * @return  string  an html with the score modified
2276
     */
2277
    public static function show_score(
2278
        $score,
2279
        $weight,
2280
        $show_percentage = true,
2281
        $use_platform_settings = true,
2282
        $show_only_percentage = false
2283
    ) {
2284
        if (is_null($score) && is_null($weight)) {
2285
            return '-';
2286
        }
2287
2288
        $maxNote = api_get_setting('exercise_max_score');
2289
        $minNote = api_get_setting('exercise_min_score');
2290
2291 View Code Duplication
        if ($use_platform_settings) {
2292
            if ($maxNote != '' && $minNote != '') {
2293
                if (!empty($weight) && intval($weight) != 0) {
2294
                    $score = $minNote + ($maxNote - $minNote) * $score / $weight;
2295
                } else {
2296
                    $score = $minNote;
2297
                }
2298
                $weight = $maxNote;
2299
            }
2300
        }
2301
        $percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
2302
2303
        // Formats values
2304
        $percentage = float_format($percentage, 1);
2305
        $score = float_format($score, 1);
2306
        $weight = float_format($weight, 1);
2307
2308
        $html = '';
2309
        if ($show_percentage) {
2310
            $parent = '('.$score.' / '.$weight.')';
2311
            $html = $percentage."%  $parent";
2312
            if ($show_only_percentage) {
2313
                $html = $percentage."% ";
2314
            }
2315
        } else {
2316
            $html = $score.' / '.$weight;
2317
        }
2318
2319
        // Over write score
2320
        $scoreBasedInModel = self::convertScoreToModel($percentage);
2321
        if (!empty($scoreBasedInModel)) {
2322
            $html = $scoreBasedInModel;
2323
        }
2324
2325
        $html = Display::span($html, ['class' => 'score_exercise']);
2326
2327
        return $html;
2328
    }
2329
2330
    /**
2331
     * @param array $model
2332
     * @param float $percentage
2333
     * @return string
2334
     */
2335
    public static function getModelStyle($model, $percentage)
2336
    {
2337
        $modelWithStyle = get_lang($model['name']);
2338
        $modelWithStyle .= ' - <span class="'.$model['css_class'].'">'.$model['css_class'].' </span> - ';
2339
        $modelWithStyle .= $percentage;
2340
2341
        return $modelWithStyle;
2342
    }
2343
2344
    /**
2345
     * @param float $percentage value between 0 and 100
2346
     * @return string
2347
     */
2348
    public static function convertScoreToModel($percentage)
2349
    {
2350
        $model = self::getCourseScoreModel();
2351
        if (!empty($model)) {
2352
            $scoreWithGrade = [];
2353
            foreach ($model['score_list'] as $item) {
2354
                if ($percentage >= $item['min']  && $percentage <= $item['max']) {
2355
                    $scoreWithGrade = $item;
2356
                    break;
2357
                }
2358
            }
2359
2360
            if (!empty($scoreWithGrade)) {
2361
                return self::getModelStyle($scoreWithGrade, $percentage);
2362
            }
2363
        }
2364
        return '';
2365
    }
2366
2367
    /**
2368
     * @return array
2369
     */
2370
    public static function getCourseScoreModel()
2371
    {
2372
        $modelList = self::getScoreModels();
2373
        if (empty($modelList)) {
2374
            return [];
2375
        }
2376
2377
        $courseInfo = api_get_course_info();
2378
        if (!empty($courseInfo)) {
2379
            $scoreModelId = api_get_course_setting('score_model_id');
2380
            if ($scoreModelId != -1) {
2381
                $modelIdList = array_column($modelList['models'], 'id');
2382
                if (in_array($scoreModelId, $modelIdList)) {
2383
                    foreach ($modelList['models'] as $item) {
2384
                        if ($item['id'] == $scoreModelId) {
2385
                            return $item;
2386
                        }
2387
                    }
2388
                }
2389
            }
2390
        }
2391
2392
        return [];
2393
    }
2394
2395
    /**
2396
     * @return array
2397
     */
2398
    public static function getScoreModels()
2399
    {
2400
        return api_get_configuration_value('score_grade_model');
2401
    }
2402
2403
    /**
2404
     * @param float $score
2405
     * @param float $weight
2406
     * @param string $pass_percentage
2407
     * @return bool
2408
     */
2409
    public static function isSuccessExerciseResult($score, $weight, $pass_percentage)
2410
    {
2411
        $percentage = float_format(
2412
            ($score / ($weight != 0 ? $weight : 1)) * 100,
2413
            1
2414
        );
2415
        if (isset($pass_percentage) && !empty($pass_percentage)) {
2416
            if ($percentage >= $pass_percentage) {
2417
                return true;
2418
            }
2419
        }
2420
        return false;
2421
    }
2422
2423
    /**
2424
     * @param float $score
2425
     * @param float $weight
2426
     * @param string $pass_percentage
2427
     * @return string
2428
     */
2429
    public static function showSuccessMessage($score, $weight, $pass_percentage)
2430
    {
2431
        $res = '';
2432
        if (self::isPassPercentageEnabled($pass_percentage)) {
2433
            $isSuccess = self::isSuccessExerciseResult(
2434
                $score,
2435
                $weight,
2436
                $pass_percentage
2437
            );
2438
2439
            if ($isSuccess) {
2440
                $html = get_lang('CongratulationsYouPassedTheTest');
2441
                $icon = Display::return_icon(
2442
                    'completed.png',
2443
                    get_lang('Correct'),
2444
                    array(),
2445
                    ICON_SIZE_MEDIUM
2446
                );
2447
            } else {
2448
                //$html .= Display::return_message(get_lang('YouDidNotReachTheMinimumScore'), 'warning');
2449
                $html = get_lang('YouDidNotReachTheMinimumScore');
2450
                $icon = Display::return_icon(
2451
                    'warning.png',
2452
                    get_lang('Wrong'),
2453
                    array(),
2454
                    ICON_SIZE_MEDIUM
2455
                );
2456
            }
2457
            $html = Display::tag('h4', $html);
2458
            $html .= Display::tag(
2459
                'h5',
2460
                $icon,
2461
                array('style' => 'width:40px; padding:2px 10px 0px 0px')
2462
            );
2463
            $res = $html;
2464
        }
2465
        return $res;
2466
    }
2467
2468
    /**
2469
     * Return true if pass_pourcentage activated (we use the pass pourcentage feature
2470
     * return false if pass_percentage = 0 (we don't use the pass pourcentage feature
2471
     * @param $value
2472
     * @return boolean
2473
     * In this version, pass_percentage and show_success_message are disabled if
2474
     * pass_percentage is set to 0
2475
     */
2476
    public static function isPassPercentageEnabled($value)
2477
    {
2478
        return $value > 0;
2479
    }
2480
2481
    /**
2482
     * Converts a numeric value in a percentage example 0.66666 to 66.67 %
2483
     * @param $value
2484
     * @return float Converted number
2485
     */
2486
    public static function convert_to_percentage($value)
2487
    {
2488
        $return = '-';
2489
        if ($value != '') {
2490
            $return = float_format($value * 100, 1).' %';
2491
        }
2492
        return $return;
2493
    }
2494
2495
    /**
2496
     * Converts a score/weight values to the platform scale
2497
     * @param   float $score
2498
     * @param   float $weight
2499
     * @deprecated seem not to be used
2500
     * @return  float   the score rounded converted to the new range
2501
     */
2502
    public static function convert_score($score, $weight)
2503
    {
2504
        $maxNote = api_get_setting('exercise_max_score');
2505
        $minNote = api_get_setting('exercise_min_score');
2506
2507 View Code Duplication
        if ($score != '' && $weight != '') {
2508
            if ($maxNote != '' && $minNote != '') {
2509
                if (!empty($weight)) {
2510
                    $score = $minNote + ($maxNote - $minNote) * $score / $weight;
2511
                } else {
2512
                    $score = $minNote;
2513
                }
2514
            }
2515
        }
2516
        $score_rounded = float_format($score, 1);
2517
2518
        return $score_rounded;
2519
    }
2520
2521
    /**
2522
     * Getting all active exercises from a course from a session
2523
     * (if a session_id is provided we will show all the exercises in the course +
2524
     * all exercises in the session)
2525
     * @param   array $course_info
2526
     * @param   int $session_id
2527
     * @param   boolean $check_publication_dates
2528
     * @param   string $search Search exercise name
2529
     * @param   boolean $search_all_sessions Search exercises in all sessions
2530
     * @param   int 0 = only inactive exercises
2531
     *                  1 = only active exercises,
2532
     *                  2 = all exercises
2533
     *                  3 = active <> -1
2534
     * @return  array   array with exercise data
2535
     */
2536
    public static function get_all_exercises(
2537
        $course_info = null,
2538
        $session_id = 0,
2539
        $check_publication_dates = false,
2540
        $search = '',
2541
        $search_all_sessions = false,
2542
        $active = 2
2543
    ) {
2544
        $course_id = api_get_course_int_id();
2545
2546
        if (!empty($course_info) && !empty($course_info['real_id'])) {
2547
            $course_id = $course_info['real_id'];
2548
        }
2549
2550
        if ($session_id == -1) {
2551
            $session_id = 0;
2552
        }
2553
2554
        $now = api_get_utc_datetime();
2555
        $time_conditions = '';
2556
2557
        if ($check_publication_dates) {
2558
            //start and end are set
2559
            $time_conditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' )  OR ";
2560
            // only start is set
2561
            $time_conditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
2562
            // only end is set
2563
            $time_conditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
2564
            // nothing is set
2565
            $time_conditions .= " (start_time IS NULL AND end_time IS NULL))  ";
2566
        }
2567
2568
        $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
2569
        $needle = !empty($search) ? "%".$search."%" : '';
2570
2571
        // Show courses by active status
2572
        $active_sql = '';
2573
        if ($active == 3) {
2574
            $active_sql = ' active <> -1 AND';
2575
        } else {
2576
            if ($active != 2) {
2577
                $active_sql = sprintf(' active = %d AND', $active);
2578
            }
2579
        }
2580
2581
        if ($search_all_sessions == true) {
2582
            $conditions = array(
2583
                'where' => array(
2584
                    $active_sql.' c_id = ? '.$needle_where.$time_conditions => array(
2585
                        $course_id,
2586
                        $needle
2587
                    )
2588
                ),
2589
                'order' => 'title'
2590
            );
2591
        } else {
2592
            if ($session_id == 0) {
2593
                $conditions = array(
2594
                    'where' => array(
2595
                        $active_sql.' session_id = ? AND c_id = ? '.$needle_where.$time_conditions => array(
2596
                            $session_id,
2597
                            $course_id,
2598
                            $needle
2599
                        )
2600
                    ),
2601
                    'order' => 'title'
2602
                );
2603
            } else {
2604
                $conditions = array(
2605
                    'where' => array(
2606
                        $active_sql.' (session_id = 0 OR session_id = ? ) AND c_id = ? '.$needle_where.$time_conditions => array(
2607
                            $session_id,
2608
                            $course_id,
2609
                            $needle
2610
                        )
2611
                    ),
2612
                    'order' => 'title'
2613
                );
2614
            }
2615
        }
2616
2617
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
2618
2619
        return Database::select('*', $table, $conditions);
2620
    }
2621
2622
    /**
2623
     * Get exercise information by id
2624
     * @param int $exerciseId Exercise Id
2625
     * @param int $courseId The course ID (necessary as c_quiz.id is not unique)
2626
     * @return array Exercise info
2627
     */
2628
    public static function get_exercise_by_id($exerciseId = 0, $courseId = 0)
2629
    {
2630
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
2631
        if (empty($courseId)) {
2632
            $courseId = api_get_course_int_id();
2633
        } else {
2634
            $courseId = intval($courseId);
2635
        }
2636
        $conditions = array(
2637
            'where' => array(
2638
                'id = ?' => array($exerciseId),
2639
                ' AND c_id = ? ' => $courseId
2640
            )
2641
        );
2642
2643
        return Database::select('*', $table, $conditions);
2644
    }
2645
2646
    /**
2647
     * Getting all exercises (active only or all)
2648
     * from a course from a session
2649
     * (if a session_id is provided we will show all the exercises in the
2650
     * course + all exercises in the session)
2651
     * @param   array   course data
2652
     * @param   int     session id
2653
     * @param    int        course c_id
2654
     * @param   boolean $only_active_exercises
2655
     * @return  array   array with exercise data
2656
     * modified by Hubert Borderiou
2657
     */
2658
    public static function get_all_exercises_for_course_id(
2659
        $course_info = null,
2660
        $session_id = 0,
2661
        $course_id = 0,
2662
        $only_active_exercises = true
2663
    ) {
2664
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
2665
2666
        if ($only_active_exercises) {
2667
            // Only active exercises.
2668
            $sql_active_exercises = "active = 1 AND ";
2669
        } else {
2670
            // Not only active means visible and invisible NOT deleted (-2)
2671
            $sql_active_exercises = "active IN (1, 0) AND ";
2672
        }
2673
2674
        if ($session_id == -1) {
2675
            $session_id = 0;
2676
        }
2677
2678
        $params = array(
2679
            $session_id,
2680
            $course_id
2681
        );
2682
2683
        if ($session_id == 0) {
2684
            $conditions = array(
2685
                'where' => array("$sql_active_exercises session_id = ? AND c_id = ?" => $params),
2686
                'order' => 'title'
2687
            );
2688
        } else {
2689
            // All exercises
2690
            $conditions = array(
2691
                'where' => array("$sql_active_exercises (session_id = 0 OR session_id = ? ) AND c_id=?" => $params),
2692
                'order' => 'title'
2693
            );
2694
        }
2695
2696
        return Database::select('*', $TBL_EXERCISES, $conditions);
2697
    }
2698
2699
    /**
2700
     * Gets the position of the score based in a given score (result/weight)
2701
     * and the exe_id based in the user list
2702
     * (NO Exercises in LPs )
2703
     * @param   float $my_score user score to be compared *attention*
2704
     * $my_score = score/weight and not just the score
2705
     * @param   int $my_exe_id exe id of the exercise
2706
     * (this is necessary because if 2 students have the same score the one
2707
     * with the minor exe_id will have a best position, just to be fair and FIFO)
2708
     * @param   int $exercise_id
2709
     * @param   string $course_code
2710
     * @param   int $session_id
2711
     * @param   array $user_list
2712
     * @param   bool $return_string
2713
     *
2714
     * @return  int     the position of the user between his friends in a course
2715
     * (or course within a session)
2716
     */
2717
    public static function get_exercise_result_ranking(
2718
        $my_score,
2719
        $my_exe_id,
2720
        $exercise_id,
2721
        $course_code,
2722
        $session_id = 0,
2723
        $user_list = array(),
2724
        $return_string = true
2725
    ) {
2726
        //No score given we return
2727
        if (is_null($my_score)) {
2728
            return '-';
2729
        }
2730
        if (empty($user_list)) {
2731
            return '-';
2732
        }
2733
2734
        $best_attempts = array();
2735
        foreach ($user_list as $user_data) {
2736
            $user_id = $user_data['user_id'];
2737
            $best_attempts[$user_id] = self::get_best_attempt_by_user(
2738
                $user_id,
2739
                $exercise_id,
2740
                $course_code,
2741
                $session_id
2742
            );
2743
        }
2744
2745
        if (empty($best_attempts)) {
2746
            return 1;
2747
        } else {
2748
            $position = 1;
2749
            $my_ranking = array();
2750 View Code Duplication
            foreach ($best_attempts as $user_id => $result) {
2751
                if (!empty($result['exe_weighting']) && intval(
2752
                        $result['exe_weighting']
2753
                    ) != 0
2754
                ) {
2755
                    $my_ranking[$user_id] = $result['exe_result'] / $result['exe_weighting'];
2756
                } else {
2757
                    $my_ranking[$user_id] = 0;
2758
                }
2759
            }
2760
            //if (!empty($my_ranking)) {
2761
            asort($my_ranking);
2762
            $position = count($my_ranking);
2763
            if (!empty($my_ranking)) {
2764
                foreach ($my_ranking as $user_id => $ranking) {
2765
                    if ($my_score >= $ranking) {
2766
                        if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
2767
                            $exe_id = $best_attempts[$user_id]['exe_id'];
2768
                            if ($my_exe_id < $exe_id) {
2769
                                $position--;
2770
                            }
2771
                        } else {
2772
                            $position--;
2773
                        }
2774
                    }
2775
                }
2776
            }
2777
            //}
2778
            $return_value = array(
2779
                'position' => $position,
2780
                'count' => count($my_ranking)
2781
            );
2782
2783
            if ($return_string) {
2784
                if (!empty($position) && !empty($my_ranking)) {
2785
                    $return_value = $position.'/'.count($my_ranking);
2786
                } else {
2787
                    $return_value = '-';
2788
                }
2789
            }
2790
            return $return_value;
2791
        }
2792
    }
2793
2794
    /**
2795
     * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
2796
     * (NO Exercises in LPs ) old functionality by attempt
2797
     * @param   float   user score to be compared attention => score/weight
2798
     * @param   int     exe id of the exercise
2799
     * (this is necessary because if 2 students have the same score the one
2800
     * with the minor exe_id will have a best position, just to be fair and FIFO)
2801
     * @param   int     exercise id
2802
     * @param   string  course code
2803
     * @param   int     session id
2804
     * @param bool $return_string
2805
     * @return  int     the position of the user between his friends in a course (or course within a session)
2806
     */
2807
    public static function get_exercise_result_ranking_by_attempt(
2808
        $my_score,
2809
        $my_exe_id,
2810
        $exercise_id,
2811
        $courseId,
2812
        $session_id = 0,
2813
        $return_string = true
2814
    ) {
2815
        if (empty($session_id)) {
2816
            $session_id = 0;
2817
        }
2818
        if (is_null($my_score)) {
2819
            return '-';
2820
        }
2821
        $user_results = Event::get_all_exercise_results(
2822
            $exercise_id,
2823
            $courseId,
2824
            $session_id,
2825
            false
2826
        );
2827
        $position_data = array();
2828
        if (empty($user_results)) {
2829
            return 1;
2830
        } else {
2831
            $position = 1;
2832
            $my_ranking = array();
2833 View Code Duplication
            foreach ($user_results as $result) {
2834
                //print_r($result);
2835
                if (!empty($result['exe_weighting']) && intval(
2836
                        $result['exe_weighting']
2837
                    ) != 0
2838
                ) {
2839
                    $my_ranking[$result['exe_id']] = $result['exe_result'] / $result['exe_weighting'];
2840
                } else {
2841
                    $my_ranking[$result['exe_id']] = 0;
2842
                }
2843
            }
2844
            asort($my_ranking);
2845
            $position = count($my_ranking);
2846
            if (!empty($my_ranking)) {
2847
                foreach ($my_ranking as $exe_id => $ranking) {
2848
                    if ($my_score >= $ranking) {
2849
                        if ($my_score == $ranking) {
2850
                            if ($my_exe_id < $exe_id) {
2851
                                $position--;
2852
                            }
2853
                        } else {
2854
                            $position--;
2855
                        }
2856
                    }
2857
                }
2858
            }
2859
            $return_value = array(
2860
                'position' => $position,
2861
                'count' => count($my_ranking)
2862
            );
2863
2864
            if ($return_string) {
2865
                if (!empty($position) && !empty($my_ranking)) {
2866
                    return $position.'/'.count($my_ranking);
2867
                }
2868
            }
2869
            return $return_value;
2870
        }
2871
    }
2872
2873
    /**
2874
     * Get the best attempt in a exercise (NO Exercises in LPs )
2875
     * @param int $exercise_id
2876
     * @param int $courseId
2877
     * @param int $session_id
2878
     *
2879
     * @return array
2880
     */
2881 View Code Duplication
    public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id)
2882
    {
2883
        $user_results = Event::get_all_exercise_results(
2884
            $exercise_id,
2885
            $courseId,
2886
            $session_id,
2887
            false
2888
        );
2889
2890
        $best_score_data = array();
2891
        $best_score = 0;
2892
        if (!empty($user_results)) {
2893
            foreach ($user_results as $result) {
2894
                if (!empty($result['exe_weighting']) &&
2895
                    intval($result['exe_weighting']) != 0
2896
                ) {
2897
                    $score = $result['exe_result'] / $result['exe_weighting'];
2898
                    if ($score >= $best_score) {
2899
                        $best_score = $score;
2900
                        $best_score_data = $result;
2901
                    }
2902
                }
2903
            }
2904
        }
2905
2906
        return $best_score_data;
2907
    }
2908
2909
    /**
2910
     * Get the best score in a exercise (NO Exercises in LPs )
2911
     * @param int $user_id
2912
     * @param int $exercise_id
2913
     * @param int $courseId
2914
     * @param int $session_id
2915
     *
2916
     * @return array
2917
     */
2918 View Code Duplication
    public static function get_best_attempt_by_user(
2919
        $user_id,
2920
        $exercise_id,
2921
        $courseId,
2922
        $session_id
2923
    )
2924
    {
2925
        $user_results = Event::get_all_exercise_results(
2926
            $exercise_id,
2927
            $courseId,
2928
            $session_id,
2929
            false,
2930
            $user_id
2931
        );
2932
        $best_score_data = array();
2933
        $best_score = 0;
2934
        if (!empty($user_results)) {
2935
            foreach ($user_results as $result) {
2936
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
2937
                    $score = $result['exe_result'] / $result['exe_weighting'];
2938
                    if ($score >= $best_score) {
2939
                        $best_score = $score;
2940
                        $best_score_data = $result;
2941
                    }
2942
                }
2943
            }
2944
        }
2945
2946
        return $best_score_data;
2947
    }
2948
2949
    /**
2950
     * Get average score (NO Exercises in LPs )
2951
     * @param    int    exercise id
2952
     * @param    int $courseId
2953
     * @param    int    session id
2954
     * @return    float    Average score
2955
     */
2956 View Code Duplication
    public static function get_average_score($exercise_id, $courseId, $session_id)
2957
    {
2958
        $user_results = Event::get_all_exercise_results(
2959
            $exercise_id,
2960
            $courseId,
2961
            $session_id
2962
        );
2963
        $avg_score = 0;
2964
        if (!empty($user_results)) {
2965
            foreach ($user_results as $result) {
2966
                if (!empty($result['exe_weighting']) && intval(
2967
                        $result['exe_weighting']
2968
                    ) != 0
2969
                ) {
2970
                    $score = $result['exe_result'] / $result['exe_weighting'];
2971
                    $avg_score += $score;
2972
                }
2973
            }
2974
            $avg_score = float_format($avg_score / count($user_results), 1);
2975
        }
2976
2977
        return $avg_score;
2978
    }
2979
2980
    /**
2981
     * Get average score by score (NO Exercises in LPs )
2982
     * @param    int    exercise id
2983
     * @param    int $courseId
2984
     * @param    int    session id
2985
     * @return    float    Average score
2986
     */
2987 View Code Duplication
    public static function get_average_score_by_course($courseId, $session_id)
2988
    {
2989
        $user_results = Event::get_all_exercise_results_by_course(
2990
            $courseId,
2991
            $session_id,
2992
            false
2993
        );
2994
        //echo $course_code.' - '.$session_id.'<br />';
2995
        $avg_score = 0;
2996
        if (!empty($user_results)) {
2997
            foreach ($user_results as $result) {
2998
                if (!empty($result['exe_weighting']) && intval(
2999
                        $result['exe_weighting']
3000
                    ) != 0
3001
                ) {
3002
                    $score = $result['exe_result'] / $result['exe_weighting'];
3003
                    $avg_score += $score;
3004
                }
3005
            }
3006
            //We asume that all exe_weighting
3007
            $avg_score = ($avg_score / count($user_results));
3008
        }
3009
3010
        return $avg_score;
3011
    }
3012
3013
    /**
3014
     * @param int $user_id
3015
     * @param int $courseId
3016
     * @param int $session_id
3017
     *
3018
     * @return float|int
3019
     */
3020 View Code Duplication
    public static function get_average_score_by_course_by_user(
3021
        $user_id,
3022
        $courseId,
3023
        $session_id
3024
    )
3025
    {
3026
        $user_results = Event::get_all_exercise_results_by_user(
3027
            $user_id,
3028
            $courseId,
3029
            $session_id
3030
        );
3031
        $avg_score = 0;
3032
        if (!empty($user_results)) {
3033
            foreach ($user_results as $result) {
3034
                if (!empty($result['exe_weighting']) && intval(
3035
                        $result['exe_weighting']
3036
                    ) != 0
3037
                ) {
3038
                    $score = $result['exe_result'] / $result['exe_weighting'];
3039
                    $avg_score += $score;
3040
                }
3041
            }
3042
            // We asumme that all exe_weighting
3043
            $avg_score = ($avg_score / count($user_results));
3044
        }
3045
3046
        return $avg_score;
3047
    }
3048
3049
    /**
3050
     * Get average score by score (NO Exercises in LPs )
3051
     * @param    int        exercise id
3052
     * @param    int $courseId
3053
     * @param    int        session id
3054
     * @return    float    Best average score
3055
     */
3056 View Code Duplication
    public static function get_best_average_score_by_exercise(
3057
        $exercise_id,
3058
        $courseId,
3059
        $session_id,
3060
        $user_count
3061
    )
3062
    {
3063
        $user_results = Event::get_best_exercise_results_by_user(
3064
            $exercise_id,
3065
            $courseId,
3066
            $session_id
3067
        );
3068
        $avg_score = 0;
3069
        if (!empty($user_results)) {
3070
            foreach ($user_results as $result) {
3071
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
3072
                    $score = $result['exe_result'] / $result['exe_weighting'];
3073
                    $avg_score += $score;
3074
                }
3075
            }
3076
            //We asumme that all exe_weighting
3077
            //$avg_score = show_score( $avg_score / count($user_results) , $result['exe_weighting']);
3078
            //$avg_score = ($avg_score / count($user_results));
3079
            if (!empty($user_count)) {
3080
                $avg_score = float_format($avg_score / $user_count, 1) * 100;
3081
            } else {
3082
                $avg_score = 0;
3083
            }
3084
        }
3085
3086
        return $avg_score;
3087
    }
3088
3089
    /**
3090
     * @param string $course_code
3091
     * @param int $session_id
3092
     *
3093
     * @return array
3094
     */
3095
    public static function get_exercises_to_be_taken($course_code, $session_id)
3096
    {
3097
        $course_info = api_get_course_info($course_code);
3098
        $exercises = self::get_all_exercises($course_info, $session_id);
3099
        $result = array();
3100
        $now = time() + 15 * 24 * 60 * 60;
3101
        foreach ($exercises as $exercise_item) {
3102
            if (isset($exercise_item['end_time']) &&
3103
                !empty($exercise_item['end_time']) &&
3104
                api_strtotime($exercise_item['end_time'], 'UTC') < $now
3105
            ) {
3106
                $result[] = $exercise_item;
3107
            }
3108
        }
3109
        return $result;
3110
    }
3111
3112
    /**
3113
     * Get student results (only in completed exercises) stats by question
3114
     * @param    int $question_id
3115
     * @param    int $exercise_id
3116
     * @param    string $course_code
3117
     * @param    int $session_id
3118
     *
3119
     **/
3120
    public static function get_student_stats_by_question(
3121
        $question_id,
3122
        $exercise_id,
3123
        $course_code,
3124
        $session_id
3125
    ) {
3126
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3127
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3128
3129
        $question_id = intval($question_id);
3130
        $exercise_id = intval($exercise_id);
3131
        $course_code = Database::escape_string($course_code);
3132
        $session_id = intval($session_id);
3133
        $courseId = api_get_course_int_id($course_code);
3134
3135
        $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
3136
    		FROM $track_exercises e
3137
    		INNER JOIN $track_attempt a
3138
    		ON (
3139
    		    a.exe_id = e.exe_id AND
3140
    		    e.c_id = a.c_id AND
3141
    		    e.session_id  = a.session_id
3142
            )
3143
    		WHERE
3144
    		    exe_exo_id 	= $exercise_id AND
3145
                a.c_id = $courseId AND
3146
                e.session_id = $session_id AND
3147
                question_id = $question_id AND
3148
                status = ''
3149
            LIMIT 1";
3150
        $result = Database::query($sql);
3151
        $return = array();
3152
        if ($result) {
3153
            $return = Database::fetch_array($result, 'ASSOC');
3154
        }
3155
3156
        return $return;
3157
    }
3158
3159
    /**
3160
     * Get the correct answer count for a fill blanks question
3161
     *
3162
     * @param int $question_id
3163
     * @param int $exercise_id
3164
     * @return int
3165
     */
3166
    public static function getNumberStudentsFillBlanksAnwserCount(
3167
        $question_id,
3168
        $exercise_id
3169
    )
3170
    {
3171
        $listStudentsId = [];
3172
        $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3173
            api_get_course_id(),
3174
            true
3175
        );
3176
        foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3177
            $listStudentsId[] = $listStudentInfo['user_id'];
3178
        }
3179
3180
        $listFillTheBlankResult = FillBlanks::getFillTheBlankTabResult(
3181
            $exercise_id,
3182
            $question_id,
3183
            $listStudentsId,
3184
            '1970-01-01',
3185
            '3000-01-01'
3186
        );
3187
3188
        $arrayCount = [];
3189
3190
        foreach ($listFillTheBlankResult as $resultCount) {
3191
            foreach ($resultCount as $index => $count) {
3192
                //this is only for declare the array index per answer
3193
                $arrayCount[$index] = 0;
3194
            }
3195
        }
3196
3197
        foreach ($listFillTheBlankResult as $resultCount) {
3198
            foreach ($resultCount as $index => $count) {
3199
                $count = ($count === 0) ? 1 : 0;
3200
                $arrayCount[$index] += $count;
3201
            }
3202
        }
3203
3204
        return $arrayCount;
3205
    }
3206
3207
    /**
3208
     * @param int $question_id
3209
     * @param int $exercise_id
3210
     * @param string $course_code
3211
     * @param int $session_id
3212
     * @param string $questionType
3213
     * @return int
3214
     */
3215
    public static function get_number_students_question_with_answer_count(
3216
        $question_id,
3217
        $exercise_id,
3218
        $course_code,
3219
        $session_id,
3220
        $questionType = ''
3221
    ) {
3222
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3223
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3224
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3225
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3226
        $courseUserSession = Database::get_main_table(
3227
            TABLE_MAIN_SESSION_COURSE_USER
3228
        );
3229
3230
        $question_id = intval($question_id);
3231
        $exercise_id = intval($exercise_id);
3232
        $courseId = api_get_course_int_id($course_code);
3233
        $session_id = intval($session_id);
3234
3235
        if ($questionType == FILL_IN_BLANKS) {
3236
            $listStudentsId = array();
3237
            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3238
                api_get_course_id(),
3239
                true
3240
            );
3241
            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3242
                $listStudentsId[] = $listStudentInfo['user_id'];
3243
            }
3244
3245
            $listFillTheBlankResult = FillBlanks::getFillTheBlankTabResult(
3246
                $exercise_id,
3247
                $question_id,
3248
                $listStudentsId,
3249
                '1970-01-01',
3250
                '3000-01-01'
3251
            );
3252
3253
            return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
3254
        }
3255
3256 View Code Duplication
        if (empty($session_id)) {
3257
            $courseCondition = "
3258
            INNER JOIN $courseUser cu
3259
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3260
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3261
        } else {
3262
            $courseCondition = "
3263
            INNER JOIN $courseUserSession cu
3264
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3265
            $courseConditionWhere = " AND cu.status = 0 ";
3266
        }
3267
3268
        $sql = "SELECT DISTINCT exe_user_id
3269
    		FROM $track_exercises e
3270
    		INNER JOIN $track_attempt a
3271
    		ON (
3272
    		    a.exe_id = e.exe_id AND
3273
    		    e.c_id = a.c_id AND
3274
    		    e.session_id  = a.session_id
3275
            )
3276
            INNER JOIN $courseTable c
3277
            ON (c.id = a.c_id)
3278
    		$courseCondition
3279
    		WHERE
3280
    		    exe_exo_id = $exercise_id AND
3281
                a.c_id = $courseId AND
3282
                e.session_id = $session_id AND
3283
                question_id = $question_id AND
3284
                answer <> '0' AND
3285
                e.status = ''
3286
                $courseConditionWhere
3287
            ";
3288
        $result = Database::query($sql);
3289
        $return = 0;
3290
        if ($result) {
3291
            $return = Database::num_rows($result);
3292
        }
3293
        return $return;
3294
    }
3295
3296
    /**
3297
     * @param int $answer_id
3298
     * @param int $question_id
3299
     * @param int $exercise_id
3300
     * @param string $course_code
3301
     * @param int $session_id
3302
     *
3303
     * @return int
3304
     */
3305
    public static function get_number_students_answer_hotspot_count(
3306
        $answer_id,
3307
        $question_id,
3308
        $exercise_id,
3309
        $course_code,
3310
        $session_id
3311
    )
3312
    {
3313
        $track_exercises = Database::get_main_table(
3314
            TABLE_STATISTIC_TRACK_E_EXERCISES
3315
        );
3316
        $track_hotspot = Database::get_main_table(
3317
            TABLE_STATISTIC_TRACK_E_HOTSPOT
3318
        );
3319
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3320
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3321
3322
        $courseUserSession = Database::get_main_table(
3323
            TABLE_MAIN_SESSION_COURSE_USER
3324
        );
3325
3326
        $question_id = intval($question_id);
3327
        $answer_id = intval($answer_id);
3328
        $exercise_id = intval($exercise_id);
3329
        $course_code = Database::escape_string($course_code);
3330
        $session_id = intval($session_id);
3331
3332 View Code Duplication
        if (empty($session_id)) {
3333
            $courseCondition = "
3334
            INNER JOIN $courseUser cu
3335
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3336
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3337
        } else {
3338
            $courseCondition = "
3339
            INNER JOIN $courseUserSession cu
3340
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3341
            $courseConditionWhere = " AND cu.status = 0 ";
3342
        }
3343
3344
        $sql = "SELECT DISTINCT exe_user_id
3345
    		FROM $track_exercises e
3346
    		INNER JOIN $track_hotspot a
3347
    		ON (a.hotspot_exe_id = e.exe_id)
3348
    		INNER JOIN $courseTable c
3349
    		ON (hotspot_course_code = c.code)
3350
    		$courseCondition
3351
    		WHERE
3352
    		    exe_exo_id              = $exercise_id AND
3353
                a.hotspot_course_code 	= '$course_code' AND
3354
                e.session_id            = $session_id AND
3355
                hotspot_answer_id       = $answer_id AND
3356
                hotspot_question_id     = $question_id AND
3357
                hotspot_correct         =  1 AND
3358
                e.status                = ''
3359
                $courseConditionWhere
3360
            ";
3361
3362
        $result = Database::query($sql);
3363
        $return = 0;
3364
        if ($result) {
3365
            $return = Database::num_rows($result);
3366
        }
3367
        return $return;
3368
    }
3369
3370
    /**
3371
     * @param int $answer_id
3372
     * @param int $question_id
3373
     * @param int $exercise_id
3374
     * @param string $course_code
3375
     * @param int $session_id
3376
     * @param string $question_type
3377
     * @param string $correct_answer
3378
     * @param string $current_answer
3379
     * @return int
3380
     */
3381
    public static function get_number_students_answer_count(
3382
        $answer_id,
3383
        $question_id,
3384
        $exercise_id,
3385
        $course_code,
3386
        $session_id,
3387
        $question_type = null,
3388
        $correct_answer = null,
3389
        $current_answer = null
3390
    ) {
3391
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3392
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3393
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3394
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3395
        $courseUserSession = Database::get_main_table(
3396
            TABLE_MAIN_SESSION_COURSE_USER
3397
        );
3398
3399
        $question_id = intval($question_id);
3400
        $answer_id = intval($answer_id);
3401
        $exercise_id = intval($exercise_id);
3402
        $courseId = api_get_course_int_id($course_code);
3403
        $course_code = Database::escape_string($course_code);
3404
        $session_id = intval($session_id);
3405
3406
        switch ($question_type) {
3407
            case FILL_IN_BLANKS:
3408
                $answer_condition = "";
3409
                $select_condition = " e.exe_id, answer ";
3410
                break;
3411
            case MATCHING:
3412
                //no break
3413
            case MATCHING_DRAGGABLE:
3414
                //no break
3415
            default:
3416
                $answer_condition = " answer = $answer_id AND ";
3417
                $select_condition = " DISTINCT exe_user_id ";
3418
        }
3419
3420 View Code Duplication
        if (empty($session_id)) {
3421
            $courseCondition = "
3422
            INNER JOIN $courseUser cu
3423
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3424
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3425
        } else {
3426
            $courseCondition = "
3427
            INNER JOIN $courseUserSession cu
3428
            ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
3429
            $courseConditionWhere = " AND cu.status = 0 ";
3430
        }
3431
3432
        $sql = "SELECT $select_condition
3433
    		FROM $track_exercises e
3434
    		INNER JOIN $track_attempt a
3435
    		ON (
3436
    		    a.exe_id = e.exe_id AND
3437
    		    e.c_id = a.c_id AND
3438
    		    e.session_id  = a.session_id
3439
            )
3440
            INNER JOIN $courseTable c
3441
            ON c.id = a.c_id
3442
    		$courseCondition
3443
    		WHERE
3444
    		    exe_exo_id = $exercise_id AND
3445
                a.c_id = $courseId AND
3446
                e.session_id = $session_id AND
3447
                $answer_condition
3448
                question_id = $question_id AND
3449
                e.status = ''
3450
                $courseConditionWhere
3451
            ";
3452
        $result = Database::query($sql);
3453
        $return = 0;
3454
        if ($result) {
3455
            $good_answers = 0;
3456
            switch ($question_type) {
3457
                case FILL_IN_BLANKS:
3458
                    while ($row = Database::fetch_array($result, 'ASSOC')) {
3459
                        $fill_blank = self::check_fill_in_blanks(
3460
                            $correct_answer,
3461
                            $row['answer'],
3462
                            $current_answer
3463
                        );
3464
                        if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) {
3465
                            $good_answers++;
3466
                        }
3467
                    }
3468
                    return $good_answers;
3469
                    break;
3470
                case MATCHING:
3471
                    //no break
3472
                case MATCHING_DRAGGABLE:
3473
                    //no break
3474
                default:
3475
                    $return = Database::num_rows($result);
3476
            }
3477
        }
3478
3479
        return $return;
3480
    }
3481
3482
    /**
3483
     * @param array $answer
3484
     * @param string $user_answer
3485
     * @return array
3486
     */
3487
    public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
3488
    {
3489
        // the question is encoded like this
3490
        // [A] B [C] D [E] F::10,10,10@1
3491
        // number 1 before the "@" means that is a switchable fill in blank question
3492
        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3493
        // means that is a normal fill blank question
3494
        // first we explode the "::"
3495
        $pre_array = explode('::', $answer);
3496
        // is switchable fill blank or not
3497
        $last = count($pre_array) - 1;
3498
        $is_set_switchable = explode('@', $pre_array[$last]);
3499
        $switchable_answer_set = false;
3500
        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3501
            $switchable_answer_set = true;
3502
        }
3503
        $answer = '';
3504
        for ($k = 0; $k < $last; $k++) {
3505
            $answer .= $pre_array[$k];
3506
        }
3507
        // splits weightings that are joined with a comma
3508
        $answerWeighting = explode(',', $is_set_switchable[0]);
3509
3510
        // we save the answer because it will be modified
3511
        //$temp = $answer;
3512
        $temp = $answer;
3513
3514
        $answer = '';
3515
        $j = 0;
3516
        //initialise answer tags
3517
        $user_tags = $correct_tags = $real_text = array();
3518
        // the loop will stop at the end of the text
3519
        while (1) {
3520
            // quits the loop if there are no more blanks (detect '[')
3521
            if (($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3575 can also be of type false; however, api_strpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3522
                // adds the end of the text
3523
                $answer = $temp;
3524
                $real_text[] = $answer;
3525
                break; //no more "blanks", quit the loop
3526
            }
3527
            // adds the piece of text that is before the blank
3528
            //and ends with '[' into a general storage array
3529
            $real_text[] = api_substr($temp, 0, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3575 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3530
            $answer .= api_substr($temp, 0, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3575 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3531
            //take the string remaining (after the last "[" we found)
3532
            $temp = api_substr($temp, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3532 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3533
            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3534
            if (($pos = api_strpos($temp, ']')) === false) {
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3532 can also be of type false; however, api_strpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3535
                // adds the end of the text
3536
                $answer .= $temp;
3537
                break;
3538
            }
3539
3540
            $str = $user_answer;
3541
3542
            preg_match_all('#\[([^[]*)\]#', $str, $arr);
3543
            $str = str_replace('\r\n', '', $str);
3544
            $choices = $arr[1];
3545
            $choice = [];
3546
            $check = false;
3547
            $i = 0;
3548
            foreach ($choices as $item) {
3549
                if ($current_answer === $item) {
3550
                    $check = true;
3551
                }
3552
                if ($check) {
3553
                    $choice[] = $item;
3554
                    $i++;
3555
                }
3556
                if ($i == 3) {
3557
                    break;
3558
                }
3559
            }
3560
            $tmp = api_strrpos($choice[$j], ' / ');
3561
3562
            if ($tmp !== false) {
3563
                $choice[$j] = api_substr($choice[$j], 0, $tmp);
3564
            }
3565
3566
            $choice[$j] = trim($choice[$j]);
3567
3568
            //Needed to let characters ' and " to work as part of an answer
3569
            $choice[$j] = stripslashes($choice[$j]);
3570
3571
            $user_tags[] = api_strtolower($choice[$j]);
3572
            //put the contents of the [] answer tag into correct_tags[]
3573
            $correct_tags[] = api_strtolower(api_substr($temp, 0, $pos));
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3532 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
Security Bug introduced by
It seems like api_substr($temp, 0, $pos) targeting api_substr() can also be of type false; however, api_strtolower() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
3574
            $j++;
3575
            $temp = api_substr($temp, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3575 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3576
        }
3577
3578
        $answer = '';
3579
        $real_correct_tags = $correct_tags;
3580
        $chosen_list = array();
3581
        $good_answer = array();
3582
3583
        for ($i = 0; $i < count($real_correct_tags); $i++) {
3584
            if (!$switchable_answer_set) {
3585
                //needed to parse ' and " characters
3586
                $user_tags[$i] = stripslashes($user_tags[$i]);
3587
                if ($correct_tags[$i] == $user_tags[$i]) {
3588
                    $good_answer[$correct_tags[$i]] = 1;
3589
                } elseif (!empty ($user_tags[$i])) {
3590
                    $good_answer[$correct_tags[$i]] = 0;
3591
                } else {
3592
                    $good_answer[$correct_tags[$i]] = 0;
3593
                }
3594
            } else {
3595
                // switchable fill in the blanks
3596
                if (in_array($user_tags[$i], $correct_tags)) {
3597
                    $correct_tags = array_diff($correct_tags, $chosen_list);
3598
                    $good_answer[$correct_tags[$i]] = 1;
3599
                } elseif (!empty ($user_tags[$i])) {
3600
                    $good_answer[$correct_tags[$i]] = 0;
3601
                } else {
3602
                    $good_answer[$correct_tags[$i]] = 0;
3603
                }
3604
            }
3605
            // adds the correct word, followed by ] to close the blank
3606
            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3607
            if (isset ($real_text[$i + 1])) {
3608
                $answer .= $real_text[$i + 1];
3609
            }
3610
        }
3611
3612
        return $good_answer;
3613
    }
3614
3615
    /**
3616
     * @param int $exercise_id
3617
     * @param string $course_code
3618
     * @param int $session_id
3619
     * @return int
3620
     */
3621 View Code Duplication
    public static function get_number_students_finish_exercise(
3622
        $exercise_id,
3623
        $course_code,
3624
        $session_id
3625
    ) {
3626
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3627
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3628
3629
        $exercise_id = intval($exercise_id);
3630
        $course_code = Database::escape_string($course_code);
3631
        $session_id = intval($session_id);
3632
3633
        $sql = "SELECT DISTINCT exe_user_id
3634
                FROM $track_exercises e
3635
                INNER JOIN $track_attempt a 
3636
                ON (a.exe_id = e.exe_id)
3637
                WHERE
3638
                    exe_exo_id 	 = $exercise_id AND
3639
                    course_code  = '$course_code' AND
3640
                    e.session_id = $session_id AND
3641
                    status = ''";
3642
        $result = Database::query($sql);
3643
        $return = 0;
3644
        if ($result) {
3645
            $return = Database::num_rows($result);
3646
3647
        }
3648
        return $return;
3649
    }
3650
3651
    /**
3652
     * @param string $in_name is the name and the id of the <select>
3653
     * @param string $in_default default value for option
3654
     * @param string $in_onchange
3655
     * @return string the html code of the <select>
3656
     */
3657
    public static function displayGroupMenu($in_name, $in_default, $in_onchange = "")
3658
    {
3659
        // check the default value of option
3660
        $tabSelected = array($in_default => " selected='selected' ");
3661
        $res = "";
3662
        $res .= "<select name='$in_name' id='$in_name' onchange='".$in_onchange."' >";
3663
        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
3664
                'AllGroups'
3665
            )." --</option>";
3666
        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
3667
                'NotInAGroup'
3668
            )." -</option>";
3669
        $tabGroups = GroupManager::get_group_list();
3670
        $currentCatId = 0;
3671
        for ($i = 0; $i < count($tabGroups); $i++) {
3672
            $tabCategory = GroupManager::get_category_from_group(
3673
                $tabGroups[$i]['iid']
3674
            );
3675
            if ($tabCategory["id"] != $currentCatId) {
3676
                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
3677
                $currentCatId = $tabCategory["id"];
3678
            }
3679
            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".$tabGroups[$i]["id"]."'>".$tabGroups[$i]["name"]."</option>";
3680
        }
3681
        $res .= "</select>";
3682
        return $res;
3683
    }
3684
3685
    /**
3686
     * @param int $exe_id
3687
     */
3688
    public static function create_chat_exercise_session($exe_id)
3689
    {
3690
        if (!isset($_SESSION['current_exercises'])) {
3691
            $_SESSION['current_exercises'] = array();
3692
        }
3693
        $_SESSION['current_exercises'][$exe_id] = true;
3694
    }
3695
3696
    /**
3697
     * @param int $exe_id
3698
     */
3699
    public static function delete_chat_exercise_session($exe_id)
3700
    {
3701
        if (isset($_SESSION['current_exercises'])) {
3702
            $_SESSION['current_exercises'][$exe_id] = false;
3703
        }
3704
    }
3705
3706
    /**
3707
     * Display the exercise results
3708
     * @param Exercise $objExercise
3709
     * @param int $exe_id
3710
     * @param bool $save_user_result save users results (true) or just show the results (false)
3711
     * @param string $remainingMessage
3712
     */
3713
    public static function displayQuestionListByAttempt(
3714
        $objExercise,
3715
        $exe_id,
3716
        $save_user_result = false,
3717
        $remainingMessage = ''
3718
    ) {
3719
        $origin = api_get_origin();
3720
3721
        // Getting attempt info
3722
        $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id(
3723
            $exe_id
3724
        );
3725
3726
        // Getting question list
3727
        $question_list = array();
3728
        if (!empty($exercise_stat_info['data_tracking'])) {
3729
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
3730
        } else {
3731
            // Try getting the question list only if save result is off
3732
            if ($save_user_result == false) {
3733
                $question_list = $objExercise->get_validated_question_list();
3734
            }
3735
            if ($objExercise->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
3736
                $question_list = $objExercise->get_validated_question_list();
3737
            }
3738
        }
3739
3740
        $counter = 1;
3741
        $total_score = $total_weight = 0;
3742
        $exercise_content = null;
3743
3744
        // Hide results
3745
        $show_results = false;
3746
        $show_only_score = false;
3747
3748
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
3749
            $show_results = true;
3750
        }
3751
3752
        if (in_array(
3753
            $objExercise->results_disabled,
3754
            array(
3755
                RESULT_DISABLE_SHOW_SCORE_ONLY,
3756
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES
3757
            )
3758
        )
3759
        ) {
3760
            $show_only_score = true;
3761
        }
3762
3763
        // Not display expected answer, but score, and feedback
3764
        $show_all_but_expected_answer = false;
3765
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
3766
            $objExercise->feedback_type == EXERCISE_FEEDBACK_TYPE_END
3767
        ) {
3768
            $show_all_but_expected_answer = true;
3769
            $show_results = true;
3770
            $show_only_score = false;
3771
        }
3772
3773
        $showTotalScoreAndUserChoicesInLastAttempt = true;
3774
3775
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
3776
            $show_only_score = true;
3777
            $show_results = true;
3778
            if ($objExercise->attempts > 0) {
3779
                $attempts = Event::getExerciseResultsByUser(
3780
                    api_get_user_id(),
3781
                    $objExercise->id,
3782
                    api_get_course_int_id(),
3783
                    api_get_session_id(),
3784
                    $exercise_stat_info['orig_lp_id'],
3785
                    $exercise_stat_info['orig_lp_item_id'],
3786
                    'desc'
3787
                );
3788
3789
                if ($attempts) {
3790
                    $numberAttempts = count($attempts);
3791
                } else {
3792
                    $numberAttempts = 0;
3793
                }
3794
3795
                if ($save_user_result) {
3796
                    $numberAttempts++;
3797
                }
3798
                if ($numberAttempts >= $objExercise->attempts) {
3799
                    $show_results = true;
3800
                    $show_only_score = false;
3801
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
3802
                } else {
3803
                    $showTotalScoreAndUserChoicesInLastAttempt = false;
3804
                }
3805
            }
3806
        }
3807
3808
        if ($show_results || $show_only_score) {
3809
            if (isset($exercise_stat_info['exe_user_id'])) {
3810
                $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
3811
                if ($user_info) {
3812
3813
                    // Shows exercise header
3814
                    echo $objExercise->show_exercise_result_header(
3815
                        $user_info,
3816
                        api_convert_and_format_date(
3817
                            $exercise_stat_info['start_date'],
3818
                            DATE_TIME_FORMAT_LONG
3819
                        ),
3820
                        $exercise_stat_info['duration'],
3821
                        $exercise_stat_info['user_ip']
3822
                    );
3823
                }
3824
            }
3825
        }
3826
3827
        // Display text when test is finished #4074 and for LP #4227
3828
        $end_of_message = $objExercise->selectTextWhenFinished();
3829
        if (!empty($end_of_message)) {
3830
            echo Display::return_message($end_of_message, 'normal', false);
3831
            echo "<div class='clear'>&nbsp;</div>";
3832
        }
3833
3834
        $question_list_answers = array();
3835
        $media_list = array();
3836
        $category_list = array();
3837
        $loadChoiceFromSession = false;
3838
        $fromDatabase = true;
3839
        $exerciseResult = null;
3840
        $exerciseResultCoordinates = null;
3841
        $delineationResults = null;
3842
        if ($objExercise->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
3843
            $loadChoiceFromSession = true;
3844
            $fromDatabase = false;
3845
            $exerciseResult = Session::read('exerciseResult');
3846
            $exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
3847
            $delineationResults = Session::read('hotspot_delineation_result');
3848
            $delineationResults = isset($delineationResults[$objExercise->id]) ? $delineationResults[$objExercise->id] : null;
3849
        }
3850
3851
        $countPendingQuestions = 0;
3852
        // Loop over all question to show results for each of them, one by one
3853
        if (!empty($question_list)) {
3854
            foreach ($question_list as $questionId) {
3855
                // creates a temporary Question object
3856
                $objQuestionTmp = Question::read($questionId);
3857
3858
                // This variable came from exercise_submit_modal.php
3859
                ob_start();
3860
                $choice = null;
3861
                $delineationChoice = null;
3862
                if ($loadChoiceFromSession) {
3863
                    $choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : null;
3864
                    $delineationChoice = isset($delineationResults[$questionId]) ? $delineationResults[$questionId] : null;
3865
                }
3866
3867
                // We're inside *one* question. Go through each possible answer for this question
3868
                $result = $objExercise->manage_answer(
3869
                    $exe_id,
3870
                    $questionId,
3871
                    $choice,
3872
                    'exercise_result',
3873
                    $exerciseResultCoordinates,
3874
                    $save_user_result,
3875
                    $fromDatabase,
3876
                    $show_results,
3877
                    $objExercise->selectPropagateNeg(),
3878
                    $delineationChoice,
3879
                    $showTotalScoreAndUserChoicesInLastAttempt
3880
                );
3881
3882
                if (empty($result)) {
3883
                    continue;
3884
                }
3885
3886
                $total_score += $result['score'];
3887
                $total_weight += $result['weight'];
3888
3889
                $question_list_answers[] = array(
3890
                    'question' => $result['open_question'],
3891
                    'answer' => $result['open_answer'],
3892
                    'answer_type' => $result['answer_type']
3893
                );
3894
3895
                $my_total_score = $result['score'];
3896
                $my_total_weight = $result['weight'];
3897
3898
                // Category report
3899
                $category_was_added_for_this_test = false;
3900 View Code Duplication
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
3901
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
3902
                        $category_list[$objQuestionTmp->category]['score'] = 0;
3903
                    }
3904
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
3905
                        $category_list[$objQuestionTmp->category]['total'] = 0;
3906
                    }
3907
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
3908
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
3909
                    $category_was_added_for_this_test = true;
3910
                }
3911
3912 View Code Duplication
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
3913
                    foreach ($objQuestionTmp->category_list as $category_id) {
3914
                        $category_list[$category_id]['score'] += $my_total_score;
3915
                        $category_list[$category_id]['total'] += $my_total_weight;
3916
                        $category_was_added_for_this_test = true;
3917
                    }
3918
                }
3919
3920
                // No category for this question!
3921
                if ($category_was_added_for_this_test == false) {
3922
                    if (!isset($category_list['none']['score'])) {
3923
                        $category_list['none']['score'] = 0;
3924
                    }
3925
                    if (!isset($category_list['none']['total'])) {
3926
                        $category_list['none']['total'] = 0;
3927
                    }
3928
3929
                    $category_list['none']['score'] += $my_total_score;
3930
                    $category_list['none']['total'] += $my_total_weight;
3931
                }
3932
3933
                if ($objExercise->selectPropagateNeg() == 0 &&
3934
                    $my_total_score < 0
3935
                ) {
3936
                    $my_total_score = 0;
3937
                }
3938
3939
                $comnt = null;
3940
                if ($show_results) {
3941
                    $comnt = Event::get_comments($exe_id, $questionId);
3942
                    $teacherAudio = ExerciseLib::getOralFeedbackAudio(
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
3943
                        $exe_id,
3944
                        $questionId,
3945
                        api_get_user_id()
3946
                    );;
3947
3948
                    if (!empty($comnt) || $teacherAudio) {
3949
                        echo '<b>'.get_lang('Feedback').'</b>';
3950
                    }
3951
3952
                    if (!empty($comnt)) {
3953
                        echo ExerciseLib::getFeedbackText($comnt);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
3954
                    }
3955
3956
                    if ($teacherAudio) {
3957
                        echo $teacherAudio;
3958
                    }
3959
                }
3960
3961
                if ($show_results) {
3962
                    $score = [
3963
                        'result' => self::show_score(
3964
                            $my_total_score,
3965
                            $my_total_weight,
3966
                            false,
3967
                            true
3968
                        ),
3969
                        'pass' => $my_total_score >= $my_total_weight ? true : false,
3970
                        'score' => $my_total_score,
3971
                        'weight' => $my_total_weight,
3972
                        'comments' => $comnt,
3973
                    ];
3974
                } else {
3975
                    $score = [];
3976
                }
3977
3978 View Code Duplication
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
3979
                    $check = $objQuestionTmp->isQuestionWaitingReview($score);
3980
                    if ($check === false) {
3981
                        $countPendingQuestions++;
3982
                    }
3983
                }
3984
3985
                $contents = ob_get_clean();
3986
                $question_content = '';
3987
                if ($show_results) {
3988
                    $question_content = '<div class="question_row_answer">';
3989
                    // Shows question title an description
3990
                    $question_content .= $objQuestionTmp->return_header(
3991
                        $objExercise,
3992
                        $counter,
3993
                        $score
3994
                    );
3995
                }
3996
                $counter++;
3997
                $question_content .= $contents;
3998
                if ($show_results) {
3999
                    $question_content .= '</div>';
4000
                }
4001
                $exercise_content .= Display::panel($question_content);
4002
4003
            } // end foreach() block that loops over all questions
4004
        }
4005
4006
        $total_score_text = null;
4007
        if ($show_results || $show_only_score) {
4008
            $total_score_text .= '<div class="question_row_score">';
4009
            $total_score_text .= self::getTotalScoreRibbon(
4010
                $objExercise,
4011
                $total_score,
4012
                $total_weight,
4013
                true,
4014
                $countPendingQuestions
4015
            );
4016
            $total_score_text .= '</div>';
4017
        }
4018
4019 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
4020
            // Adding total
4021
            $category_list['total'] = array(
4022
                'score' => $total_score,
4023
                'total' => $total_weight
4024
            );
4025
            echo TestCategory::get_stats_table_by_attempt(
4026
                $objExercise->id,
4027
                $category_list
4028
            );
4029
        }
4030
4031
        if ($show_all_but_expected_answer) {
4032
            $exercise_content .= "<div class='normal-message'>".get_lang(
4033
                    "ExerciseWithFeedbackWithoutCorrectionComment"
4034
                )."</div>";
4035
        }
4036
4037
        // Remove audio auto play from questions on results page - refs BT#7939
4038
        $exercise_content = preg_replace(
4039
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
4040
            '',
4041
            $exercise_content
4042
        );
4043
4044
        echo $total_score_text;
4045
        echo $exercise_content;
4046
4047
        if (!$show_only_score) {
4048
            echo $total_score_text;
4049
        }
4050
4051
        if (!empty($remainingMessage)) {
4052
            echo Display::return_message($remainingMessage, 'normal', false);
4053
        }
4054
4055
        if ($save_user_result) {
4056
            // Tracking of results
4057
            if ($exercise_stat_info) {
4058
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
4059
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
4060
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
4061
4062
                if (api_is_allowed_to_session_edit()) {
4063
                    Event::update_event_exercise(
4064
                        $exercise_stat_info['exe_id'],
4065
                        $objExercise->selectId(),
4066
                        $total_score,
4067
                        $total_weight,
4068
                        api_get_session_id(),
4069
                        $learnpath_id,
4070
                        $learnpath_item_id,
4071
                        $learnpath_item_view_id,
4072
                        $exercise_stat_info['exe_duration'],
4073
                        $question_list,
4074
                        '',
4075
                        array()
4076
                    );
4077
                }
4078
            }
4079
4080
            // Send notification at the end
4081
            if (!api_is_allowed_to_edit(null, true) &&
4082
                !api_is_excluded_user_type()
4083
            ) {
4084
                $objExercise->send_mail_notification_for_exam(
4085
                    'end',
4086
                    $question_list_answers,
4087
                    $origin,
4088
                    $exe_id,
4089
                    $total_score,
4090
                    $total_weight
4091
                );
4092
            }
4093
        }
4094
    }
4095
4096
    /**
4097
     * @param string $class
4098
     * @param string $scoreLabel
4099
     * @param string $result
4100
     *
4101
     * @return string
4102
     */
4103
    public static function getQuestionRibbon($class, $scoreLabel, $result)
4104
    {
4105
        return '<div class="ribbon">
4106
                    <div class="rib rib-'.$class.'">
4107
                        <h3>'.$scoreLabel.'</h3>
4108
                    </div> 
4109
                    <h4>'.get_lang('Score').': '.$result.'</h4>
4110
                </div>'
4111
        ;
4112
    }
4113
4114
    /**
4115
     * @param Exercise $objExercise
4116
     * @param float $score
4117
     * @param float $weight
4118
     * @param bool $checkPassPercentage
4119
     * @param int $countPendingQuestions
4120
     * @return string
4121
     */
4122
    public static function getTotalScoreRibbon(
4123
        $objExercise,
4124
        $score,
4125
        $weight,
4126
        $checkPassPercentage = false,
4127
        $countPendingQuestions = 0
4128
    ) {
4129
        $passPercentage = $objExercise->selectPassPercentage();
4130
        $ribbon = '<div class="title-score">';
4131
        if ($checkPassPercentage) {
4132
            $isSuccess = self::isSuccessExerciseResult(
4133
                $score,
4134
                $weight,
4135
                $passPercentage
4136
            );
4137
            // Color the final test score if pass_percentage activated
4138
            $class = '';
4139
            if (self::isPassPercentageEnabled($passPercentage)) {
4140
                if ($isSuccess) {
4141
                    $class = ' ribbon-total-success';
4142
                } else {
4143
                    $class = ' ribbon-total-error';
4144
                }
4145
            }
4146
            $ribbon .= '<div class="total '.$class.'">';
4147
        } else {
4148
            $ribbon .= '<div class="total">';
4149
        }
4150
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
4151
        $ribbon .= self::show_score($score, $weight, false, true);
4152
        $ribbon .= '</h3>';
4153
        $ribbon .= '</div>';
4154
        if ($checkPassPercentage) {
4155
            $ribbon .= self::showSuccessMessage(
4156
                $score,
4157
                $weight,
4158
                $passPercentage
4159
            );
4160
        }
4161
        $ribbon .= '</div>';
4162
4163
        if (!empty($countPendingQuestions)) {
4164
            $ribbon .= '<br />';
4165
            $ribbon .= Display::return_message(
4166
                sprintf(
4167
                    get_lang('TempScoreXQuestionsNotCorrectedYet'),
4168
                    $countPendingQuestions
4169
                ),
4170
                'warning'
4171
            );
4172
        }
4173
4174
        return $ribbon;
4175
    }
4176
4177
    /**
4178
     * @param int $countLetter
4179
     * @return mixed
4180
     */
4181
    public static function detectInputAppropriateClass($countLetter)
4182
    {
4183
        $limits = array(
4184
            0 => 'input-mini',
4185
            10 => 'input-mini',
4186
            15 => 'input-medium',
4187
            20 => 'input-xlarge',
4188
            40 => 'input-xlarge',
4189
            60 => 'input-xxlarge',
4190
            100 => 'input-xxlarge',
4191
            200 => 'input-xxlarge',
4192
        );
4193
4194
        foreach ($limits as $size => $item) {
4195
            if ($countLetter <= $size) {
4196
                return $item;
4197
            }
4198
        }
4199
        return $limits[0];
4200
    }
4201
4202
    /**
4203
     * @param int $senderId
4204
     * @param array $course_info
4205
     * @param string $test
4206
     * @param string $url
4207
     *
4208
     * @return string
4209
     */
4210
    public static function getEmailNotification($senderId, $course_info, $test, $url)
4211
    {
4212
        $teacher_info = api_get_user_info($senderId);
4213
        $from_name = api_get_person_name(
4214
            $teacher_info['firstname'],
4215
            $teacher_info['lastname'],
4216
            null,
4217
            PERSON_NAME_EMAIL_ADDRESS
4218
        );
4219
4220
        $message = '<p>'.get_lang('DearStudentEmailIntroduction').'</p><p>'.get_lang('AttemptVCC');
4221
        $message .= '<h3>'.get_lang('CourseName').'</h3><p>'.Security::remove_XSS($course_info['name']).'';
4222
        $message .= '<h3>'.get_lang('Exercise').'</h3><p>'.Security::remove_XSS($test);
4223
        $message .= '<p>'.get_lang('ClickLinkToViewComment').' <br /><a href="#url#">#url#</a><br />';
4224
        $message .= '<p>'.get_lang('Regards').'</p>';
4225
        $message .= $from_name;
4226
        $message = str_replace("#test#", Security::remove_XSS($test), $message);
4227
        $message = str_replace("#url#", $url, $message);
4228
4229
        return $message;
4230
    }
4231
4232
    /**
4233
     * @return string
4234
     */
4235
    public static function getNotCorrectedYetText()
4236
    {
4237
        return Display::return_message(get_lang('notCorrectedYet'), 'warning');
4238
    }
4239
4240
    /**
4241
     * @param string $message
4242
     * @return string
4243
     */
4244
    public static function getFeedbackText($message)
4245
    {
4246
        // Old style
4247
        //return '<div id="question_feedback">'.$message.'</div>';
4248
        return Display::return_message($message, 'warning', false);
4249
    }
4250
4251
    /**
4252
     * Get the recorder audio component for save a teacher audio feedback
4253
     * @param int $attemptId
4254
     * @param int $questionId
4255
     * @param int $userId
4256
     * @return string
4257
     */
4258
    public static function getOralFeedbackForm($attemptId, $questionId, $userId)
4259
    {
4260
        $view = new Template('', false, false, false, false, false, false);
4261
        $view->assign('user_id', $userId);
4262
        $view->assign('question_id', $questionId);
4263
        $view->assign('directory', "/../exercises/teacher_audio/$attemptId/");
4264
        $view->assign('file_name', "{$questionId}_{$userId}");
4265
        $template = $view->get_template('exercise/oral_expression.tpl');
4266
4267
        return $view->fetch($template);
4268
    }
4269
4270
    /**
4271
     * Get the audio componen for a teacher audio feedback
4272
     * @param int $attemptId
4273
     * @param int $questionId
4274
     * @param int $userId
4275
     * @return string
4276
     */
4277
    public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
4278
    {
4279
        $courseInfo = api_get_course_info();
4280
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
4281
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
4282
        $fileName = "{$questionId}_{$userId}";
4283
        $filePath = null;
4284
4285
        if (file_exists("$sysCourseDir/exercises/teacher_audio/$attemptId/$fileName.ogg")) {
4286
            $filePath = "$webCourseDir/exercises/teacher_audio/$attemptId/$fileName.ogg";
4287
        } elseif (file_exists("$sysCourseDir/exercises/teacher_audio/$attemptId/$fileName.wav.wav")) {
4288
            $filePath = "$webCourseDir/exercises/teacher_audio/$attemptId/$fileName.wav.wav";
4289
        }
4290
4291
        if (!$filePath) {
4292
            return '';
4293
        }
4294
4295
        return Display::tag(
4296
            'audio',
4297
            null,
4298
            ['src' => $filePath]
4299
        );
4300
    }
4301
}
4302