Test Setup Failed
Push — master ( 4e700f...c7183e )
by Julito
63:12
created

ExerciseLib::get_time_control_key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 3
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
6
/**
7
 * Class ExerciseLib
8
 * shows a question and its answers
9
 * @author Olivier Brouckaert <[email protected]>
10
 * @author Hubert Borderiou 2011-10-21
11
 * @author ivantcholakov2009-07-20
12
 *
13
 */
14
class ExerciseLib
15
{
16
    /**
17
     * Shows a question
18
     *
19
     * @param int $questionId $questionId question id
20
     * @param bool $only_questions if true only show the questions, no exercise title
21
     * @param bool $origin i.e = learnpath
22
     * @param string $current_item current item from the list of questions
23
     * @param bool $show_title
24
     * @param bool $freeze
25
     * @param array $user_choice
26
     * @param bool $show_comment
27
     * @param null $exercise_feedback
28
     * @param bool $show_answers
29
     * @return bool|int
30
     */
31
    public static function showQuestion(
32
        $questionId,
33
        $only_questions = false,
34
        $origin = false,
35
        $current_item = '',
36
        $show_title = true,
37
        $freeze = false,
38
        $user_choice = array(),
39
        $show_comment = false,
40
        $exercise_feedback = null,
41
        $show_answers = false
42
    ) {
43
        $course_id = api_get_course_int_id();
44
        $course = api_get_course_info_by_id($course_id);
45
        // Change false to true in the following line to enable answer hinting
46
        $debug_mark_answer = $show_answers;
47
48
        // Reads question information
49
        if (!$objQuestionTmp = Question::read($questionId)) {
50
            // Question not found
51
            return false;
52
        }
53
54
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
55
            $show_comment = false;
56
        }
57
58
        $answerType = $objQuestionTmp->selectType();
59
        $pictureName = $objQuestionTmp->getPictureFilename();
60
        $s = '';
61
62
        if ($answerType != HOT_SPOT &&
63
            $answerType != HOT_SPOT_DELINEATION &&
64
            $answerType != ANNOTATION
65
        ) {
66
            // Question is not a hotspot
67
            if (!$only_questions) {
68
                $questionDescription = $objQuestionTmp->selectDescription();
69
                if ($show_title) {
70
                    TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
71
                    $titleToDisplay = null;
72
                    if ($answerType == READING_COMPREHENSION) {
73
                        // In READING_COMPREHENSION, the title of the question
74
                        // contains the question itself, which can only be
75
                        // shown at the end of the given time, so hide for now
76
                        $titleToDisplay = Display::div(
77
                            $current_item.'. '.get_lang('ReadingComprehension'),
78
                            ['class' => 'question_title']
79
                        );
80
                    } else {
81
                        $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
82
                    }
83
                    echo $titleToDisplay;
84
                }
85
                if (!empty($questionDescription) && $answerType != READING_COMPREHENSION) {
86
                    echo Display::div(
87
                        $questionDescription,
88
                        array('class' => 'question_description')
89
                    );
90
                }
91
            }
92
93
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) &&
94
                $freeze
95
            ) {
96
                return '';
97
            }
98
99
            echo '<div class="question_options">';
100
            // construction of the Answer object (also gets all answers details)
101
            $objAnswerTmp = new Answer($questionId);
102
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
103
            $quiz_question_options = Question::readQuestionOption(
104
                $questionId,
105
                $course_id
106
            );
107
108
            // For "matching" type here, we need something a little bit special
109
            // because the match between the suggestions and the answers cannot be
110
            // done easily (suggestions and answers are in the same table), so we
111
            // have to go through answers first (elems with "correct" value to 0).
112
            $select_items = array();
113
            //This will contain the number of answers on the left side. We call them
114
            // suggestions here, for the sake of comprehensions, while the ones
115
            // on the right side are called answers
116
            $num_suggestions = 0;
117
            if (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
118
                if ($answerType == DRAGGABLE) {
119
                    $isVertical = $objQuestionTmp->extra == 'v';
120
                    $s .= '
121
                        <div class="col-md-12 ui-widget ui-helper-clearfix">
122
                            <div class="clearfix">
123
                            <ul class="exercise-draggable-answer '.($isVertical ? '' : 'list-inline').'"
124
                                id="question-'.$questionId.'" data-question="'.$questionId.'">
125
                    ';
126
                } else {
127
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">
128
                           <table class="data_table">';
129
                }
130
131
                // Iterate through answers
132
                $x = 1;
133
                //mark letters for each answer
134
                $letter = 'A';
135
                $answer_matching = array();
136
                $cpt1 = array();
137
                for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
138
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
139
                    $numAnswer = $objAnswerTmp->selectAutoId($answerId);
140
                    if ($answerCorrect == 0) {
141
                        // options (A, B, C, ...) that will be put into the list-box
142
                        // have the "correct" field set to 0 because they are answer
143
                        $cpt1[$x] = $letter;
144
                        $answer_matching[$x] = $objAnswerTmp->selectAnswerByAutoId(
145
                            $numAnswer
146
                        );
147
                        $x++;
148
                        $letter++;
149
                    }
150
                }
151
152
                $i = 1;
153
                $select_items[0]['id'] = 0;
154
                $select_items[0]['letter'] = '--';
155
                $select_items[0]['answer'] = '';
156
                foreach ($answer_matching as $id => $value) {
157
                    $select_items[$i]['id'] = $value['id_auto'];
158
                    $select_items[$i]['letter'] = $cpt1[$id];
159
                    $select_items[$i]['answer'] = $value['answer'];
160
                    $i++;
161
                }
162
163
                $user_choice_array_position = array();
164
                if (!empty($user_choice)) {
165
                    foreach ($user_choice as $item) {
166
                        $user_choice_array_position[$item['position']] = $item['answer'];
167
                    }
168
                }
169
                $num_suggestions = ($nbrAnswers - $x) + 1;
170
            } elseif ($answerType == FREE_ANSWER) {
171
                $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
172
                $form = new FormValidator('free_choice_'.$questionId);
173
                $config = array(
174
                    'ToolbarSet' => 'TestFreeAnswer'
175
                );
176
                $form->addHtmlEditor(
177
                    "choice[".$questionId."]",
178
                    null,
179
                    false,
180
                    false,
181
                    $config
182
                );
183
                $form->setDefaults(
184
                    array("choice[".$questionId."]" => $fck_content)
185
                );
186
                $s .= $form->returnForm();
187
            } elseif ($answerType == ORAL_EXPRESSION) {
188
                // Add nanog
189
                if (api_get_setting('enable_record_audio') == 'true') {
190
                    //@todo pass this as a parameter
191
                    global $exercise_stat_info, $exerciseId, $exe_id;
192
193
                    if (!empty($exercise_stat_info)) {
194
                        $objQuestionTmp->initFile(
195
                            api_get_session_id(),
196
                            api_get_user_id(),
197
                            $exercise_stat_info['exe_exo_id'],
198
                            $exercise_stat_info['exe_id']
199
                        );
200
                    } else {
201
                        $objQuestionTmp->initFile(
202
                            api_get_session_id(),
203
                            api_get_user_id(),
204
                            $exerciseId,
205
                            'temp_exe'
206
                        );
207
                    }
208
209
                    echo $objQuestionTmp->returnRecorder();
210
                }
211
212
                $form = new FormValidator('free_choice_'.$questionId);
213
                $config = array(
214
                    'ToolbarSet' => 'TestFreeAnswer'
215
                );
216
                $form->addHtmlEditor(
217
                    "choice[".$questionId."]",
218
                    null,
219
                    false,
220
                    false,
221
                    $config
222
                );
223
                $s .= $form->returnForm();
224
            }
225
226
            // Now navigate through the possible answers, using the max number of
227
            // answers for the question as a limiter
228
            $lines_count = 1; // a counter for matching-type answers
229
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
230
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
231
            ) {
232
                $header = Display::tag('th', get_lang('Options'));
233
                foreach ($objQuestionTmp->options as $item) {
234
                    if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
235
                        if (in_array($item, $objQuestionTmp->options)) {
0 ignored issues
show
Bug introduced by
The property options does not seem to exist in Question.

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

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

Loading history...
236
                            $header .= Display::tag('th', get_lang($item));
237
                        } else {
238
                            $header .= Display::tag('th', $item);
239
                        }
240
                    } else {
241
                        $header .= Display::tag('th', $item);
242
                    }
243
                }
244
                if ($show_comment) {
245
                    $header .= Display::tag('th', get_lang('Feedback'));
246
                }
247
                $s .= '<table class="table table-hover table-striped">';
248
                $s .= Display::tag(
249
                    'tr',
250
                    $header,
251
                    array('style' => 'text-align:left;')
252
                );
253
            }
254
255
            if ($show_comment) {
256
                if (in_array(
257
                    $answerType,
258
                    array(
259
                        MULTIPLE_ANSWER,
260
                        MULTIPLE_ANSWER_COMBINATION,
261
                        UNIQUE_ANSWER,
262
                        UNIQUE_ANSWER_IMAGE,
263
                        UNIQUE_ANSWER_NO_OPTION,
264
                        GLOBAL_MULTIPLE_ANSWER,
265
                    )
266
                )) {
267
                    $header = Display::tag('th', get_lang('Options'));
268
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
269
                        $header .= Display::tag('th', get_lang('Feedback'));
270
                    }
271
                    $s .= '<table class="table table-hover table-striped">';
272
                    $s .= Display::tag(
273
                        'tr',
274
                        $header,
275
                        array('style' => 'text-align:left;')
276
                    );
277
                }
278
            }
279
280
            $matching_correct_answer = 0;
281
            $user_choice_array = array();
282
            if (!empty($user_choice)) {
283
                foreach ($user_choice as $item) {
284
                    $user_choice_array[] = $item['answer'];
285
                }
286
            }
287
288
            $hidingClass = '';
289
            if ($answerType == READING_COMPREHENSION) {
290
                $objQuestionTmp->processText(
291
                    $objQuestionTmp->selectDescription()
292
                );
293
                $hidingClass = 'hide-reading-answers';
294
            }
295
            if ($answerType == READING_COMPREHENSION) {
296
                $s .= Display::div(
297
                    $objQuestionTmp->selectTitle(),
298
                    ['class' => 'question_title '.$hidingClass]
299
                );
300
            }
301
302
            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
303
                $answer = $objAnswerTmp->selectAnswer($answerId);
304
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
305
                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
306
                $comment = $objAnswerTmp->selectComment($answerId);
307
                $attributes = array();
308
309
                switch ($answerType) {
310
                    case UNIQUE_ANSWER:
311
                        //no break
312
                    case UNIQUE_ANSWER_NO_OPTION:
313
                        //no break
314
                    case UNIQUE_ANSWER_IMAGE:
315
                        //no break
316
                    case READING_COMPREHENSION:
317
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
318
                        if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
319
                            $attributes = array(
320
                                'id' => $input_id,
321
                                'checked' => 1,
322
                                'selected' => 1
323
                            );
324
                        } else {
325
                            $attributes = array('id' => $input_id);
326
                        }
327
328
                        if ($debug_mark_answer) {
329
                            if ($answerCorrect) {
330
                                $attributes['checked'] = 1;
331
                                $attributes['selected'] = 1;
332
                            }
333
                        }
334
335
                        if ($show_comment) {
336
                            $s .= '<tr><td>';
337
                        }
338
339
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
340
                            if ($show_comment) {
341
                                if (empty($comment)) {
342
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
343
                                        . 'class="exercise-unique-answer-image" style="text-align: center">';
344
                                } else {
345
                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
346
                                        . 'class="exercise-unique-answer-image col-xs-6 col-sm-12" style="text-align: center">';
347
                                }
348
                            } else {
349
                                $s .= '<div id="answer'.$questionId.$numAnswer.'" '
350
                                    . 'class="exercise-unique-answer-image col-xs-6 col-md-3" style="text-align: center">';
351
                            }
352
                        }
353
354
                        $answer = Security::remove_XSS($answer, STUDENT);
355
                        $s .= Display::input(
356
                            'hidden',
357
                            'choice2['.$questionId.']',
358
                            '0'
359
                        );
360
361
                        $answer_input = null;
362
363
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
364
                            $attributes['style'] = 'display: none;';
365
                            $answer = '<div class="thumbnail">'.$answer.'</div>';
366
                        }
367
368
                        $answer_input .= '<label class="radio '.$hidingClass.'">';
369
                        $answer_input .= Display::input(
370
                            'radio',
371
                            'choice['.$questionId.']',
372
                            $numAnswer,
373
                            $attributes
374
                        );
375
                        $answer_input .= $answer;
376
                        $answer_input .= '</label>';
377
378
                        if ($answerType == UNIQUE_ANSWER_IMAGE) {
379
                            $answer_input .= "</div>";
380
                        }
381
382
                        if ($show_comment) {
383
                            $s .= $answer_input;
384
                            $s .= '</td>';
385
                            $s .= '<td>';
386
                            $s .= $comment;
387
                            $s .= '</td>';
388
                            $s .= '</tr>';
389
                        } else {
390
                            $s .= $answer_input;
391
                        }
392
                        break;
393
                    case MULTIPLE_ANSWER:
394
                        //no break
395
                    case MULTIPLE_ANSWER_TRUE_FALSE:
396
                        //no break
397
                    case GLOBAL_MULTIPLE_ANSWER:
398
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
399
                        $answer = Security::remove_XSS($answer, STUDENT);
400
401
                        if (in_array($numAnswer, $user_choice_array)) {
402
                            $attributes = array(
403
                                'id' => $input_id,
404
                                'checked' => 1,
405
                                'selected' => 1
406
                            );
407
                        } else {
408
                            $attributes = array('id' => $input_id);
409
                        }
410
411
                        if ($debug_mark_answer) {
412
                            if ($answerCorrect) {
413
                                $attributes['checked'] = 1;
414
                                $attributes['selected'] = 1;
415
                            }
416
                        }
417
418
                        if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
419
                            $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
420
421
                            $answer_input = '<label class="checkbox">';
422
                            $answer_input .= Display::input(
423
                                'checkbox',
424
                                'choice['.$questionId.']['.$numAnswer.']',
425
                                $numAnswer,
426
                                $attributes
427
                            );
428
                            $answer_input .= $answer;
429
                            $answer_input .= '</label>';
430
431
                            if ($show_comment) {
432
                                $s .= '<tr><td>';
433
                                $s .= $answer_input;
434
                                $s .= '</td>';
435
                                $s .= '<td>';
436
                                $s .= $comment;
437
                                $s .= '</td>';
438
                                $s .= '</tr>';
439
                            } else {
440
                                $s .= $answer_input;
441
                            }
442
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
443
                            $my_choice = array();
444
                            if (!empty($user_choice_array)) {
445
                                foreach ($user_choice_array as $item) {
446
                                    $item = explode(':', $item);
447
                                    $my_choice[$item[0]] = $item[1];
448
                                }
449
                            }
450
451
                            $s .= '<tr>';
452
                            $s .= Display::tag('td', $answer);
453
454
                            if (!empty($quiz_question_options)) {
455
                                foreach ($quiz_question_options as $id => $item) {
456
                                    if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
457
                                        $attributes = array(
458
                                            'checked' => 1,
459
                                            'selected' => 1
460
                                        );
461
                                    } else {
462
                                        $attributes = array();
463
                                    }
464
465
                                    if ($debug_mark_answer) {
466
                                        if ($id == $answerCorrect) {
467
                                            $attributes['checked'] = 1;
468
                                            $attributes['selected'] = 1;
469
                                        }
470
                                    }
471
                                    $s .= Display::tag(
472
                                        'td',
473
                                        Display::input(
474
                                            'radio',
475
                                            'choice['.$questionId.']['.$numAnswer.']',
476
                                            $id,
477
                                            $attributes
478
                                        ),
479
                                        array('style' => '')
480
                                    );
481
                                }
482
                            }
483
484
                            if ($show_comment) {
485
                                $s .= '<td>';
486
                                $s .= $comment;
487
                                $s .= '</td>';
488
                            }
489
                            $s .= '</tr>';
490
                        }
491
                        break;
492
                    case MULTIPLE_ANSWER_COMBINATION:
493
                        // multiple answers
494
                        $input_id = 'choice-'.$questionId.'-'.$answerId;
495
496
                        if (in_array($numAnswer, $user_choice_array)) {
497
                            $attributes = array(
498
                                'id' => $input_id,
499
                                'checked' => 1,
500
                                'selected' => 1
501
                            );
502
                        } else {
503
                            $attributes = array('id' => $input_id);
504
                        }
505
506
                        if ($debug_mark_answer) {
507
                            if ($answerCorrect) {
508
                                $attributes['checked'] = 1;
509
                                $attributes['selected'] = 1;
510
                            }
511
                        }
512
513
                        $answer = Security::remove_XSS($answer, STUDENT);
514
                        $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
515
                        $answer_input .= '<label class="checkbox">';
516
                        $answer_input .= Display::input(
517
                            'checkbox',
518
                            'choice['.$questionId.']['.$numAnswer.']',
519
                            1,
520
                            $attributes
521
                        );
522
                        $answer_input .= $answer;
523
                        $answer_input .= '</label>';
524
525
                        if ($show_comment) {
526
                            $s .= '<tr>';
527
                            $s .= '<td>';
528
                            $s .= $answer_input;
529
                            $s .= '</td>';
530
                            $s .= '<td>';
531
                            $s .= $comment;
532
                            $s .= '</td>';
533
                            $s .= '</tr>';
534
                        } else {
535
                            $s .= $answer_input;
536
                        }
537
                        break;
538
                    case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
539
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
540
541
                        $my_choice = array();
542
                        if (!empty($user_choice_array)) {
543
                            foreach ($user_choice_array as $item) {
544
                                $item = explode(':', $item);
545
                                if (isset($item[1]) && isset($item[0])) {
546
                                    $my_choice[$item[0]] = $item[1];
547
                                }
548
                            }
549
                        }
550
                        $answer = Security::remove_XSS($answer, STUDENT);
551
                        $s .= '<tr>';
552
                        $s .= Display::tag('td', $answer);
553
                        foreach ($objQuestionTmp->options as $key => $item) {
554
                            if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
555
                                $attributes = array(
556
                                    'checked' => 1,
557
                                    'selected' => 1
558
                                );
559
                            } else {
560
                                $attributes = array();
561
                            }
562
563
                            if ($debug_mark_answer) {
564
                                if ($key == $answerCorrect) {
565
                                    $attributes['checked'] = 1;
566
                                    $attributes['selected'] = 1;
567
                                }
568
                            }
569
                            $s .= Display::tag(
570
                                'td',
571
                                Display::input(
572
                                    'radio',
573
                                    'choice['.$questionId.']['.$numAnswer.']',
574
                                    $key,
575
                                    $attributes
576
                                )
577
                            );
578
                        }
579
580
                        if ($show_comment) {
581
                            $s .= '<td>';
582
                            $s .= $comment;
583
                            $s .= '</td>';
584
                        }
585
                        $s .= '</tr>';
586
                        break;
587
                    case FILL_IN_BLANKS:
588
                        // display the question, with field empty, for student to fill it,
589
                        // or filled to display the answer in the Question preview of the exercise/admin.php page
590
                        $displayForStudent = true;
591
                        $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
592
593
                        list($answer) = explode('::', $answer);
0 ignored issues
show
Unused Code introduced by
The assignment to $answer is unused. Consider omitting it like so list($first,,$third).

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

Consider the following code example.

<?php

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

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

print $a . " - " . $c;

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

Instead, the list call could have been.

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1280
                    $images_folder_visibility = api_get_item_visibility(
1281
                        $course,
1282
                        'document',
1283
                        $docId,
1284
                        api_get_session_id()
1285
                    );
1286
1287
                    if (!$images_folder_visibility) {
1288
                        echo Display::return_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'), 'warning');
1289
                    }
1290
                }
1291
1292
                if ($freeze) {
1293
                    echo Display::img(
1294
                        api_get_path(WEB_COURSE_PATH).$course['path'].'/document/images/'.$pictureName,
1295
                        $objQuestionTmp->selectTitle(),
1296
                        ['width' => '600px']
1297
                    );
1298
1299
                    return 0;
1300
                }
1301
            }
1302
1303
            if (!$only_questions) {
1304
                if ($show_title) {
1305
                    TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
1306
                    echo $objQuestionTmp->getTitleToDisplay($current_item);
1307
                }
1308
                echo '
1309
                    <input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />
1310
                    <div class="exercise_questions">
1311
                        '.$objQuestionTmp->selectDescription().'
1312
                        <div class="row">
1313
                            <div class="col-sm-8 col-md-9">
1314
                                <div id="annotation-canvas-'.$questionId.'" class="annotation-canvas center-block">
1315
                                </div>
1316
                                <script>
1317
                                    AnnotationQuestion({
1318
                                        questionId: '.$questionId.',
1319
                                        exerciseId: '.$exe_id.',
1320
                                        relPath: \''.$relPath.'\'
1321
                                    });
1322
                                </script>
1323
                            </div>
1324
                            <div class="col-sm-4 col-md-3">
1325
                                <div class="well well-sm" id="annotation-toolbar-'.$questionId.'">
1326
                                    <div class="btn-toolbar">
1327
                                        <div class="btn-group" data-toggle="buttons">
1328
                                            <label class="btn btn-default active"
1329
                                                aria-label="'.get_lang('AddAnnotationPath').'">
1330
                                                <input type="radio" value="0" name="'.$questionId.'-options" autocomplete="off" checked>
1331
                                                <span class="fa fa-pencil" aria-hidden="true"></span>
1332
                                            </label>
1333
                                            <label class="btn btn-default"
1334
                                                aria-label="'.get_lang('AddAnnotationText').'">
1335
                                                <input type="radio" value="1" name="'.$questionId.'-options" autocomplete="off">
1336
                                                <span class="fa fa-font fa-fw" aria-hidden="true"></span>
1337
                                            </label>
1338
                                        </div>
1339
                                    </div>
1340
                                    <ul class="list-unstyled"></ul>
1341
                                </div>
1342
                            </div>
1343
                        </div>
1344
                    </div>
1345
                ';
1346
            }
1347
1348
            $objAnswerTmp = new Answer($questionId);
1349
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
1350
1351
            unset($objAnswerTmp, $objQuestionTmp);
1352
1353
        }
1354
        return $nbrAnswers;
1355
    }
1356
1357
    /**
1358
     * @param int $exe_id
1359
     * @return array
1360
     */
1361
    public static function get_exercise_track_exercise_info($exe_id)
1362
    {
1363
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1364
        $TBL_TRACK_EXERCICES = Database::get_main_table(
1365
            TABLE_STATISTIC_TRACK_E_EXERCISES
1366
        );
1367
        $TBL_COURSE = Database::get_main_table(TABLE_MAIN_COURSE);
1368
        $exe_id = intval($exe_id);
1369
        $result = array();
1370
        if (!empty($exe_id)) {
1371
            $sql = " SELECT q.*, tee.*
1372
                FROM $TBL_EXERCICES as q
1373
                INNER JOIN $TBL_TRACK_EXERCICES as tee
1374
                ON q.id = tee.exe_exo_id
1375
                INNER JOIN $TBL_COURSE c
1376
                ON c.id = tee.c_id
1377
                WHERE tee.exe_id = $exe_id
1378
                AND q.c_id = c.id";
1379
1380
            $res_fb_type = Database::query($sql);
1381
            $result = Database::fetch_array($res_fb_type, 'ASSOC');
1382
        }
1383
1384
        return $result;
1385
    }
1386
1387
    /**
1388
     * Validates the time control key
1389
     * @param int $exercise_id
1390
     * @param int $lp_id
1391
     * @param int $lp_item_id
1392
     * @return bool
1393
     */
1394
    public static function exercise_time_control_is_valid(
1395
        $exercise_id,
1396
        $lp_id = 0,
1397
        $lp_item_id = 0
1398
    ) {
1399
        $course_id = api_get_course_int_id();
1400
        $exercise_id = intval($exercise_id);
1401
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1402
        $sql = "SELECT expired_time FROM $TBL_EXERCICES
1403
            WHERE c_id = $course_id AND id = $exercise_id";
1404
        $result = Database::query($sql);
1405
        $row = Database::fetch_array($result, 'ASSOC');
1406
        if (!empty($row['expired_time'])) {
1407
            $current_expired_time_key = self::get_time_control_key(
1408
                $exercise_id,
1409
                $lp_id,
1410
                $lp_item_id
1411
            );
1412
            if (isset($_SESSION['expired_time'][$current_expired_time_key])) {
1413
                $current_time = time();
1414
                $expired_time = api_strtotime(
1415
                    $_SESSION['expired_time'][$current_expired_time_key],
1416
                    'UTC'
1417
                );
1418
                $total_time_allowed = $expired_time + 30;
1419
                if ($total_time_allowed < $current_time) {
1420
                    return false;
1421
                }
1422
                return true;
1423
            } else {
1424
                return false;
1425
            }
1426
        } else {
1427
            return true;
1428
        }
1429
    }
1430
1431
    /**
1432
     * Deletes the time control token
1433
     *
1434
     * @param int $exercise_id
1435
     * @param int $lp_id
1436
     * @param int $lp_item_id
1437
     */
1438
    public static function exercise_time_control_delete(
1439
        $exercise_id,
1440
        $lp_id = 0,
1441
        $lp_item_id = 0
1442
    ) {
1443
        $current_expired_time_key = self::get_time_control_key(
1444
            $exercise_id,
1445
            $lp_id,
1446
            $lp_item_id
1447
        );
1448
        unset($_SESSION['expired_time'][$current_expired_time_key]);
1449
    }
1450
1451
    /**
1452
     * Generates the time control key
1453
     */
1454
    public static function get_time_control_key(
1455
        $exercise_id,
1456
        $lp_id = 0,
1457
        $lp_item_id = 0
1458
    ) {
1459
        $exercise_id = intval($exercise_id);
1460
        $lp_id = intval($lp_id);
1461
        $lp_item_id = intval($lp_item_id);
1462
        return
1463
            api_get_course_int_id().'_'.
1464
            api_get_session_id().'_'.
1465
            $exercise_id.'_'.
1466
            api_get_user_id().'_'.
1467
            $lp_id.'_'.
1468
            $lp_item_id;
1469
    }
1470
1471
    /**
1472
     * Get session time control
1473
     *
1474
     * @param int $exercise_id
1475
     * @param int $lp_id
1476
     * @param int $lp_item_id
1477
     * @return int
1478
     */
1479
    public static function get_session_time_control_key(
1480
        $exercise_id,
1481
        $lp_id = 0,
1482
        $lp_item_id = 0
1483
    ) {
1484
        $return_value = 0;
1485
        $time_control_key = self::get_time_control_key(
1486
            $exercise_id,
1487
            $lp_id,
1488
            $lp_item_id
1489
        );
1490
        if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) {
1491
            $return_value = $_SESSION['expired_time'][$time_control_key];
1492
        }
1493
        return $return_value;
1494
    }
1495
1496
    /**
1497
     * Gets count of exam results
1498
     * @todo this function should be moved in a library  + no global calls
1499
     */
1500
    public static function get_count_exam_results($exercise_id, $extra_where_conditions)
1501
    {
1502
        $count = self::get_exam_results_data(
1503
            null,
1504
            null,
1505
            null,
1506
            null,
1507
            $exercise_id,
1508
            $extra_where_conditions,
1509
            true
1510
        );
1511
        return $count;
1512
    }
1513
1514
    /**
1515
     * @param string $in_hotpot_path
1516
     * @return int
1517
     */
1518
    public static function get_count_exam_hotpotatoes_results($in_hotpot_path)
1519
    {
1520
        return self::get_exam_results_hotpotatoes_data(
1521
            0,
1522
            0,
1523
            '',
1524
            '',
1525
            $in_hotpot_path,
1526
            true,
1527
            ''
1528
        );
1529
    }
1530
1531
    /**
1532
     * @param int $in_from
1533
     * @param int $in_number_of_items
1534
     * @param int $in_column
1535
     * @param int $in_direction
1536
     * @param string $in_hotpot_path
1537
     * @param bool $in_get_count
1538
     * @param null $where_condition
1539
     * @return array|int
1540
     */
1541
    public static function get_exam_results_hotpotatoes_data(
1542
        $in_from,
1543
        $in_number_of_items,
1544
        $in_column,
1545
        $in_direction,
1546
        $in_hotpot_path,
1547
        $in_get_count = false,
1548
        $where_condition = null
1549
    ) {
1550
        $courseId = api_get_course_int_id();
1551
        // by default in_column = 1 If parameters given, it is the name of the column witch is the bdd field name
1552
        if ($in_column == 1) {
1553
            $in_column = 'firstname';
1554
        }
1555
        $in_hotpot_path = Database::escape_string($in_hotpot_path);
1556
        $in_direction = Database::escape_string($in_direction);
1557
        $in_column = Database::escape_string($in_column);
1558
        $in_number_of_items = intval($in_number_of_items);
1559
        $in_from = intval($in_from);
1560
1561
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(
1562
            TABLE_STATISTIC_TRACK_E_HOTPOTATOES
1563
        );
1564
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
1565
1566
        $sql = "SELECT * FROM $TBL_TRACK_HOTPOTATOES thp
1567
            JOIN $TBL_USER u ON thp.exe_user_id = u.user_id
1568
            WHERE thp.c_id = $courseId AND exe_name LIKE '$in_hotpot_path%'";
1569
1570
        // just count how many answers
1571
        if ($in_get_count) {
1572
            $res = Database::query($sql);
1573
            return Database::num_rows($res);
1574
        }
1575
        // get a number of sorted results
1576
        $sql .= " $where_condition
1577
            ORDER BY $in_column $in_direction
1578
            LIMIT $in_from, $in_number_of_items";
1579
1580
        $res = Database::query($sql);
1581
        $result = array();
1582
        $apiIsAllowedToEdit = api_is_allowed_to_edit();
1583
        $urlBase = api_get_path(WEB_CODE_PATH).
1584
            'exercise/hotpotatoes_exercise_report.php?action=delete&'.
1585
            api_get_cidreq().'&id=';
1586
        while ($data = Database::fetch_array($res)) {
1587
            $actions = null;
1588
1589
            if ($apiIsAllowedToEdit) {
1590
                $url = $urlBase.$data['id'].'&path='.$data['exe_name'];
1591
                $actions = Display::url(
1592
                    Display::return_icon('delete.png', get_lang('Delete')),
1593
                    $url
1594
                );
1595
            }
1596
1597
            $result[] = array(
1598
                'firstname' => $data['firstname'],
1599
                'lastname' => $data['lastname'],
1600
                'username' => $data['username'],
1601
                'group_name' => implode(
1602
                    "<br/>",
1603
                    GroupManager::get_user_group_name($data['user_id'])
1604
                ),
1605
                'exe_date' => $data['exe_date'],
1606
                'score' => $data['exe_result'].' / '.$data['exe_weighting'],
1607
                'actions' => $actions,
1608
            );
1609
        }
1610
1611
        return $result;
1612
    }
1613
1614
    /**
1615
     * @param string $exercisePath
1616
     * @param int $userId
1617
     * @param int $courseId
1618
     * @param int $sessionId
1619
     *
1620
     * @return array
1621
     */
1622 View Code Duplication
    public static function getLatestHotPotatoResult(
1623
        $exercisePath,
1624
        $userId,
1625
        $courseId,
1626
        $sessionId
1627
    ) {
1628
        $table = Database::get_main_table(
1629
            TABLE_STATISTIC_TRACK_E_HOTPOTATOES
1630
        );
1631
        $exercisePath = Database::escape_string($exercisePath);
1632
        $userId = intval($userId);
1633
1634
        $sql = "SELECT * FROM $table
1635
            WHERE
1636
                c_id = $courseId AND
1637
                exe_name LIKE '$exercisePath%' AND
1638
                exe_user_id = $userId
1639
            ORDER BY id
1640
            LIMIT 1";
1641
        $result = Database::query($sql);
1642
        $attempt = array();
1643
        if (Database::num_rows($result)) {
1644
            $attempt = Database::fetch_array($result, 'ASSOC');
1645
        }
1646
        return $attempt;
1647
    }
1648
1649
    /**
1650
     * Gets the exam'data results
1651
     * @todo this function should be moved in a library  + no global calls
1652
     * @param int $from
1653
     * @param int $number_of_items
1654
     * @param int $column
1655
     * @param string $direction
1656
     * @param int $exercise_id
1657
     * @param null $extra_where_conditions
1658
     * @param bool $get_count
1659
     * @param string $courseCode
1660
     * @return array
1661
     */
1662
    public static function get_exam_results_data(
1663
        $from,
1664
        $number_of_items,
1665
        $column,
1666
        $direction,
1667
        $exercise_id,
1668
        $extra_where_conditions = null,
1669
        $get_count = false,
1670
        $courseCode = null
1671
    ) {
1672
        //@todo replace all this globals
1673
        global $documentPath, $filter;
1674
1675
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
1676
        $courseInfo = api_get_course_info($courseCode);
1677
        $course_id = $courseInfo['real_id'];
1678
        $sessionId = api_get_session_id();
1679
        $is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_allowed_to_edit(true) || api_is_drh() || api_is_student_boss();
1680
1681
        $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
1682
        $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
1683
        $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
1684
        $TBL_GROUP = Database::get_course_table(TABLE_GROUP);
1685
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1686
        $TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
1687
        $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1688
1689
        $session_id_and = ' AND te.session_id = '.$sessionId.' ';
1690
        $exercise_id = intval($exercise_id);
1691
1692
        $exercise_where = '';
1693
        if (!empty($exercise_id)) {
1694
            $exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.'  ';
1695
        }
1696
1697
        $hotpotatoe_where = '';
1698
        if (!empty($_GET['path'])) {
1699
            $hotpotatoe_path = Database::escape_string($_GET['path']);
1700
            $hotpotatoe_where .= ' AND exe_name = "'.$hotpotatoe_path.'"  ';
1701
        }
1702
1703
        // sql for chamilo-type tests for teacher / tutor view
1704
        $sql_inner_join_tbl_track_exercices = "
1705
        (
1706
            SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised
1707
            FROM $TBL_TRACK_EXERCICES ttte LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
1708
            ON (ttte.exe_id = tr.exe_id)
1709
            WHERE
1710
                c_id = $course_id AND
1711
                exe_exo_id = $exercise_id AND
1712
                ttte.session_id = ".$sessionId."
1713
        )";
1714
1715
        if ($is_allowedToEdit) {
1716
            //@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries
1717
            // Hack in order to filter groups
1718
            $sql_inner_join_tbl_user = '';
1719
            if (strpos($extra_where_conditions, 'group_id')) {
1720
                $sql_inner_join_tbl_user = "
1721
                (
1722
                    SELECT
1723
                        u.user_id,
1724
                        firstname,
1725
                        lastname,
1726
                        official_code,
1727
                        email,
1728
                        username,
1729
                        g.name as group_name,
1730
                        g.id as group_id
1731
                    FROM $TBL_USER u
1732
                    INNER JOIN $TBL_GROUP_REL_USER gru
1733
                    ON (gru.user_id = u.user_id AND gru.c_id=".$course_id.")
1734
                    INNER JOIN $TBL_GROUP g
1735
                    ON (gru.group_id = g.id AND g.c_id=".$course_id.")
1736
                )";
1737
            }
1738
1739
            if (strpos($extra_where_conditions, 'group_all')) {
1740
                $extra_where_conditions = str_replace(
1741
                    "AND (  group_id = 'group_all'  )",
1742
                    '',
1743
                    $extra_where_conditions
1744
                );
1745
                $extra_where_conditions = str_replace(
1746
                    "AND group_id = 'group_all'",
1747
                    '',
1748
                    $extra_where_conditions
1749
                );
1750
                $extra_where_conditions = str_replace(
1751
                    "group_id = 'group_all' AND",
1752
                    '',
1753
                    $extra_where_conditions
1754
                );
1755
1756
                $sql_inner_join_tbl_user = "
1757
                (
1758
                    SELECT
1759
                        u.user_id,
1760
                        firstname,
1761
                        lastname,
1762
                        official_code,
1763
                        email,
1764
                        username,
1765
                        '' as group_name,
1766
                        '' as group_id
1767
                    FROM $TBL_USER u
1768
                )";
1769
                $sql_inner_join_tbl_user = null;
1770
            }
1771
1772
            if (strpos($extra_where_conditions, 'group_none')) {
1773
                $extra_where_conditions = str_replace(
1774
                    "AND (  group_id = 'group_none'  )",
1775
                    "AND (  group_id is null  )",
1776
                    $extra_where_conditions
1777
                );
1778
                $extra_where_conditions = str_replace(
1779
                    "AND group_id = 'group_none'",
1780
                    "AND (  group_id is null  )",
1781
                    $extra_where_conditions
1782
                );
1783
                $sql_inner_join_tbl_user = "
1784
            (
1785
                SELECT
1786
                    u.user_id,
1787
                    firstname,
1788
                    lastname,
1789
                    official_code,
1790
                    email,
1791
                    username,
1792
                    g.name as group_name,
1793
                    g.id as group_id
1794
                FROM $TBL_USER u
1795
                LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
1796
                ON ( gru.user_id = u.user_id AND gru.c_id=".$course_id." )
1797
                LEFT OUTER JOIN $TBL_GROUP g
1798
                ON (gru.group_id = g.id AND g.c_id = ".$course_id.")
1799
            )";
1800
            }
1801
1802
            // All
1803
            $is_empty_sql_inner_join_tbl_user = false;
1804
            if (empty($sql_inner_join_tbl_user)) {
1805
                $is_empty_sql_inner_join_tbl_user = true;
1806
                $sql_inner_join_tbl_user = "
1807
            (
1808
                SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code
1809
                FROM $TBL_USER u
1810
                WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
1811
            )";
1812
            }
1813
1814
            $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
1815
            $sqlWhereOption = "  AND gru.c_id = ".$course_id." AND gru.user_id = user.user_id ";
1816
            $first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
1817
1818
            if ($get_count) {
1819
                $sql_select = "SELECT count(te.exe_id) ";
1820
            } else {
1821
                $sql_select = "SELECT DISTINCT
1822
                    user_id,
1823
                    $first_and_last_name,
1824
                    official_code,
1825
                    ce.title,
1826
                    username,
1827
                    te.exe_result,
1828
                    te.exe_weighting,
1829
                    te.exe_date,
1830
                    te.exe_id,
1831
                    email as exemail,
1832
                    te.start_date,
1833
                    ce.expired_time,
1834
                    steps_counter,
1835
                    exe_user_id,
1836
                    te.exe_duration,
1837
                    te.status as completion_status,
1838
                    propagate_neg,
1839
                    revised,
1840
                    group_name,
1841
                    group_id,
1842
                    orig_lp_id,
1843
                    te.user_ip";
1844
            }
1845
1846
            $sql = " $sql_select
1847
                FROM $TBL_EXERCICES AS ce
1848
                INNER JOIN $sql_inner_join_tbl_track_exercices AS te
1849
                ON (te.exe_exo_id = ce.id)
1850
                INNER JOIN $sql_inner_join_tbl_user AS user
1851
                ON (user.user_id = exe_user_id)
1852
                WHERE
1853
                    te.c_id = ".$course_id." $session_id_and AND
1854
                    ce.active <>-1 AND 
1855
                    ce.c_id = ".$course_id."
1856
                    $exercise_where
1857
                    $extra_where_conditions
1858
                ";
1859
1860
            // sql for hotpotatoes tests for teacher / tutor view
1861
            if ($get_count) {
1862
                $hpsql_select = "SELECT count(username)";
1863
            } else {
1864
                $hpsql_select = "SELECT
1865
                    $first_and_last_name ,
1866
                    username,
1867
                    official_code,
1868
                    tth.exe_name,
1869
                    tth.exe_result ,
1870
                    tth.exe_weighting,
1871
                    tth.exe_date";
1872
            }
1873
1874
            $hpsql = " $hpsql_select
1875
                FROM
1876
                    $TBL_TRACK_HOTPOTATOES tth,
1877
                    $TBL_USER user
1878
                    $sqlFromOption
1879
                WHERE
1880
                    user.user_id=tth.exe_user_id
1881
                    AND tth.c_id = ".$course_id."
1882
                    $hotpotatoe_where
1883
                    $sqlWhereOption
1884
                    AND user.status NOT IN(".api_get_users_status_ignored_in_reports('string').")
1885
                ORDER BY
1886
                    tth.c_id ASC,
1887
                    tth.exe_date DESC";
1888
        }
1889
1890
        if ($get_count) {
1891
            $resx = Database::query($sql);
1892
            $rowx = Database::fetch_row($resx, 'ASSOC');
1893
1894
            return $rowx[0];
1895
        }
1896
1897
        $teacher_list = CourseManager::get_teacher_list_from_course_code(
1898
            $courseCode
1899
        );
1900
        $teacher_id_list = array();
1901
        if (!empty($teacher_list)) {
1902
            foreach ($teacher_list as $teacher) {
1903
                $teacher_id_list[] = $teacher['user_id'];
1904
            }
1905
        }
1906
1907
        $list_info = array();
1908
1909
        // Simple exercises
1910
        if (empty($hotpotatoe_where)) {
1911
            $column = !empty($column) ? Database::escape_string($column) : null;
1912
            $from = intval($from);
1913
            $number_of_items = intval($number_of_items);
1914
1915
            if (!empty($column)) {
1916
                $sql .= " ORDER BY $column $direction ";
1917
            }
1918
            $sql .= " LIMIT $from, $number_of_items";
1919
1920
            $results = array();
1921
            $resx = Database::query($sql);
1922
            while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
1923
                $results[] = $rowx;
1924
            }
1925
1926
            $group_list = GroupManager::get_group_list(null, $courseInfo);
1927
            $clean_group_list = array();
1928
1929
            if (!empty($group_list)) {
1930
                foreach ($group_list as $group) {
1931
                    $clean_group_list[$group['id']] = $group['name'];
1932
                }
1933
            }
1934
1935
            $lp_list_obj = new LearnpathList(api_get_user_id());
1936
            $lp_list = $lp_list_obj->get_flat_list();
1937
1938
            $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
1939
1940
            if (is_array($results)) {
1941
                $users_array_id = array();
1942
                $from_gradebook = false;
1943
                if (isset($_GET['gradebook']) && $_GET['gradebook'] == 'view') {
1944
                    $from_gradebook = true;
1945
                }
1946
                $sizeof = count($results);
1947
                $user_list_id = array();
1948
                $locked = api_resource_is_locked_by_gradebook(
1949
                    $exercise_id,
1950
                    LINK_EXERCISE
1951
                );
1952
1953
                $timeNow = strtotime(api_get_utc_datetime());
1954
                // Looping results
1955
                for ($i = 0; $i < $sizeof; $i++) {
1956
                    $revised = $results[$i]['revised'];
1957
                    if ($results[$i]['completion_status'] == 'incomplete') {
1958
                        // If the exercise was incomplete, we need to determine
1959
                        // if it is still into the time allowed, or if its
1960
                        // allowed time has expired and it can be closed
1961
                        // (it's "unclosed")
1962
                        $minutes = $results[$i]['expired_time'];
1963
                        if ($minutes == 0) {
1964
                            // There's no time limit, so obviously the attempt
1965
                            // can still be "ongoing", but the teacher should
1966
                            // be able to choose to close it, so mark it as
1967
                            // "unclosed" instead of "ongoing"
1968
                            $revised = 2;
1969
                        } else {
1970
                            $allowedSeconds = $minutes * 60;
1971
                            $timeAttemptStarted = strtotime($results[$i]['start_date']);
1972
                            $secondsSinceStart = $timeNow - $timeAttemptStarted;
1973
                            if ($secondsSinceStart > $allowedSeconds) {
1974
                                $revised = 2; // mark as "unclosed"
1975
                            } else {
1976
                                $revised = 3; // mark as "ongoing"
1977
                            }
1978
                        }
1979
                    }
1980
1981
                    if ($from_gradebook && ($is_allowedToEdit)) {
1982
                        if (in_array(
1983
                            $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'],
1984
                            $users_array_id
1985
                        )) {
1986
                            continue;
1987
                        }
1988
                        $users_array_id[] = $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'];
1989
                    }
1990
1991
                    $lp_obj = isset($results[$i]['orig_lp_id']) && isset($lp_list[$results[$i]['orig_lp_id']]) ? $lp_list[$results[$i]['orig_lp_id']] : null;
1992
                    if (empty($lp_obj)) {
1993
                        // Try to get the old id (id instead of iid)
1994
                        $lpNewId = isset($results[$i]['orig_lp_id']) && isset($oldIds[$results[$i]['orig_lp_id']]) ? $oldIds[$results[$i]['orig_lp_id']] : null;
1995
                        if ($lpNewId) {
1996
                            $lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
1997
                        }
1998
                    }
1999
                    $lp_name = null;
2000
                    if ($lp_obj) {
2001
                        $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$results[$i]['orig_lp_id'];
2002
                        $lp_name = Display::url(
2003
                            $lp_obj['lp_name'],
2004
                            $url,
2005
                            array('target' => '_blank')
2006
                        );
2007
                    }
2008
2009
                    // Add all groups by user
2010
                    $group_name_list = null;
2011
                    if ($is_empty_sql_inner_join_tbl_user) {
2012
                        $group_list = GroupManager::get_group_ids(
2013
                            api_get_course_int_id(),
2014
                            $results[$i]['user_id']
2015
                        );
2016
2017
                        foreach ($group_list as $id) {
2018
                            $group_name_list .= $clean_group_list[$id].'<br/>';
2019
                        }
2020
                        $results[$i]['group_name'] = $group_name_list;
2021
                    }
2022
2023
                    $results[$i]['exe_duration'] = !empty($results[$i]['exe_duration']) ? round($results[$i]['exe_duration'] / 60) : 0;
2024
2025
                    $user_list_id[] = $results[$i]['exe_user_id'];
2026
                    $id = $results[$i]['exe_id'];
2027
                    $dt = api_convert_and_format_date($results[$i]['exe_weighting']);
2028
2029
                    // we filter the results if we have the permission to
2030
                    if (isset($results[$i]['results_disabled'])) {
2031
                        $result_disabled = intval(
2032
                            $results[$i]['results_disabled']
2033
                        );
2034
                    } else {
2035
                        $result_disabled = 0;
2036
                    }
2037
2038
                    if ($result_disabled == 0) {
2039
                        $my_res = $results[$i]['exe_result'];
2040
                        $my_total = $results[$i]['exe_weighting'];
2041
2042
                        $results[$i]['start_date'] = api_get_local_time(
2043
                            $results[$i]['start_date']
2044
                        );
2045
                        $results[$i]['exe_date'] = api_get_local_time(
2046
                            $results[$i]['exe_date']
2047
                        );
2048
2049
                        if (!$results[$i]['propagate_neg'] && $my_res < 0) {
2050
                            $my_res = 0;
2051
                        }
2052
2053
                        $score = self::show_score($my_res, $my_total);
2054
2055
                        $actions = '<div class="pull-right">';
2056
                        if ($is_allowedToEdit) {
2057
                            if (isset($teacher_id_list)) {
2058
                                if (in_array(
2059
                                    $results[$i]['exe_user_id'],
2060
                                    $teacher_id_list
2061
                                )) {
2062
                                    $actions .= Display::return_icon(
2063
                                        'teacher.png',
2064
                                        get_lang('Teacher')
2065
                                    );
2066
                                }
2067
                            }
2068
                            $revisedLabel = '';
2069
                            switch ($revised) {
2070
                                case 0:
2071
                                    $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=qualify&id=$id'>".
2072
                                        Display:: return_icon(
2073
                                            'quiz.png',
2074
                                            get_lang('Qualify')
2075
                                        );
2076
                                    $actions .= '</a>';
2077
                                    $revisedLabel = Display::label(
2078
                                        get_lang('NotValidated'),
2079
                                        'info'
2080
                                    );
2081
                                    break;
2082 View Code Duplication
                                case 1:
2083
                                    $actions .= "<a href='exercise_show.php?".api_get_cidreq()."&action=edit&id=$id'>".
2084
                                        Display:: return_icon(
2085
                                            'edit.png',
2086
                                            get_lang('Edit'),
2087
                                            array(),
2088
                                            ICON_SIZE_SMALL
2089
                                        );
2090
                                    $actions .= '</a>';
2091
                                    $revisedLabel = Display::label(
2092
                                        get_lang('Validated'),
2093
                                        'success'
2094
                                    );
2095
                                    break;
2096
                                case 2: //finished but not marked as such
2097
                                    $actions .= '<a href="exercise_report.php?'
2098
                                        . api_get_cidreq()
2099
                                        . '&exerciseId='
2100
                                        . $exercise_id
2101
                                        . '&a=close&id='
2102
                                        . $id
2103
                                        . '">'.
2104
                                        Display:: return_icon(
2105
                                            'lock.png',
2106
                                            get_lang('MarkAttemptAsClosed'),
2107
                                            array(),
2108
                                            ICON_SIZE_SMALL
2109
                                        );
2110
                                    $actions .= '</a>';
2111
                                    $revisedLabel = Display::label(
2112
                                        get_lang('Unclosed'),
2113
                                        'warning'
2114
                                    );
2115
                                    break;
2116 View Code Duplication
                                case 3: //still ongoing
2117
                                    $actions .= "".
2118
                                        Display:: return_icon(
2119
                                            'clock.png',
2120
                                            get_lang('AttemptStillOngoingPleaseWait'),
2121
                                            array(),
2122
                                            ICON_SIZE_SMALL
2123
                                        );
2124
                                    $actions .= '';
2125
                                    $revisedLabel = Display::label(
2126
                                        get_lang('Ongoing'),
2127
                                        'danger'
2128
                                    );
2129
                                    break;
2130
                            }
2131
2132
                            if ($filter == 2) {
2133
                                $actions .= ' <a href="exercise_history.php?'.api_get_cidreq().'&exe_id='.$id.'">'.
2134
                                    Display:: return_icon(
2135
                                        'history.png',
2136
                                        get_lang('ViewHistoryChange')
2137
                                    ).'</a>';
2138
                            }
2139
2140
                            //Admin can always delete the attempt
2141
                            if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
2142
                                $ip = TrackingUserLog::get_ip_from_user_event(
2143
                                    $results[$i]['exe_user_id'],
2144
                                    api_get_utc_datetime(),
2145
                                    false
2146
                                );
2147
                                $actions .= '<a href="http://www.whatsmyip.org/ip-geo-location/?ip='.$ip.'" target="_blank">'
2148
                                . Display::return_icon('info.png', $ip)
2149
                                .'</a>';
2150
2151
2152
                                $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'.
2153
                                    api_get_cidreq().'&'.
2154
                                    http_build_query([
2155
                                        'id' => $id,
2156
                                        'exercise' => $exercise_id,
2157
                                        'user' => $results[$i]['exe_user_id']
2158
                                    ]);
2159
                                $actions .= Display::url(
2160
                                    Display::return_icon('reload.png', get_lang('RecalculateResults')),
2161
                                    $recalculateUrl,
2162
                                    [
2163
                                        'data-exercise' => $exercise_id,
2164
                                        'data-user' => $results[$i]['exe_user_id'],
2165
                                        'data-id' => $id,
2166
                                        'class' => 'exercise-recalculate'
2167
                                    ]
2168
                                );
2169
2170
                                $delete_link = '<a href="exercise_report.php?'.api_get_cidreq().'&filter_by_user='.intval($_GET['filter_by_user']).'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'"
2171
                                onclick="javascript:if(!confirm(\'' . sprintf(
2172
                                        get_lang('DeleteAttempt'),
2173
                                        $results[$i]['username'],
2174
                                        $dt
2175
                                    ).'\')) return false;">'.Display:: return_icon(
2176
                                        'delete.png',
2177
                                        get_lang('Delete')
2178
                                    ).'</a>';
2179
                                $delete_link = utf8_encode($delete_link);
2180
2181
                                if (api_is_drh() && !api_is_platform_admin()) {
2182
                                    $delete_link = null;
2183
                                }
2184
                                if ($revised == 3) {
2185
                                    $delete_link = null;
2186
                                }
2187
                                $actions .= $delete_link;
2188
                            }
2189
2190
                        } else {
2191
                            $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.api_get_cidreq().'&id='.$results[$i]['exe_id'].'&id_session='.$sessionId;
2192
                            $attempt_link = Display::url(
2193
                                get_lang('Show'),
2194
                                $attempt_url,
2195
                                [
2196
                                    'class' => 'ajax btn btn-default',
2197
                                    'data-title' => get_lang('Show')
2198
                                ]
2199
                            );
2200
                            $actions .= $attempt_link;
2201
                        }
2202
                        $actions .= '</div>';
2203
2204
                        $results[$i]['id'] = $results[$i]['exe_id'];
2205
2206
                        if ($is_allowedToEdit) {
2207
                            $results[$i]['status'] = $revisedLabel;
2208
                            $results[$i]['score'] = $score;
2209
                            $results[$i]['lp'] = $lp_name;
2210
                            $results[$i]['actions'] = $actions;
2211
                            $list_info[] = $results[$i];
2212
                        } else {
2213
                            $results[$i]['status'] = $revisedLabel;
2214
                            $results[$i]['score'] = $score;
2215
                            $results[$i]['actions'] = $actions;
2216
                            $list_info[] = $results[$i];
2217
                        }
2218
                    }
2219
                }
2220
            }
2221
        } else {
2222
            $hpresults = StatsUtils::getManyResultsXCol($hpsql, 6);
2223
            // Print HotPotatoes test results.
2224
            if (is_array($hpresults)) {
2225
                for ($i = 0; $i < sizeof($hpresults); $i++) {
2226
                    $hp_title = GetQuizName($hpresults[$i][3], $documentPath);
2227
                    if ($hp_title == '') {
2228
                        $hp_title = basename($hpresults[$i][3]);
2229
                    }
2230
2231
                    $hp_date = api_get_local_time(
2232
                        $hpresults[$i][6],
2233
                        null,
2234
                        date_default_timezone_get()
2235
                    );
2236
                    $hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2)
2237
                        .'% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')';
2238
                    if ($is_allowedToEdit) {
2239
                        $list_info[] = array(
2240
                            $hpresults[$i][0],
2241
                            $hpresults[$i][1],
2242
                            $hpresults[$i][2],
2243
                            '',
2244
                            $hp_title,
2245
                            '-',
2246
                            $hp_date,
2247
                            $hp_result,
2248
                            '-'
2249
                        );
2250
                    } else {
2251
                        $list_info[] = array(
2252
                            $hp_title,
2253
                            '-',
2254
                            $hp_date,
2255
                            $hp_result,
2256
                            '-'
2257
                        );
2258
                    }
2259
                }
2260
            }
2261
        }
2262
2263
        return $list_info;
2264
    }
2265
2266
    /**
2267
     * Converts the score with the exercise_max_note and exercise_min_score
2268
     * the platform settings + formats the results using the float_format function
2269
     *
2270
     * @param float $score
2271
     * @param float $weight
2272
     * @param bool $show_percentage show percentage or not
2273
     * @param bool $use_platform_settings use or not the platform settings
2274
     * @param bool $show_only_percentage
2275
     * @return  string  an html with the score modified
2276
     */
2277
    public static function show_score(
2278
        $score,
2279
        $weight,
2280
        $show_percentage = true,
2281
        $use_platform_settings = true,
2282
        $show_only_percentage = false
2283
    ) {
2284
        if (is_null($score) && is_null($weight)) {
2285
            return '-';
2286
        }
2287
2288
        $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 displayQuestionListByAttempt(
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 View Code Duplication
                if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) {
3821
                    if (!isset($category_list[$objQuestionTmp->category]['score'])) {
3822
                        $category_list[$objQuestionTmp->category]['score'] = 0;
3823
                    }
3824
                    if (!isset($category_list[$objQuestionTmp->category]['total'])) {
3825
                        $category_list[$objQuestionTmp->category]['total'] = 0;
3826
                    }
3827
                    $category_list[$objQuestionTmp->category]['score'] += $my_total_score;
3828
                    $category_list[$objQuestionTmp->category]['total'] += $my_total_weight;
3829
                    $category_was_added_for_this_test = true;
3830
                }
3831
3832 View Code Duplication
                if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
3833
                    foreach ($objQuestionTmp->category_list as $category_id) {
3834
                        $category_list[$category_id]['score'] += $my_total_score;
3835
                        $category_list[$category_id]['total'] += $my_total_weight;
3836
                        $category_was_added_for_this_test = true;
3837
                    }
3838
                }
3839
3840
                // No category for this question!
3841
                if ($category_was_added_for_this_test == false) {
3842
                    if (!isset($category_list['none']['score'])) {
3843
                        $category_list['none']['score'] = 0;
3844
                    }
3845
                    if (!isset($category_list['none']['total'])) {
3846
                        $category_list['none']['total'] = 0;
3847
                    }
3848
3849
                    $category_list['none']['score'] += $my_total_score;
3850
                    $category_list['none']['total'] += $my_total_weight;
3851
                }
3852
3853
                if ($objExercise->selectPropagateNeg() == 0 &&
3854
                    $my_total_score < 0
3855
                ) {
3856
                    $my_total_score = 0;
3857
                }
3858
3859
                $comnt = null;
3860
                if ($show_results) {
3861
                    $comnt = Event::get_comments($exe_id, $questionId);
3862
                    $teacherAudio = ExerciseLib::getOralFeedbackAudio($exe_id, $questionId, api_get_user_id());;
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...
3863
3864
                    if (!empty($comnt) || $teacherAudio) {
3865
                        echo '<b>'.get_lang('Feedback').'</b>';
3866
                    }
3867
3868
                    if (!empty($comnt)) {
3869
                        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...
3870
                    }
3871
3872
                    if ($teacherAudio) {
3873
                        echo $teacherAudio;
3874
                    }
3875
                }
3876
3877
                if ($show_results) {
3878
                    $score = [
3879
                        'result' => self::show_score(
3880
                            $my_total_score,
3881
                            $my_total_weight,
3882
                            false,
3883
                            true
3884
                        ),
3885
                        'pass' => $my_total_score >= $my_total_weight ? true : false,
3886
                        'score' => $my_total_score,
3887
                        'weight' => $my_total_weight,
3888
                        'comments' => $comnt,
3889
                    ];
3890
                } else {
3891
                    $score = [];
3892
                }
3893
3894 View Code Duplication
                if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
3895
                    $check = $objQuestionTmp->isQuestionWaitingReview($score);
3896
                    if ($check === false) {
3897
                        $countPendingQuestions++;
3898
                    }
3899
                }
3900
3901
                $contents = ob_get_clean();
3902
                $question_content = '';
3903
                if ($show_results) {
3904
                    $question_content = '<div class="question_row_answer">';
3905
                    // Shows question title an description
3906
                    $question_content .= $objQuestionTmp->return_header(
3907
                        $objExercise,
3908
                        $counter,
3909
                        $score
3910
                    );
3911
                }
3912
                $counter++;
3913
                $question_content .= $contents;
3914
                if ($show_results) {
3915
                    $question_content .= '</div>';
3916
                }
3917
3918
                $exercise_content .= Display::panel($question_content);
3919
            } // end foreach() block that loops over all questions
3920
        }
3921
3922
        $total_score_text = null;
3923
        if ($show_results || $show_only_score) {
3924
            $total_score_text .= '<div class="question_row_score">';
3925
            $total_score_text .= self::getTotalScoreRibbon(
3926
                $objExercise,
3927
                $total_score,
3928
                $total_weight,
3929
                true,
3930
                $countPendingQuestions
3931
            );
3932
            $total_score_text .= '</div>';
3933
        }
3934
3935 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
3936
            // Adding total
3937
            $category_list['total'] = array(
3938
                'score' => $total_score,
3939
                'total' => $total_weight
3940
            );
3941
            echo TestCategory::get_stats_table_by_attempt(
3942
                $objExercise->id,
3943
                $category_list
3944
            );
3945
        }
3946
3947
        if ($show_all_but_expected_answer) {
3948
            $exercise_content .= "<div class='normal-message'>".get_lang(
3949
                    "ExerciseWithFeedbackWithoutCorrectionComment"
3950
                )."</div>";
3951
        }
3952
3953
        // Remove audio auto play from questions on results page - refs BT#7939
3954
        $exercise_content = preg_replace(
3955
            ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'],
3956
            '',
3957
            $exercise_content
3958
        );
3959
3960
        echo $total_score_text;
3961
        echo $exercise_content;
3962
3963
        if (!$show_only_score) {
3964
            echo $total_score_text;
3965
        }
3966
3967
        if (!empty($remainingMessage)) {
3968
            echo Display::return_message($remainingMessage, 'normal', false);
3969
        }
3970
3971
        if ($save_user_result) {
3972
            // Tracking of results
3973
            if ($exercise_stat_info) {
3974
                $learnpath_id = $exercise_stat_info['orig_lp_id'];
3975
                $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
3976
                $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
3977
3978
                if (api_is_allowed_to_session_edit()) {
3979
                    Event::update_event_exercise(
3980
                        $exercise_stat_info['exe_id'],
3981
                        $objExercise->selectId(),
3982
                        $total_score,
3983
                        $total_weight,
3984
                        api_get_session_id(),
3985
                        $learnpath_id,
3986
                        $learnpath_item_id,
3987
                        $learnpath_item_view_id,
3988
                        $exercise_stat_info['exe_duration'],
3989
                        $question_list,
3990
                        '',
3991
                        array()
3992
                    );
3993
                }
3994
            }
3995
3996
            // Send notification at the end
3997
            if (!api_is_allowed_to_edit(null, true) &&
3998
                !api_is_excluded_user_type()
3999
            ) {
4000
                $objExercise->send_mail_notification_for_exam(
4001
                    'end',
4002
                    $question_list_answers,
4003
                    $origin,
4004
                    $exe_id,
4005
                    $total_score,
4006
                    $total_weight
4007
                );
4008
            }
4009
        }
4010
    }
4011
4012
    /**
4013
     * @param string $class
4014
     * @param string $scoreLabel
4015
     * @param string $result
4016
     *
4017
     * @return string
4018
     */
4019
    public static function getQuestionRibbon($class, $scoreLabel, $result)
4020
    {
4021
        return '<div class="ribbon">
4022
                    <div class="rib rib-'.$class.'">
4023
                        <h3>'.$scoreLabel.'</h3>
4024
                    </div> 
4025
                    <h4>'.get_lang('Score').': '.$result.'</h4>
4026
                </div>'
4027
        ;
4028
    }
4029
4030
    /**
4031
     * @param Exercise $objExercise
4032
     * @param float $score
4033
     * @param float $weight
4034
     * @param bool $checkPassPercentage
4035
     * @param int $countPendingQuestions
4036
     * @return string
4037
     */
4038
    public static function getTotalScoreRibbon(
4039
        $objExercise,
4040
        $score,
4041
        $weight,
4042
        $checkPassPercentage = false,
4043
        $countPendingQuestions = 0
4044
    ) {
4045
        $passPercentage = $objExercise->selectPassPercentage();
4046
        $ribbon = '<div class="title-score">';
4047
        if ($checkPassPercentage) {
4048
            $isSuccess = self::isSuccessExerciseResult(
4049
                $score,
4050
                $weight,
4051
                $passPercentage
4052
            );
4053
            // Color the final test score if pass_percentage activated
4054
            $class = '';
4055
            if (self::isPassPercentageEnabled($passPercentage)) {
4056
                if ($isSuccess) {
4057
                    $class = ' ribbon-total-success';
4058
                } else {
4059
                    $class = ' ribbon-total-error';
4060
                }
4061
            }
4062
            $ribbon .= '<div class="total '.$class.'">';
4063
        } else {
4064
            $ribbon .= '<div class="total">';
4065
        }
4066
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
4067
        $ribbon .= self::show_score($score, $weight, false, true);
4068
        $ribbon .= '</h3>';
4069
        $ribbon .= '</div>';
4070
        if ($checkPassPercentage) {
4071
            $ribbon .= self::showSuccessMessage(
4072
                $score,
4073
                $weight,
4074
                $passPercentage
4075
            );
4076
        }
4077
        $ribbon .= '</div>';
4078
4079
        if (!empty($countPendingQuestions)) {
4080
            $ribbon .= '<br />';
4081
            $ribbon .= Display::return_message(
4082
                sprintf(
4083
                    get_lang('TempScoreXQuestionsNotCorrectedYet'),
4084
                    $countPendingQuestions
4085
                ),
4086
                'warning'
4087
            );
4088
        }
4089
4090
        return $ribbon;
4091
    }
4092
4093
    /**
4094
     * @param int $countLetter
4095
     * @return mixed
4096
     */
4097
    public static function detectInputAppropriateClass($countLetter)
4098
    {
4099
        $limits = array(
4100
            0 => 'input-mini',
4101
            10 => 'input-mini',
4102
            15 => 'input-medium',
4103
            20 => 'input-xlarge',
4104
            40 => 'input-xlarge',
4105
            60 => 'input-xxlarge',
4106
            100 => 'input-xxlarge',
4107
            200 => 'input-xxlarge',
4108
        );
4109
4110
        foreach ($limits as $size => $item) {
4111
            if ($countLetter <= $size) {
4112
                return $item;
4113
            }
4114
        }
4115
        return $limits[0];
4116
    }
4117
4118
    /**
4119
     * @param int $senderId
4120
     * @param array $course_info
4121
     * @param string $test
4122
     * @param string $url
4123
     *
4124
     * @return string
4125
     */
4126
    public static function getEmailNotification($senderId, $course_info, $test, $url)
4127
    {
4128
        $teacher_info = api_get_user_info($senderId);
4129
        $from_name = api_get_person_name(
4130
            $teacher_info['firstname'],
4131
            $teacher_info['lastname'],
4132
            null,
4133
            PERSON_NAME_EMAIL_ADDRESS
4134
        );
4135
4136
        $message = '<p>'.get_lang('DearStudentEmailIntroduction').'</p><p>'.get_lang('AttemptVCC');
4137
        $message .= '<h3>'.get_lang('CourseName').'</h3><p>'.Security::remove_XSS($course_info['name']).'';
4138
        $message .= '<h3>'.get_lang('Exercise').'</h3><p>'.Security::remove_XSS($test);
4139
        $message .= '<p>'.get_lang('ClickLinkToViewComment').' <br /><a href="#url#">#url#</a><br />';
4140
        $message .= '<p>'.get_lang('Regards').'</p>';
4141
        $message .= $from_name;
4142
        $message = str_replace("#test#", Security::remove_XSS($test), $message);
4143
        $message = str_replace("#url#", $url, $message);
4144
4145
        return $message;
4146
    }
4147
4148
    /**
4149
     * @return string
4150
     */
4151
    public static function getNotCorrectedYetText()
4152
    {
4153
        return Display::return_message(get_lang('notCorrectedYet'), 'warning');
4154
    }
4155
4156
    /**
4157
     * @param string $message
4158
     * @return string
4159
     */
4160
    public static function getFeedbackText($message)
4161
    {
4162
        // Old style
4163
        //return '<div id="question_feedback">'.$message.'</div>';
4164
        return Display::return_message($message, 'warning', false);
4165
    }
4166
4167
    /**
4168
     * Get the recorder audio component for save a teacher audio feedback
4169
     * @param int $attemptId
4170
     * @param int $questionId
4171
     * @param int $userId
4172
     * @return string
4173
     */
4174
    public static function getOralFeedbackForm($attemptId, $questionId, $userId)
4175
    {
4176
        $view = new Template('', false, false, false, false, false, false);
4177
        $view->assign('user_id', $userId);
4178
        $view->assign('question_id', $questionId);
4179
        $view->assign('directory', "/../exercises/teacher_audio/$attemptId/");
4180
        $view->assign('file_name', "{$questionId}_{$userId}");
4181
        $template = $view->get_template('exercise/oral_expression.tpl');
4182
4183
        return $view->fetch($template);
4184
    }
4185
4186
    /**
4187
     * Get the audio componen for a teacher audio feedback
4188
     * @param int $attemptId
4189
     * @param int $questionId
4190
     * @param int $userId
4191
     * @return string
4192
     */
4193
    public static function getOralFeedbackAudio($attemptId, $questionId, $userId)
4194
    {
4195
        $courseInfo = api_get_course_info();
4196
        $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'];
4197
        $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path'];
4198
        $fileName = "{$questionId}_{$userId}";
4199
        $filePath = null;
4200
4201
        if (file_exists("$sysCourseDir/exercises/teacher_audio/$attemptId/$fileName.ogg")) {
4202
            $filePath = "$webCourseDir/exercises/teacher_audio/$attemptId/$fileName.ogg";
4203
        } elseif (file_exists("$sysCourseDir/exercises/teacher_audio/$attemptId/$fileName.wav.wav")) {
4204
            $filePath = "$webCourseDir/exercises/teacher_audio/$attemptId/$fileName.wav.wav";
4205
        }
4206
4207
        if (!$filePath) {
4208
            return '';
4209
        }
4210
4211
        return Display::tag(
4212
            'audio',
4213
            null,
4214
            ['src' => $filePath]
4215
        );
4216
    }
4217
}
4218