Test Setup Failed
Push — master ( f71949...6c6bd7 )
by Julito
55:21
created

ExerciseLib::get_question_ribbon()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 32
nc 8
nop 4
dl 0
loc 44
rs 8.439
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ExerciseLib::getQuestionRibbon() 0 10 1
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 && $answerType != HOT_SPOT_DELINEATION && $answerType != ANNOTATION) {
63
            // Question is not a hotspot
64
            if (!$only_questions) {
65
                $questionDescription = $objQuestionTmp->selectDescription();
66
                if ($show_title) {
67
                    TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
68
                    $titleToDisplay = null;
69
                    if ($answerType == READING_COMPREHENSION) {
70
                        // In READING_COMPREHENSION, the title of the question
71
                        // contains the question itself, which can only be
72
                        // shown at the end of the given time, so hide for now
73
                        $titleToDisplay = Display::div(
74
                            $current_item.'. '.get_lang('ReadingComprehension'),
75
                            ['class' => 'question_title']
76
                        );
77
                    } else {
78
                        $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
79
                    }
80
                    echo $titleToDisplay;
81
                }
82
                if (!empty($questionDescription) && $answerType != READING_COMPREHENSION) {
83
                    echo Display::div(
84
                        $questionDescription,
85
                        array('class' => 'question_description')
86
                    );
87
                }
88
            }
89
90
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) &&
91
                $freeze
92
            ) {
93
                return '';
94
            }
95
96
            echo '<div class="question_options">';
97
            // construction of the Answer object (also gets all answers details)
98
            $objAnswerTmp = new Answer($questionId);
99
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
100
            $quiz_question_options = Question::readQuestionOption(
101
                $questionId,
102
                $course_id
103
            );
104
105
            // For "matching" type here, we need something a little bit special
106
            // because the match between the suggestions and the answers cannot be
107
            // done easily (suggestions and answers are in the same table), so we
108
            // have to go through answers first (elems with "correct" value to 0).
109
            $select_items = array();
110
            //This will contain the number of answers on the left side. We call them
111
            // suggestions here, for the sake of comprehensions, while the ones
112
            // on the right side are called answers
113
            $num_suggestions = 0;
114
            if (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
115
                if ($answerType == DRAGGABLE) {
116
                    $isVertical = $objQuestionTmp->extra == 'v';
117
                    $s .= '
118
                        <div class="col-md-12 ui-widget ui-helper-clearfix">
119
                            <div class="clearfix">
120
                            <ul class="exercise-draggable-answer '.($isVertical ? '' : 'list-inline').'"
121
                                id="question-'.$questionId.'" data-question="'.$questionId.'">
122
                    ';
123
                } else {
124
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">
125
                           <table class="data_table">';
126
                }
127
128
                // Iterate through answers
129
                $x = 1;
130
                //mark letters for each answer
131
                $letter = 'A';
132
                $answer_matching = array();
133
                $cpt1 = array();
134
                for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
135
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
136
                    $numAnswer = $objAnswerTmp->selectAutoId($answerId);
137
                    if ($answerCorrect == 0) {
138
                        // options (A, B, C, ...) that will be put into the list-box
139
                        // have the "correct" field set to 0 because they are answer
140
                        $cpt1[$x] = $letter;
141
                        $answer_matching[$x] = $objAnswerTmp->selectAnswerByAutoId(
142
                            $numAnswer
143
                        );
144
                        $x++;
145
                        $letter++;
146
                    }
147
                }
148
149
                $i = 1;
150
                $select_items[0]['id'] = 0;
151
                $select_items[0]['letter'] = '--';
152
                $select_items[0]['answer'] = '';
153
                foreach ($answer_matching as $id => $value) {
154
                    $select_items[$i]['id'] = $value['id_auto'];
155
                    $select_items[$i]['letter'] = $cpt1[$id];
156
                    $select_items[$i]['answer'] = $value['answer'];
157
                    $i++;
158
                }
159
160
                $user_choice_array_position = array();
161
                if (!empty($user_choice)) {
162
                    foreach ($user_choice as $item) {
163
                        $user_choice_array_position[$item['position']] = $item['answer'];
164
                    }
165
                }
166
                $num_suggestions = ($nbrAnswers - $x) + 1;
167
            } elseif ($answerType == FREE_ANSWER) {
168
                $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
169
                $form = new FormValidator('free_choice_'.$questionId);
170
                $config = array(
171
                    'ToolbarSet' => 'TestFreeAnswer'
172
                );
173
                $form->addHtmlEditor(
174
                    "choice[".$questionId."]",
175
                    null,
176
                    false,
177
                    false,
178
                    $config
179
                );
180
                $form->setDefaults(
181
                    array("choice[".$questionId."]" => $fck_content)
182
                );
183
                $s .= $form->returnForm();
184
            } elseif ($answerType == ORAL_EXPRESSION) {
185
                // Add nanog
186
                if (api_get_setting('enable_record_audio') == 'true') {
187
                    //@todo pass this as a parameter
188
                    global $exercise_stat_info, $exerciseId, $exe_id;
189
190
                    if (!empty($exercise_stat_info)) {
191
                        $objQuestionTmp->initFile(
192
                            api_get_session_id(),
193
                            api_get_user_id(),
194
                            $exercise_stat_info['exe_exo_id'],
195
                            $exercise_stat_info['exe_id']
196
                        );
197
                    } else {
198
                        $objQuestionTmp->initFile(
199
                            api_get_session_id(),
200
                            api_get_user_id(),
201
                            $exerciseId,
202
                            'temp_exe'
203
                        );
204
                    }
205
206
                    echo $objQuestionTmp->returnRecorder();
207
                }
208
209
                $form = new FormValidator('free_choice_'.$questionId);
210
                $config = array(
211
                    'ToolbarSet' => 'TestFreeAnswer'
212
                );
213
                $form->addHtmlEditor(
214
                    "choice[".$questionId."]",
215
                    null,
216
                    false,
217
                    false,
218
                    $config
219
                );
220
                $s .= $form->returnForm();
221
            }
222
223
            // Now navigate through the possible answers, using the max number of
224
            // answers for the question as a limiter
225
            $lines_count = 1; // a counter for matching-type answers
226
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
227
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
228
            ) {
229
                $header = Display::tag('th', get_lang('Options'));
230
                foreach ($objQuestionTmp->options as $item) {
231
                    if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
232
                        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...
233
                            $header .= Display::tag('th', get_lang($item));
234
                        } else {
235
                            $header .= Display::tag('th', $item);
236
                        }
237
                    } else {
238
                        $header .= Display::tag('th', $item);
239
                    }
240
                }
241
                if ($show_comment) {
242
                    $header .= Display::tag('th', get_lang('Feedback'));
243
                }
244
                $s .= '<table class="table table-hover table-striped">';
245
                $s .= Display::tag(
246
                    'tr',
247
                    $header,
248
                    array('style' => 'text-align:left;')
249
                );
250
            }
251
252
            if ($show_comment) {
253
                if (in_array(
254
                    $answerType,
255
                    array(
256
                        MULTIPLE_ANSWER,
257
                        MULTIPLE_ANSWER_COMBINATION,
258
                        UNIQUE_ANSWER,
259
                        UNIQUE_ANSWER_IMAGE,
260
                        UNIQUE_ANSWER_NO_OPTION,
261
                        GLOBAL_MULTIPLE_ANSWER,
262
                    )
263
                )) {
264
                    $header = Display::tag('th', get_lang('Options'));
265
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
266
                        $header .= Display::tag('th', get_lang('Feedback'));
267
                    }
268
                    $s .= '<table class="table table-hover table-striped">';
269
                    $s .= Display::tag(
270
                        'tr',
271
                        $header,
272
                        array('style' => 'text-align:left;')
273
                    );
274
                }
275
            }
276
277
            $matching_correct_answer = 0;
278
            $user_choice_array = array();
279
            if (!empty($user_choice)) {
280
                foreach ($user_choice as $item) {
281
                    $user_choice_array[] = $item['answer'];
282
                }
283
            }
284
285
            $hidingClass = '';
286
            if ($answerType == READING_COMPREHENSION) {
287
                $objQuestionTmp->processText(
288
                    $objQuestionTmp->selectDescription()
289
                );
290
                $hidingClass = 'hide-reading-answers';
291
            }
292
            if ($answerType == READING_COMPREHENSION) {
293
                $s .= Display::div(
294
                    $objQuestionTmp->selectTitle(),
295
                    ['class' => 'question_title '.$hidingClass]
296
                );
297
            }
298
299
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
300
                $answer = $objAnswerTmp->selectAnswer($answerId);
301
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
302
                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
303
                $comment = $objAnswerTmp->selectComment($answerId);
304
                $attributes = array();
305
306
                switch ($answerType) {
307
                    case UNIQUE_ANSWER:
308
                        //no break
309
                    case UNIQUE_ANSWER_NO_OPTION:
310
                        //no break
311
                    case UNIQUE_ANSWER_IMAGE:
312
                        //no break
313
                    case READING_COMPREHENSION:
314
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
315
316
                        if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
317
                            $attributes = array(
318
                                'id' => $input_id,
319
                                'checked' => 1,
320
                                'selected' => 1
321
                            );
322
                        } else {
323
                            $attributes = array('id' => $input_id);
324
                        }
325
326
                        if ($debug_mark_answer) {
327
                            if ($answerCorrect) {
328
                                $attributes['checked'] = 1;
329
                                $attributes['selected'] = 1;
330
                            }
331
                        }
332
333
                        if ($show_comment) {
334
                            $s .= '<tr><td>';
335
                        }
336
337
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
338
                            if ($show_comment) {
339
                                if (empty($comment)) {
340
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
341
                                        . 'class="exercise-unique-answer-image" style="text-align: center">';
342
                                } else {
343
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
344
                                        . 'class="exercise-unique-answer-image col-xs-6 col-sm-12" style="text-align: center">';
345
                                }
346
                            } else {
347
                                $s .= '<div id="answer'.$questionId.$numAnswer.'" '
348
                                    . 'class="exercise-unique-answer-image col-xs-6 col-md-3" style="text-align: center">';
349
                            }
350
                        }
351
352
                        $answer = Security::remove_XSS($answer, STUDENT);
353
                        $s .= Display::input(
354
                            'hidden',
355
                            'choice2['.$questionId.']',
356
                            '0'
357
                        );
358
359
                        $answer_input = null;
360
361
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
362
                            $attributes['style'] = 'display: none;';
363
                            $answer = '<div class="thumbnail">'.$answer.'</div>';
364
                        }
365
366
                        $answer_input .= '<label class="radio '.$hidingClass.'">';
367
                        $answer_input .= Display::input(
368
                            'radio',
369
                            'choice['.$questionId.']',
370
                            $numAnswer,
371
                            $attributes
372
                        );
373
                        $answer_input .= $answer;
374
                        $answer_input .= '</label>';
375
376
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
377
                            $answer_input .= "</div>";
378
                        }
379
380
                        if ($show_comment) {
381
                            $s .= $answer_input;
382
                            $s .= '</td>';
383
                            $s .= '<td>';
384
                            $s .= $comment;
385
                            $s .= '</td>';
386
                            $s .= '</tr>';
387
                        } else {
388
                            $s .= $answer_input;
389
                        }
390
                        break;
391
                    case MULTIPLE_ANSWER:
392
                        //no break
393
                    case MULTIPLE_ANSWER_TRUE_FALSE:
394
                        //no break
395
                    case GLOBAL_MULTIPLE_ANSWER:
396
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
397
                        $answer = Security::remove_XSS($answer, STUDENT);
398
399
                        if (in_array($numAnswer, $user_choice_array)) {
400
                            $attributes = array(
401
                                'id' => $input_id,
402
                                'checked' => 1,
403
                                'selected' => 1
404
                            );
405
                        } else {
406
                            $attributes = array('id' => $input_id);
407
                        }
408
409
                        if ($debug_mark_answer) {
410
                            if ($answerCorrect) {
411
                                $attributes['checked'] = 1;
412
                                $attributes['selected'] = 1;
413
                            }
414
                        }
415
416
                        if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
417
                            $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
418
419
                            $answer_input = '<label class="checkbox">';
420
                            $answer_input .= Display::input(
421
                                'checkbox',
422
                                'choice['.$questionId.']['.$numAnswer.']',
423
                                $numAnswer,
424
                                $attributes
425
                            );
426
                            $answer_input .= $answer;
427
                            $answer_input .= '</label>';
428
429
                            if ($show_comment) {
430
                                $s .= '<tr><td>';
431
                                $s .= $answer_input;
432
                                $s .= '</td>';
433
                                $s .= '<td>';
434
                                $s .= $comment;
435
                                $s .= '</td>';
436
                                $s .= '</tr>';
437
                            } else {
438
                                $s .= $answer_input;
439
                            }
440
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
441
                            $my_choice = array();
442
                            if (!empty($user_choice_array)) {
443
                                foreach ($user_choice_array as $item) {
444
                                    $item = explode(':', $item);
445
                                    $my_choice[$item[0]] = $item[1];
446
                                }
447
                            }
448
449
                            $s .= '<tr>';
450
                            $s .= Display::tag('td', $answer);
451
452
                            if (!empty($quiz_question_options)) {
453
                                foreach ($quiz_question_options as $id => $item) {
454
                                    if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
455
                                        $attributes = array(
456
                                            'checked' => 1,
457
                                            'selected' => 1
458
                                        );
459
                                    } else {
460
                                        $attributes = array();
461
                                    }
462
463
                                    if ($debug_mark_answer) {
464
                                        if ($id == $answerCorrect) {
465
                                            $attributes['checked'] = 1;
466
                                            $attributes['selected'] = 1;
467
                                        }
468
                                    }
469
                                    $s .= Display::tag(
470
                                        'td',
471
                                        Display::input(
472
                                            'radio',
473
                                            'choice['.$questionId.']['.$numAnswer.']',
474
                                            $id,
475
                                            $attributes
476
                                        ),
477
                                        array('style' => '')
478
                                    );
479
                                }
480
                            }
481
482
                            if ($show_comment) {
483
                                $s .= '<td>';
484
                                $s .= $comment;
485
                                $s .= '</td>';
486
                            }
487
                            $s .= '</tr>';
488
                        }
489
                        break;
490
                    case MULTIPLE_ANSWER_COMBINATION:
491
                        // multiple answers
492
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
493
494
                        if (in_array($numAnswer, $user_choice_array)) {
495
                            $attributes = array(
496
                                'id' => $input_id,
497
                                'checked' => 1,
498
                                'selected' => 1
499
                            );
500
                        } else {
501
                            $attributes = array('id' => $input_id);
502
                        }
503
504
                        if ($debug_mark_answer) {
505
                            if ($answerCorrect) {
506
                                $attributes['checked'] = 1;
507
                                $attributes['selected'] = 1;
508
                            }
509
                        }
510
511
                        $answer = Security::remove_XSS($answer, STUDENT);
512
                        $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
513
                        $answer_input .= '<label class="checkbox">';
514
                        $answer_input .= Display::input(
515
                            'checkbox',
516
                            'choice['.$questionId.']['.$numAnswer.']',
517
                            1,
518
                            $attributes
519
                        );
520
                        $answer_input .= $answer;
521
                        $answer_input .= '</label>';
522
523
                        if ($show_comment) {
524
                            $s .= '<tr>';
525
                            $s .= '<td>';
526
                            $s .= $answer_input;
527
                            $s .= '</td>';
528
                            $s .= '<td>';
529
                            $s .= $comment;
530
                            $s .= '</td>';
531
                            $s .= '</tr>';
532
                        } else {
533
                            $s .= $answer_input;
534
                        }
535
                        break;
536
                    case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
537
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
538
539
                        $my_choice = array();
540
                        if (!empty($user_choice_array)) {
541
                            foreach ($user_choice_array as $item) {
542
                                $item = explode(':', $item);
543
                                if (isset($item[1]) && isset($item[0])) {
544
                                    $my_choice[$item[0]] = $item[1];
545
                                }
546
                            }
547
                        }
548
                        $answer = Security::remove_XSS($answer, STUDENT);
549
                        $s .= '<tr>';
550
                        $s .= Display::tag('td', $answer);
551
                        foreach ($objQuestionTmp->options as $key => $item) {
552
                            if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
553
                                $attributes = array(
554
                                    'checked' => 1,
555
                                    'selected' => 1
556
                                );
557
                            } else {
558
                                $attributes = array();
559
                            }
560
561
                            if ($debug_mark_answer) {
562
                                if ($key == $answerCorrect) {
563
                                    $attributes['checked'] = 1;
564
                                    $attributes['selected'] = 1;
565
                                }
566
                            }
567
                            $s .= Display::tag(
568
                                'td',
569
                                Display::input(
570
                                    'radio',
571
                                    'choice['.$questionId.']['.$numAnswer.']',
572
                                    $key,
573
                                    $attributes
574
                                )
575
                            );
576
                        }
577
578
                        if ($show_comment) {
579
                            $s .= '<td>';
580
                            $s .= $comment;
581
                            $s .= '</td>';
582
                        }
583
                        $s .= '</tr>';
584
                        break;
585
                    case FILL_IN_BLANKS:
586
                        // display the question, with field empty, for student to fill it,
587
                        // or filled to display the answer in the Question preview of the exercise/admin.php page
588
                        $displayForStudent = true;
589
                        $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
590
591
                        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...
592
                        // Correct answers
593
                        $correctAnswerList = $listAnswerInfo['tabwords'];
594
595
                        // Student's answer
596
                        $studentAnswerList = array();
597
                        if (isset($user_choice[0]['answer'])) {
598
                            $arrayStudentAnswer = FillBlanks::getAnswerInfo($user_choice[0]['answer'], true);
599
                            $studentAnswerList = $arrayStudentAnswer['studentanswer'];
600
                        }
601
602
                        // If the question must be shown with the answer (in page exercise/admin.php) for teacher preview
603
                        // set the student-answer to the correct answer
604
                        if ($debug_mark_answer) {
605
                            $studentAnswerList = $correctAnswerList;
606
                            $displayForStudent = false;
607
                        }
608
609
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
610
                            $answer = '';
611
                            for ($i = 0; $i < count($listAnswerInfo['commonwords']) - 1; $i++) {
612
                                // display the common word
613
                                $answer .= $listAnswerInfo['commonwords'][$i];
614
                                // display the blank word
615
                                $correctItem = $listAnswerInfo['tabwords'][$i];
616
                                if (isset($studentAnswerList[$i])) {
617
                                    // If student already started this test and answered this question,
618
                                    // fill the blank with his previous answers
619
                                    // may be "" if student viewed the question, but did not fill the blanks
620
                                    $correctItem = $studentAnswerList[$i];
621
                                }
622
                                $attributes['style'] = "width:".$listAnswerInfo['tabinputsize'][$i]."px";
623
                                $answer .= FillBlanks::getFillTheBlankHtml(
624
                                    $current_item,
625
                                    $questionId,
626
                                    $correctItem,
627
                                    $attributes,
628
                                    $answer,
629
                                    $listAnswerInfo,
630
                                    $displayForStudent,
631
                                    $i
632
                                );
633
                            }
634
                            // display the last common word
635
                            $answer .= $listAnswerInfo['commonwords'][$i];
636
                        } else {
637
                            // display empty [input] with the right width for student to fill it
638
                            $answer = '';
639
                            for ($i = 0; $i < count($listAnswerInfo['commonwords']) - 1; $i++) {
640
                                // display the common words
641
                                $answer .= $listAnswerInfo['commonwords'][$i];
642
                                // display the blank word
643
                                $attributes["style"] = "width:".$listAnswerInfo['tabinputsize'][$i]."px";
644
                                $answer .= FillBlanks::getFillTheBlankHtml(
645
                                    $current_item,
646
                                    $questionId,
647
                                    '',
648
                                    $attributes,
649
                                    $answer,
650
                                    $listAnswerInfo,
651
                                    $displayForStudent,
652
                                    $i
653
                                );
654
                            }
655
                            // display the last common word
656
                            $answer .= $listAnswerInfo['commonwords'][$i];
657
                        }
658
                        $s .= $answer;
659
                        break;
660
                    case CALCULATED_ANSWER:
661
                        /*
662
                         * In the CALCULATED_ANSWER test
663
                         * you mustn't have [ and ] in the textarea
664
                         * you mustn't have @@ in the textarea
665
                         * the text to find mustn't be empty or contains only spaces
666
                         * the text to find mustn't contains HTML tags
667
                         * the text to find mustn't contains char "
668
                         */
669
                        if ($origin !== null) {
670
                            global $exe_id;
671
                            $trackAttempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
672
                            $sql = 'SELECT answer FROM '.$trackAttempts.'
673
                                    WHERE exe_id=' . $exe_id.' AND question_id='.$questionId;
674
                            $rsLastAttempt = Database::query($sql);
675
                            $rowLastAttempt = Database::fetch_array($rsLastAttempt);
676
                            $answer = $rowLastAttempt['answer'];
677
                            if (empty($answer)) {
678
                                $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(
679
                                    1,
680
                                    $nbrAnswers
681
                                );
682
                                $answer = $objAnswerTmp->selectAnswer(
683
                                    $_SESSION['calculatedAnswerId'][$questionId]
684
                                );
685
                            }
686
                        }
687
688
                        list($answer) = explode('@@', $answer);
689
                        // $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
690
                        api_preg_match_all(
691
                            '/\[[^]]+\]/',
692
                            $answer,
693
                            $correctAnswerList
694
                        );
695
696
                        // get student answer to display it if student go back to previous calculated answer question in a test
697
                        if (isset($user_choice[0]['answer'])) {
698
                            api_preg_match_all(
699
                                '/\[[^]]+\]/',
700
                                $answer,
701
                                $studentAnswerList
702
                            );
703
                            $studentAnswerListTobecleaned = $studentAnswerList[0];
704
                            $studentAnswerList = array();
705
706
                            for ($i = 0; $i < count(
707
                                $studentAnswerListTobecleaned
708
                            ); $i++) {
709
                                $answerCorrected = $studentAnswerListTobecleaned[$i];
710
                                $answerCorrected = api_preg_replace(
711
                                    '| / <font color="green"><b>.*$|',
712
                                    '',
713
                                    $answerCorrected
714
                                );
715
                                $answerCorrected = api_preg_replace(
716
                                    '/^\[/',
717
                                    '',
718
                                    $answerCorrected
719
                                );
720
                                $answerCorrected = api_preg_replace(
721
                                    '|^<font color="red"><s>|',
722
                                    '',
723
                                    $answerCorrected
724
                                );
725
                                $answerCorrected = api_preg_replace(
726
                                    '|</s></font>$|',
727
                                    '',
728
                                    $answerCorrected
729
                                );
730
                                $answerCorrected = '['.$answerCorrected.']';
731
                                $studentAnswerList[] = $answerCorrected;
732
                            }
733
                        }
734
735
                        // If display preview of answer in test view for exemple, set the student answer to the correct answers
736
                        if ($debug_mark_answer) {
737
                            // contain the rights answers surronded with brackets
738
                            $studentAnswerList = $correctAnswerList[0];
739
                        }
740
741
                        /*
742
                        Split the response by bracket
743
                        tabComments is an array with text surrounding the text to find
744
                        we add a space before and after the answerQuestion to be sure to
745
                        have a block of text before and after [xxx] patterns
746
                        so we have n text to find ([xxx]) and n+1 block of texts before,
747
                        between and after the text to find
748
                        */
749
                        $tabComments = api_preg_split(
750
                            '/\[[^]]+\]/',
751
                            ' '.$answer.' '
752
                        );
753
                        if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
754
                            $answer = "";
755
                            $i = 0;
756
                            foreach ($studentAnswerList as $studentItem) {
757
                                // remove surronding brackets
758
                                $studentResponse = api_substr(
759
                                    $studentItem,
760
                                    1,
761
                                    api_strlen($studentItem) - 2
762
                                );
763
                                $size = strlen($studentItem);
764
                                $attributes['class'] = self::detectInputAppropriateClass(
765
                                    $size
766
                                );
767
768
                                $answer .= $tabComments[$i].
769
                                    Display::input(
770
                                        'text',
771
                                        "choice[$questionId][]",
772
                                        $studentResponse,
773
                                        $attributes
774
                                    );
775
                                $i++;
776
                            }
777
                            $answer .= $tabComments[$i];
778
                        } else {
779
                            // display exercise with empty input fields
780
                            // every [xxx] are replaced with an empty input field
781
                            foreach ($correctAnswerList[0] as $item) {
782
                                $size = strlen($item);
783
                                $attributes['class'] = self::detectInputAppropriateClass(
784
                                    $size
785
                                );
786
                                $answer = str_replace(
787
                                    $item,
788
                                    Display::input(
789
                                        'text',
790
                                        "choice[$questionId][]",
791
                                        '',
792
                                        $attributes
793
                                    ),
794
                                    $answer
795
                                );
796
                            }
797
                        }
798
                        if ($origin !== null) {
799
                            $s = $answer;
800
                            break;
801
                        } else {
802
                            $s .= $answer;
803
                        }
804
                        break;
805
                    case MATCHING:
806
                        // matching type, showing suggestions and answers
807
                        // TODO: replace $answerId by $numAnswer
808
                        if ($answerCorrect != 0) {
809
                            // only show elements to be answered (not the contents of
810
                            // the select boxes, who are correct = 0)
811
                            $s .= '<tr><td width="45%" valign="top">';
812
                            $parsed_answer = $answer;
813
                            // Left part questions
814
                            $s .= '<p class="indent">'.$lines_count.'.&nbsp;'.$parsed_answer.'</p></td>';
815
                            // Middle part (matches selects)
816
                            // Id of select is # question + # of option
817
                            $s .= '<td width="10%" valign="top" align="center">
818
                                <div class="select-matching">
819
                                <select id="choice_id_'.$current_item.'_'.$lines_count.'" name="choice['.$questionId.']['.$numAnswer.']">';
820
821
                            // fills the list-box
822
                            foreach ($select_items as $key => $val) {
823
                                // set $debug_mark_answer to true at function start to
824
                                // show the correct answer with a suffix '-x'
825
                                $selected = '';
826
                                if ($debug_mark_answer) {
827
                                    if ($val['id'] == $answerCorrect) {
828
                                        $selected = 'selected="selected"';
829
                                    }
830
                                }
831
                                //$user_choice_array_position
832
                                if (isset($user_choice_array_position[$numAnswer]) && $val['id'] == $user_choice_array_position[$numAnswer]) {
833
                                    $selected = 'selected="selected"';
834
                                }
835
                                $s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
836
837
                            }  // end foreach()
838
839
                            $s .= '</select></div></td><td width="5%" class="separate">&nbsp;</td>';
840
                            $s .= '<td width="40%" valign="top" >';
841
                            if (isset($select_items[$lines_count])) {
842
                                $s .= '<div class="text-right"><p class="indent">'.$select_items[$lines_count]['letter'].'.&nbsp; '.$select_items[$lines_count]['answer'].'</p></div>';
843
                            } else {
844
                                $s .= '&nbsp;';
845
                            }
846
                            $s .= '</td>';
847
                            $s .= '</tr>';
848
                            $lines_count++;
849
                            //if the left side of the "matching" has been completely
850
                            // shown but the right side still has values to show...
851
                            if (($lines_count - 1) == $num_suggestions) {
852
                                // if it remains answers to shown at the right side
853
                                while (isset($select_items[$lines_count])) {
854
                                    $s .= '<tr>
855
                                      <td colspan="2"></td>
856
                                      <td valign="top">';
857
                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'];
858
                                    $s .= "</td>
859
                                </tr>";
860
                                    $lines_count++;
861
                                }    // end while()
862
                            }  // end if()
863
                            $matching_correct_answer++;
864
                        }
865
                        break;
866
                    case DRAGGABLE:
867
                        if ($answerCorrect) {
868
                            $parsed_answer = $answer;
869
                            /*$lines_count = '';
870
                            $data = $objAnswerTmp->getAnswerByAutoId($numAnswer);
871
                            $data = $objAnswerTmp->getAnswerByAutoId($data['correct']);
872
                            $lines_count = $data['answer'];*/
873
                            $windowId = $questionId.'_'.$lines_count;
874
                            $s .= '<li class="touch-items" id="'.$windowId.'">';
875
                            $s .= Display::div(
876
                                $parsed_answer,
877
                                [
878
                                    'id' => "window_$windowId",
879
                                    'class' => "window{$questionId}_question_draggable exercise-draggable-answer-option"
880
                                ]
881
                            );
882
883
                            $draggableSelectOptions = [];
884
                            $selectedValue = 0;
885
                            $selectedIndex = 0;
886
887 View Code Duplication
                            if ($user_choice) {
888
                                foreach ($user_choice as $chosen) {
889
                                    if ($answerCorrect != $chosen['answer']) {
890
                                        continue;
891
                                    }
892
893
                                    $selectedValue = $chosen['answer'];
894
                                }
895
                            }
896
897
                            foreach ($select_items as $key => $select_item) {
898
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
899
                            }
900
901
                            foreach ($draggableSelectOptions as $value => $text) {
902
                                if ($value == $selectedValue) {
903
                                    break;
904
                                }
905
                                $selectedIndex++;
906
                            }
907
908
                            $s .= Display::select(
909
                                "choice[$questionId][$numAnswer]",
910
                                $draggableSelectOptions,
911
                                $selectedValue,
912
                                [
913
                                    'id' => "window_{$windowId}_select",
914
                                    'class' => 'select_option hidden',
915
                                ],
916
                                false
917
                            );
918
919
                            if ($selectedValue && $selectedIndex) {
920
                                $s .= "
921
                                    <script>
922
                                        $(function() {
923
                                            DraggableAnswer.deleteItem(
924
                                                $('#{$questionId}_$lines_count'),
925
                                                $('#drop_{$questionId}_{$selectedIndex}')
926
                                            );
927
                                        });
928
                                    </script>
929
                                ";
930
                            }
931
932
                            if (isset($select_items[$lines_count])) {
933
                                $s .= Display::div(
934
                                    Display::tag(
935
                                        'b',
936
                                        $select_items[$lines_count]['letter']
937
                                    ).$select_items[$lines_count]['answer'],
938
                                    [
939
                                        'id' => "window_{$windowId}_answer",
940
                                        'class' => 'hidden'
941
                                    ]
942
                                );
943
                            } else {
944
                                $s .= '&nbsp;';
945
                            }
946
947
                            $lines_count++;
948
949
                            if (($lines_count - 1) == $num_suggestions) {
950
                                while (isset($select_items[$lines_count])) {
951
                                    $s .= Display::tag('b', $select_items[$lines_count]['letter']);
952
                                    $s .= $select_items[$lines_count]['answer'];
953
                                    $lines_count++;
954
                                }
955
                            }
956
957
                            $matching_correct_answer++;
958
959
                            $s .= '</li>';
960
                        }
961
                        break;
962
                    case MATCHING_DRAGGABLE:
963
                        if ($answerId == 1) {
964
                            echo $objAnswerTmp->getJs();
965
                        }
966
967
                        if ($answerCorrect != 0) {
968
                            $parsed_answer = $answer;
969
                            $windowId = "{$questionId}_{$lines_count}";
970
971
                            $s .= <<<HTML
972
                            <tr>
973
                                <td widht="45%">
974
                                    <div id="window_{$windowId}" class="window window_left_question window{$questionId}_question">
975
                                        <strong>$lines_count.</strong> $parsed_answer
976
                                    </div>
977
                                </td>
978
                                <td width="10%">
979
HTML;
980
981
                            $draggableSelectOptions = [];
982
                            $selectedValue = 0;
983
                            $selectedIndex = 0;
984
985 View Code Duplication
                            if ($user_choice) {
986
                                foreach ($user_choice as $chosen) {
987
                                    if ($answerCorrect != $chosen['answer']) {
988
                                        continue;
989
                                    }
990
                                    $selectedValue = $chosen['answer'];
991
                                }
992
                            }
993
994
                            foreach ($select_items as $key => $select_item) {
995
                                $draggableSelectOptions[$select_item['id']] = $select_item['letter'];
996
                            }
997
998
                            foreach ($draggableSelectOptions as $value => $text) {
999
                                if ($value == $selectedValue) {
1000
                                    break;
1001
                                }
1002
                                $selectedIndex++;
1003
                            }
1004
1005
                            $s .= Display::select(
1006
                                "choice[$questionId][$numAnswer]",
1007
                                $draggableSelectOptions,
1008
                                $selectedValue,
1009
                                [
1010
                                    'id' => "window_{$windowId}_select",
1011
                                    'class' => 'hidden'
1012
                                ],
1013
                                false
1014
                            );
1015
1016
                            if (!empty($answerCorrect) && !empty($selectedValue)) {
1017
                                // Show connect if is not freeze (question preview)
1018
                                if (!$freeze) {
1019
                                    $s .= "
1020
                                        <script>
1021
                                            $(document).on('ready', function () {
1022
                                                jsPlumb.ready(function() {
1023
                                                    jsPlumb.connect({
1024
                                                        source: 'window_$windowId',
1025
                                                        target: 'window_{$questionId}_{$selectedIndex}_answer',
1026
                                                        endpoint: ['Blank', {radius: 15}],
1027
                                                        anchors: ['RightMiddle', 'LeftMiddle'],
1028
                                                        paintStyle: {strokeStyle: '#8A8888', lineWidth: 8},
1029
                                                        connector: [
1030
                                                            MatchingDraggable.connectorType,
1031
                                                            {curvines: MatchingDraggable.curviness}
1032
                                                        ]
1033
                                                    });
1034
                                                });
1035
                                            });
1036
                                        </script>
1037
                                    ";
1038
                                }
1039
                            }
1040
1041
                            $s .= <<<HTML
1042
                            </td>
1043
                            <td width="45%">
1044
HTML;
1045
1046 View Code Duplication
                            if (isset($select_items[$lines_count])) {
1047
                                $s .= <<<HTML
1048
                                <div id="window_{$windowId}_answer" class="window window_right_question">
1049
                                    <strong>{$select_items[$lines_count]['letter']}.</strong> {$select_items[$lines_count]['answer']}
1050
                                </div>
1051
HTML;
1052
                            } else {
1053
                                $s .= '&nbsp;';
1054
                            }
1055
1056
                            $s .= '</td></tr>';
1057
1058
                            $lines_count++;
1059
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, $courseCode);
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
        $max_note = api_get_setting('exercise_max_score');
2289
        $min_note = api_get_setting('exercise_min_score');
2290
2291 View Code Duplication
        if ($use_platform_settings) {
2292
            if ($max_note != '' && $min_note != '') {
2293
                if (!empty($weight) && intval($weight) != 0) {
2294
                    $score = $min_note + ($max_note - $min_note) * $score / $weight;
2295
                } else {
2296
                    $score = $min_note;
2297
                }
2298
                $weight = $max_note;
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 = null;
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
        $html = Display::span($html, array('class' => 'score_exercise'));
2319
2320
        return $html;
2321
    }
2322
2323
    /**
2324
     * @param float $score
2325
     * @param float $weight
2326
     * @param string $pass_percentage
2327
     * @return bool
2328
     */
2329
    public static function isSuccessExerciseResult($score, $weight, $pass_percentage)
2330
    {
2331
        $percentage = float_format(
2332
            ($score / ($weight != 0 ? $weight : 1)) * 100,
2333
            1
2334
        );
2335
        if (isset($pass_percentage) && !empty($pass_percentage)) {
2336
            if ($percentage >= $pass_percentage) {
2337
                return true;
2338
            }
2339
        }
2340
        return false;
2341
    }
2342
2343
    /**
2344
     * @param float $score
2345
     * @param float $weight
2346
     * @param string $pass_percentage
2347
     * @return string
2348
     */
2349
    public static function showSuccessMessage($score, $weight, $pass_percentage)
2350
    {
2351
        $res = '';
2352
        if (self::isPassPercentageEnabled($pass_percentage)) {
2353
            $isSuccess = self::isSuccessExerciseResult(
2354
                $score,
2355
                $weight,
2356
                $pass_percentage
2357
            );
2358
2359
            if ($isSuccess) {
2360
                $html = get_lang('CongratulationsYouPassedTheTest');
2361
                $icon = Display::return_icon(
2362
                    'completed.png',
2363
                    get_lang('Correct'),
2364
                    array(),
2365
                    ICON_SIZE_MEDIUM
2366
                );
2367
            } else {
2368
                //$html .= Display::return_message(get_lang('YouDidNotReachTheMinimumScore'), 'warning');
2369
                $html = get_lang('YouDidNotReachTheMinimumScore');
2370
                $icon = Display::return_icon(
2371
                    'warning.png',
2372
                    get_lang('Wrong'),
2373
                    array(),
2374
                    ICON_SIZE_MEDIUM
2375
                );
2376
            }
2377
            $html = Display::tag('h4', $html);
2378
            $html .= Display::tag(
2379
                'h5',
2380
                $icon,
2381
                array('style' => 'width:40px; padding:2px 10px 0px 0px')
2382
            );
2383
            $res = $html;
2384
        }
2385
        return $res;
2386
    }
2387
2388
    /**
2389
     * Return true if pass_pourcentage activated (we use the pass pourcentage feature
2390
     * return false if pass_percentage = 0 (we don't use the pass pourcentage feature
2391
     * @param $value
2392
     * @return boolean
2393
     * In this version, pass_percentage and show_success_message are disabled if
2394
     * pass_percentage is set to 0
2395
     */
2396
    public static function isPassPercentageEnabled($value)
2397
    {
2398
        return $value > 0;
2399
    }
2400
2401
    /**
2402
     * Converts a numeric value in a percentage example 0.66666 to 66.67 %
2403
     * @param $value
2404
     * @return float Converted number
2405
     */
2406
    public static function convert_to_percentage($value)
2407
    {
2408
        $return = '-';
2409
        if ($value != '') {
2410
            $return = float_format($value * 100, 1).' %';
2411
        }
2412
        return $return;
2413
    }
2414
2415
    /**
2416
     * Converts a score/weight values to the platform scale
2417
     * @param   float $score
2418
     * @param   float $weight
2419
     * @deprecated seem not to be used
2420
     * @return  float   the score rounded converted to the new range
2421
     */
2422
    public static function convert_score($score, $weight)
2423
    {
2424
        $max_note = api_get_setting('exercise_max_score');
2425
        $min_note = api_get_setting('exercise_min_score');
2426
2427 View Code Duplication
        if ($score != '' && $weight != '') {
2428
            if ($max_note != '' && $min_note != '') {
2429
                if (!empty($weight)) {
2430
                    $score = $min_note + ($max_note - $min_note) * $score / $weight;
2431
                } else {
2432
                    $score = $min_note;
2433
                }
2434
            }
2435
        }
2436
        $score_rounded = float_format($score, 1);
2437
2438
        return $score_rounded;
2439
    }
2440
2441
    /**
2442
     * Getting all active exercises from a course from a session
2443
     * (if a session_id is provided we will show all the exercises in the course +
2444
     * all exercises in the session)
2445
     * @param   array $course_info
2446
     * @param   int $session_id
2447
     * @param   boolean $check_publication_dates
2448
     * @param   string $search Search exercise name
2449
     * @param   boolean $search_all_sessions Search exercises in all sessions
2450
     * @param   int 0 = only inactive exercises
2451
     *                  1 = only active exercises,
2452
     *                  2 = all exercises
2453
     *                  3 = active <> -1
2454
     * @return  array   array with exercise data
2455
     */
2456
    public static function get_all_exercises(
2457
        $course_info = null,
2458
        $session_id = 0,
2459
        $check_publication_dates = false,
2460
        $search = '',
2461
        $search_all_sessions = false,
2462
        $active = 2
2463
    ) {
2464
        $course_id = api_get_course_int_id();
2465
2466
        if (!empty($course_info) && !empty($course_info['real_id'])) {
2467
            $course_id = $course_info['real_id'];
2468
        }
2469
2470
        if ($session_id == -1) {
2471
            $session_id = 0;
2472
        }
2473
2474
        $now = api_get_utc_datetime();
2475
        $time_conditions = '';
2476
2477
        if ($check_publication_dates) {
2478
            //start and end are set
2479
            $time_conditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' )  OR ";
2480
            // only start is set
2481
            $time_conditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR ";
2482
            // only end is set
2483
            $time_conditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
2484
            // nothing is set
2485
            $time_conditions .= " (start_time IS NULL AND end_time IS NULL))  ";
2486
        }
2487
2488
        $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
2489
        $needle = !empty($search) ? "%".$search."%" : '';
2490
2491
        // Show courses by active status
2492
        $active_sql = '';
2493
        if ($active == 3) {
2494
            $active_sql = ' active <> -1 AND';
2495
        } else {
2496
            if ($active != 2) {
2497
                $active_sql = sprintf(' active = %d AND', $active);
2498
            }
2499
        }
2500
2501
        if ($search_all_sessions == true) {
2502
            $conditions = array(
2503
                'where' => array(
2504
                    $active_sql.' c_id = ? '.$needle_where.$time_conditions => array(
2505
                        $course_id,
2506
                        $needle
2507
                    )
2508
                ),
2509
                'order' => 'title'
2510
            );
2511
        } else {
2512
            if ($session_id == 0) {
2513
                $conditions = array(
2514
                    'where' => array(
2515
                        $active_sql.' session_id = ? AND c_id = ? '.$needle_where.$time_conditions => array(
2516
                            $session_id,
2517
                            $course_id,
2518
                            $needle
2519
                        )
2520
                    ),
2521
                    'order' => 'title'
2522
                );
2523
            } else {
2524
                $conditions = array(
2525
                    'where' => array(
2526
                        $active_sql.' (session_id = 0 OR session_id = ? ) AND c_id = ? '.$needle_where.$time_conditions => array(
2527
                            $session_id,
2528
                            $course_id,
2529
                            $needle
2530
                        )
2531
                    ),
2532
                    'order' => 'title'
2533
                );
2534
            }
2535
        }
2536
2537
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
2538
2539
        return Database::select('*', $table, $conditions);
2540
    }
2541
2542
    /**
2543
     * Get exercise information by id
2544
     * @param int $exerciseId Exercise Id
2545
     * @param int $courseId The course ID (necessary as c_quiz.id is not unique)
2546
     * @return array Exercise info
2547
     */
2548
    public static function get_exercise_by_id($exerciseId = 0, $courseId = 0)
2549
    {
2550
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
2551
        if (empty($courseId)) {
2552
            $courseId = api_get_course_int_id();
2553
        } else {
2554
            $courseId = intval($courseId);
2555
        }
2556
        $conditions = array(
2557
            'where' => array(
2558
                'id = ?' => array($exerciseId),
2559
                ' AND c_id = ? ' => $courseId
2560
            )
2561
        );
2562
2563
        return Database::select('*', $table, $conditions);
2564
    }
2565
2566
    /**
2567
     * Getting all exercises (active only or all)
2568
     * from a course from a session
2569
     * (if a session_id is provided we will show all the exercises in the
2570
     * course + all exercises in the session)
2571
     * @param   array   course data
2572
     * @param   int     session id
2573
     * @param    int        course c_id
2574
     * @param   boolean $only_active_exercises
2575
     * @return  array   array with exercise data
2576
     * modified by Hubert Borderiou
2577
     */
2578
    public static function get_all_exercises_for_course_id(
2579
        $course_info = null,
2580
        $session_id = 0,
2581
        $course_id = 0,
2582
        $only_active_exercises = true
2583
    ) {
2584
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
2585
2586
        if ($only_active_exercises) {
2587
            // Only active exercises.
2588
            $sql_active_exercises = "active = 1 AND ";
2589
        } else {
2590
            // Not only active means visible and invisible NOT deleted (-2)
2591
            $sql_active_exercises = "active IN (1, 0) AND ";
2592
        }
2593
2594
        if ($session_id == -1) {
2595
            $session_id = 0;
2596
        }
2597
2598
        $params = array(
2599
            $session_id,
2600
            $course_id
2601
        );
2602
2603
        if ($session_id == 0) {
2604
            $conditions = array(
2605
                'where' => array("$sql_active_exercises session_id = ? AND c_id = ?" => $params),
2606
                'order' => 'title'
2607
            );
2608
        } else {
2609
            // All exercises
2610
            $conditions = array(
2611
                'where' => array("$sql_active_exercises (session_id = 0 OR session_id = ? ) AND c_id=?" => $params),
2612
                'order' => 'title'
2613
            );
2614
        }
2615
2616
        return Database::select('*', $TBL_EXERCISES, $conditions);
2617
    }
2618
2619
    /**
2620
     * Gets the position of the score based in a given score (result/weight)
2621
     * and the exe_id based in the user list
2622
     * (NO Exercises in LPs )
2623
     * @param   float $my_score user score to be compared *attention*
2624
     * $my_score = score/weight and not just the score
2625
     * @param   int $my_exe_id exe id of the exercise
2626
     * (this is necessary because if 2 students have the same score the one
2627
     * with the minor exe_id will have a best position, just to be fair and FIFO)
2628
     * @param   int $exercise_id
2629
     * @param   string $course_code
2630
     * @param   int $session_id
2631
     * @param   array $user_list
2632
     * @param   bool $return_string
2633
     *
2634
     * @return  int     the position of the user between his friends in a course
2635
     * (or course within a session)
2636
     */
2637
    public static function get_exercise_result_ranking(
2638
        $my_score,
2639
        $my_exe_id,
2640
        $exercise_id,
2641
        $course_code,
2642
        $session_id = 0,
2643
        $user_list = array(),
2644
        $return_string = true
2645
    ) {
2646
        //No score given we return
2647
        if (is_null($my_score)) {
2648
            return '-';
2649
        }
2650
        if (empty($user_list)) {
2651
            return '-';
2652
        }
2653
2654
        $best_attempts = array();
2655
        foreach ($user_list as $user_data) {
2656
            $user_id = $user_data['user_id'];
2657
            $best_attempts[$user_id] = self::get_best_attempt_by_user(
2658
                $user_id,
2659
                $exercise_id,
2660
                $course_code,
2661
                $session_id
2662
            );
2663
        }
2664
2665
        if (empty($best_attempts)) {
2666
            return 1;
2667
        } else {
2668
            $position = 1;
2669
            $my_ranking = array();
2670 View Code Duplication
            foreach ($best_attempts as $user_id => $result) {
2671
                if (!empty($result['exe_weighting']) && intval(
2672
                        $result['exe_weighting']
2673
                    ) != 0
2674
                ) {
2675
                    $my_ranking[$user_id] = $result['exe_result'] / $result['exe_weighting'];
2676
                } else {
2677
                    $my_ranking[$user_id] = 0;
2678
                }
2679
            }
2680
            //if (!empty($my_ranking)) {
2681
            asort($my_ranking);
2682
            $position = count($my_ranking);
2683
            if (!empty($my_ranking)) {
2684
                foreach ($my_ranking as $user_id => $ranking) {
2685
                    if ($my_score >= $ranking) {
2686
                        if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) {
2687
                            $exe_id = $best_attempts[$user_id]['exe_id'];
2688
                            if ($my_exe_id < $exe_id) {
2689
                                $position--;
2690
                            }
2691
                        } else {
2692
                            $position--;
2693
                        }
2694
                    }
2695
                }
2696
            }
2697
            //}
2698
            $return_value = array(
2699
                'position' => $position,
2700
                'count' => count($my_ranking)
2701
            );
2702
2703
            if ($return_string) {
2704
                if (!empty($position) && !empty($my_ranking)) {
2705
                    $return_value = $position.'/'.count($my_ranking);
2706
                } else {
2707
                    $return_value = '-';
2708
                }
2709
            }
2710
            return $return_value;
2711
        }
2712
    }
2713
2714
    /**
2715
     * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts
2716
     * (NO Exercises in LPs ) old functionality by attempt
2717
     * @param   float   user score to be compared attention => score/weight
2718
     * @param   int     exe id of the exercise
2719
     * (this is necessary because if 2 students have the same score the one
2720
     * with the minor exe_id will have a best position, just to be fair and FIFO)
2721
     * @param   int     exercise id
2722
     * @param   string  course code
2723
     * @param   int     session id
2724
     * @param bool $return_string
2725
     * @return  int     the position of the user between his friends in a course (or course within a session)
2726
     */
2727
    public static function get_exercise_result_ranking_by_attempt(
2728
        $my_score,
2729
        $my_exe_id,
2730
        $exercise_id,
2731
        $courseId,
2732
        $session_id = 0,
2733
        $return_string = true
2734
    ) {
2735
        if (empty($session_id)) {
2736
            $session_id = 0;
2737
        }
2738
        if (is_null($my_score)) {
2739
            return '-';
2740
        }
2741
        $user_results = Event::get_all_exercise_results(
2742
            $exercise_id,
2743
            $courseId,
2744
            $session_id,
2745
            false
2746
        );
2747
        $position_data = array();
2748
        if (empty($user_results)) {
2749
            return 1;
2750
        } else {
2751
            $position = 1;
2752
            $my_ranking = array();
2753 View Code Duplication
            foreach ($user_results as $result) {
2754
                //print_r($result);
2755
                if (!empty($result['exe_weighting']) && intval(
2756
                        $result['exe_weighting']
2757
                    ) != 0
2758
                ) {
2759
                    $my_ranking[$result['exe_id']] = $result['exe_result'] / $result['exe_weighting'];
2760
                } else {
2761
                    $my_ranking[$result['exe_id']] = 0;
2762
                }
2763
            }
2764
            asort($my_ranking);
2765
            $position = count($my_ranking);
2766
            if (!empty($my_ranking)) {
2767
                foreach ($my_ranking as $exe_id => $ranking) {
2768
                    if ($my_score >= $ranking) {
2769
                        if ($my_score == $ranking) {
2770
                            if ($my_exe_id < $exe_id) {
2771
                                $position--;
2772
                            }
2773
                        } else {
2774
                            $position--;
2775
                        }
2776
                    }
2777
                }
2778
            }
2779
            $return_value = array(
2780
                'position' => $position,
2781
                'count' => count($my_ranking)
2782
            );
2783
2784
            if ($return_string) {
2785
                if (!empty($position) && !empty($my_ranking)) {
2786
                    return $position.'/'.count($my_ranking);
2787
                }
2788
            }
2789
            return $return_value;
2790
        }
2791
    }
2792
2793
    /**
2794
     * Get the best attempt in a exercise (NO Exercises in LPs )
2795
     * @param int $exercise_id
2796
     * @param int $courseId
2797
     * @param int $session_id
2798
     *
2799
     * @return array
2800
     */
2801 View Code Duplication
    public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id)
2802
    {
2803
        $user_results = Event::get_all_exercise_results(
2804
            $exercise_id,
2805
            $courseId,
2806
            $session_id,
2807
            false
2808
        );
2809
2810
        $best_score_data = array();
2811
        $best_score = 0;
2812
        if (!empty($user_results)) {
2813
            foreach ($user_results as $result) {
2814
                if (!empty($result['exe_weighting']) &&
2815
                    intval($result['exe_weighting']) != 0
2816
                ) {
2817
                    $score = $result['exe_result'] / $result['exe_weighting'];
2818
                    if ($score >= $best_score) {
2819
                        $best_score = $score;
2820
                        $best_score_data = $result;
2821
                    }
2822
                }
2823
            }
2824
        }
2825
2826
        return $best_score_data;
2827
    }
2828
2829
    /**
2830
     * Get the best score in a exercise (NO Exercises in LPs )
2831
     * @param int $user_id
2832
     * @param int $exercise_id
2833
     * @param int $courseId
2834
     * @param int $session_id
2835
     *
2836
     * @return array
2837
     */
2838 View Code Duplication
    public static function get_best_attempt_by_user(
2839
        $user_id,
2840
        $exercise_id,
2841
        $courseId,
2842
        $session_id
2843
    )
2844
    {
2845
        $user_results = Event::get_all_exercise_results(
2846
            $exercise_id,
2847
            $courseId,
2848
            $session_id,
2849
            false,
2850
            $user_id
2851
        );
2852
        $best_score_data = array();
2853
        $best_score = 0;
2854
        if (!empty($user_results)) {
2855
            foreach ($user_results as $result) {
2856
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
2857
                    $score = $result['exe_result'] / $result['exe_weighting'];
2858
                    if ($score >= $best_score) {
2859
                        $best_score = $score;
2860
                        $best_score_data = $result;
2861
                    }
2862
                }
2863
            }
2864
        }
2865
2866
        return $best_score_data;
2867
    }
2868
2869
    /**
2870
     * Get average score (NO Exercises in LPs )
2871
     * @param    int    exercise id
2872
     * @param    int $courseId
2873
     * @param    int    session id
2874
     * @return    float    Average score
2875
     */
2876 View Code Duplication
    public static function get_average_score($exercise_id, $courseId, $session_id)
2877
    {
2878
        $user_results = Event::get_all_exercise_results(
2879
            $exercise_id,
2880
            $courseId,
2881
            $session_id
2882
        );
2883
        $avg_score = 0;
2884
        if (!empty($user_results)) {
2885
            foreach ($user_results as $result) {
2886
                if (!empty($result['exe_weighting']) && intval(
2887
                        $result['exe_weighting']
2888
                    ) != 0
2889
                ) {
2890
                    $score = $result['exe_result'] / $result['exe_weighting'];
2891
                    $avg_score += $score;
2892
                }
2893
            }
2894
            $avg_score = float_format($avg_score / count($user_results), 1);
2895
        }
2896
2897
        return $avg_score;
2898
    }
2899
2900
    /**
2901
     * Get average score by score (NO Exercises in LPs )
2902
     * @param    int    exercise id
2903
     * @param    int $courseId
2904
     * @param    int    session id
2905
     * @return    float    Average score
2906
     */
2907 View Code Duplication
    public static function get_average_score_by_course($courseId, $session_id)
2908
    {
2909
        $user_results = Event::get_all_exercise_results_by_course(
2910
            $courseId,
2911
            $session_id,
2912
            false
2913
        );
2914
        //echo $course_code.' - '.$session_id.'<br />';
2915
        $avg_score = 0;
2916
        if (!empty($user_results)) {
2917
            foreach ($user_results as $result) {
2918
                if (!empty($result['exe_weighting']) && intval(
2919
                        $result['exe_weighting']
2920
                    ) != 0
2921
                ) {
2922
                    $score = $result['exe_result'] / $result['exe_weighting'];
2923
                    $avg_score += $score;
2924
                }
2925
            }
2926
            //We asume that all exe_weighting
2927
            $avg_score = ($avg_score / count($user_results));
2928
        }
2929
2930
        return $avg_score;
2931
    }
2932
2933
    /**
2934
     * @param int $user_id
2935
     * @param int $courseId
2936
     * @param int $session_id
2937
     *
2938
     * @return float|int
2939
     */
2940 View Code Duplication
    public static function get_average_score_by_course_by_user(
2941
        $user_id,
2942
        $courseId,
2943
        $session_id
2944
    )
2945
    {
2946
        $user_results = Event::get_all_exercise_results_by_user(
2947
            $user_id,
2948
            $courseId,
2949
            $session_id
2950
        );
2951
        $avg_score = 0;
2952
        if (!empty($user_results)) {
2953
            foreach ($user_results as $result) {
2954
                if (!empty($result['exe_weighting']) && intval(
2955
                        $result['exe_weighting']
2956
                    ) != 0
2957
                ) {
2958
                    $score = $result['exe_result'] / $result['exe_weighting'];
2959
                    $avg_score += $score;
2960
                }
2961
            }
2962
            // We asumme that all exe_weighting
2963
            $avg_score = ($avg_score / count($user_results));
2964
        }
2965
2966
        return $avg_score;
2967
    }
2968
2969
    /**
2970
     * Get average score by score (NO Exercises in LPs )
2971
     * @param    int        exercise id
2972
     * @param    int $courseId
2973
     * @param    int        session id
2974
     * @return    float    Best average score
2975
     */
2976 View Code Duplication
    public static function get_best_average_score_by_exercise(
2977
        $exercise_id,
2978
        $courseId,
2979
        $session_id,
2980
        $user_count
2981
    )
2982
    {
2983
        $user_results = Event::get_best_exercise_results_by_user(
2984
            $exercise_id,
2985
            $courseId,
2986
            $session_id
2987
        );
2988
        $avg_score = 0;
2989
        if (!empty($user_results)) {
2990
            foreach ($user_results as $result) {
2991
                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
2992
                    $score = $result['exe_result'] / $result['exe_weighting'];
2993
                    $avg_score += $score;
2994
                }
2995
            }
2996
            //We asumme that all exe_weighting
2997
            //$avg_score = show_score( $avg_score / count($user_results) , $result['exe_weighting']);
2998
            //$avg_score = ($avg_score / count($user_results));
2999
            if (!empty($user_count)) {
3000
                $avg_score = float_format($avg_score / $user_count, 1) * 100;
3001
            } else {
3002
                $avg_score = 0;
3003
            }
3004
        }
3005
3006
        return $avg_score;
3007
    }
3008
3009
    /**
3010
     * @param string $course_code
3011
     * @param int $session_id
3012
     *
3013
     * @return array
3014
     */
3015
    public static function get_exercises_to_be_taken($course_code, $session_id)
3016
    {
3017
        $course_info = api_get_course_info($course_code);
3018
        $exercises = self::get_all_exercises($course_info, $session_id);
3019
        $result = array();
3020
        $now = time() + 15 * 24 * 60 * 60;
3021
        foreach ($exercises as $exercise_item) {
3022
            if (isset($exercise_item['end_time']) &&
3023
                !empty($exercise_item['end_time']) &&
3024
                api_strtotime($exercise_item['end_time'], 'UTC') < $now
3025
            ) {
3026
                $result[] = $exercise_item;
3027
            }
3028
        }
3029
        return $result;
3030
    }
3031
3032
    /**
3033
     * Get student results (only in completed exercises) stats by question
3034
     * @param    int $question_id
3035
     * @param    int $exercise_id
3036
     * @param    string $course_code
3037
     * @param    int $session_id
3038
     *
3039
     **/
3040
    public static function get_student_stats_by_question(
3041
        $question_id,
3042
        $exercise_id,
3043
        $course_code,
3044
        $session_id
3045
    ) {
3046
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3047
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3048
3049
        $question_id = intval($question_id);
3050
        $exercise_id = intval($exercise_id);
3051
        $course_code = Database::escape_string($course_code);
3052
        $session_id = intval($session_id);
3053
        $courseId = api_get_course_int_id($course_code);
3054
3055
        $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average
3056
    		FROM $track_exercises e
3057
    		INNER JOIN $track_attempt a
3058
    		ON (
3059
    		    a.exe_id = e.exe_id AND
3060
    		    e.c_id = a.c_id AND
3061
    		    e.session_id  = a.session_id
3062
            )
3063
    		WHERE
3064
    		    exe_exo_id 	= $exercise_id AND
3065
                a.c_id = $courseId AND
3066
                e.session_id = $session_id AND
3067
                question_id = $question_id AND
3068
                status = ''
3069
            LIMIT 1";
3070
        $result = Database::query($sql);
3071
        $return = array();
3072
        if ($result) {
3073
            $return = Database::fetch_array($result, 'ASSOC');
3074
        }
3075
3076
        return $return;
3077
    }
3078
3079
    /**
3080
     * Get the correct answer count for a fill blanks question
3081
     *
3082
     * @param int $question_id
3083
     * @param int $exercise_id
3084
     * @return int
3085
     */
3086
    public static function getNumberStudentsFillBlanksAnwserCount(
3087
        $question_id,
3088
        $exercise_id
3089
    )
3090
    {
3091
        $listStudentsId = [];
3092
        $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3093
            api_get_course_id(),
3094
            true
3095
        );
3096
        foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3097
            $listStudentsId[] = $listStudentInfo['user_id'];
3098
        }
3099
3100
        $listFillTheBlankResult = FillBlanks::getFillTheBlankTabResult(
3101
            $exercise_id,
3102
            $question_id,
3103
            $listStudentsId,
3104
            '1970-01-01',
3105
            '3000-01-01'
3106
        );
3107
3108
        $arrayCount = [];
3109
3110
        foreach ($listFillTheBlankResult as $resultCount) {
3111
            foreach ($resultCount as $index => $count) {
3112
                //this is only for declare the array index per answer
3113
                $arrayCount[$index] = 0;
3114
            }
3115
        }
3116
3117
        foreach ($listFillTheBlankResult as $resultCount) {
3118
            foreach ($resultCount as $index => $count) {
3119
                $count = ($count === 0) ? 1 : 0;
3120
                $arrayCount[$index] += $count;
3121
            }
3122
        }
3123
3124
        return $arrayCount;
3125
    }
3126
3127
    /**
3128
     * @param int $question_id
3129
     * @param int $exercise_id
3130
     * @param string $course_code
3131
     * @param int $session_id
3132
     * @param string $questionType
3133
     * @return int
3134
     */
3135
    public static function get_number_students_question_with_answer_count(
3136
        $question_id,
3137
        $exercise_id,
3138
        $course_code,
3139
        $session_id,
3140
        $questionType = ''
3141
    ) {
3142
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3143
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3144
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3145
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3146
        $courseUserSession = Database::get_main_table(
3147
            TABLE_MAIN_SESSION_COURSE_USER
3148
        );
3149
3150
        $question_id = intval($question_id);
3151
        $exercise_id = intval($exercise_id);
3152
        $courseId = api_get_course_int_id($course_code);
3153
        $session_id = intval($session_id);
3154
3155
        if ($questionType == FILL_IN_BLANKS) {
3156
            $listStudentsId = array();
3157
            $listAllStudentInfo = CourseManager::get_student_list_from_course_code(
3158
                api_get_course_id(),
3159
                true
3160
            );
3161
            foreach ($listAllStudentInfo as $i => $listStudentInfo) {
3162
                $listStudentsId[] = $listStudentInfo['user_id'];
3163
            }
3164
3165
            $listFillTheBlankResult = FillBlanks::getFillTheBlankTabResult(
3166
                $exercise_id,
3167
                $question_id,
3168
                $listStudentsId,
3169
                '1970-01-01',
3170
                '3000-01-01'
3171
            );
3172
3173
            return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult);
3174
        }
3175
3176 View Code Duplication
        if (empty($session_id)) {
3177
            $courseCondition = "
3178
            INNER JOIN $courseUser cu
3179
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3180
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3181
        } else {
3182
            $courseCondition = "
3183
            INNER JOIN $courseUserSession cu
3184
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3185
            $courseConditionWhere = " AND cu.status = 0 ";
3186
        }
3187
3188
        $sql = "SELECT DISTINCT exe_user_id
3189
    		FROM $track_exercises e
3190
    		INNER JOIN $track_attempt a
3191
    		ON (
3192
    		    a.exe_id = e.exe_id AND
3193
    		    e.c_id = a.c_id AND
3194
    		    e.session_id  = a.session_id
3195
            )
3196
            INNER JOIN $courseTable c
3197
            ON (c.id = a.c_id)
3198
    		$courseCondition
3199
    		WHERE
3200
    		    exe_exo_id = $exercise_id AND
3201
                a.c_id = $courseId AND
3202
                e.session_id = $session_id AND
3203
                question_id = $question_id AND
3204
                answer <> '0' AND
3205
                e.status = ''
3206
                $courseConditionWhere
3207
            ";
3208
        $result = Database::query($sql);
3209
        $return = 0;
3210
        if ($result) {
3211
            $return = Database::num_rows($result);
3212
        }
3213
        return $return;
3214
    }
3215
3216
    /**
3217
     * @param int $answer_id
3218
     * @param int $question_id
3219
     * @param int $exercise_id
3220
     * @param string $course_code
3221
     * @param int $session_id
3222
     *
3223
     * @return int
3224
     */
3225
    public static function get_number_students_answer_hotspot_count(
3226
        $answer_id,
3227
        $question_id,
3228
        $exercise_id,
3229
        $course_code,
3230
        $session_id
3231
    )
3232
    {
3233
        $track_exercises = Database::get_main_table(
3234
            TABLE_STATISTIC_TRACK_E_EXERCISES
3235
        );
3236
        $track_hotspot = Database::get_main_table(
3237
            TABLE_STATISTIC_TRACK_E_HOTSPOT
3238
        );
3239
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3240
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3241
3242
        $courseUserSession = Database::get_main_table(
3243
            TABLE_MAIN_SESSION_COURSE_USER
3244
        );
3245
3246
        $question_id = intval($question_id);
3247
        $answer_id = intval($answer_id);
3248
        $exercise_id = intval($exercise_id);
3249
        $course_code = Database::escape_string($course_code);
3250
        $session_id = intval($session_id);
3251
3252 View Code Duplication
        if (empty($session_id)) {
3253
            $courseCondition = "
3254
            INNER JOIN $courseUser cu
3255
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3256
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3257
        } else {
3258
            $courseCondition = "
3259
            INNER JOIN $courseUserSession cu
3260
            ON cu.c_id = c.id AND cu.user_id = exe_user_id";
3261
            $courseConditionWhere = " AND cu.status = 0 ";
3262
        }
3263
3264
        $sql = "SELECT DISTINCT exe_user_id
3265
    		FROM $track_exercises e
3266
    		INNER JOIN $track_hotspot a
3267
    		ON (a.hotspot_exe_id = e.exe_id)
3268
    		INNER JOIN $courseTable c
3269
    		ON (hotspot_course_code = c.code)
3270
    		$courseCondition
3271
    		WHERE
3272
    		    exe_exo_id              = $exercise_id AND
3273
                a.hotspot_course_code 	= '$course_code' AND
3274
                e.session_id            = $session_id AND
3275
                hotspot_answer_id       = $answer_id AND
3276
                hotspot_question_id     = $question_id AND
3277
                hotspot_correct         =  1 AND
3278
                e.status                = ''
3279
                $courseConditionWhere
3280
            ";
3281
3282
        $result = Database::query($sql);
3283
        $return = 0;
3284
        if ($result) {
3285
            $return = Database::num_rows($result);
3286
        }
3287
        return $return;
3288
    }
3289
3290
    /**
3291
     * @param int $answer_id
3292
     * @param int $question_id
3293
     * @param int $exercise_id
3294
     * @param string $course_code
3295
     * @param int $session_id
3296
     * @param string $question_type
3297
     * @param string $correct_answer
3298
     * @param string $current_answer
3299
     * @return int
3300
     */
3301
    public static function get_number_students_answer_count(
3302
        $answer_id,
3303
        $question_id,
3304
        $exercise_id,
3305
        $course_code,
3306
        $session_id,
3307
        $question_type = null,
3308
        $correct_answer = null,
3309
        $current_answer = null
3310
    ) {
3311
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3312
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3313
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3314
        $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3315
        $courseUserSession = Database::get_main_table(
3316
            TABLE_MAIN_SESSION_COURSE_USER
3317
        );
3318
3319
        $question_id = intval($question_id);
3320
        $answer_id = intval($answer_id);
3321
        $exercise_id = intval($exercise_id);
3322
        $courseId = api_get_course_int_id($course_code);
3323
        $course_code = Database::escape_string($course_code);
3324
        $session_id = intval($session_id);
3325
3326
        switch ($question_type) {
3327
            case FILL_IN_BLANKS:
3328
                $answer_condition = "";
3329
                $select_condition = " e.exe_id, answer ";
3330
                break;
3331
            case MATCHING:
3332
                //no break
3333
            case MATCHING_DRAGGABLE:
3334
                //no break
3335
            default:
3336
                $answer_condition = " answer = $answer_id AND ";
3337
                $select_condition = " DISTINCT exe_user_id ";
3338
        }
3339
3340 View Code Duplication
        if (empty($session_id)) {
3341
            $courseCondition = "
3342
            INNER JOIN $courseUser cu
3343
            ON cu.c_id = c.id AND cu.user_id  = exe_user_id";
3344
            $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT;
3345
        } else {
3346
            $courseCondition = "
3347
            INNER JOIN $courseUserSession cu
3348
            ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
3349
            $courseConditionWhere = " AND cu.status = 0 ";
3350
        }
3351
3352
        $sql = "SELECT $select_condition
3353
    		FROM $track_exercises e
3354
    		INNER JOIN $track_attempt a
3355
    		ON (
3356
    		    a.exe_id = e.exe_id AND
3357
    		    e.c_id = a.c_id AND
3358
    		    e.session_id  = a.session_id
3359
            )
3360
            INNER JOIN $courseTable c
3361
            ON c.id = a.c_id
3362
    		$courseCondition
3363
    		WHERE
3364
    		    exe_exo_id = $exercise_id AND
3365
                a.c_id = $courseId AND
3366
                e.session_id = $session_id AND
3367
                $answer_condition
3368
                question_id = $question_id AND
3369
                e.status = ''
3370
                $courseConditionWhere
3371
            ";
3372
        $result = Database::query($sql);
3373
        $return = 0;
3374
        if ($result) {
3375
            $good_answers = 0;
3376
            switch ($question_type) {
3377
                case FILL_IN_BLANKS:
3378
                    while ($row = Database::fetch_array($result, 'ASSOC')) {
3379
                        $fill_blank = self::check_fill_in_blanks(
3380
                            $correct_answer,
3381
                            $row['answer'],
3382
                            $current_answer
3383
                        );
3384
                        if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) {
3385
                            $good_answers++;
3386
                        }
3387
                    }
3388
                    return $good_answers;
3389
                    break;
3390
                case MATCHING:
3391
                    //no break
3392
                case MATCHING_DRAGGABLE:
3393
                    //no break
3394
                default:
3395
                    $return = Database::num_rows($result);
3396
            }
3397
        }
3398
3399
        return $return;
3400
    }
3401
3402
    /**
3403
     * @param array $answer
3404
     * @param string $user_answer
3405
     * @return array
3406
     */
3407
    public static function check_fill_in_blanks($answer, $user_answer, $current_answer)
3408
    {
3409
        // the question is encoded like this
3410
        // [A] B [C] D [E] F::10,10,10@1
3411
        // number 1 before the "@" means that is a switchable fill in blank question
3412
        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3413
        // means that is a normal fill blank question
3414
        // first we explode the "::"
3415
        $pre_array = explode('::', $answer);
3416
        // is switchable fill blank or not
3417
        $last = count($pre_array) - 1;
3418
        $is_set_switchable = explode('@', $pre_array[$last]);
3419
        $switchable_answer_set = false;
3420
        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3421
            $switchable_answer_set = true;
3422
        }
3423
        $answer = '';
3424
        for ($k = 0; $k < $last; $k++) {
3425
            $answer .= $pre_array[$k];
3426
        }
3427
        // splits weightings that are joined with a comma
3428
        $answerWeighting = explode(',', $is_set_switchable[0]);
3429
3430
        // we save the answer because it will be modified
3431
        //$temp = $answer;
3432
        $temp = $answer;
3433
3434
        $answer = '';
3435
        $j = 0;
3436
        //initialise answer tags
3437
        $user_tags = $correct_tags = $real_text = array();
3438
        // the loop will stop at the end of the text
3439
        while (1) {
3440
            // quits the loop if there are no more blanks (detect '[')
3441
            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 3495 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...
3442
                // adds the end of the text
3443
                $answer = $temp;
3444
                $real_text[] = $answer;
3445
                break; //no more "blanks", quit the loop
3446
            }
3447
            // adds the piece of text that is before the blank
3448
            //and ends with '[' into a general storage array
3449
            $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 3495 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...
3450
            $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 3495 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...
3451
            //take the string remaining (after the last "[" we found)
3452
            $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 3452 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...
3453
            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3454
            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 3452 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...
3455
                // adds the end of the text
3456
                $answer .= $temp;
3457
                break;
3458
            }
3459
3460
            $str = $user_answer;
3461
3462
            preg_match_all('#\[([^[]*)\]#', $str, $arr);
3463
            $str = str_replace('\r\n', '', $str);
3464
            $choices = $arr[1];
3465
            $choice = [];
3466
            $check = false;
3467
            $i = 0;
3468
            foreach ($choices as $item) {
3469
                if ($current_answer === $item) {
3470
                    $check = true;
3471
                }
3472
                if ($check) {
3473
                    $choice[] = $item;
3474
                    $i++;
3475
                }
3476
                if ($i == 3) {
3477
                    break;
3478
                }
3479
            }
3480
            $tmp = api_strrpos($choice[$j], ' / ');
3481
3482
            if ($tmp !== false) {
3483
                $choice[$j] = api_substr($choice[$j], 0, $tmp);
3484
            }
3485
3486
            $choice[$j] = trim($choice[$j]);
3487
3488
            //Needed to let characters ' and " to work as part of an answer
3489
            $choice[$j] = stripslashes($choice[$j]);
3490
3491
            $user_tags[] = api_strtolower($choice[$j]);
3492
            //put the contents of the [] answer tag into correct_tags[]
3493
            $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 3452 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...
3494
            $j++;
3495
            $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 3495 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...
3496
        }
3497
3498
        $answer = '';
3499
        $real_correct_tags = $correct_tags;
3500
        $chosen_list = array();
3501
        $good_answer = array();
3502
3503
        for ($i = 0; $i < count($real_correct_tags); $i++) {
3504
            if (!$switchable_answer_set) {
3505
                //needed to parse ' and " characters
3506
                $user_tags[$i] = stripslashes($user_tags[$i]);
3507
                if ($correct_tags[$i] == $user_tags[$i]) {
3508
                    $good_answer[$correct_tags[$i]] = 1;
3509
                } elseif (!empty ($user_tags[$i])) {
3510
                    $good_answer[$correct_tags[$i]] = 0;
3511
                } else {
3512
                    $good_answer[$correct_tags[$i]] = 0;
3513
                }
3514
            } else {
3515
                // switchable fill in the blanks
3516
                if (in_array($user_tags[$i], $correct_tags)) {
3517
                    $correct_tags = array_diff($correct_tags, $chosen_list);
3518
                    $good_answer[$correct_tags[$i]] = 1;
3519
                } elseif (!empty ($user_tags[$i])) {
3520
                    $good_answer[$correct_tags[$i]] = 0;
3521
                } else {
3522
                    $good_answer[$correct_tags[$i]] = 0;
3523
                }
3524
            }
3525
            // adds the correct word, followed by ] to close the blank
3526
            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3527
            if (isset ($real_text[$i + 1])) {
3528
                $answer .= $real_text[$i + 1];
3529
            }
3530
        }
3531
3532
        return $good_answer;
3533
    }
3534
3535
    /**
3536
     * @param int $exercise_id
3537
     * @param string $course_code
3538
     * @param int $session_id
3539
     * @return int
3540
     */
3541 View Code Duplication
    public static function get_number_students_finish_exercise(
3542
        $exercise_id,
3543
        $course_code,
3544
        $session_id
3545
    ) {
3546
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3547
        $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3548
3549
        $exercise_id = intval($exercise_id);
3550
        $course_code = Database::escape_string($course_code);
3551
        $session_id = intval($session_id);
3552
3553
        $sql = "SELECT DISTINCT exe_user_id
3554
                FROM $track_exercises e
3555
                INNER JOIN $track_attempt a 
3556
                ON (a.exe_id = e.exe_id)
3557
                WHERE
3558
                    exe_exo_id 	 = $exercise_id AND
3559
                    course_code  = '$course_code' AND
3560
                    e.session_id = $session_id AND
3561
                    status = ''";
3562
        $result = Database::query($sql);
3563
        $return = 0;
3564
        if ($result) {
3565
            $return = Database::num_rows($result);
3566
3567
        }
3568
        return $return;
3569
    }
3570
3571
    /**
3572
     * @param string $in_name is the name and the id of the <select>
3573
     * @param string $in_default default value for option
3574
     * @param string $in_onchange
3575
     * @return string the html code of the <select>
3576
     */
3577
    public static function displayGroupMenu($in_name, $in_default, $in_onchange = "")
3578
    {
3579
        // check the default value of option
3580
        $tabSelected = array($in_default => " selected='selected' ");
3581
        $res = "";
3582
        $res .= "<select name='$in_name' id='$in_name' onchange='".$in_onchange."' >";
3583
        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
3584
                'AllGroups'
3585
            )." --</option>";
3586
        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
3587
                'NotInAGroup'
3588
            )." -</option>";
3589
        $tabGroups = GroupManager::get_group_list();
3590
        $currentCatId = 0;
3591
        for ($i = 0; $i < count($tabGroups); $i++) {
3592
            $tabCategory = GroupManager::get_category_from_group(
3593
                $tabGroups[$i]['iid']
3594
            );
3595
            if ($tabCategory["id"] != $currentCatId) {
3596
                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
3597
                $currentCatId = $tabCategory["id"];
3598
            }
3599
            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".$tabGroups[$i]["id"]."'>".$tabGroups[$i]["name"]."</option>";
3600
        }
3601
        $res .= "</select>";
3602
        return $res;
3603
    }
3604
3605
    /**
3606
     * @param int $exe_id
3607
     */
3608
    public static function create_chat_exercise_session($exe_id)
3609
    {
3610
        if (!isset($_SESSION['current_exercises'])) {
3611
            $_SESSION['current_exercises'] = array();
3612
        }
3613
        $_SESSION['current_exercises'][$exe_id] = true;
3614
    }
3615
3616
    /**
3617
     * @param int $exe_id
3618
     */
3619
    public static function delete_chat_exercise_session($exe_id)
3620
    {
3621
        if (isset($_SESSION['current_exercises'])) {
3622
            $_SESSION['current_exercises'][$exe_id] = false;
3623
        }
3624
    }
3625
3626
    /**
3627
     * Display the exercise results
3628
     * @param Exercise $objExercise
3629
     * @param int $exe_id
3630
     * @param bool $save_user_result save users results (true) or just show the results (false)
3631
     * @param string $remainingMessage
3632
     */
3633
    public static function display_question_list_by_attempt(
3634
        $objExercise,
3635
        $exe_id,
3636
        $save_user_result = false,
3637
        $remainingMessage = ''
3638
    ) {
3639
        $origin = api_get_origin();
3640
3641
        // Getting attempt info
3642
        $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id(
3643
            $exe_id
3644
        );
3645
3646
        // Getting question list
3647
        $question_list = array();
3648
        if (!empty($exercise_stat_info['data_tracking'])) {
3649
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
3650
        } else {
3651
            // Try getting the question list only if save result is off
3652
            if ($save_user_result == false) {
3653
                $question_list = $objExercise->get_validated_question_list();
3654
            }
3655
            if ($objExercise->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
3656
                $question_list = $objExercise->get_validated_question_list();
3657
            }
3658
        }
3659
3660
        $counter = 1;
3661
        $total_score = $total_weight = 0;
3662
        $exercise_content = null;
3663
3664
        // Hide results
3665
        $show_results = false;
3666
        $show_only_score = false;
3667
3668
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
3669
            $show_results = true;
3670
        }
3671
3672
        if (in_array(
3673
            $objExercise->results_disabled,
3674
            array(
3675
                RESULT_DISABLE_SHOW_SCORE_ONLY,
3676
                RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES
3677
            )
3678
        )
3679
        ) {
3680
            $show_only_score = true;
3681
        }
3682
3683
        // Not display expected answer, but score, and feedback
3684
        $show_all_but_expected_answer = false;
3685
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
3686
            $objExercise->feedback_type == EXERCISE_FEEDBACK_TYPE_END
3687
        ) {
3688
            $show_all_but_expected_answer = true;
3689
            $show_results = true;
3690
            $show_only_score = false;
3691
        }
3692
3693
        $showTotalScoreAndUserChoicesInLastAttempt = true;
3694
3695
        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
3696
            $show_only_score = true;
3697
            $show_results = true;
3698
            if ($objExercise->attempts > 0) {
3699
                $attempts = Event::getExerciseResultsByUser(
3700
                    api_get_user_id(),
3701
                    $objExercise->id,
3702
                    api_get_course_int_id(),
3703
                    api_get_session_id(),
3704
                    $exercise_stat_info['orig_lp_id'],
3705
                    $exercise_stat_info['orig_lp_item_id'],
3706
                    'desc'
3707
                );
3708
3709
                if ($attempts) {
3710
                    $numberAttempts = count($attempts);
3711
                } else {
3712
                    $numberAttempts = 0;
3713
                }
3714
3715
                if ($save_user_result) {
3716
                    $numberAttempts++;
3717
                }
3718
                if ($numberAttempts >= $objExercise->attempts) {
3719
                    $show_results = true;
3720
                    $show_only_score = false;
3721
                    $showTotalScoreAndUserChoicesInLastAttempt = true;
3722
                } else {
3723
                    $showTotalScoreAndUserChoicesInLastAttempt = false;
3724
                }
3725
            }
3726
        }
3727
3728
        if ($show_results || $show_only_score) {
3729
            if (isset($exercise_stat_info['exe_user_id'])) {
3730
                $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
3731
                if ($user_info) {
3732
3733
                    // Shows exercise header
3734
                    echo $objExercise->show_exercise_result_header(
3735
                        $user_info,
3736
                        api_convert_and_format_date(
3737
                            $exercise_stat_info['start_date'],
3738
                            DATE_TIME_FORMAT_LONG
3739
                        ),
3740
                        $exercise_stat_info['duration'],
3741
                        $exercise_stat_info['user_ip']
3742
                    );
3743
                }
3744
            }
3745
        }
3746
3747
        // Display text when test is finished #4074 and for LP #4227
3748
        $end_of_message = $objExercise->selectTextWhenFinished();
3749
        if (!empty($end_of_message)) {
3750
            echo Display::return_message($end_of_message, 'normal', false);
3751
            echo "<div class='clear'>&nbsp;</div>";
3752
        }
3753
3754
        $question_list_answers = array();
3755
        $media_list = array();
3756
        $category_list = array();
3757
        $loadChoiceFromSession = false;
3758
        $fromDatabase = true;
3759
        $exerciseResult = null;
3760
        $exerciseResultCoordinates = null;
3761
        $delineationResults = null;
3762
        if ($objExercise->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
3763
            $loadChoiceFromSession = true;
3764
            $fromDatabase = false;
3765
            $exerciseResult = Session::read('exerciseResult');
3766
            $exerciseResultCoordinates = Session::read('exerciseResultCoordinates');
3767
            $delineationResults = Session::read('hotspot_delineation_result');
3768
            $delineationResults = isset($delineationResults[$objExercise->id]) ? $delineationResults[$objExercise->id] : null;
3769
        }
3770
3771
        $countPendingQuestions = 0;
3772
        // Loop over all question to show results for each of them, one by one
3773
        if (!empty($question_list)) {
3774
            foreach ($question_list as $questionId) {
3775
                // creates a temporary Question object
3776
                $objQuestionTmp = Question::read($questionId);
3777
3778
                // This variable came from exercise_submit_modal.php
3779
                ob_start();
3780
                $choice = null;
3781
                $delineationChoice = null;
3782
                if ($loadChoiceFromSession) {
3783
                    $choice = isset($exerciseResult[$questionId]) ? $exerciseResult[$questionId] : null;
3784
                    $delineationChoice = isset($delineationResults[$questionId]) ? $delineationResults[$questionId] : null;
3785
                }
3786
3787
                // We're inside *one* question. Go through each possible answer for this question
3788
                $result = $objExercise->manage_answer(
3789
                    $exe_id,
3790
                    $questionId,
3791
                    $choice,
3792
                    'exercise_result',
3793
                    $exerciseResultCoordinates,
3794
                    $save_user_result,
3795
                    $fromDatabase,
3796
                    $show_results,
3797
                    $objExercise->selectPropagateNeg(),
3798
                    $delineationChoice,
3799
                    $showTotalScoreAndUserChoicesInLastAttempt
3800
                );
3801
3802
                if (empty($result)) {
3803
                    continue;
3804
                }
3805
3806
                $total_score += $result['score'];
3807
                $total_weight += $result['weight'];
3808
3809
                $question_list_answers[] = array(
3810
                    'question' => $result['open_question'],
3811
                    'answer' => $result['open_answer'],
3812
                    'answer_type' => $result['answer_type']
3813
                );
3814
3815
                $my_total_score = $result['score'];
3816
                $my_total_weight = $result['weight'];
3817
3818
                // Category report
3819
                $category_was_added_for_this_test = false;
3820
3821 View Code Duplication
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
3822
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
3823
                        $category_list[$objQuestionTmp->category]['score'] = 0;
3824
                    }
3825
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
3826
                        $category_list[$objQuestionTmp->category]['total'] = 0;
3827
                    }
3828
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
3829
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
3830
                    $category_was_added_for_this_test = true;
3831
                }
3832
3833 View Code Duplication
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
3834
                    foreach ($objQuestionTmp->category_list as $category_id) {
3835
                        $category_list[$category_id]['score'] += $my_total_score;
3836
                        $category_list[$category_id]['total'] += $my_total_weight;
3837
                        $category_was_added_for_this_test = true;
3838
                    }
3839
                }
3840
3841
                // No category for this question!
3842
                if ($category_was_added_for_this_test == false) {
3843
                    if (!isset($category_list['none']['score'])) {
3844
                        $category_list['none']['score'] = 0;
3845
                    }
3846
                    if (!isset($category_list['none']['total'])) {
3847
                        $category_list['none']['total'] = 0;
3848
                    }
3849
3850
                    $category_list['none']['score'] += $my_total_score;
3851
                    $category_list['none']['total'] += $my_total_weight;
3852
                }
3853
3854
                if ($objExercise->selectPropagateNeg() == 0 && $my_total_score < 0
3855
                ) {
3856
                    $my_total_score = 0;
3857
                }
3858
3859
                $comnt = null;
3860 View Code Duplication
                if ($show_results) {
3861
                    $comnt = Event::get_comments($exe_id, $questionId);
3862
                    if (!empty($comnt)) {
3863
                        echo '<b>'.get_lang('Feedback').'</b>';
3864
                        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...
3865
                    }
3866
                }
3867
3868
                if ($show_results) {
3869
                    $score = array(
3870
                        'result' => self::show_score(
3871
                            $my_total_score,
3872
                            $my_total_weight,
3873
                            false,
3874
                            true
3875
                        ),
3876
                        'pass' => $my_total_score >= $my_total_weight ? true : false,
3877
                        'score' => $my_total_score,
3878
                        'weight' => $my_total_weight,
3879
                        'comments' => $comnt,
3880
                    );
3881
                } else {
3882
                    $score = array();
3883
                }
3884
3885 View Code Duplication
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION])) {
3886
                    $check = $objQuestionTmp->isQuestionWaitingReview($score);
3887
                    if ($check === false) {
3888
                        $countPendingQuestions++;
3889
                    }
3890
                }
3891
3892
                $contents = ob_get_clean();
3893
                $question_content = '';
3894
                if ($show_results) {
3895
                    $question_content = '<div class="question_row_answer">';
3896
                    // Shows question title an description
3897
                    $question_content .= $objQuestionTmp->return_header(
3898
                        null,
3899
                        $counter,
3900
                        $score
3901
                    );
3902
                }
3903
                $counter++;
3904
                $question_content .= $contents;
3905
                if ($show_results) {
3906
                    $question_content .= '</div>';
3907
                }
3908
3909
                $exercise_content .= $question_content;
3910
            } // end foreach() block that loops over all questions
3911
        }
3912
3913
        $total_score_text = null;
3914
        if ($show_results || $show_only_score) {
3915
            $total_score_text .= '<div class="question_row_score">';
3916
            $total_score_text .= self::getTotalScoreRibbon(
3917
                $objExercise,
3918
                $total_score,
3919
                $total_weight,
3920
                true,
3921
                $countPendingQuestions
3922
            );
3923
            $total_score_text .= '</div>';
3924
        }
3925
3926 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
3927
            // Adding total
3928
            $category_list['total'] = array(
3929
                'score' => $total_score,
3930
                'total' => $total_weight
3931
            );
3932
            echo TestCategory::get_stats_table_by_attempt(
3933
                $objExercise->id,
3934
                $category_list
3935
            );
3936
        }
3937
3938
        if ($show_all_but_expected_answer) {
3939
            $exercise_content .= "<div class='normal-message'>".get_lang(
3940
                    "ExerciseWithFeedbackWithoutCorrectionComment"
3941
                )."</div>";
3942
        }
3943
        // Remove audio auto play from questions on results page - refs BT#7939
3944
        $exercise_content = preg_replace(
3945
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
3946
            '',
3947
            $exercise_content
3948
        );
3949
3950
        echo $total_score_text;
3951
        echo $exercise_content;
3952
3953
        if (!$show_only_score) {
3954
            echo $total_score_text;
3955
        }
3956
3957
        if (!empty($remainingMessage)) {
3958
            echo Display::return_message($remainingMessage, 'normal', false);
3959
        }
3960
3961
        if ($save_user_result) {
3962
            // Tracking of results
3963
            if ($exercise_stat_info) {
3964
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
3965
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
3966
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
3967
3968
                if (api_is_allowed_to_session_edit()) {
3969
                    Event::update_event_exercise(
3970
                        $exercise_stat_info['exe_id'],
3971
                        $objExercise->selectId(),
3972
                        $total_score,
3973
                        $total_weight,
3974
                        api_get_session_id(),
3975
                        $learnpath_id,
3976
                        $learnpath_item_id,
3977
                        $learnpath_item_view_id,
3978
                        $exercise_stat_info['exe_duration'],
3979
                        $question_list,
3980
                        '',
3981
                        array()
3982
                    );
3983
                }
3984
            }
3985
3986
            // Send notification at the end
3987
            if (!api_is_allowed_to_edit(null, true) &&
3988
                !api_is_excluded_user_type()
3989
            ) {
3990
                $objExercise->send_mail_notification_for_exam(
3991
                    'end',
3992
                    $question_list_answers,
3993
                    $origin,
3994
                    $exe_id,
3995
                    $total_score,
3996
                    $total_weight
3997
                );
3998
            }
3999
        }
4000
    }
4001
4002
    /**
4003
     * @param string $class
4004
     * @param string $scoreLabel
4005
     * @param string $result
4006
     *
4007
     * @return string
4008
     */
4009
    public static function getQuestionRibbon($class, $scoreLabel, $result)
4010
    {
4011
        return '<div class="ribbon">
4012
                    <div class="rib rib-'.$class.'">
4013
                        <h3>'.$scoreLabel.'</h3>
4014
                    </div> 
4015
                    <h4>'.get_lang('Score').': '.$result.'</h4>
4016
                </div>'
4017
        ;
4018
    }
4019
4020
    /**
4021
     * @param Exercise $objExercise
4022
     * @param float $score
4023
     * @param float $weight
4024
     * @param bool $checkPassPercentage
4025
     * @param int $countPendingQuestions
4026
     * @return string
4027
     */
4028
    public static function getTotalScoreRibbon(
4029
        $objExercise,
4030
        $score,
4031
        $weight,
4032
        $checkPassPercentage = false,
4033
        $countPendingQuestions = 0
4034
    ) {
4035
        $passPercentage = $objExercise->selectPassPercentage();
4036
        $ribbon = '<div class="title-score">';
4037
        if ($checkPassPercentage) {
4038
            $isSuccess = self::isSuccessExerciseResult(
4039
                $score,
4040
                $weight,
4041
                $passPercentage
4042
            );
4043
            // Color the final test score if pass_percentage activated
4044
            $class = '';
4045
            if (self::isPassPercentageEnabled($passPercentage)) {
4046
                if ($isSuccess) {
4047
                    $class = ' ribbon-total-success';
4048
                } else {
4049
                    $class = ' ribbon-total-error';
4050
                }
4051
            }
4052
            $ribbon .= '<div class="total '.$class.'">';
4053
        } else {
4054
            $ribbon .= '<div class="total">';
4055
        }
4056
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
4057
        $ribbon .= self::show_score($score, $weight, false, true);
4058
        $ribbon .= '</h3>';
4059
        $ribbon .= '</div>';
4060
        if ($checkPassPercentage) {
4061
            $ribbon .= self::showSuccessMessage(
4062
                $score,
4063
                $weight,
4064
                $passPercentage
4065
            );
4066
        }
4067
        $ribbon .= '</div>';
4068
4069
        if (!empty($countPendingQuestions)) {
4070
            $ribbon .= '<br />';
4071
            $ribbon .= Display::return_message(
4072
                sprintf(
4073
                    get_lang('TempScoreXQuestionsNotCorrectedYet'),
4074
                    $countPendingQuestions
4075
                ),
4076
                'warning'
4077
            );
4078
        }
4079
4080
        return $ribbon;
4081
    }
4082
4083
    /**
4084
     * @param int $countLetter
4085
     * @return mixed
4086
     */
4087
    public static function detectInputAppropriateClass($countLetter)
4088
    {
4089
        $limits = array(
4090
            0 => 'input-mini',
4091
            10 => 'input-mini',
4092
            15 => 'input-medium',
4093
            20 => 'input-xlarge',
4094
            40 => 'input-xlarge',
4095
            60 => 'input-xxlarge',
4096
            100 => 'input-xxlarge',
4097
            200 => 'input-xxlarge',
4098
        );
4099
4100
        foreach ($limits as $size => $item) {
4101
            if ($countLetter <= $size) {
4102
                return $item;
4103
            }
4104
        }
4105
        return $limits[0];
4106
    }
4107
4108
    /**
4109
     * @param int $senderId
4110
     * @param array $course_info
4111
     * @param string $test
4112
     * @param string $url
4113
     *
4114
     * @return string
4115
     */
4116
    public static function getEmailNotification($senderId, $course_info, $test, $url)
4117
    {
4118
        $teacher_info = api_get_user_info($senderId);
4119
        $from_name = api_get_person_name(
4120
            $teacher_info['firstname'],
4121
            $teacher_info['lastname'],
4122
            null,
4123
            PERSON_NAME_EMAIL_ADDRESS
4124
        );
4125
4126
        $message = '<p>'.get_lang('DearStudentEmailIntroduction').'</p><p>'.get_lang('AttemptVCC');
4127
        $message .= '<h3>'.get_lang('CourseName').'</h3><p>'.Security::remove_XSS($course_info['name']).'';
4128
        $message .= '<h3>'.get_lang('Exercise').'</h3><p>'.Security::remove_XSS($test);
4129
        $message .= '<p>'.get_lang('ClickLinkToViewComment').' <br /><a href="#url#">#url#</a><br />';
4130
        $message .= '<p>'.get_lang('Regards').'</p>';
4131
        $message .= $from_name;
4132
        $message = str_replace("#test#", Security::remove_XSS($test), $message);
4133
        $message = str_replace("#url#", $url, $message);
4134
4135
        return $message;
4136
    }
4137
4138
    /**
4139
     * @return string
4140
     */
4141
    public static function getNotCorrectedYetText()
4142
    {
4143
        return Display::return_message(get_lang('notCorrectedYet'), 'warning');
4144
    }
4145
4146
    /**
4147
     * @param string $message
4148
     * @return string
4149
     */
4150
    public static function getFeedbackText($message)
4151
    {
4152
        // Old style
4153
        //return '<div id="question_feedback">'.$message.'</div>';
4154
        return Display::return_message($message, 'warning', false);
4155
    }
4156
}
4157