Passed
Push — master ( ccb5fd...57fe34 )
by Julito
13:03
created

Exercise::removeAllQuestionToRemind()   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nop 1
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\TrackEHotspot;
5
use ChamiloSession as Session;
6
7
/**
8
 * Class Exercise.
9
 *
10
 * Allows to instantiate an object of type Exercise
11
 *
12
 * @package chamilo.exercise
13
 *
14
 * @todo use getters and setters correctly
15
 *
16
 * @author Olivier Brouckaert
17
 * @author Julio Montoya Cleaning exercises
18
 * Modified by Hubert Borderiou #294
19
 */
20
class Exercise
21
{
22
    public $iId;
23
    public $id;
24
    public $name;
25
    public $title;
26
    public $exercise;
27
    public $description;
28
    public $sound;
29
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
30
    public $random;
31
    public $random_answers;
32
    public $active;
33
    public $timeLimit;
34
    public $attempts;
35
    public $feedback_type;
36
    public $end_time;
37
    public $start_time;
38
    public $questionList; // array with the list of this exercise's questions
39
    /* including question list of the media */
40
    public $questionListUncompressed;
41
    public $results_disabled;
42
    public $expired_time;
43
    public $course;
44
    public $course_id;
45
    public $propagate_neg;
46
    public $saveCorrectAnswers;
47
    public $review_answers;
48
    public $randomByCat;
49
    public $text_when_finished;
50
    public $display_category_name;
51
    public $pass_percentage;
52
    public $edit_exercise_in_lp = false;
53
    public $is_gradebook_locked = false;
54
    public $exercise_was_added_in_lp = false;
55
    public $lpList = [];
56
    public $force_edit_exercise_in_lp = false;
57
    public $categories;
58
    public $categories_grouping = true;
59
    public $endButton = 0;
60
    public $categoryWithQuestionList;
61
    public $mediaList;
62
    public $loadQuestionAJAX = false;
63
    // Notification send to the teacher.
64
    public $emailNotificationTemplate = null;
65
    // Notification send to the student.
66
    public $emailNotificationTemplateToUser = null;
67
    public $countQuestions = 0;
68
    public $fastEdition = false;
69
    public $modelType = 1;
70
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
71
    public $hideQuestionTitle = 0;
72
    public $scoreTypeModel = 0;
73
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
74
    public $globalCategoryId = null;
75
    public $onSuccessMessage = null;
76
    public $onFailedMessage = null;
77
    public $emailAlert;
78
    public $notifyUserByEmail = '';
79
    public $sessionId = 0;
80
    public $questionFeedbackEnabled = false;
81
    public $questionTypeWithFeedback;
82
    public $showPreviousButton;
83
    public $notifications;
84
    public $export = false;
85
    public $autolaunch;
86
    public $exerciseCategoryId;
87
88
    /**
89
     * Constructor of the class.
90
     *
91
     * @param int $courseId
92
     *
93
     * @author Olivier Brouckaert
94
     */
95
    public function __construct($courseId = 0)
96
    {
97
        $this->iId = 0;
98
        $this->id = 0;
99
        $this->exercise = '';
100
        $this->description = '';
101
        $this->sound = '';
102
        $this->type = ALL_ON_ONE_PAGE;
103
        $this->random = 0;
104
        $this->random_answers = 0;
105
        $this->active = 1;
106
        $this->questionList = [];
107
        $this->timeLimit = 0;
108
        $this->end_time = '';
109
        $this->start_time = '';
110
        $this->results_disabled = 1;
111
        $this->expired_time = 0;
112
        $this->propagate_neg = 0;
113
        $this->saveCorrectAnswers = 0;
114
        $this->review_answers = false;
115
        $this->randomByCat = 0;
116
        $this->text_when_finished = '';
117
        $this->display_category_name = 0;
118
        $this->pass_percentage = 0;
119
        $this->modelType = 1;
120
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
121
        $this->endButton = 0;
122
        $this->scoreTypeModel = 0;
123
        $this->globalCategoryId = null;
124
        $this->notifications = [];
125
        $this->exerciseCategoryId = 0;
126
127
        if (!empty($courseId)) {
128
            $courseInfo = api_get_course_info_by_id($courseId);
129
        } else {
130
            $courseInfo = api_get_course_info();
131
        }
132
        $this->course_id = $courseInfo['real_id'];
133
        $this->course = $courseInfo;
134
        $this->sessionId = api_get_session_id();
135
136
        // ALTER TABLE c_quiz_question ADD COLUMN feedback text;
137
        $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback');
138
        $this->showPreviousButton = true;
139
    }
140
141
    /**
142
     * Reads exercise information from the data base.
143
     *
144
     * @author Olivier Brouckaert
145
     *
146
     * @param int  $id                - exercise Id
147
     * @param bool $parseQuestionList
148
     *
149
     * @return bool - true if exercise exists, otherwise false
150
     */
151
    public function read($id, $parseQuestionList = true)
152
    {
153
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
154
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
155
156
        $id = (int) $id;
157
        if (empty($this->course_id)) {
158
            return false;
159
        }
160
161
        $sql = "SELECT * FROM $table 
162
                WHERE c_id = ".$this->course_id." AND id = ".$id;
163
        $result = Database::query($sql);
164
165
        // if the exercise has been found
166
        if ($object = Database::fetch_object($result)) {
167
            $this->iId = $object->iid;
168
            $this->id = $id;
169
            $this->exercise = $object->title;
170
            $this->name = $object->title;
171
            $this->title = $object->title;
172
            $this->description = $object->description;
173
            $this->sound = $object->sound;
174
            $this->type = $object->type;
175
            if (empty($this->type)) {
176
                $this->type = ONE_PER_PAGE;
177
            }
178
            $this->random = $object->random;
179
            $this->random_answers = $object->random_answers;
180
            $this->active = $object->active;
181
            $this->results_disabled = $object->results_disabled;
182
            $this->attempts = $object->max_attempt;
183
            $this->feedback_type = $object->feedback_type;
184
            $this->sessionId = $object->session_id;
185
            $this->propagate_neg = $object->propagate_neg;
186
            $this->saveCorrectAnswers = $object->save_correct_answers;
187
            $this->randomByCat = $object->random_by_category;
188
            $this->text_when_finished = $object->text_when_finished;
189
            $this->display_category_name = $object->display_category_name;
190
            $this->pass_percentage = $object->pass_percentage;
191
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
192
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
193
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
194
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
195
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0;
196
            $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0;
197
            $this->exerciseCategoryId = isset($object->exercise_category_id) ? (int) $object->exercise_category_id : 0;
198
199
            $this->notifications = [];
200
            if (!empty($object->notifications)) {
201
                $this->notifications = explode(',', $object->notifications);
202
            }
203
204
            if (isset($object->show_previous_button)) {
205
                $this->showPreviousButton = $object->show_previous_button == 1 ? true : false;
206
            }
207
208
            $sql = "SELECT lp_id, max_score
209
                    FROM $tableLpItem
210
                    WHERE   
211
                        c_id = {$this->course_id} AND
212
                        item_type = '".TOOL_QUIZ."' AND
213
                        path = '".$id."'";
214
            $result = Database::query($sql);
215
216
            if (Database::num_rows($result) > 0) {
217
                $this->exercise_was_added_in_lp = true;
218
                $this->lpList = Database::store_result($result, 'ASSOC');
219
            }
220
221
            $this->force_edit_exercise_in_lp = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
222
223
            $this->edit_exercise_in_lp = true;
224
            if ($this->exercise_was_added_in_lp) {
225
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
226
            }
227
228
            if (!empty($object->end_time)) {
229
                $this->end_time = $object->end_time;
230
            }
231
            if (!empty($object->start_time)) {
232
                $this->start_time = $object->start_time;
233
            }
234
235
            // Control time
236
            $this->expired_time = $object->expired_time;
237
238
            // Checking if question_order is correctly set
239
            if ($parseQuestionList) {
240
                $this->setQuestionList(true);
241
            }
242
243
            //overload questions list with recorded questions list
244
            //load questions only for exercises of type 'one question per page'
245
            //this is needed only is there is no questions
246
247
            // @todo not sure were in the code this is used somebody mess with the exercise tool
248
            // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
249
            /*global $_configuration, $questionList;
250
            if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST'
251
                && defined('QUESTION_LIST_ALREADY_LOGGED') &&
252
                isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']
253
            ) {
254
                $this->questionList = $questionList;
255
            }*/
256
            return true;
257
        }
258
259
        return false;
260
    }
261
262
    /**
263
     * @return string
264
     */
265
    public function getCutTitle()
266
    {
267
        $title = $this->getUnformattedTitle();
268
269
        return cut($title, EXERCISE_MAX_NAME_SIZE);
270
    }
271
272
    /**
273
     * returns the exercise ID.
274
     *
275
     * @author Olivier Brouckaert
276
     *
277
     * @return int - exercise ID
278
     */
279
    public function selectId()
280
    {
281
        return $this->id;
282
    }
283
284
    /**
285
     * returns the exercise title.
286
     *
287
     * @author Olivier Brouckaert
288
     *
289
     * @param bool $unformattedText Optional. Get the title without HTML tags
290
     *
291
     * @return string - exercise title
292
     */
293
    public function selectTitle($unformattedText = false)
294
    {
295
        if ($unformattedText) {
296
            return $this->getUnformattedTitle();
297
        }
298
299
        return $this->exercise;
300
    }
301
302
    /**
303
     * returns the number of attempts setted.
304
     *
305
     * @return int - exercise attempts
306
     */
307
    public function selectAttempts()
308
    {
309
        return $this->attempts;
310
    }
311
312
    /** returns the number of FeedbackType  *
313
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback.
314
     *
315
     * @return int - exercise attempts
316
     */
317
    public function selectFeedbackType()
318
    {
319
        return $this->feedback_type;
320
    }
321
322
    /**
323
     * returns the time limit.
324
     *
325
     * @return int
326
     */
327
    public function selectTimeLimit()
328
    {
329
        return $this->timeLimit;
330
    }
331
332
    /**
333
     * returns the exercise description.
334
     *
335
     * @author Olivier Brouckaert
336
     *
337
     * @return string - exercise description
338
     */
339
    public function selectDescription()
340
    {
341
        return $this->description;
342
    }
343
344
    /**
345
     * returns the exercise sound file.
346
     *
347
     * @author Olivier Brouckaert
348
     *
349
     * @return string - exercise description
350
     */
351
    public function selectSound()
352
    {
353
        return $this->sound;
354
    }
355
356
    /**
357
     * returns the exercise type.
358
     *
359
     * @author Olivier Brouckaert
360
     *
361
     * @return int - exercise type
362
     */
363
    public function selectType()
364
    {
365
        return $this->type;
366
    }
367
368
    /**
369
     * @return int
370
     */
371
    public function getModelType()
372
    {
373
        return $this->modelType;
374
    }
375
376
    /**
377
     * @return int
378
     */
379
    public function selectEndButton()
380
    {
381
        return $this->endButton;
382
    }
383
384
    /**
385
     * @author hubert borderiou 30-11-11
386
     *
387
     * @return int : do we display the question category name for students
388
     */
389
    public function selectDisplayCategoryName()
390
    {
391
        return $this->display_category_name;
392
    }
393
394
    /**
395
     * @return int
396
     */
397
    public function selectPassPercentage()
398
    {
399
        return $this->pass_percentage;
400
    }
401
402
    /**
403
     * Modify object to update the switch display_category_name.
404
     *
405
     * @author hubert borderiou 30-11-11
406
     *
407
     * @param int $value is an integer 0 or 1
408
     */
409
    public function updateDisplayCategoryName($value)
410
    {
411
        $this->display_category_name = $value;
412
    }
413
414
    /**
415
     * @author hubert borderiou 28-11-11
416
     *
417
     * @return string html text : the text to display ay the end of the test
418
     */
419
    public function selectTextWhenFinished()
420
    {
421
        return $this->text_when_finished;
422
    }
423
424
    /**
425
     * @param string $text
426
     *
427
     * @author hubert borderiou 28-11-11
428
     */
429
    public function updateTextWhenFinished($text)
430
    {
431
        $this->text_when_finished = $text;
432
    }
433
434
    /**
435
     * return 1 or 2 if randomByCat.
436
     *
437
     * @author hubert borderiou
438
     *
439
     * @return int - quiz random by category
440
     */
441
    public function getRandomByCategory()
442
    {
443
        return $this->randomByCat;
444
    }
445
446
    /**
447
     * return 0 if no random by cat
448
     * return 1 if random by cat, categories shuffled
449
     * return 2 if random by cat, categories sorted by alphabetic order.
450
     *
451
     * @author hubert borderiou
452
     *
453
     * @return int - quiz random by category
454
     */
455
    public function isRandomByCat()
456
    {
457
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
458
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
459
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
460
        } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
461
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
462
        }
463
464
        return $res;
465
    }
466
467
    /**
468
     * return nothing
469
     * update randomByCat value for object.
470
     *
471
     * @param int $random
472
     *
473
     * @author hubert borderiou
474
     */
475
    public function updateRandomByCat($random)
476
    {
477
        $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
478
        if (in_array(
479
            $random,
480
            [
481
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
482
                EXERCISE_CATEGORY_RANDOM_ORDERED,
483
                EXERCISE_CATEGORY_RANDOM_DISABLED,
484
            ]
485
        )) {
486
            $this->randomByCat = $random;
487
        }
488
    }
489
490
    /**
491
     * Tells if questions are selected randomly, and if so returns the draws.
492
     *
493
     * @author Carlos Vargas
494
     *
495
     * @return int - results disabled exercise
496
     */
497
    public function selectResultsDisabled()
498
    {
499
        return $this->results_disabled;
500
    }
501
502
    /**
503
     * tells if questions are selected randomly, and if so returns the draws.
504
     *
505
     * @author Olivier Brouckaert
506
     *
507
     * @return bool
508
     */
509
    public function isRandom()
510
    {
511
        $isRandom = false;
512
        // "-1" means all questions will be random
513
        if ($this->random > 0 || $this->random == -1) {
514
            $isRandom = true;
515
        }
516
517
        return $isRandom;
518
    }
519
520
    /**
521
     * returns random answers status.
522
     *
523
     * @author Juan Carlos Rana
524
     */
525
    public function getRandomAnswers()
526
    {
527
        return $this->random_answers;
528
    }
529
530
    /**
531
     * Same as isRandom() but has a name applied to values different than 0 or 1.
532
     *
533
     * @return int
534
     */
535
    public function getShuffle()
536
    {
537
        return $this->random;
538
    }
539
540
    /**
541
     * returns the exercise status (1 = enabled ; 0 = disabled).
542
     *
543
     * @author Olivier Brouckaert
544
     *
545
     * @return int - 1 if enabled, otherwise 0
546
     */
547
    public function selectStatus()
548
    {
549
        return $this->active;
550
    }
551
552
    /**
553
     * If false the question list will be managed as always if true
554
     * the question will be filtered
555
     * depending of the exercise settings (table c_quiz_rel_category).
556
     *
557
     * @param bool $status active or inactive grouping
558
     */
559
    public function setCategoriesGrouping($status)
560
    {
561
        $this->categories_grouping = (bool) $status;
562
    }
563
564
    /**
565
     * @return int
566
     */
567
    public function getHideQuestionTitle()
568
    {
569
        return $this->hideQuestionTitle;
570
    }
571
572
    /**
573
     * @param $value
574
     */
575
    public function setHideQuestionTitle($value)
576
    {
577
        $this->hideQuestionTitle = (int) $value;
578
    }
579
580
    /**
581
     * @return int
582
     */
583
    public function getScoreTypeModel()
584
    {
585
        return $this->scoreTypeModel;
586
    }
587
588
    /**
589
     * @param int $value
590
     */
591
    public function setScoreTypeModel($value)
592
    {
593
        $this->scoreTypeModel = (int) $value;
594
    }
595
596
    /**
597
     * @return int
598
     */
599
    public function getGlobalCategoryId()
600
    {
601
        return $this->globalCategoryId;
602
    }
603
604
    /**
605
     * @param int $value
606
     */
607
    public function setGlobalCategoryId($value)
608
    {
609
        if (is_array($value) && isset($value[0])) {
610
            $value = $value[0];
611
        }
612
        $this->globalCategoryId = (int) $value;
613
    }
614
615
    /**
616
     * @param int    $start
617
     * @param int    $limit
618
     * @param int    $sidx
619
     * @param string $sord
620
     * @param array  $whereCondition
621
     * @param array  $extraFields
622
     *
623
     * @return array
624
     */
625
    public function getQuestionListPagination(
626
        $start,
627
        $limit,
628
        $sidx,
629
        $sord,
630
        $whereCondition = [],
631
        $extraFields = []
632
    ) {
633
        if (!empty($this->id)) {
634
            $category_list = TestCategory::getListOfCategoriesNameForTest(
635
                $this->id,
636
                false
637
            );
638
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
639
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
640
641
            $sql = "SELECT q.iid
642
                    FROM $TBL_EXERCICE_QUESTION e 
643
                    INNER JOIN $TBL_QUESTIONS  q
644
                    ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
645
					WHERE e.exercice_id	= '".$this->id."' ";
646
647
            $orderCondition = "ORDER BY question_order";
648
649
            if (!empty($sidx) && !empty($sord)) {
650
                if ($sidx == 'question') {
651
                    if (in_array(strtolower($sord), ['desc', 'asc'])) {
652
                        $orderCondition = " ORDER BY q.$sidx $sord";
653
                    }
654
                }
655
            }
656
657
            $sql .= $orderCondition;
658
            $limitCondition = null;
659
            if (isset($start) && isset($limit)) {
660
                $start = intval($start);
661
                $limit = intval($limit);
662
                $limitCondition = " LIMIT $start, $limit";
663
            }
664
            $sql .= $limitCondition;
665
            $result = Database::query($sql);
666
            $questions = [];
667
            if (Database::num_rows($result)) {
668
                if (!empty($extraFields)) {
669
                    $extraFieldValue = new ExtraFieldValue('question');
670
                }
671
                while ($question = Database::fetch_array($result, 'ASSOC')) {
672
                    /** @var Question $objQuestionTmp */
673
                    $objQuestionTmp = Question::read($question['iid']);
674
                    $category_labels = TestCategory::return_category_labels(
675
                        $objQuestionTmp->category_list,
676
                        $category_list
677
                    );
678
679
                    if (empty($category_labels)) {
680
                        $category_labels = "-";
681
                    }
682
683
                    // Question type
684
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
685
686
                    $question_media = null;
687
                    if (!empty($objQuestionTmp->parent_id)) {
688
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
689
                        $question_media = Question::getMediaLabel($objQuestionMedia->question);
690
                    }
691
692
                    $questionType = Display::tag(
693
                        'div',
694
                        Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media
695
                    );
696
697
                    $question = [
698
                        'id' => $question['iid'],
699
                        'question' => $objQuestionTmp->selectTitle(),
700
                        'type' => $questionType,
701
                        'category' => Display::tag(
702
                            'div',
703
                            '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'
704
                        ),
705
                        'score' => $objQuestionTmp->selectWeighting(),
706
                        'level' => $objQuestionTmp->level,
707
                    ];
708
709
                    if (!empty($extraFields)) {
710
                        foreach ($extraFields as $extraField) {
711
                            $value = $extraFieldValue->get_values_by_handler_and_field_id(
712
                                $question['id'],
713
                                $extraField['id']
714
                            );
715
                            $stringValue = null;
716
                            if ($value) {
717
                                $stringValue = $value['field_value'];
718
                            }
719
                            $question[$extraField['field_variable']] = $stringValue;
720
                        }
721
                    }
722
                    $questions[] = $question;
723
                }
724
            }
725
726
            return $questions;
727
        }
728
    }
729
730
    /**
731
     * Get question count per exercise from DB (any special treatment).
732
     *
733
     * @return int
734
     */
735
    public function getQuestionCount()
736
    {
737
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
738
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
739
        $sql = "SELECT count(q.id) as count
740
                FROM $TBL_EXERCICE_QUESTION e 
741
                INNER JOIN $TBL_QUESTIONS q
742
                ON (e.question_id = q.id AND e.c_id = q.c_id)
743
                WHERE 
744
                    e.c_id = {$this->course_id} AND 
745
                    e.exercice_id = ".$this->id;
746
        $result = Database::query($sql);
747
748
        $count = 0;
749
        if (Database::num_rows($result)) {
750
            $row = Database::fetch_array($result);
751
            $count = (int) $row['count'];
752
        }
753
754
        return $count;
755
    }
756
757
    /**
758
     * @return array
759
     */
760
    public function getQuestionOrderedListByName()
761
    {
762
        if (empty($this->course_id) || empty($this->id)) {
763
            return [];
764
        }
765
766
        $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
767
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
768
769
        // Getting question list from the order (question list drag n drop interface ).
770
        $sql = "SELECT e.question_id
771
                FROM $exerciseQuestionTable e 
772
                INNER JOIN $questionTable q
773
                ON (e.question_id= q.id AND e.c_id = q.c_id)
774
                WHERE 
775
                    e.c_id = {$this->course_id} AND 
776
                    e.exercice_id = '".$this->id."'
777
                ORDER BY q.question";
778
        $result = Database::query($sql);
779
        $list = [];
780
        if (Database::num_rows($result)) {
781
            $list = Database::store_result($result, 'ASSOC');
782
        }
783
784
        return $list;
785
    }
786
787
    /**
788
     * Selecting question list depending in the exercise-category
789
     * relationship (category table in exercise settings).
790
     *
791
     * @param array $question_list
792
     * @param int   $questionSelectionType
793
     *
794
     * @return array
795
     */
796
    public function getQuestionListWithCategoryListFilteredByCategorySettings(
797
        $question_list,
798
        $questionSelectionType
799
    ) {
800
        $result = [
801
            'question_list' => [],
802
            'category_with_questions_list' => [],
803
        ];
804
805
        // Order/random categories
806
        $cat = new TestCategory();
807
808
        // Setting category order.
809
        switch ($questionSelectionType) {
810
            case EX_Q_SELECTION_ORDERED: // 1
811
            case EX_Q_SELECTION_RANDOM:  // 2
812
                // This options are not allowed here.
813
                break;
814
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
815
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
816
                    $this,
817
                    $this->course['real_id'],
818
                    'title ASC',
819
                    false,
820
                    true
821
                );
822
823
                $questions_by_category = TestCategory::getQuestionsByCat(
824
                    $this->id,
825
                    $question_list,
826
                    $categoriesAddedInExercise
827
                );
828
829
                $question_list = $this->pickQuestionsPerCategory(
830
                    $categoriesAddedInExercise,
831
                    $question_list,
832
                    $questions_by_category,
833
                    true,
834
                    false
835
                );
836
                break;
837
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
838
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
839
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
840
                    $this,
841
                    $this->course['real_id'],
842
                    null,
843
                    true,
844
                    true
845
                );
846
                $questions_by_category = TestCategory::getQuestionsByCat(
847
                    $this->id,
848
                    $question_list,
849
                    $categoriesAddedInExercise
850
                );
851
                $question_list = $this->pickQuestionsPerCategory(
852
                    $categoriesAddedInExercise,
853
                    $question_list,
854
                    $questions_by_category,
855
                    true,
856
                    false
857
                );
858
                break;
859
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
860
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
861
                    $this,
862
                    $this->course['real_id'],
863
                    'title ASC',
864
                    false,
865
                    true
866
                );
867
                $questions_by_category = TestCategory::getQuestionsByCat(
868
                    $this->id,
869
                    $question_list,
870
                    $categoriesAddedInExercise
871
                );
872
                $question_list = $this->pickQuestionsPerCategory(
873
                    $categoriesAddedInExercise,
874
                    $question_list,
875
                    $questions_by_category,
876
                    true,
877
                    true
878
                );
879
                break;
880
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
881
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
882
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
883
                    $this,
884
                    $this->course['real_id'],
885
                    null,
886
                    true,
887
                    true
888
                );
889
890
                $questions_by_category = TestCategory::getQuestionsByCat(
891
                    $this->id,
892
                    $question_list,
893
                    $categoriesAddedInExercise
894
                );
895
896
                $question_list = $this->pickQuestionsPerCategory(
897
                    $categoriesAddedInExercise,
898
                    $question_list,
899
                    $questions_by_category,
900
                    true,
901
                    true
902
                );
903
                break;
904
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
905
                break;
906
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
907
                break;
908
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
909
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
910
                    $this,
911
                    $this->course['real_id'],
912
                    'root ASC, lft ASC',
913
                    false,
914
                    true
915
                );
916
                $questions_by_category = TestCategory::getQuestionsByCat(
917
                    $this->id,
918
                    $question_list,
919
                    $categoriesAddedInExercise
920
                );
921
                $question_list = $this->pickQuestionsPerCategory(
922
                    $categoriesAddedInExercise,
923
                    $question_list,
924
                    $questions_by_category,
925
                    true,
926
                    false
927
                );
928
                break;
929
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
930
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
931
                    $this,
932
                    $this->course['real_id'],
933
                    'root, lft ASC',
934
                    false,
935
                    true
936
                );
937
                $questions_by_category = TestCategory::getQuestionsByCat(
938
                    $this->id,
939
                    $question_list,
940
                    $categoriesAddedInExercise
941
                );
942
                $question_list = $this->pickQuestionsPerCategory(
943
                    $categoriesAddedInExercise,
944
                    $question_list,
945
                    $questions_by_category,
946
                    true,
947
                    true
948
                );
949
                break;
950
        }
951
952
        $result['question_list'] = isset($question_list) ? $question_list : [];
953
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : [];
954
        $parentsLoaded = [];
955
        // Adding category info in the category list with question list:
956
        if (!empty($questions_by_category)) {
957
            $newCategoryList = [];
958
            $em = Database::getManager();
959
960
            foreach ($questions_by_category as $categoryId => $questionList) {
961
                $cat = new TestCategory();
962
                $cat = $cat->getCategory($categoryId);
963
                if ($cat) {
964
                    $cat = (array) $cat;
965
                    $cat['iid'] = $cat['id'];
966
                }
967
968
                $categoryParentInfo = null;
969
                // Parent is not set no loop here
970
                if (isset($cat['parent_id']) && !empty($cat['parent_id'])) {
971
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryEntity */
972
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
973
                        $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
974
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
975
                    } else {
976
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
977
                    }
978
                    $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');
979
                    $path = $repo->getPath($categoryEntity);
980
981
                    $index = 0;
982
                    if ($this->categoryMinusOne) {
983
                        //$index = 1;
984
                    }
985
986
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent */
987
                    foreach ($path as $categoryParent) {
988
                        $visibility = $categoryParent->getVisibility();
989
                        if ($visibility == 0) {
990
                            $categoryParentId = $categoryId;
991
                            $categoryTitle = $cat['title'];
992
                            if (count($path) > 1) {
993
                                continue;
994
                            }
995
                        } else {
996
                            $categoryParentId = $categoryParent->getIid();
997
                            $categoryTitle = $categoryParent->getTitle();
998
                        }
999
1000
                        $categoryParentInfo['id'] = $categoryParentId;
1001
                        $categoryParentInfo['iid'] = $categoryParentId;
1002
                        $categoryParentInfo['parent_path'] = null;
1003
                        $categoryParentInfo['title'] = $categoryTitle;
1004
                        $categoryParentInfo['name'] = $categoryTitle;
1005
                        $categoryParentInfo['parent_id'] = null;
1006
                        break;
1007
                    }
1008
                }
1009
                $cat['parent_info'] = $categoryParentInfo;
1010
                $newCategoryList[$categoryId] = [
1011
                    'category' => $cat,
1012
                    'question_list' => $questionList,
1013
                ];
1014
            }
1015
1016
            $result['category_with_questions_list'] = $newCategoryList;
1017
        }
1018
1019
        return $result;
1020
    }
1021
1022
    /**
1023
     * returns the array with the question ID list.
1024
     *
1025
     * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory
1026
     * @param bool $adminView    Whether we should return all questions (admin view) or
1027
     *                           just a list limited by the max number of random questions
1028
     *
1029
     * @author Olivier Brouckaert
1030
     *
1031
     * @return array - question ID list
1032
     */
1033
    public function selectQuestionList($fromDatabase = false, $adminView = false)
1034
    {
1035
        if ($fromDatabase && !empty($this->id)) {
1036
            $nbQuestions = $this->getQuestionCount();
1037
            $questionSelectionType = $this->getQuestionSelectionType();
1038
1039
            switch ($questionSelectionType) {
1040
                case EX_Q_SELECTION_ORDERED:
1041
                    $questionList = $this->getQuestionOrderedList();
1042
                    break;
1043
                case EX_Q_SELECTION_RANDOM:
1044
                    // Not a random exercise, or if there are not at least 2 questions
1045
                    if ($this->random == 0 || $nbQuestions < 2) {
1046
                        $questionList = $this->getQuestionOrderedList();
1047
                    } else {
1048
                        $questionList = $this->getRandomList($adminView);
1049
                    }
1050
                    break;
1051
                default:
1052
                    $questionList = $this->getQuestionOrderedList();
1053
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1054
                        $questionList,
1055
                        $questionSelectionType
1056
                    );
1057
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1058
                    $questionList = $result['question_list'];
1059
                    break;
1060
            }
1061
1062
            return $questionList;
1063
        }
1064
1065
        return $this->questionList;
1066
    }
1067
1068
    /**
1069
     * returns the number of questions in this exercise.
1070
     *
1071
     * @author Olivier Brouckaert
1072
     *
1073
     * @return int - number of questions
1074
     */
1075
    public function selectNbrQuestions()
1076
    {
1077
        return count($this->questionList);
1078
    }
1079
1080
    /**
1081
     * @return int
1082
     */
1083
    public function selectPropagateNeg()
1084
    {
1085
        return $this->propagate_neg;
1086
    }
1087
1088
    /**
1089
     * @return int
1090
     */
1091
    public function selectSaveCorrectAnswers()
1092
    {
1093
        return $this->saveCorrectAnswers;
1094
    }
1095
1096
    /**
1097
     * Selects questions randomly in the question list.
1098
     *
1099
     * @author Olivier Brouckaert
1100
     * @author Hubert Borderiou 15 nov 2011
1101
     *
1102
     * @param bool $adminView Whether we should return all
1103
     *                        questions (admin view) or just a list limited by the max number of random questions
1104
     *
1105
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1106
     *               without randomizing, otherwise, returns the list with questions selected randomly
1107
     */
1108
    public function getRandomList($adminView = false)
1109
    {
1110
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1111
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1112
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1113
1114
        // Random with limit
1115
        $randomLimit = " ORDER BY RAND() LIMIT $random";
1116
1117
        // Random with no limit
1118
        if ($random == -1) {
1119
            $randomLimit = ' ORDER BY RAND() ';
1120
        }
1121
1122
        // Admin see the list in default order
1123
        if ($adminView === true) {
1124
            // If viewing it as admin for edition, don't show it randomly, use title + id
1125
            $randomLimit = 'ORDER BY e.question_order';
1126
        }
1127
1128
        $sql = "SELECT e.question_id
1129
                FROM $quizRelQuestion e 
1130
                INNER JOIN $question q
1131
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1132
                WHERE 
1133
                    e.c_id = {$this->course_id} AND 
1134
                    e.exercice_id = '".Database::escape_string($this->id)."'
1135
                    $randomLimit ";
1136
        $result = Database::query($sql);
1137
        $questionList = [];
1138
        while ($row = Database::fetch_object($result)) {
1139
            $questionList[] = $row->question_id;
1140
        }
1141
1142
        return $questionList;
1143
    }
1144
1145
    /**
1146
     * returns 'true' if the question ID is in the question list.
1147
     *
1148
     * @author Olivier Brouckaert
1149
     *
1150
     * @param int $questionId - question ID
1151
     *
1152
     * @return bool - true if in the list, otherwise false
1153
     */
1154
    public function isInList($questionId)
1155
    {
1156
        $inList = false;
1157
        if (is_array($this->questionList)) {
1158
            $inList = in_array($questionId, $this->questionList);
1159
        }
1160
1161
        return $inList;
1162
    }
1163
1164
    /**
1165
     * If current exercise has a question.
1166
     *
1167
     * @param int $questionId
1168
     *
1169
     * @return int
1170
     */
1171
    public function hasQuestion($questionId)
1172
    {
1173
        $questionId = (int) $questionId;
1174
1175
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1176
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1177
        $sql = "SELECT q.id
1178
                FROM $TBL_EXERCICE_QUESTION e 
1179
                INNER JOIN $TBL_QUESTIONS q
1180
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1181
                WHERE 
1182
                    q.id = $questionId AND
1183
                    e.c_id = {$this->course_id} AND 
1184
                    e.exercice_id = ".$this->id;
1185
1186
        $result = Database::query($sql);
1187
1188
        return Database::num_rows($result) > 0;
1189
    }
1190
1191
    /**
1192
     * changes the exercise title.
1193
     *
1194
     * @author Olivier Brouckaert
1195
     *
1196
     * @param string $title - exercise title
1197
     */
1198
    public function updateTitle($title)
1199
    {
1200
        $this->title = $this->exercise = $title;
1201
    }
1202
1203
    /**
1204
     * changes the exercise max attempts.
1205
     *
1206
     * @param int $attempts - exercise max attempts
1207
     */
1208
    public function updateAttempts($attempts)
1209
    {
1210
        $this->attempts = $attempts;
1211
    }
1212
1213
    /**
1214
     * changes the exercise feedback type.
1215
     *
1216
     * @param int $feedback_type
1217
     */
1218
    public function updateFeedbackType($feedback_type)
1219
    {
1220
        $this->feedback_type = $feedback_type;
1221
    }
1222
1223
    /**
1224
     * changes the exercise description.
1225
     *
1226
     * @author Olivier Brouckaert
1227
     *
1228
     * @param string $description - exercise description
1229
     */
1230
    public function updateDescription($description)
1231
    {
1232
        $this->description = $description;
1233
    }
1234
1235
    /**
1236
     * changes the exercise expired_time.
1237
     *
1238
     * @author Isaac flores
1239
     *
1240
     * @param int $expired_time The expired time of the quiz
1241
     */
1242
    public function updateExpiredTime($expired_time)
1243
    {
1244
        $this->expired_time = $expired_time;
1245
    }
1246
1247
    /**
1248
     * @param $value
1249
     */
1250
    public function updatePropagateNegative($value)
1251
    {
1252
        $this->propagate_neg = $value;
1253
    }
1254
1255
    /**
1256
     * @param $value int
1257
     */
1258
    public function updateSaveCorrectAnswers($value)
1259
    {
1260
        $this->saveCorrectAnswers = $value;
1261
    }
1262
1263
    /**
1264
     * @param $value
1265
     */
1266
    public function updateReviewAnswers($value)
1267
    {
1268
        $this->review_answers = isset($value) && $value ? true : false;
1269
    }
1270
1271
    /**
1272
     * @param $value
1273
     */
1274
    public function updatePassPercentage($value)
1275
    {
1276
        $this->pass_percentage = $value;
1277
    }
1278
1279
    /**
1280
     * @param string $text
1281
     */
1282
    public function updateEmailNotificationTemplate($text)
1283
    {
1284
        $this->emailNotificationTemplate = $text;
1285
    }
1286
1287
    /**
1288
     * @param string $text
1289
     */
1290
    public function setEmailNotificationTemplateToUser($text)
1291
    {
1292
        $this->emailNotificationTemplateToUser = $text;
1293
    }
1294
1295
    /**
1296
     * @param string $value
1297
     */
1298
    public function setNotifyUserByEmail($value)
1299
    {
1300
        $this->notifyUserByEmail = $value;
1301
    }
1302
1303
    /**
1304
     * @param int $value
1305
     */
1306
    public function updateEndButton($value)
1307
    {
1308
        $this->endButton = (int) $value;
1309
    }
1310
1311
    /**
1312
     * @param string $value
1313
     */
1314
    public function setOnSuccessMessage($value)
1315
    {
1316
        $this->onSuccessMessage = $value;
1317
    }
1318
1319
    /**
1320
     * @param string $value
1321
     */
1322
    public function setOnFailedMessage($value)
1323
    {
1324
        $this->onFailedMessage = $value;
1325
    }
1326
1327
    /**
1328
     * @param $value
1329
     */
1330
    public function setModelType($value)
1331
    {
1332
        $this->modelType = (int) $value;
1333
    }
1334
1335
    /**
1336
     * @param int $value
1337
     */
1338
    public function setQuestionSelectionType($value)
1339
    {
1340
        $this->questionSelectionType = (int) $value;
1341
    }
1342
1343
    /**
1344
     * @return int
1345
     */
1346
    public function getQuestionSelectionType()
1347
    {
1348
        return (int) $this->questionSelectionType;
1349
    }
1350
1351
    /**
1352
     * @param array $categories
1353
     */
1354
    public function updateCategories($categories)
1355
    {
1356
        if (!empty($categories)) {
1357
            $categories = array_map('intval', $categories);
1358
            $this->categories = $categories;
1359
        }
1360
    }
1361
1362
    /**
1363
     * changes the exercise sound file.
1364
     *
1365
     * @author Olivier Brouckaert
1366
     *
1367
     * @param string $sound  - exercise sound file
1368
     * @param string $delete - ask to delete the file
1369
     */
1370
    public function updateSound($sound, $delete)
1371
    {
1372
        global $audioPath, $documentPath;
1373
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1374
1375
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1376
            $this->sound = $sound['name'];
1377
1378
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1379
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1380
                        WHERE 
1381
                            c_id = ".$this->course_id." AND 
1382
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1383
                $result = Database::query($sql);
1384
1385
                if (!Database::num_rows($result)) {
1386
                    DocumentManager::addDocument(
1387
                        $this->course,
1388
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1389
                        'file',
1390
                        $sound['size'],
1391
                        $sound['name']
1392
                    );
1393
                }
1394
            }
1395
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1396
            $this->sound = '';
1397
        }
1398
    }
1399
1400
    /**
1401
     * changes the exercise type.
1402
     *
1403
     * @author Olivier Brouckaert
1404
     *
1405
     * @param int $type - exercise type
1406
     */
1407
    public function updateType($type)
1408
    {
1409
        $this->type = $type;
1410
    }
1411
1412
    /**
1413
     * sets to 0 if questions are not selected randomly
1414
     * if questions are selected randomly, sets the draws.
1415
     *
1416
     * @author Olivier Brouckaert
1417
     *
1418
     * @param int $random - 0 if not random, otherwise the draws
1419
     */
1420
    public function setRandom($random)
1421
    {
1422
        $this->random = $random;
1423
    }
1424
1425
    /**
1426
     * sets to 0 if answers are not selected randomly
1427
     * if answers are selected randomly.
1428
     *
1429
     * @author Juan Carlos Rana
1430
     *
1431
     * @param int $random_answers - random answers
1432
     */
1433
    public function updateRandomAnswers($random_answers)
1434
    {
1435
        $this->random_answers = $random_answers;
1436
    }
1437
1438
    /**
1439
     * enables the exercise.
1440
     *
1441
     * @author Olivier Brouckaert
1442
     */
1443
    public function enable()
1444
    {
1445
        $this->active = 1;
1446
    }
1447
1448
    /**
1449
     * disables the exercise.
1450
     *
1451
     * @author Olivier Brouckaert
1452
     */
1453
    public function disable()
1454
    {
1455
        $this->active = 0;
1456
    }
1457
1458
    /**
1459
     * Set disable results.
1460
     */
1461
    public function disable_results()
1462
    {
1463
        $this->results_disabled = true;
1464
    }
1465
1466
    /**
1467
     * Enable results.
1468
     */
1469
    public function enable_results()
1470
    {
1471
        $this->results_disabled = false;
1472
    }
1473
1474
    /**
1475
     * @param int $results_disabled
1476
     */
1477
    public function updateResultsDisabled($results_disabled)
1478
    {
1479
        $this->results_disabled = (int) $results_disabled;
1480
    }
1481
1482
    /**
1483
     * updates the exercise in the data base.
1484
     *
1485
     * @param string $type_e
1486
     *
1487
     * @author Olivier Brouckaert
1488
     */
1489
    public function save($type_e = '')
1490
    {
1491
        $_course = $this->course;
1492
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1493
1494
        $id = $this->id;
1495
        $exercise = $this->exercise;
1496
        $description = $this->description;
1497
        $sound = $this->sound;
1498
        $type = $this->type;
1499
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1500
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1501
        $random = $this->random;
1502
        $random_answers = $this->random_answers;
1503
        $active = $this->active;
1504
        $propagate_neg = (int) $this->propagate_neg;
1505
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1506
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1507
        $randomByCat = (int) $this->randomByCat;
1508
        $text_when_finished = $this->text_when_finished;
1509
        $display_category_name = (int) $this->display_category_name;
1510
        $pass_percentage = (int) $this->pass_percentage;
1511
        $session_id = $this->sessionId;
1512
1513
        // If direct we do not show results
1514
        $results_disabled = (int) $this->results_disabled;
1515
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1516
            $results_disabled = 0;
1517
        }
1518
        $expired_time = (int) $this->expired_time;
1519
1520
        // Exercise already exists
1521
        if ($id) {
1522
            // we prepare date in the database using the api_get_utc_datetime() function
1523
            $start_time = null;
1524
            if (!empty($this->start_time)) {
1525
                $start_time = $this->start_time;
1526
            }
1527
1528
            $end_time = null;
1529
            if (!empty($this->end_time)) {
1530
                $end_time = $this->end_time;
1531
            }
1532
1533
            $params = [
1534
                'title' => $exercise,
1535
                'description' => $description,
1536
            ];
1537
1538
            $paramsExtra = [];
1539
            if ($type_e != 'simple') {
1540
                $paramsExtra = [
1541
                    'sound' => $sound,
1542
                    'type' => $type,
1543
                    'random' => $random,
1544
                    'random_answers' => $random_answers,
1545
                    'active' => $active,
1546
                    'feedback_type' => $feedback_type,
1547
                    'start_time' => $start_time,
1548
                    'end_time' => $end_time,
1549
                    'max_attempt' => $attempts,
1550
                    'expired_time' => $expired_time,
1551
                    'propagate_neg' => $propagate_neg,
1552
                    'save_correct_answers' => $saveCorrectAnswers,
1553
                    'review_answers' => $review_answers,
1554
                    'random_by_category' => $randomByCat,
1555
                    'text_when_finished' => $text_when_finished,
1556
                    'display_category_name' => $display_category_name,
1557
                    'pass_percentage' => $pass_percentage,
1558
                    'results_disabled' => $results_disabled,
1559
                    'question_selection_type' => $this->getQuestionSelectionType(),
1560
                    'hide_question_title' => $this->getHideQuestionTitle(),
1561
                ];
1562
1563
                $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1564
                if ($allow === true) {
1565
                    $paramsExtra['show_previous_button'] = $this->showPreviousButton();
1566
                }
1567
1568
                $allow = api_get_configuration_value('allow_exercise_categories');
1569
                if ($allow === true) {
1570
                    $paramsExtra['exercise_category_id'] = $this->getExerciseCategoryId();
1571
                }
1572
1573
                $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1574
                if ($allow === true) {
1575
                    $notifications = $this->getNotifications();
1576
                    $notifications = implode(',', $notifications);
1577
                    $paramsExtra['notifications'] = $notifications;
1578
                }
1579
            }
1580
1581
            $params = array_merge($params, $paramsExtra);
1582
1583
            Database::update(
1584
                $TBL_EXERCISES,
1585
                $params,
1586
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1587
            );
1588
1589
            // update into the item_property table
1590
            api_item_property_update(
1591
                $_course,
1592
                TOOL_QUIZ,
1593
                $id,
1594
                'QuizUpdated',
1595
                api_get_user_id()
1596
            );
1597
1598
            if (api_get_setting('search_enabled') == 'true') {
1599
                $this->search_engine_edit();
1600
            }
1601
        } else {
1602
            // Creates a new exercise
1603
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1604
            // for date because, bellow, we call function api_set_default_visibility()
1605
            // In this function, api_set_default_visibility,
1606
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1607
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1608
            $start_time = null;
1609
            if (!empty($this->start_time)) {
1610
                $start_time = $this->start_time;
1611
            }
1612
1613
            $end_time = null;
1614
            if (!empty($this->end_time)) {
1615
                $end_time = $this->end_time;
1616
            }
1617
1618
            $params = [
1619
                'c_id' => $this->course_id,
1620
                'start_time' => $start_time,
1621
                'end_time' => $end_time,
1622
                'title' => $exercise,
1623
                'description' => $description,
1624
                'sound' => $sound,
1625
                'type' => $type,
1626
                'random' => $random,
1627
                'random_answers' => $random_answers,
1628
                'active' => $active,
1629
                'results_disabled' => $results_disabled,
1630
                'max_attempt' => $attempts,
1631
                'feedback_type' => $feedback_type,
1632
                'expired_time' => $expired_time,
1633
                'session_id' => $session_id,
1634
                'review_answers' => $review_answers,
1635
                'random_by_category' => $randomByCat,
1636
                'text_when_finished' => $text_when_finished,
1637
                'display_category_name' => $display_category_name,
1638
                'pass_percentage' => $pass_percentage,
1639
                'save_correct_answers' => (int) $saveCorrectAnswers,
1640
                'propagate_neg' => $propagate_neg,
1641
                'hide_question_title' => $this->getHideQuestionTitle(),
1642
            ];
1643
1644
            $allow = api_get_configuration_value('allow_exercise_categories');
1645
            if ($allow === true) {
1646
                $params['exercise_category_id'] = $this->getExerciseCategoryId();
1647
            }
1648
1649
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1650
            if ($allow === true) {
1651
                $params['show_previous_button'] = $this->showPreviousButton();
1652
            }
1653
1654
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1655
            if ($allow === true) {
1656
                $notifications = $this->getNotifications();
1657
                $params['notifications'] = '';
1658
                if (!empty($notifications)) {
1659
                    $notifications = implode(',', $notifications);
1660
                    $params['notifications'] = $notifications;
1661
                }
1662
            }
1663
1664
            $this->id = $this->iId = Database::insert($TBL_EXERCISES, $params);
1665
1666
            if ($this->id) {
1667
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1668
                Database::query($sql);
1669
1670
                $sql = "UPDATE $TBL_EXERCISES
1671
                        SET question_selection_type= ".$this->getQuestionSelectionType()."
1672
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1673
                Database::query($sql);
1674
1675
                // insert into the item_property table
1676
                api_item_property_update(
1677
                    $this->course,
1678
                    TOOL_QUIZ,
1679
                    $this->id,
1680
                    'QuizAdded',
1681
                    api_get_user_id()
1682
                );
1683
1684
                // This function save the quiz again, carefull about start_time
1685
                // and end_time if you remove this line (see above)
1686
                api_set_default_visibility(
1687
                    $this->id,
1688
                    TOOL_QUIZ,
1689
                    null,
1690
                    $this->course
1691
                );
1692
1693
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1694
                    $this->search_engine_save();
1695
                }
1696
            }
1697
        }
1698
1699
        $this->save_categories_in_exercise($this->categories);
1700
1701
        // Updates the question position
1702
        $this->update_question_positions();
1703
1704
        return $this->iId;
1705
    }
1706
1707
    /**
1708
     * Updates question position.
1709
     *
1710
     * @return bool
1711
     */
1712
    public function update_question_positions()
1713
    {
1714
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1715
        // Fixes #3483 when updating order
1716
        $questionList = $this->selectQuestionList(true);
1717
1718
        $this->id = (int) $this->id;
1719
1720
        if (empty($this->id)) {
1721
            return false;
1722
        }
1723
1724
        if (!empty($questionList)) {
1725
            foreach ($questionList as $position => $questionId) {
1726
                $position = (int) $position;
1727
                $questionId = (int) $questionId;
1728
                $sql = "UPDATE $table SET
1729
                            question_order ='".$position."'
1730
                        WHERE
1731
                            c_id = ".$this->course_id." AND
1732
                            question_id = ".$questionId." AND
1733
                            exercice_id=".$this->id;
1734
                Database::query($sql);
1735
            }
1736
        }
1737
1738
        return true;
1739
    }
1740
1741
    /**
1742
     * Adds a question into the question list.
1743
     *
1744
     * @author Olivier Brouckaert
1745
     *
1746
     * @param int $questionId - question ID
1747
     *
1748
     * @return bool - true if the question has been added, otherwise false
1749
     */
1750
    public function addToList($questionId)
1751
    {
1752
        // checks if the question ID is not in the list
1753
        if (!$this->isInList($questionId)) {
1754
            // selects the max position
1755
            if (!$this->selectNbrQuestions()) {
1756
                $pos = 1;
1757
            } else {
1758
                if (is_array($this->questionList)) {
1759
                    $pos = max(array_keys($this->questionList)) + 1;
1760
                }
1761
            }
1762
            $this->questionList[$pos] = $questionId;
1763
1764
            return true;
1765
        }
1766
1767
        return false;
1768
    }
1769
1770
    /**
1771
     * removes a question from the question list.
1772
     *
1773
     * @author Olivier Brouckaert
1774
     *
1775
     * @param int $questionId - question ID
1776
     *
1777
     * @return bool - true if the question has been removed, otherwise false
1778
     */
1779
    public function removeFromList($questionId)
1780
    {
1781
        // searches the position of the question ID in the list
1782
        $pos = array_search($questionId, $this->questionList);
1783
        // question not found
1784
        if ($pos === false) {
1785
            return false;
1786
        } else {
1787
            // dont reduce the number of random question if we use random by category option, or if
1788
            // random all questions
1789
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1790
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1791
                    $this->random--;
1792
                    $this->save();
1793
                }
1794
            }
1795
            // deletes the position from the array containing the wanted question ID
1796
            unset($this->questionList[$pos]);
1797
1798
            return true;
1799
        }
1800
    }
1801
1802
    /**
1803
     * deletes the exercise from the database
1804
     * Notice : leaves the question in the data base.
1805
     *
1806
     * @author Olivier Brouckaert
1807
     */
1808
    public function delete()
1809
    {
1810
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
1811
1812
        if ($limitTeacherAccess && !api_is_platform_admin()) {
1813
            return false;
1814
        }
1815
1816
        $locked = api_resource_is_locked_by_gradebook(
1817
            $this->id,
1818
            LINK_EXERCISE
1819
        );
1820
1821
        if ($locked) {
1822
            return false;
1823
        }
1824
1825
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1826
        $sql = "UPDATE $table SET active='-1'
1827
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1828
        Database::query($sql);
1829
1830
        api_item_property_update(
1831
            $this->course,
1832
            TOOL_QUIZ,
1833
            $this->id,
1834
            'QuizDeleted',
1835
            api_get_user_id()
1836
        );
1837
        api_item_property_update(
1838
            $this->course,
1839
            TOOL_QUIZ,
1840
            $this->id,
1841
            'delete',
1842
            api_get_user_id()
1843
        );
1844
1845
        Skill::deleteSkillsFromItem($this->iId, ITEM_TYPE_EXERCISE);
1846
1847
        if (api_get_setting('search_enabled') === 'true' &&
1848
            extension_loaded('xapian')
1849
        ) {
1850
            $this->search_engine_delete();
1851
        }
1852
1853
        $linkInfo = GradebookUtils::isResourceInCourseGradebook(
1854
            $this->course['code'],
1855
            LINK_EXERCISE,
1856
            $this->id,
1857
            $this->sessionId
1858
        );
1859
        if ($linkInfo !== false) {
1860
            GradebookUtils::remove_resource_from_course_gradebook($linkInfo['id']);
1861
        }
1862
1863
        return true;
1864
    }
1865
1866
    /**
1867
     * Creates the form to create / edit an exercise.
1868
     *
1869
     * @param FormValidator $form
1870
     * @param string        $type
1871
     */
1872
    public function createForm($form, $type = 'full')
1873
    {
1874
        if (empty($type)) {
1875
            $type = 'full';
1876
        }
1877
1878
        // form title
1879
        if (!empty($_GET['exerciseId'])) {
1880
            $form_title = get_lang('ModifyExercise');
1881
        } else {
1882
            $form_title = get_lang('NewEx');
1883
        }
1884
1885
        $form->addElement('header', $form_title);
1886
1887
        // Title.
1888
        if (api_get_configuration_value('save_titles_as_html')) {
1889
            $form->addHtmlEditor(
1890
                'exerciseTitle',
1891
                get_lang('ExerciseName'),
1892
                false,
1893
                false,
1894
                ['ToolbarSet' => 'Minimal']
1895
            );
1896
        } else {
1897
            $form->addElement(
1898
                'text',
1899
                'exerciseTitle',
1900
                get_lang('ExerciseName'),
1901
                ['id' => 'exercise_title']
1902
            );
1903
        }
1904
1905
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1906
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1907
1908
        if (api_get_configuration_value('allow_exercise_categories')) {
1909
            $categoryManager = new ExerciseCategoryManager();
1910
            $categories = $categoryManager->getCategories(api_get_course_int_id());
1911
            $options = [];
1912
            if (!empty($categories)) {
1913
                /** @var \Chamilo\CourseBundle\Entity\CExerciseCategory $category */
1914
                foreach ($categories as $category) {
1915
                    $options[$category->getId()] = $category->getName();
1916
                }
1917
            }
1918
1919
            $form->addSelect(
1920
                'exercise_category_id',
1921
                get_lang('Category'),
1922
                $options,
1923
                ['placeholder' => get_lang('SelectAnOption')]
1924
            );
1925
        }
1926
1927
        $editor_config = [
1928
            'ToolbarSet' => 'TestQuestionDescription',
1929
            'Width' => '100%',
1930
            'Height' => '150',
1931
        ];
1932
        if (is_array($type)) {
1933
            $editor_config = array_merge($editor_config, $type);
1934
        }
1935
1936
        $form->addHtmlEditor(
1937
            'exerciseDescription',
1938
            get_lang('ExerciseDescription'),
1939
            false,
1940
            false,
1941
            $editor_config
1942
        );
1943
1944
        $skillList = [];
1945
        if ($type === 'full') {
1946
            //Can't modify a DirectFeedback question
1947
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1948
                // feedback type
1949
                $radios_feedback = [];
1950
                $radios_feedback[] = $form->createElement(
1951
                    'radio',
1952
                    'exerciseFeedbackType',
1953
                    null,
1954
                    get_lang('ExerciseAtTheEndOfTheTest'),
1955
                    '0',
1956
                    [
1957
                        'id' => 'exerciseType_0',
1958
                        'onclick' => 'check_feedback()',
1959
                    ]
1960
                );
1961
1962
                if (api_get_setting('enable_quiz_scenario') === 'true') {
1963
                    // Can't convert a question from one feedback to another
1964
                    // if there is more than 1 question already added
1965
                    if ($this->selectNbrQuestions() == 0) {
1966
                        $radios_feedback[] = $form->createElement(
1967
                            'radio',
1968
                            'exerciseFeedbackType',
1969
                            null,
1970
                            get_lang('DirectFeedback'),
1971
                            '1',
1972
                            [
1973
                                'id' => 'exerciseType_1',
1974
                                'onclick' => 'check_direct_feedback()',
1975
                            ]
1976
                        );
1977
                    }
1978
                }
1979
1980
                $radios_feedback[] = $form->createElement(
1981
                    'radio',
1982
                    'exerciseFeedbackType',
1983
                    null,
1984
                    get_lang('NoFeedback'),
1985
                    '2',
1986
                    ['id' => 'exerciseType_2']
1987
                );
1988
                $form->addGroup(
1989
                    $radios_feedback,
1990
                    null,
1991
                    [
1992
                        get_lang('FeedbackType'),
1993
                        get_lang('FeedbackDisplayOptions'),
1994
                    ]
1995
                );
1996
1997
                // Type of results display on the final page
1998
                $this->setResultDisabledGroup($form);
1999
2000
                // Type of questions disposition on page
2001
                $radios = [];
2002
                $radios[] = $form->createElement(
2003
                    'radio',
2004
                    'exerciseType',
2005
                    null,
2006
                    get_lang('SimpleExercise'),
2007
                    '1',
2008
                    [
2009
                        'onclick' => 'check_per_page_all()',
2010
                        'id' => 'option_page_all',
2011
                    ]
2012
                );
2013
                $radios[] = $form->createElement(
2014
                    'radio',
2015
                    'exerciseType',
2016
                    null,
2017
                    get_lang('SequentialExercise'),
2018
                    '2',
2019
                    [
2020
                        'onclick' => 'check_per_page_one()',
2021
                        'id' => 'option_page_one',
2022
                    ]
2023
                );
2024
2025
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2026
            } else {
2027
                // if is Direct feedback but has not questions we can allow to modify the question type
2028
                if ($this->getQuestionCount() === 0) {
2029
                    // feedback type
2030
                    $radios_feedback = [];
2031
                    $radios_feedback[] = $form->createElement(
2032
                        'radio',
2033
                        'exerciseFeedbackType',
2034
                        null,
2035
                        get_lang('ExerciseAtTheEndOfTheTest'),
2036
                        '0',
2037
                        ['id' => 'exerciseType_0', 'onclick' => 'check_feedback()']
2038
                    );
2039
2040
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
2041
                        $radios_feedback[] = $form->createElement(
2042
                            'radio',
2043
                            'exerciseFeedbackType',
2044
                            null,
2045
                            get_lang('DirectFeedback'),
2046
                            '1',
2047
                            ['id' => 'exerciseType_1', 'onclick' => 'check_direct_feedback()']
2048
                        );
2049
                    }
2050
                    $radios_feedback[] = $form->createElement(
2051
                        'radio',
2052
                        'exerciseFeedbackType',
2053
                        null,
2054
                        get_lang('NoFeedback'),
2055
                        '2',
2056
                        ['id' => 'exerciseType_2']
2057
                    );
2058
                    $form->addGroup(
2059
                        $radios_feedback,
2060
                        null,
2061
                        [get_lang('FeedbackType'), get_lang('FeedbackDisplayOptions')]
2062
                    );
2063
2064
                    $this->setResultDisabledGroup($form);
2065
2066
                    // Type of questions disposition on page
2067
                    $radios = [];
2068
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
2069
                    $radios[] = $form->createElement(
2070
                        'radio',
2071
                        'exerciseType',
2072
                        null,
2073
                        get_lang('SequentialExercise'),
2074
                        '2'
2075
                    );
2076
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2077
                } else {
2078
                    $group = $this->setResultDisabledGroup($form);
2079
                    $group->freeze();
2080
2081
                    // we force the options to the DirectFeedback exercisetype
2082
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2083
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2084
2085
                    // Type of questions disposition on page
2086
                    $radios[] = $form->createElement(
2087
                        'radio',
2088
                        'exerciseType',
2089
                        null,
2090
                        get_lang('SimpleExercise'),
2091
                        '1',
2092
                        [
2093
                            'onclick' => 'check_per_page_all()',
2094
                            'id' => 'option_page_all',
2095
                        ]
2096
                    );
2097
                    $radios[] = $form->createElement(
2098
                        'radio',
2099
                        'exerciseType',
2100
                        null,
2101
                        get_lang('SequentialExercise'),
2102
                        '2',
2103
                        [
2104
                            'onclick' => 'check_per_page_one()',
2105
                            'id' => 'option_page_one',
2106
                        ]
2107
                    );
2108
2109
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2110
                    $type_group->freeze();
2111
                }
2112
            }
2113
2114
            $option = [
2115
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2116
                //  Defined by user
2117
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2118
                // 1-10, All
2119
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2120
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2121
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2122
                // A 123 B 456 C 78 (0, 1, all)
2123
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2124
                // C 78 B 456 A 123
2125
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2126
                // A 321 B 654 C 87
2127
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2128
                // C 87 B 654 A 321
2129
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2130
                /*    B 456 C 78 A 123
2131
                        456 78 123
2132
                        123 456 78
2133
                */
2134
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2135
                /*
2136
                    A 123 B 456 C 78
2137
                    B 456 C 78 A 123
2138
                    B 654 C 87 A 321
2139
                    654 87 321
2140
                    165 842 73
2141
                */
2142
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2143
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2144
            ];
2145
2146
            $form->addElement(
2147
                'select',
2148
                'question_selection_type',
2149
                [get_lang('QuestionSelection')],
2150
                $option,
2151
                [
2152
                    'id' => 'questionSelection',
2153
                    'onchange' => 'checkQuestionSelection()',
2154
                ]
2155
            );
2156
2157
            $displayMatrix = 'none';
2158
            $displayRandom = 'none';
2159
            $selectionType = $this->getQuestionSelectionType();
2160
            switch ($selectionType) {
2161
                case EX_Q_SELECTION_RANDOM:
2162
                    $displayRandom = 'block';
2163
                    break;
2164
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2165
                    $displayMatrix = 'block';
2166
                    break;
2167
            }
2168
2169
            $form->addElement(
2170
                'html',
2171
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2172
            );
2173
            // Number of random question.
2174
            $max = ($this->id > 0) ? $this->getQuestionCount() : 10;
2175
            $option = range(0, $max);
2176
            $option[0] = get_lang('No');
2177
            $option[-1] = get_lang('AllQuestionsShort');
2178
            $form->addElement(
2179
                'select',
2180
                'randomQuestions',
2181
                [
2182
                    get_lang('RandomQuestions'),
2183
                    get_lang('RandomQuestionsHelp'),
2184
                ],
2185
                $option,
2186
                ['id' => 'randomQuestions']
2187
            );
2188
            $form->addElement('html', '</div>');
2189
2190
            $form->addElement(
2191
                'html',
2192
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2193
            );
2194
2195
            // Category selection.
2196
            $cat = new TestCategory();
2197
            $cat_form = $cat->returnCategoryForm($this);
2198
            if (empty($cat_form)) {
2199
                $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>';
2200
            }
2201
            $form->addElement('label', null, $cat_form);
2202
            $form->addElement('html', '</div>');
2203
2204
            // Category name.
2205
            $radio_display_cat_name = [
2206
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2207
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'),
2208
            ];
2209
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2210
2211
            // Random answers.
2212
            $radios_random_answers = [
2213
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2214
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'),
2215
            ];
2216
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2217
2218
            // Hide question title.
2219
            $group = [
2220
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2221
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'),
2222
            ];
2223
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2224
2225
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
2226
2227
            if ($allow === true) {
2228
                // Hide question title.
2229
                $group = [
2230
                    $form->createElement(
2231
                        'radio',
2232
                        'show_previous_button',
2233
                        null,
2234
                        get_lang('Yes'),
2235
                        '1'
2236
                    ),
2237
                    $form->createElement(
2238
                        'radio',
2239
                        'show_previous_button',
2240
                        null,
2241
                        get_lang('No'),
2242
                        '0'
2243
                    ),
2244
                ];
2245
                $form->addGroup($group, null, get_lang('ShowPreviousButton'));
2246
            }
2247
2248
            // Attempts
2249
            $attempt_option = range(0, 10);
2250
            $attempt_option[0] = get_lang('Infinite');
2251
2252
            $form->addElement(
2253
                'select',
2254
                'exerciseAttempts',
2255
                get_lang('ExerciseAttempts'),
2256
                $attempt_option,
2257
                ['id' => 'exerciseAttempts']
2258
            );
2259
2260
            // Exercise time limit
2261
            $form->addElement(
2262
                'checkbox',
2263
                'activate_start_date_check',
2264
                null,
2265
                get_lang('EnableStartTime'),
2266
                ['onclick' => 'activate_start_date()']
2267
            );
2268
2269
            if (!empty($this->start_time)) {
2270
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2271
            } else {
2272
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2273
            }
2274
2275
            $form->addElement('date_time_picker', 'start_time');
2276
            $form->addElement('html', '</div>');
2277
            $form->addElement(
2278
                'checkbox',
2279
                'activate_end_date_check',
2280
                null,
2281
                get_lang('EnableEndTime'),
2282
                ['onclick' => 'activate_end_date()']
2283
            );
2284
2285
            if (!empty($this->end_time)) {
2286
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2287
            } else {
2288
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2289
            }
2290
2291
            $form->addElement('date_time_picker', 'end_time');
2292
            $form->addElement('html', '</div>');
2293
2294
            $display = 'block';
2295
            $form->addElement(
2296
                'checkbox',
2297
                'propagate_neg',
2298
                null,
2299
                get_lang('PropagateNegativeResults')
2300
            );
2301
            $form->addCheckBox(
2302
                'save_correct_answers',
2303
                null,
2304
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2305
            );
2306
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2307
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2308
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2309
2310
            // Timer control
2311
            $form->addElement(
2312
                'checkbox',
2313
                'enabletimercontrol',
2314
                null,
2315
                get_lang('EnableTimerControl'),
2316
                [
2317
                    'onclick' => 'option_time_expired()',
2318
                    'id' => 'enabletimercontrol',
2319
                    'onload' => 'check_load_time()',
2320
                ]
2321
            );
2322
2323
            $expired_date = (int) $this->selectExpiredTime();
2324
2325
            if (($expired_date != '0')) {
2326
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2327
            } else {
2328
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2329
            }
2330
            $form->addText(
2331
                'enabletimercontroltotalminutes',
2332
                get_lang('ExerciseTotalDurationInMinutes'),
2333
                false,
2334
                [
2335
                    'id' => 'enabletimercontroltotalminutes',
2336
                    'cols-size' => [2, 2, 8],
2337
                ]
2338
            );
2339
            $form->addElement('html', '</div>');
2340
            $form->addElement(
2341
                'text',
2342
                'pass_percentage',
2343
                [get_lang('PassPercentage'), null, '%'],
2344
                ['id' => 'pass_percentage']
2345
            );
2346
2347
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2348
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2349
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2350
2351
            // add the text_when_finished textbox
2352
            $form->addHtmlEditor(
2353
                'text_when_finished',
2354
                get_lang('TextWhenFinished'),
2355
                false,
2356
                false,
2357
                $editor_config
2358
            );
2359
2360
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
2361
            if ($allow === true) {
2362
                $settings = ExerciseLib::getNotificationSettings();
2363
                $group = [];
2364
                foreach ($settings as $itemId => $label) {
2365
                    $group[] = $form->createElement(
2366
                        'checkbox',
2367
                        'notifications[]',
2368
                        null,
2369
                        $label,
2370
                        ['value' => $itemId]
2371
                    );
2372
                }
2373
                $form->addGroup($group, '', [get_lang('EmailNotifications')]);
2374
            }
2375
2376
            $form->addCheckBox(
2377
                'update_title_in_lps',
2378
                null,
2379
                get_lang('UpdateTitleInLps')
2380
            );
2381
2382
            $defaults = [];
2383
            if (api_get_setting('search_enabled') === 'true') {
2384
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2385
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2386
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2387
                $specific_fields = get_specific_field_list();
2388
2389
                foreach ($specific_fields as $specific_field) {
2390
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2391
                    $filter = [
2392
                        'c_id' => api_get_course_int_id(),
2393
                        'field_id' => $specific_field['id'],
2394
                        'ref_id' => $this->id,
2395
                        'tool_id' => "'".TOOL_QUIZ."'",
2396
                    ];
2397
                    $values = get_specific_field_values_list($filter, ['value']);
2398
                    if (!empty($values)) {
2399
                        $arr_str_values = [];
2400
                        foreach ($values as $value) {
2401
                            $arr_str_values[] = $value['value'];
2402
                        }
2403
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2404
                    }
2405
                }
2406
            }
2407
2408
            $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_EXERCISE, $this->iId);
2409
2410
            /*$extraField = new ExtraField('exercise');
2411
            $extraField->addElements(
2412
                $form,
2413
                $this->iId,
2414
                [], //exclude
2415
                false, // filter
2416
                false, // tag as select
2417
                [], //show only fields
2418
                [], // order fields
2419
                [] // extra data
2420
            );*/
2421
            $form->addElement('html', '</div>'); //End advanced setting
2422
            $form->addElement('html', '</div>');
2423
        }
2424
2425
        // submit
2426
        if (isset($_GET['exerciseId'])) {
2427
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2428
        } else {
2429
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2430
        }
2431
2432
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2433
2434
        // defaults
2435
        if ($type == 'full') {
2436
            // rules
2437
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2438
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2439
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2440
2441
            if ($this->id > 0) {
2442
                //if ($this->random > $this->selectNbrQuestions()) {
2443
                //    $defaults['randomQuestions'] = $this->selectNbrQuestions();
2444
                //} else {
2445
                $defaults['randomQuestions'] = $this->random;
2446
                //}
2447
2448
                $defaults['randomAnswers'] = $this->getRandomAnswers();
2449
                $defaults['exerciseType'] = $this->selectType();
2450
                $defaults['exerciseTitle'] = $this->get_formated_title();
2451
                $defaults['exerciseDescription'] = $this->selectDescription();
2452
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2453
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2454
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2455
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2456
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2457
                $defaults['review_answers'] = $this->review_answers;
2458
                $defaults['randomByCat'] = $this->getRandomByCategory();
2459
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2460
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2461
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2462
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2463
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2464
                $defaults['show_previous_button'] = $this->showPreviousButton();
2465
                $defaults['exercise_category_id'] = $this->getExerciseCategoryId();
2466
2467
                if (!empty($this->start_time)) {
2468
                    $defaults['activate_start_date_check'] = 1;
2469
                }
2470
                if (!empty($this->end_time)) {
2471
                    $defaults['activate_end_date_check'] = 1;
2472
                }
2473
2474
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2475
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2476
2477
                // Get expired time
2478
                if ($this->expired_time != '0') {
2479
                    $defaults['enabletimercontrol'] = 1;
2480
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2481
                } else {
2482
                    $defaults['enabletimercontroltotalminutes'] = 0;
2483
                }
2484
                $defaults['skills'] = array_keys($skillList);
2485
                $defaults['notifications'] = $this->getNotifications();
2486
            } else {
2487
                $defaults['exerciseType'] = 2;
2488
                $defaults['exerciseAttempts'] = 0;
2489
                $defaults['randomQuestions'] = 0;
2490
                $defaults['randomAnswers'] = 0;
2491
                $defaults['exerciseDescription'] = '';
2492
                $defaults['exerciseFeedbackType'] = 0;
2493
                $defaults['results_disabled'] = 0;
2494
                $defaults['randomByCat'] = 0;
2495
                $defaults['text_when_finished'] = '';
2496
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2497
                $defaults['display_category_name'] = 1;
2498
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2499
                $defaults['pass_percentage'] = '';
2500
                $defaults['end_button'] = $this->selectEndButton();
2501
                $defaults['question_selection_type'] = 1;
2502
                $defaults['hide_question_title'] = 0;
2503
                $defaults['show_previous_button'] = 1;
2504
                $defaults['on_success_message'] = null;
2505
                $defaults['on_failed_message'] = null;
2506
            }
2507
        } else {
2508
            $defaults['exerciseTitle'] = $this->selectTitle();
2509
            $defaults['exerciseDescription'] = $this->selectDescription();
2510
        }
2511
2512
        if (api_get_setting('search_enabled') === 'true') {
2513
            $defaults['index_document'] = 'checked="checked"';
2514
        }
2515
        $form->setDefaults($defaults);
2516
2517
        // Freeze some elements.
2518
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2519
            $elementsToFreeze = [
2520
                'randomQuestions',
2521
                //'randomByCat',
2522
                'exerciseAttempts',
2523
                'propagate_neg',
2524
                'enabletimercontrol',
2525
                'review_answers',
2526
            ];
2527
2528
            foreach ($elementsToFreeze as $elementName) {
2529
                /** @var HTML_QuickForm_element $element */
2530
                $element = $form->getElement($elementName);
2531
                $element->freeze();
2532
            }
2533
        }
2534
    }
2535
2536
    /**
2537
     * function which process the creation of exercises.
2538
     *
2539
     * @param FormValidator $form
2540
     * @param string
2541
     *
2542
     * @return int c_quiz.iid
2543
     */
2544
    public function processCreation($form, $type = '')
2545
    {
2546
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2547
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2548
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2549
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2550
        $this->updateType($form->getSubmitValue('exerciseType'));
2551
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2552
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2553
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2554
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2555
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2556
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2557
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2558
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2559
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2560
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2561
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2562
        $this->updateCategories($form->getSubmitValue('category'));
2563
        $this->updateEndButton($form->getSubmitValue('end_button'));
2564
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2565
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2566
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2567
        $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2568
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2569
        $this->setModelType($form->getSubmitValue('model_type'));
2570
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2571
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2572
        $this->sessionId = api_get_session_id();
2573
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2574
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2575
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2576
        $this->setShowPreviousButton($form->getSubmitValue('show_previous_button'));
2577
        $this->setNotifications($form->getSubmitValue('notifications'));
2578
        $this->setExerciseCategoryId($form->getSubmitValue('exercise_category_id'));
2579
2580
        $this->start_time = null;
2581
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2582
            $start_time = $form->getSubmitValue('start_time');
2583
            $this->start_time = api_get_utc_datetime($start_time);
2584
        }
2585
2586
        $this->end_time = null;
2587
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2588
            $end_time = $form->getSubmitValue('end_time');
2589
            $this->end_time = api_get_utc_datetime($end_time);
2590
        }
2591
2592
        $this->expired_time = 0;
2593
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2594
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2595
            if ($this->expired_time == 0) {
2596
                $this->expired_time = $expired_total_time;
2597
            }
2598
        }
2599
2600
        $this->random_answers = 0;
2601
        if ($form->getSubmitValue('randomAnswers') == 1) {
2602
            $this->random_answers = 1;
2603
        }
2604
2605
        // Update title in all LPs that have this quiz added
2606
        if ($form->getSubmitValue('update_title_in_lps') == 1) {
2607
            $courseId = api_get_course_int_id();
2608
            $table = Database::get_course_table(TABLE_LP_ITEM);
2609
            $sql = "SELECT * FROM $table 
2610
                    WHERE 
2611
                        c_id = $courseId AND 
2612
                        item_type = 'quiz' AND 
2613
                        path = '".$this->id."'
2614
                    ";
2615
            $result = Database::query($sql);
2616
            $items = Database::store_result($result);
2617
            if (!empty($items)) {
2618
                foreach ($items as $item) {
2619
                    $itemId = $item['iid'];
2620
                    $sql = "UPDATE $table SET title = '".$this->title."'                             
2621
                            WHERE iid = $itemId AND c_id = $courseId ";
2622
                    Database::query($sql);
2623
                }
2624
            }
2625
        }
2626
2627
        $iId = $this->save($type);
2628
        if (!empty($iId)) {
2629
            /*$values = $form->getSubmitValues();
2630
            $values['item_id'] = $iId;
2631
            $extraFieldValue = new ExtraFieldValue('exercise');
2632
            $extraFieldValue->saveFieldValues($values);*/
2633
2634
            Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iId);
2635
        }
2636
    }
2637
2638
    public function search_engine_save()
2639
    {
2640
        if ($_POST['index_document'] != 1) {
2641
            return;
2642
        }
2643
        $course_id = api_get_course_id();
2644
2645
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2646
2647
        $specific_fields = get_specific_field_list();
2648
        $ic_slide = new IndexableChunk();
2649
2650
        $all_specific_terms = '';
2651
        foreach ($specific_fields as $specific_field) {
2652
            if (isset($_REQUEST[$specific_field['code']])) {
2653
                $sterms = trim($_REQUEST[$specific_field['code']]);
2654
                if (!empty($sterms)) {
2655
                    $all_specific_terms .= ' '.$sterms;
2656
                    $sterms = explode(',', $sterms);
2657
                    foreach ($sterms as $sterm) {
2658
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2659
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2660
                    }
2661
                }
2662
            }
2663
        }
2664
2665
        // build the chunk to index
2666
        $ic_slide->addValue("title", $this->exercise);
2667
        $ic_slide->addCourseId($course_id);
2668
        $ic_slide->addToolId(TOOL_QUIZ);
2669
        $xapian_data = [
2670
            SE_COURSE_ID => $course_id,
2671
            SE_TOOL_ID => TOOL_QUIZ,
2672
            SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2673
            SE_USER => (int) api_get_user_id(),
2674
        ];
2675
        $ic_slide->xapian_data = serialize($xapian_data);
2676
        $exercise_description = $all_specific_terms.' '.$this->description;
2677
        $ic_slide->addValue("content", $exercise_description);
2678
2679
        $di = new ChamiloIndexer();
2680
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2681
        $di->connectDb(null, null, $lang);
2682
        $di->addChunk($ic_slide);
2683
2684
        //index and return search engine document id
2685
        $did = $di->index();
2686
        if ($did) {
2687
            // save it to db
2688
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2689
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2690
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2691
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2692
            Database::query($sql);
2693
        }
2694
    }
2695
2696
    public function search_engine_edit()
2697
    {
2698
        // update search enchine and its values table if enabled
2699
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2700
            $course_id = api_get_course_id();
2701
2702
            // actually, it consists on delete terms from db,
2703
            // insert new ones, create a new search engine document, and remove the old one
2704
            // get search_did
2705
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2706
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2707
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2708
            $res = Database::query($sql);
2709
2710
            if (Database::num_rows($res) > 0) {
2711
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2712
2713
                $se_ref = Database::fetch_array($res);
2714
                $specific_fields = get_specific_field_list();
2715
                $ic_slide = new IndexableChunk();
2716
2717
                $all_specific_terms = '';
2718
                foreach ($specific_fields as $specific_field) {
2719
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2720
                    if (isset($_REQUEST[$specific_field['code']])) {
2721
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2722
                        $all_specific_terms .= ' '.$sterms;
2723
                        $sterms = explode(',', $sterms);
2724
                        foreach ($sterms as $sterm) {
2725
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2726
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2727
                        }
2728
                    }
2729
                }
2730
2731
                // build the chunk to index
2732
                $ic_slide->addValue('title', $this->exercise);
2733
                $ic_slide->addCourseId($course_id);
2734
                $ic_slide->addToolId(TOOL_QUIZ);
2735
                $xapian_data = [
2736
                    SE_COURSE_ID => $course_id,
2737
                    SE_TOOL_ID => TOOL_QUIZ,
2738
                    SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2739
                    SE_USER => (int) api_get_user_id(),
2740
                ];
2741
                $ic_slide->xapian_data = serialize($xapian_data);
2742
                $exercise_description = $all_specific_terms.' '.$this->description;
2743
                $ic_slide->addValue("content", $exercise_description);
2744
2745
                $di = new ChamiloIndexer();
2746
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2747
                $di->connectDb(null, null, $lang);
2748
                $di->remove_document($se_ref['search_did']);
2749
                $di->addChunk($ic_slide);
2750
2751
                //index and return search engine document id
2752
                $did = $di->index();
2753
                if ($did) {
2754
                    // save it to db
2755
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2756
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2757
                    Database::query($sql);
2758
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2759
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2760
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2761
                    Database::query($sql);
2762
                }
2763
            } else {
2764
                $this->search_engine_save();
2765
            }
2766
        }
2767
    }
2768
2769
    public function search_engine_delete()
2770
    {
2771
        // remove from search engine if enabled
2772
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2773
            $course_id = api_get_course_id();
2774
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2775
            $sql = 'SELECT * FROM %s
2776
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2777
                    LIMIT 1';
2778
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2779
            $res = Database::query($sql);
2780
            if (Database::num_rows($res) > 0) {
2781
                $row = Database::fetch_array($res);
2782
                $di = new ChamiloIndexer();
2783
                $di->remove_document($row['search_did']);
2784
                unset($di);
2785
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2786
                foreach ($this->questionList as $question_i) {
2787
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2788
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2789
                    $qres = Database::query($sql);
2790
                    if (Database::num_rows($qres) > 0) {
2791
                        $qrow = Database::fetch_array($qres);
2792
                        $objQuestion = Question::getInstance($qrow['type']);
2793
                        $objQuestion = Question::read((int) $question_i);
2794
                        $objQuestion->search_engine_edit($this->id, false, true);
2795
                        unset($objQuestion);
2796
                    }
2797
                }
2798
            }
2799
            $sql = 'DELETE FROM %s 
2800
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2801
                    LIMIT 1';
2802
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2803
            Database::query($sql);
2804
2805
            // remove terms from db
2806
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2807
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2808
        }
2809
    }
2810
2811
    public function selectExpiredTime()
2812
    {
2813
        return $this->expired_time;
2814
    }
2815
2816
    /**
2817
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2818
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2819
     * Works with exercises in sessions.
2820
     *
2821
     * @param bool   $cleanLpTests
2822
     * @param string $cleanResultBeforeDate
2823
     *
2824
     * @return int quantity of user's exercises deleted
2825
     */
2826
    public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
2827
    {
2828
        $sessionId = api_get_session_id();
2829
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2830
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2831
2832
        $sql_where = '  AND
2833
                        orig_lp_id = 0 AND
2834
                        orig_lp_item_id = 0';
2835
2836
        // if we want to delete results from LP too
2837
        if ($cleanLpTests) {
2838
            $sql_where = '';
2839
        }
2840
2841
        // if we want to delete attempts before date $cleanResultBeforeDate
2842
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2843
2844
        if (!empty($cleanResultBeforeDate)) {
2845
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2846
            if (api_is_valid_date($cleanResultBeforeDate)) {
2847
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2848
            } else {
2849
                return 0;
2850
            }
2851
        }
2852
2853
        $sql = "SELECT exe_id
2854
            FROM $table_track_e_exercises
2855
            WHERE
2856
                c_id = ".api_get_course_int_id()." AND
2857
                exe_exo_id = ".$this->id." AND
2858
                session_id = ".$sessionId." ".
2859
                $sql_where;
2860
2861
        $result = Database::query($sql);
2862
        $exe_list = Database::store_result($result);
2863
2864
        // deleting TRACK_E_ATTEMPT table
2865
        // check if exe in learning path or not
2866
        $i = 0;
2867
        if (is_array($exe_list) && count($exe_list) > 0) {
2868
            foreach ($exe_list as $item) {
2869
                $sql = "DELETE FROM $table_track_e_attempt
2870
                        WHERE exe_id = '".$item['exe_id']."'";
2871
                Database::query($sql);
2872
                $i++;
2873
            }
2874
        }
2875
2876
        // delete TRACK_E_EXERCISES table
2877
        $sql = "DELETE FROM $table_track_e_exercises
2878
                WHERE 
2879
                  c_id = ".api_get_course_int_id()." AND 
2880
                  exe_exo_id = ".$this->id." $sql_where AND 
2881
                  session_id = ".$sessionId;
2882
        Database::query($sql);
2883
2884
        $this->generateStats($this->id, api_get_course_info(), $sessionId);
2885
2886
        Event::addEvent(
2887
            LOG_EXERCISE_RESULT_DELETE,
2888
            LOG_EXERCISE_ID,
2889
            $this->id,
2890
            null,
2891
            null,
2892
            api_get_course_int_id(),
2893
            $sessionId
2894
        );
2895
2896
        return $i;
2897
    }
2898
2899
    /**
2900
     * Copies an exercise (duplicate all questions and answers).
2901
     */
2902
    public function copyExercise()
2903
    {
2904
        $exerciseObject = $this;
2905
        $categories = $exerciseObject->getCategoriesInExercise();
2906
        // Get all questions no matter the order/category settings
2907
        $questionList = $exerciseObject->getQuestionOrderedList();
2908
        // Force the creation of a new exercise
2909
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
2910
        // Hides the new exercise
2911
        $exerciseObject->updateStatus(false);
2912
        $exerciseObject->updateId(0);
2913
        $exerciseObject->save();
2914
        $newId = $exerciseObject->selectId();
2915
        if ($newId && !empty($questionList)) {
2916
            // Question creation
2917
            foreach ($questionList as $oldQuestionId) {
2918
                $oldQuestionObj = Question::read($oldQuestionId);
2919
                $newQuestionId = $oldQuestionObj->duplicate();
2920
                if ($newQuestionId) {
2921
                    $newQuestionObj = Question::read($newQuestionId);
2922
                    if (isset($newQuestionObj) && $newQuestionObj) {
2923
                        $newQuestionObj->addToList($newId);
2924
                        if (!empty($oldQuestionObj->category)) {
2925
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
2926
                        }
2927
2928
                        // This should be moved to the duplicate function
2929
                        $newAnswerObj = new Answer($oldQuestionId);
2930
                        $newAnswerObj->read();
2931
                        $newAnswerObj->duplicate($newQuestionObj);
2932
                    }
2933
                }
2934
            }
2935
            if (!empty($categories)) {
2936
                $newCategoryList = [];
2937
                foreach ($categories as $category) {
2938
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
2939
                }
2940
                $exerciseObject->save_categories_in_exercise($newCategoryList);
2941
            }
2942
        }
2943
    }
2944
2945
    /**
2946
     * Changes the exercise status.
2947
     *
2948
     * @param string $status - exercise status
2949
     */
2950
    public function updateStatus($status)
2951
    {
2952
        $this->active = $status;
2953
    }
2954
2955
    /**
2956
     * @param int    $lp_id
2957
     * @param int    $lp_item_id
2958
     * @param int    $lp_item_view_id
2959
     * @param string $status
2960
     *
2961
     * @return array
2962
     */
2963
    public function get_stat_track_exercise_info(
2964
        $lp_id = 0,
2965
        $lp_item_id = 0,
2966
        $lp_item_view_id = 0,
2967
        $status = 'incomplete'
2968
    ) {
2969
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2970
        if (empty($lp_id)) {
2971
            $lp_id = 0;
2972
        }
2973
        if (empty($lp_item_id)) {
2974
            $lp_item_id = 0;
2975
        }
2976
        if (empty($lp_item_view_id)) {
2977
            $lp_item_view_id = 0;
2978
        }
2979
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
2980
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
2981
					   c_id                 = '.api_get_course_int_id().' AND
2982
					   status 				= '."'".Database::escape_string($status)."'".' AND
2983
					   orig_lp_id 			= '."'".$lp_id."'".' AND
2984
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
2985
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
2986
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
2987
2988
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2989
2990
        $result = Database::query($sql_track);
2991
        $new_array = [];
2992
        if (Database::num_rows($result) > 0) {
2993
            $new_array = Database::fetch_array($result, 'ASSOC');
2994
            $new_array['num_exe'] = Database::num_rows($result);
2995
        }
2996
2997
        return $new_array;
2998
    }
2999
3000
    /**
3001
     * Saves a test attempt.
3002
     *
3003
     * @param int $clock_expired_time clock_expired_time
3004
     * @param int  int lp id
3005
     * @param int  int lp item id
3006
     * @param int  int lp item_view id
3007
     * @param array $questionList
3008
     * @param float $weight
3009
     *
3010
     * @return int
3011
     */
3012
    public function save_stat_track_exercise_info(
3013
        $clock_expired_time = 0,
3014
        $safe_lp_id = 0,
3015
        $safe_lp_item_id = 0,
3016
        $safe_lp_item_view_id = 0,
3017
        $questionList = [],
3018
        $weight = 0
3019
    ) {
3020
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3021
        $safe_lp_id = intval($safe_lp_id);
3022
        $safe_lp_item_id = intval($safe_lp_item_id);
3023
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
3024
3025
        if (empty($safe_lp_id)) {
3026
            $safe_lp_id = 0;
3027
        }
3028
        if (empty($safe_lp_item_id)) {
3029
            $safe_lp_item_id = 0;
3030
        }
3031
        if (empty($clock_expired_time)) {
3032
            $clock_expired_time = null;
3033
        }
3034
3035
        $questionList = array_map('intval', $questionList);
3036
3037
        $params = [
3038
            'exe_exo_id' => $this->id,
3039
            'exe_user_id' => api_get_user_id(),
3040
            'c_id' => api_get_course_int_id(),
3041
            'status' => 'incomplete',
3042
            'session_id' => api_get_session_id(),
3043
            'data_tracking' => implode(',', $questionList),
3044
            'start_date' => api_get_utc_datetime(),
3045
            'orig_lp_id' => $safe_lp_id,
3046
            'orig_lp_item_id' => $safe_lp_item_id,
3047
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
3048
            'max_score' => $weight,
3049
            'user_ip' => Database::escape_string(api_get_real_ip()),
3050
            'exe_date' => api_get_utc_datetime(),
3051
            'score' => 0,
3052
            'steps_counter' => 0,
3053
            'exe_duration' => 0,
3054
            'expired_time_control' => $clock_expired_time,
3055
            'questions_to_check' => '',
3056
        ];
3057
3058
        $id = Database::insert($track_exercises, $params);
3059
3060
        return $id;
3061
    }
3062
3063
    /**
3064
     * @param int    $question_id
3065
     * @param int    $questionNum
3066
     * @param array  $questions_in_media
3067
     * @param string $currentAnswer
3068
     * @param array  $myRemindList
3069
     *
3070
     * @return string
3071
     */
3072
    public function show_button(
3073
        $question_id,
3074
        $questionNum,
3075
        $questions_in_media = [],
3076
        $currentAnswer = '',
3077
        $myRemindList = []
3078
    ) {
3079
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3080
        $nbrQuestions = $this->getQuestionCount();
3081
        $buttonList = [];
3082
        $html = $label = '';
3083
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3084
3085
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
3086
            $urlTitle = get_lang('ContinueTest');
3087
            if ($questionNum == count($this->questionList)) {
3088
                $urlTitle = get_lang('EndTest');
3089
            }
3090
3091
            $html .= Display::url(
3092
                $urlTitle,
3093
                'exercise_submit_modal.php?'.http_build_query([
3094
                    'learnpath_id' => $safe_lp_id,
3095
                    'learnpath_item_id' => $safe_lp_item_id,
3096
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
3097
                    'origin' => $origin,
3098
                    'hotspot' => $hotspot_get,
3099
                    'nbrQuestions' => $nbrQuestions,
3100
                    'num' => $questionNum,
3101
                    'exerciseType' => $this->type,
3102
                    'exerciseId' => $this->id,
3103
                    'reminder' => empty($myRemindList) ? null : 2,
3104
                ]),
3105
                [
3106
                    'class' => 'ajax btn btn-default',
3107
                    'data-title' => $urlTitle,
3108
                    'data-size' => 'md',
3109
                ]
3110
            );
3111
            $html .= '<br />';
3112
        } else {
3113
            // User
3114
            if (api_is_allowed_to_session_edit()) {
3115
                $endReminderValue = false;
3116
                if (!empty($myRemindList)) {
3117
                    $endValue = end($myRemindList);
3118
                    if ($endValue == $question_id) {
3119
                        $endReminderValue = true;
3120
                    }
3121
                }
3122
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3123
                    if ($this->review_answers) {
3124
                        $label = get_lang('ReviewQuestions');
3125
                        $class = 'btn btn-success';
3126
                    } else {
3127
                        $label = get_lang('EndTest');
3128
                        $class = 'btn btn-warning';
3129
                    }
3130
                } else {
3131
                    $label = get_lang('NextQuestion');
3132
                    $class = 'btn btn-primary';
3133
                }
3134
                // used to select it with jquery
3135
                $class .= ' question-validate-btn';
3136
                if ($this->type == ONE_PER_PAGE) {
3137
                    if ($questionNum != 1) {
3138
                        if ($this->showPreviousButton()) {
3139
                            $prev_question = $questionNum - 2;
3140
                            $showPreview = true;
3141
                            if (!empty($myRemindList)) {
3142
                                $beforeId = null;
3143
                                for ($i = 0; $i < count($myRemindList); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3144
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3145
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3146
                                        break;
3147
                                    }
3148
                                }
3149
3150
                                if (empty($beforeId)) {
3151
                                    $showPreview = false;
3152
                                } else {
3153
                                    $num = 0;
3154
                                    foreach ($this->questionList as $originalQuestionId) {
3155
                                        if ($originalQuestionId == $beforeId) {
3156
                                            break;
3157
                                        }
3158
                                        $num++;
3159
                                    }
3160
                                    $prev_question = $num;
3161
                                    //$question_id = $beforeId;
3162
                                }
3163
                            }
3164
3165
                            if ($showPreview) {
3166
                                $buttonList[] = Display::button(
3167
                                    'previous_question_and_save',
3168
                                    get_lang('PreviousQuestion'),
3169
                                    [
3170
                                        'type' => 'button',
3171
                                        'class' => 'btn btn-default',
3172
                                        'data-prev' => $prev_question,
3173
                                        'data-question' => $question_id,
3174
                                    ]
3175
                                );
3176
                            }
3177
                        }
3178
                    }
3179
3180
                    // Next question
3181
                    if (!empty($questions_in_media)) {
3182
                        $buttonList[] = Display::button(
3183
                            'save_question_list',
3184
                            $label,
3185
                            [
3186
                                'type' => 'button',
3187
                                'class' => $class,
3188
                                'data-list' => implode(",", $questions_in_media),
3189
                            ]
3190
                        );
3191
                    } else {
3192
                        $buttonList[] = Display::button(
3193
                            'save_now',
3194
                            $label,
3195
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3196
                        );
3197
                    }
3198
                    $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3199
3200
                    $html .= implode(PHP_EOL, $buttonList);
3201
                } else {
3202
                    if ($this->review_answers) {
3203
                        $all_label = get_lang('ReviewQuestions');
3204
                        $class = 'btn btn-success';
3205
                    } else {
3206
                        $all_label = get_lang('EndTest');
3207
                        $class = 'btn btn-warning';
3208
                    }
3209
                    // used to select it with jquery
3210
                    $class .= ' question-validate-btn';
3211
                    $buttonList[] = Display::button(
3212
                        'validate_all',
3213
                        $all_label,
3214
                        ['type' => 'button', 'class' => $class]
3215
                    );
3216
                    $buttonList[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_response']);
3217
                    $html .= implode(PHP_EOL, $buttonList);
3218
                }
3219
            }
3220
        }
3221
3222
        return $html;
3223
    }
3224
3225
    /**
3226
     * So the time control will work.
3227
     *
3228
     * @param string $time_left
3229
     *
3230
     * @return string
3231
     */
3232
    public function showTimeControlJS($time_left)
3233
    {
3234
        $time_left = (int) $time_left;
3235
        $script = "redirectExerciseToResult();";
3236
        if ($this->type == ALL_ON_ONE_PAGE) {
3237
            $script = "save_now_all('validate');";
3238
        }
3239
3240
        return "<script>
3241
            function openClockWarning() {
3242
                $('#clock_warning').dialog({
3243
                    modal:true,
3244
                    height:250,
3245
                    closeOnEscape: false,
3246
                    resizable: false,
3247
                    buttons: {
3248
                        '".addslashes(get_lang("EndTest"))."': function() {
3249
                            $('#clock_warning').dialog('close');
3250
                        }
3251
                    },
3252
                    close: function() {
3253
                        send_form();
3254
                    }
3255
                });
3256
                
3257
                $('#clock_warning').dialog('open');
3258
                $('#counter_to_redirect').epiclock({
3259
                    mode: $.epiclock.modes.countdown,
3260
                    offset: {seconds: 5},
3261
                    format: 's'
3262
                }).bind('timer', function () {
3263
                    send_form();
3264
                });
3265
            }
3266
3267
            function send_form() {
3268
                if ($('#exercise_form').length) {
3269
                    $script
3270
                } else {
3271
                    // In exercise_reminder.php
3272
                    final_submit();
3273
                }
3274
            }
3275
3276
            function onExpiredTimeExercise() {
3277
                $('#wrapper-clock').hide();
3278
                $('#expired-message-id').show();
3279
                // Fixes bug #5263
3280
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3281
                openClockWarning();
3282
            }
3283
3284
			$(function() {
3285
				// time in seconds when using minutes there are some seconds lost
3286
                var time_left = parseInt(".$time_left.");
3287
                $('#exercise_clock_warning').epiclock({
3288
                    mode: $.epiclock.modes.countdown,
3289
                    offset: {seconds: time_left},
3290
                    format: 'x:i:s',
3291
                    renderer: 'minute'
3292
                }).bind('timer', function () {
3293
                    onExpiredTimeExercise();
3294
                });
3295
	       		$('#submit_save').click(function () {});
3296
	    });
3297
	    </script>";
3298
    }
3299
3300
    /**
3301
     * This function was originally found in the exercise_show.php.
3302
     *
3303
     * @param int    $exeId
3304
     * @param int    $questionId
3305
     * @param mixed  $choice                                    the user-selected option
3306
     * @param string $from                                      function is called from 'exercise_show' or
3307
     *                                                          'exercise_result'
3308
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] =
3309
     *                                                          coordinates
3310
     * @param bool   $saved_results                             save results in the DB or just show the reponse
3311
     * @param bool   $from_database                             gets information from DB or from the current selection
3312
     * @param bool   $show_result                               show results or not
3313
     * @param int    $propagate_neg
3314
     * @param array  $hotspot_delineation_result
3315
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3316
     * @param bool   $updateResults
3317
     *
3318
     * @todo    reduce parameters of this function
3319
     *
3320
     * @return string html code
3321
     */
3322
    public function manage_answer(
3323
        $exeId,
3324
        $questionId,
3325
        $choice,
3326
        $from = 'exercise_show',
3327
        $exerciseResultCoordinates = [],
3328
        $saved_results = true,
3329
        $from_database = false,
3330
        $show_result = true,
3331
        $propagate_neg = 0,
3332
        $hotspot_delineation_result = [],
3333
        $showTotalScoreAndUserChoicesInLastAttempt = true,
3334
        $updateResults = false
3335
    ) {
3336
        $debug = false;
3337
        //needed in order to use in the exercise_attempt() for the time
3338
        global $learnpath_id, $learnpath_item_id;
3339
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3340
        $em = Database::getManager();
3341
        $feedback_type = $this->selectFeedbackType();
3342
        $results_disabled = $this->selectResultsDisabled();
3343
3344
        if ($debug) {
3345
            error_log("<------ manage_answer ------> ");
3346
            error_log('exe_id: '.$exeId);
3347
            error_log('$from:  '.$from);
3348
            error_log('$saved_results: '.intval($saved_results));
3349
            error_log('$from_database: '.intval($from_database));
3350
            error_log('$show_result: '.intval($show_result));
3351
            error_log('$propagate_neg: '.$propagate_neg);
3352
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3353
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3354
            error_log('$learnpath_id: '.$learnpath_id);
3355
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3356
            error_log('$choice: '.print_r($choice, 1));
3357
        }
3358
3359
        $final_overlap = 0;
3360
        $final_missing = 0;
3361
        $final_excess = 0;
3362
        $overlap_color = 0;
3363
        $missing_color = 0;
3364
        $excess_color = 0;
3365
        $threadhold1 = 0;
3366
        $threadhold2 = 0;
3367
        $threadhold3 = 0;
3368
        $arrques = null;
3369
        $arrans = null;
3370
        $studentChoice = null;
3371
        $expectedAnswer = '';
3372
        $calculatedChoice = '';
3373
        $calculatedStatus = '';
3374
        $questionId = (int) $questionId;
3375
        $exeId = (int) $exeId;
3376
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3377
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3378
3379
        // Creates a temporary Question object
3380
        $course_id = $this->course_id;
3381
        $objQuestionTmp = Question::read($questionId, $this->course);
3382
3383
        if ($objQuestionTmp === false) {
3384
            return false;
3385
        }
3386
3387
        $questionName = $objQuestionTmp->selectTitle();
3388
        $questionWeighting = $objQuestionTmp->selectWeighting();
3389
        $answerType = $objQuestionTmp->selectType();
3390
        $quesId = $objQuestionTmp->selectId();
3391
        $extra = $objQuestionTmp->extra;
3392
        $next = 1; //not for now
3393
        $totalWeighting = 0;
3394
        $totalScore = 0;
3395
3396
        // Extra information of the question
3397
        if ((
3398
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
3399
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
3400
            )
3401
            && !empty($extra)
3402
        ) {
3403
            $extra = explode(':', $extra);
3404
            if ($debug) {
3405
                error_log(print_r($extra, 1));
3406
            }
3407
            // Fixes problems with negatives values using intval
3408
3409
            $true_score = floatval(trim($extra[0]));
3410
            $false_score = floatval(trim($extra[1]));
3411
            $doubt_score = floatval(trim($extra[2]));
3412
        }
3413
3414
        // Construction of the Answer object
3415
        $objAnswerTmp = new Answer($questionId, $course_id);
3416
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3417
3418
        if ($debug) {
3419
            error_log('Count of answers: '.$nbrAnswers);
3420
            error_log('$answerType: '.$answerType);
3421
        }
3422
3423
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
3424
            $choiceTmp = $choice;
3425
            $choice = isset($choiceTmp['choice']) ? $choiceTmp['choice'] : '';
3426
            $choiceDegreeCertainty = isset($choiceTmp['choiceDegreeCertainty']) ? $choiceTmp['choiceDegreeCertainty'] : '';
3427
        }
3428
3429
        if ($answerType == FREE_ANSWER ||
3430
            $answerType == ORAL_EXPRESSION ||
3431
            $answerType == CALCULATED_ANSWER ||
3432
            $answerType == ANNOTATION
3433
        ) {
3434
            $nbrAnswers = 1;
3435
        }
3436
3437
        $generatedFile = '';
3438
        if ($answerType == ORAL_EXPRESSION) {
3439
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3440
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3441
3442
            $objQuestionTmp->initFile(
3443
                api_get_session_id(),
3444
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3445
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3446
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3447
            );
3448
3449
            // Probably this attempt came in an exercise all question by page
3450
            if ($feedback_type == 0) {
3451
                $objQuestionTmp->replaceWithRealExe($exeId);
3452
            }
3453
            $generatedFile = $objQuestionTmp->getFileUrl();
3454
        }
3455
3456
        $user_answer = '';
3457
        // Get answer list for matching
3458
        $sql = "SELECT id_auto, id, answer
3459
                FROM $table_ans
3460
                WHERE c_id = $course_id AND question_id = $questionId";
3461
        $res_answer = Database::query($sql);
3462
3463
        $answerMatching = [];
3464
        while ($real_answer = Database::fetch_array($res_answer)) {
3465
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3466
        }
3467
3468
        $real_answers = [];
3469
        $quiz_question_options = Question::readQuestionOption(
3470
            $questionId,
3471
            $course_id
3472
        );
3473
3474
        $organs_at_risk_hit = 0;
3475
        $questionScore = 0;
3476
        $answer_correct_array = [];
3477
        $orderedHotspots = [];
3478
3479
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3480
            $orderedHotspots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3481
                [
3482
                    'hotspotQuestionId' => $questionId,
3483
                    'course' => $course_id,
3484
                    'hotspotExeId' => $exeId,
3485
                ],
3486
                ['hotspotAnswerId' => 'ASC']
3487
            );
3488
        }
3489
        if ($debug) {
3490
            error_log('Start answer loop ');
3491
        }
3492
3493
        $userAnsweredQuestion = false;
3494
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3495
            $answer = $objAnswerTmp->selectAnswer($answerId);
3496
            $answerComment = $objAnswerTmp->selectComment($answerId);
3497
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3498
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3499
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3500
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3501
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3502
3503
            if ($debug) {
3504
                error_log("answer auto id: $answerAutoId ");
3505
                error_log("answer correct: $answerCorrect ");
3506
            }
3507
3508
            // Delineation
3509
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3510
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3511
3512
            switch ($answerType) {
3513
                case UNIQUE_ANSWER:
3514
                case UNIQUE_ANSWER_IMAGE:
3515
                case UNIQUE_ANSWER_NO_OPTION:
3516
                case READING_COMPREHENSION:
3517
                    if ($from_database) {
3518
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3519
                                WHERE
3520
                                    exe_id = '".$exeId."' AND
3521
                                    question_id= '".$questionId."'";
3522
                        $result = Database::query($sql);
3523
                        $choice = Database::result($result, 0, 'answer');
3524
3525
                        if ($userAnsweredQuestion === false) {
3526
                            $userAnsweredQuestion = !empty($choice);
3527
                        }
3528
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3529
                        if ($studentChoice) {
3530
                            $questionScore += $answerWeighting;
3531
                            $totalScore += $answerWeighting;
3532
                        }
3533
                    } else {
3534
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3535
                        if ($studentChoice) {
3536
                            $questionScore += $answerWeighting;
3537
                            $totalScore += $answerWeighting;
3538
                        }
3539
                    }
3540
                    break;
3541
                case MULTIPLE_ANSWER_TRUE_FALSE:
3542
                    if ($from_database) {
3543
                        $choice = [];
3544
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3545
                                WHERE
3546
                                    exe_id = $exeId AND
3547
                                    question_id = ".$questionId;
3548
3549
                        $result = Database::query($sql);
3550
                        while ($row = Database::fetch_array($result)) {
3551
                            $values = explode(':', $row['answer']);
3552
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3553
                            $option = isset($values[1]) ? $values[1] : '';
3554
                            $choice[$my_answer_id] = $option;
3555
                        }
3556
                    }
3557
3558
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3559
                    if (!empty($studentChoice)) {
3560
                        if ($studentChoice == $answerCorrect) {
3561
                            $questionScore += $true_score;
3562
                        } else {
3563
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3564
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3565
                            ) {
3566
                                $questionScore += $doubt_score;
3567
                            } else {
3568
                                $questionScore += $false_score;
3569
                            }
3570
                        }
3571
                    } else {
3572
                        // If no result then the user just hit don't know
3573
                        $studentChoice = 3;
3574
                        $questionScore += $doubt_score;
3575
                    }
3576
                    $totalScore = $questionScore;
3577
                    break;
3578
                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
3579
                    if ($from_database) {
3580
                        $choice = [];
3581
                        $choiceDegreeCertainty = [];
3582
                        $sql = "SELECT answer 
3583
                            FROM $TBL_TRACK_ATTEMPT
3584
                            WHERE 
3585
                            exe_id = $exeId AND question_id = $questionId";
3586
3587
                        $result = Database::query($sql);
3588
                        while ($row = Database::fetch_array($result)) {
3589
                            $ind = $row['answer'];
3590
                            $values = explode(':', $ind);
3591
                            $myAnswerId = $values[0];
3592
                            $option = $values[1];
3593
                            $percent = $values[2];
3594
                            $choice[$myAnswerId] = $option;
3595
                            $choiceDegreeCertainty[$myAnswerId] = $percent;
3596
                        }
3597
                    }
3598
3599
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3600
                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ?
3601
                        $choiceDegreeCertainty[$answerAutoId] : null;
3602
3603
                    // student score update
3604
                    if (!empty($studentChoice)) {
3605
                        if ($studentChoice == $answerCorrect) {
3606
                            // correct answer and student is Unsure or PrettySur
3607
                            if (isset($quiz_question_options[$studentChoiceDegree]) &&
3608
                                $quiz_question_options[$studentChoiceDegree]['position'] >= 3 &&
3609
                                $quiz_question_options[$studentChoiceDegree]['position'] < 9
3610
                            ) {
3611
                                $questionScore += $true_score;
3612
                            } else {
3613
                                // student ignore correct answer
3614
                                $questionScore += $doubt_score;
3615
                            }
3616
                        } else {
3617
                            // false answer and student is Unsure or PrettySur
3618
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3619
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3620
                                $questionScore += $false_score;
3621
                            } else {
3622
                                // student ignore correct answer
3623
                                $questionScore += $doubt_score;
3624
                            }
3625
                        }
3626
                    }
3627
                    $totalScore = $questionScore;
3628
                    break;
3629
                case MULTIPLE_ANSWER: //2
3630
                    if ($from_database) {
3631
                        $choice = [];
3632
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3633
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3634
                        $resultans = Database::query($sql);
3635
                        while ($row = Database::fetch_array($resultans)) {
3636
                            $choice[$row['answer']] = 1;
3637
                        }
3638
3639
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3640
                        $real_answers[$answerId] = (bool) $studentChoice;
3641
3642
                        if ($studentChoice) {
3643
                            $questionScore += $answerWeighting;
3644
                        }
3645
                    } else {
3646
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3647
                        $real_answers[$answerId] = (bool) $studentChoice;
3648
3649
                        if (isset($studentChoice)) {
3650
                            $questionScore += $answerWeighting;
3651
                        }
3652
                    }
3653
                    $totalScore += $answerWeighting;
3654
3655
                    if ($debug) {
3656
                        error_log("studentChoice: $studentChoice");
3657
                    }
3658
                    break;
3659
                case GLOBAL_MULTIPLE_ANSWER:
3660
                    if ($from_database) {
3661
                        $choice = [];
3662
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3663
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3664
                        $resultans = Database::query($sql);
3665
                        while ($row = Database::fetch_array($resultans)) {
3666
                            $choice[$row['answer']] = 1;
3667
                        }
3668
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3669
                        $real_answers[$answerId] = (bool) $studentChoice;
3670
                        if ($studentChoice) {
3671
                            $questionScore += $answerWeighting;
3672
                        }
3673
                    } else {
3674
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3675
                        if (isset($studentChoice)) {
3676
                            $questionScore += $answerWeighting;
3677
                        }
3678
                        $real_answers[$answerId] = (bool) $studentChoice;
3679
                    }
3680
                    $totalScore += $answerWeighting;
3681
                    if ($debug) {
3682
                        error_log("studentChoice: $studentChoice");
3683
                    }
3684
                    break;
3685
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3686
                    if ($from_database) {
3687
                        $choice = [];
3688
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3689
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3690
                        $resultans = Database::query($sql);
3691
                        while ($row = Database::fetch_array($resultans)) {
3692
                            $result = explode(':', $row['answer']);
3693
                            if (isset($result[0])) {
3694
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3695
                                $option = isset($result[1]) ? $result[1] : '';
3696
                                $choice[$my_answer_id] = $option;
3697
                            }
3698
                        }
3699
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3700
3701
                        $real_answers[$answerId] = false;
3702
                        if ($answerCorrect == $studentChoice) {
3703
                            $real_answers[$answerId] = true;
3704
                        }
3705
                    } else {
3706
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3707
                        $real_answers[$answerId] = false;
3708
                        if ($answerCorrect == $studentChoice) {
3709
                            $real_answers[$answerId] = true;
3710
                        }
3711
                    }
3712
                    break;
3713
                case MULTIPLE_ANSWER_COMBINATION:
3714
                    if ($from_database) {
3715
                        $choice = [];
3716
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3717
                                WHERE exe_id = $exeId AND question_id= $questionId";
3718
                        $resultans = Database::query($sql);
3719
                        while ($row = Database::fetch_array($resultans)) {
3720
                            $choice[$row['answer']] = 1;
3721
                        }
3722
3723
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3724
                        if ($answerCorrect == 1) {
3725
                            $real_answers[$answerId] = false;
3726
                            if ($studentChoice) {
3727
                                $real_answers[$answerId] = true;
3728
                            }
3729
                        } else {
3730
                            $real_answers[$answerId] = true;
3731
                            if ($studentChoice) {
3732
                                $real_answers[$answerId] = false;
3733
                            }
3734
                        }
3735
                    } else {
3736
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3737
                        if ($answerCorrect == 1) {
3738
                            $real_answers[$answerId] = false;
3739
                            if ($studentChoice) {
3740
                                $real_answers[$answerId] = true;
3741
                            }
3742
                        } else {
3743
                            $real_answers[$answerId] = true;
3744
                            if ($studentChoice) {
3745
                                $real_answers[$answerId] = false;
3746
                            }
3747
                        }
3748
                    }
3749
                    break;
3750
                case FILL_IN_BLANKS:
3751
                    $str = '';
3752
                    $answerFromDatabase = '';
3753
                    if ($from_database) {
3754
                        $sql = "SELECT answer
3755
                                FROM $TBL_TRACK_ATTEMPT
3756
                                WHERE
3757
                                    exe_id = $exeId AND
3758
                                    question_id= ".intval($questionId);
3759
                        $result = Database::query($sql);
3760
                        if ($debug) {
3761
                            error_log($sql);
3762
                        }
3763
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3764
                    }
3765
3766
                    // if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3767
                    if (false) {
3768
                        // the question is encoded like this
3769
                        // [A] B [C] D [E] F::10,10,10@1
3770
                        // number 1 before the "@" means that is a switchable fill in blank question
3771
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3772
                        // means that is a normal fill blank question
3773
                        // first we explode the "::"
3774
                        $pre_array = explode('::', $answer);
3775
3776
                        // is switchable fill blank or not
3777
                        $last = count($pre_array) - 1;
3778
                        $is_set_switchable = explode('@', $pre_array[$last]);
3779
                        $switchable_answer_set = false;
3780
                        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3781
                            $switchable_answer_set = true;
3782
                        }
3783
                        $answer = '';
3784
                        for ($k = 0; $k < $last; $k++) {
3785
                            $answer .= $pre_array[$k];
3786
                        }
3787
                        // splits weightings that are joined with a comma
3788
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3789
                        // we save the answer because it will be modified
3790
                        $temp = $answer;
3791
                        $answer = '';
3792
                        $j = 0;
3793
                        //initialise answer tags
3794
                        $user_tags = $correct_tags = $real_text = [];
3795
                        // the loop will stop at the end of the text
3796
                        while (1) {
3797
                            // quits the loop if there are no more blanks (detect '[')
3798
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3799
                                // adds the end of the text
3800
                                $answer = $temp;
3801
                                $real_text[] = $answer;
3802
                                break; //no more "blanks", quit the loop
3803
                            }
3804
                            // adds the piece of text that is before the blank
3805
                            //and ends with '[' into a general storage array
3806
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3807
                            $answer .= api_substr($temp, 0, $pos + 1);
3808
                            //take the string remaining (after the last "[" we found)
3809
                            $temp = api_substr($temp, $pos + 1);
3810
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3811
                            if (($pos = api_strpos($temp, ']')) === false) {
3812
                                // adds the end of the text
3813
                                $answer .= $temp;
3814
                                break;
3815
                            }
3816
                            if ($from_database) {
3817
                                $str = $answerFromDatabase;
3818
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3819
                                $str = str_replace('\r\n', '', $str);
3820
3821
                                $choice = $arr[1];
3822
                                if (isset($choice[$j])) {
3823
                                    $tmp = api_strrpos($choice[$j], ' / ');
3824
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3825
                                    $choice[$j] = trim($choice[$j]);
3826
                                    // Needed to let characters ' and " to work as part of an answer
3827
                                    $choice[$j] = stripslashes($choice[$j]);
3828
                                } else {
3829
                                    $choice[$j] = null;
3830
                                }
3831
                            } else {
3832
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3833
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3834
                            }
3835
3836
                            $user_tags[] = $choice[$j];
3837
                            // Put the contents of the [] answer tag into correct_tags[]
3838
                            $correct_tags[] = api_substr($temp, 0, $pos);
3839
                            $j++;
3840
                            $temp = api_substr($temp, $pos + 1);
3841
                        }
3842
                        $answer = '';
3843
                        $real_correct_tags = $correct_tags;
3844
                        $chosen_list = [];
3845
3846
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3847
                            if ($i == 0) {
3848
                                $answer .= $real_text[0];
3849
                            }
3850
                            if (!$switchable_answer_set) {
3851
                                // Needed to parse ' and " characters
3852
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3853
                                if ($correct_tags[$i] == $user_tags[$i]) {
3854
                                    // gives the related weighting to the student
3855
                                    $questionScore += $answerWeighting[$i];
3856
                                    // increments total score
3857
                                    $totalScore += $answerWeighting[$i];
3858
                                    // adds the word in green at the end of the string
3859
                                    $answer .= $correct_tags[$i];
3860
                                } elseif (!empty($user_tags[$i])) {
3861
                                    // else if the word entered by the student IS NOT the same as
3862
                                    // the one defined by the professor
3863
                                    // adds the word in red at the end of the string, and strikes it
3864
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3865
                                } else {
3866
                                    // adds a tabulation if no word has been typed by the student
3867
                                    $answer .= ''; // remove &nbsp; that causes issue
3868
                                }
3869
                            } else {
3870
                                // switchable fill in the blanks
3871
                                if (in_array($user_tags[$i], $correct_tags)) {
3872
                                    $chosen_list[] = $user_tags[$i];
3873
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3874
                                    // gives the related weighting to the student
3875
                                    $questionScore += $answerWeighting[$i];
3876
                                    // increments total score
3877
                                    $totalScore += $answerWeighting[$i];
3878
                                    // adds the word in green at the end of the string
3879
                                    $answer .= $user_tags[$i];
3880
                                } elseif (!empty($user_tags[$i])) {
3881
                                    // else if the word entered by the student IS NOT the same
3882
                                    // as the one defined by the professor
3883
                                    // adds the word in red at the end of the string, and strikes it
3884
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3885
                                } else {
3886
                                    // adds a tabulation if no word has been typed by the student
3887
                                    $answer .= ''; // remove &nbsp; that causes issue
3888
                                }
3889
                            }
3890
3891
                            // adds the correct word, followed by ] to close the blank
3892
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3893
                            if (isset($real_text[$i + 1])) {
3894
                                $answer .= $real_text[$i + 1];
3895
                            }
3896
                        }
3897
                    } else {
3898
                        // insert the student result in the track_e_attempt table, field answer
3899
                        // $answer is the answer like in the c_quiz_answer table for the question
3900
                        // student data are choice[]
3901
                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
3902
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3903
                        $answerWeighting = $listCorrectAnswers['weighting'];
3904
                        // user choices is an array $choice
3905
3906
                        // get existing user data in n the BDD
3907
                        if ($from_database) {
3908
                            $listStudentResults = FillBlanks::getAnswerInfo(
3909
                                $answerFromDatabase,
3910
                                true
3911
                            );
3912
                            $choice = $listStudentResults['student_answer'];
3913
                        }
3914
3915
                        // loop other all blanks words
3916
                        if (!$switchableAnswerSet) {
3917
                            // not switchable answer, must be in the same place than teacher order
3918
                            for ($i = 0; $i < count($listCorrectAnswers['words']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3919
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3920
                                $correctAnswer = $listCorrectAnswers['words'][$i];
3921
3922
                                if ($debug) {
3923
                                    error_log("Student answer: $i");
3924
                                    error_log($studentAnswer);
3925
                                }
3926
3927
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3928
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3929
                                // ENT_QUOTES is used in order to transform ' to &#039;
3930
                                if (!$from_database) {
3931
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3932
                                    if ($debug) {
3933
                                        error_log("Student answer cleaned:");
3934
                                        error_log($studentAnswer);
3935
                                    }
3936
                                }
3937
3938
                                $isAnswerCorrect = 0;
3939
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
3940
                                    // gives the related weighting to the student
3941
                                    $questionScore += $answerWeighting[$i];
3942
                                    // increments total score
3943
                                    $totalScore += $answerWeighting[$i];
3944
                                    $isAnswerCorrect = 1;
3945
                                }
3946
                                if ($debug) {
3947
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
3948
                                }
3949
3950
                                $studentAnswerToShow = $studentAnswer;
3951
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3952
                                if ($debug) {
3953
                                    error_log("Fill in blank type: $type");
3954
                                }
3955
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3956
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3957
                                    if ($studentAnswer != '') {
3958
                                        foreach ($listMenu as $item) {
3959
                                            if (sha1($item) == $studentAnswer) {
3960
                                                $studentAnswerToShow = $item;
3961
                                            }
3962
                                        }
3963
                                    }
3964
                                }
3965
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3966
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
3967
                            }
3968
                        } else {
3969
                            // switchable answer
3970
                            $listStudentAnswerTemp = $choice;
3971
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
3972
3973
                            // for every teacher answer, check if there is a student answer
3974
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3975
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3976
                                $studentAnswerToShow = $studentAnswer;
3977
3978
                                if ($debug) {
3979
                                    error_log("Student answer: $i");
3980
                                    error_log($studentAnswer);
3981
                                }
3982
3983
                                $found = false;
3984
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3985
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3986
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3987
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3988
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3989
                                        if (!empty($studentAnswer)) {
3990
                                            foreach ($listMenu as $key => $item) {
3991
                                                if ($key == $correctAnswer) {
3992
                                                    $studentAnswerToShow = $item;
3993
                                                    break;
3994
                                                }
3995
                                            }
3996
                                        }
3997
                                    }
3998
3999
                                    if (!$found) {
4000
                                        if (FillBlanks::isStudentAnswerGood(
4001
                                            $studentAnswer,
4002
                                            $correctAnswer,
4003
                                            $from_database
4004
                                        )
4005
                                        ) {
4006
                                            $questionScore += $answerWeighting[$i];
4007
                                            $totalScore += $answerWeighting[$i];
4008
                                            $listTeacherAnswerTemp[$j] = '';
4009
                                            $found = true;
4010
                                        }
4011
                                    }
4012
                                }
4013
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4014
                                if (!$found) {
4015
                                    $listCorrectAnswers['student_score'][$i] = 0;
4016
                                } else {
4017
                                    $listCorrectAnswers['student_score'][$i] = 1;
4018
                                }
4019
                            }
4020
                        }
4021
                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
4022
                    }
4023
                    break;
4024
                case CALCULATED_ANSWER:
4025
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
4026
                    if (!empty($calculatedAnswerList)) {
4027
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
4028
                        $preArray = explode('@@', $answer);
4029
                        $last = count($preArray) - 1;
4030
                        $answer = '';
4031
                        for ($k = 0; $k < $last; $k++) {
4032
                            $answer .= $preArray[$k];
4033
                        }
4034
                        $answerWeighting = [$answerWeighting];
4035
                        // we save the answer because it will be modified
4036
                        $temp = $answer;
4037
                        $answer = '';
4038
                        $j = 0;
4039
                        // initialise answer tags
4040
                        $userTags = $correctTags = $realText = [];
4041
                        // the loop will stop at the end of the text
4042
                        while (1) {
4043
                            // quits the loop if there are no more blanks (detect '[')
4044
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4045
                                // adds the end of the text
4046
                                $answer = $temp;
4047
                                $realText[] = $answer;
4048
                                break; //no more "blanks", quit the loop
4049
                            }
4050
                            // adds the piece of text that is before the blank
4051
                            // and ends with '[' into a general storage array
4052
                            $realText[] = api_substr($temp, 0, $pos + 1);
4053
                            $answer .= api_substr($temp, 0, $pos + 1);
4054
                            // take the string remaining (after the last "[" we found)
4055
                            $temp = api_substr($temp, $pos + 1);
4056
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4057
                            if (($pos = api_strpos($temp, ']')) === false) {
4058
                                // adds the end of the text
4059
                                $answer .= $temp;
4060
                                break;
4061
                            }
4062
4063
                            if ($from_database) {
4064
                                $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
4065
                                    WHERE
4066
                                        exe_id = '".$exeId."' AND
4067
                                        question_id = ".intval($questionId);
4068
                                $result = Database::query($sql);
4069
                                $str = Database::result($result, 0, 'answer');
4070
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4071
                                $str = str_replace('\r\n', '', $str);
4072
                                $choice = $arr[1];
4073
                                if (isset($choice[$j])) {
4074
                                    $tmp = api_strrpos($choice[$j], ' / ');
4075
4076
                                    if ($tmp) {
4077
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4078
                                    } else {
4079
                                        $tmp = ltrim($tmp, '[');
4080
                                        $tmp = rtrim($tmp, ']');
4081
                                    }
4082
4083
                                    $choice[$j] = trim($choice[$j]);
4084
                                    // Needed to let characters ' and " to work as part of an answer
4085
                                    $choice[$j] = stripslashes($choice[$j]);
4086
                                } else {
4087
                                    $choice[$j] = null;
4088
                                }
4089
                            } else {
4090
                                // This value is the user input not escaped while correct answer is escaped by ckeditor
4091
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4092
                            }
4093
                            $userTags[] = $choice[$j];
4094
                            // put the contents of the [] answer tag into correct_tags[]
4095
                            $correctTags[] = api_substr($temp, 0, $pos);
4096
                            $j++;
4097
                            $temp = api_substr($temp, $pos + 1);
4098
                        }
4099
                        $answer = '';
4100
                        $realCorrectTags = $correctTags;
4101
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4102
                        $expectedAnswer = '';
4103
                        $calculatedChoice = '';
4104
4105
                        for ($i = 0; $i < count($realCorrectTags); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4106
                            if ($i == 0) {
4107
                                $answer .= $realText[0];
4108
                            }
4109
                            // Needed to parse ' and " characters
4110
                            $userTags[$i] = stripslashes($userTags[$i]);
4111
                            if ($correctTags[$i] == $userTags[$i]) {
4112
                                // gives the related weighting to the student
4113
                                $questionScore += $answerWeighting[$i];
4114
                                // increments total score
4115
                                $totalScore += $answerWeighting[$i];
4116
                                // adds the word in green at the end of the string
4117
                                $answer .= $correctTags[$i];
4118
                                $calculatedChoice = $correctTags[$i];
4119
                            } elseif (!empty($userTags[$i])) {
4120
                                // else if the word entered by the student IS NOT the same as
4121
                                // the one defined by the professor
4122
                                // adds the word in red at the end of the string, and strikes it
4123
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4124
                                $calculatedChoice = $userTags[$i];
4125
                            } else {
4126
                                // adds a tabulation if no word has been typed by the student
4127
                                $answer .= ''; // remove &nbsp; that causes issue
4128
                            }
4129
                            // adds the correct word, followed by ] to close the blank
4130
4131
                            if ($this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM) {
4132
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4133
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4134
                                $expectedAnswer = $realCorrectTags[$i];
4135
                            }
4136
                            $answer .= ']';
4137
4138
                            if (isset($realText[$i + 1])) {
4139
                                $answer .= $realText[$i + 1];
4140
                            }
4141
                        }
4142
                    } else {
4143
                        if ($from_database) {
4144
                            $sql = "SELECT *
4145
                                FROM $TBL_TRACK_ATTEMPT
4146
                                WHERE
4147
                                    exe_id = $exeId AND
4148
                                    question_id= ".intval($questionId);
4149
                            $result = Database::query($sql);
4150
                            $resultData = Database::fetch_array($result, 'ASSOC');
4151
                            $answer = $resultData['answer'];
4152
                            $questionScore = $resultData['marks'];
4153
                        }
4154
                    }
4155
                    break;
4156
                case FREE_ANSWER:
4157
                    if ($from_database) {
4158
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4159
                                 WHERE 
4160
                                    exe_id = $exeId AND 
4161
                                    question_id= ".$questionId;
4162
                        $result = Database::query($sql);
4163
                        $data = Database::fetch_array($result);
4164
4165
                        $choice = $data['answer'];
4166
                        $choice = str_replace('\r\n', '', $choice);
4167
                        $choice = stripslashes($choice);
4168
                        $questionScore = $data['marks'];
4169
4170
                        if ($questionScore == -1) {
4171
                            $totalScore += 0;
4172
                        } else {
4173
                            $totalScore += $questionScore;
4174
                        }
4175
                        if ($questionScore == '') {
4176
                            $questionScore = 0;
4177
                        }
4178
                        $arrques = $questionName;
4179
                        $arrans = $choice;
4180
                    } else {
4181
                        $studentChoice = $choice;
4182
                        if ($studentChoice) {
4183
                            //Fixing negative puntation see #2193
4184
                            $questionScore = 0;
4185
                            $totalScore += 0;
4186
                        }
4187
                    }
4188
                    break;
4189
                case ORAL_EXPRESSION:
4190
                    if ($from_database) {
4191
                        $query = "SELECT answer, marks 
4192
                                  FROM $TBL_TRACK_ATTEMPT
4193
                                  WHERE 
4194
                                        exe_id = $exeId AND 
4195
                                        question_id = $questionId
4196
                                 ";
4197
                        $resq = Database::query($query);
4198
                        $row = Database::fetch_assoc($resq);
4199
                        $choice = $row['answer'];
4200
                        $choice = str_replace('\r\n', '', $choice);
4201
                        $choice = stripslashes($choice);
4202
                        $questionScore = $row['marks'];
4203
                        if ($questionScore == -1) {
4204
                            $totalScore += 0;
4205
                        } else {
4206
                            $totalScore += $questionScore;
4207
                        }
4208
                        $arrques = $questionName;
4209
                        $arrans = $choice;
4210
                    } else {
4211
                        $studentChoice = $choice;
4212
                        if ($studentChoice) {
4213
                            //Fixing negative puntation see #2193
4214
                            $questionScore = 0;
4215
                            $totalScore += 0;
4216
                        }
4217
                    }
4218
                    break;
4219
                case DRAGGABLE:
4220
                case MATCHING_DRAGGABLE:
4221
                case MATCHING:
4222
                    if ($from_database) {
4223
                        $sql = "SELECT id, answer, id_auto
4224
                                FROM $table_ans
4225
                                WHERE
4226
                                    c_id = $course_id AND
4227
                                    question_id = $questionId AND
4228
                                    correct = 0
4229
                                ";
4230
                        $result = Database::query($sql);
4231
                        // Getting the real answer
4232
                        $real_list = [];
4233
                        while ($realAnswer = Database::fetch_array($result)) {
4234
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4235
                        }
4236
4237
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4238
                                FROM $table_ans
4239
                                WHERE
4240
                                    c_id = $course_id AND
4241
                                    question_id = $questionId AND
4242
                                    correct <> 0
4243
                                ORDER BY id_auto";
4244
                        $result = Database::query($sql);
4245
                        $options = [];
4246
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4247
                            $options[] = $row;
4248
                        }
4249
4250
                        $questionScore = 0;
4251
                        $counterAnswer = 1;
4252
                        foreach ($options as $a_answers) {
4253
                            $i_answer_id = $a_answers['id']; //3
4254
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4255
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4256
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4257
4258
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4259
                                    WHERE
4260
                                        exe_id = '$exeId' AND
4261
                                        question_id = '$questionId' AND
4262
                                        position = '$i_answer_id_auto'";
4263
                            $result = Database::query($sql);
4264
                            $s_user_answer = 0;
4265
                            if (Database::num_rows($result) > 0) {
4266
                                //  rich - good looking
4267
                                $s_user_answer = Database::result($result, 0, 0);
4268
                            }
4269
                            $i_answerWeighting = $a_answers['ponderation'];
4270
                            $user_answer = '';
4271
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4272
4273
                            if (!empty($s_user_answer)) {
4274
                                if ($answerType == DRAGGABLE) {
4275
                                    if ($s_user_answer == $i_answer_correct_answer) {
4276
                                        $questionScore += $i_answerWeighting;
4277
                                        $totalScore += $i_answerWeighting;
4278
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4279
                                        if ($this->showExpectedChoice()) {
4280
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4281
                                        }
4282
                                        $status = Display::label(get_lang('Correct'), 'success');
4283
                                    } else {
4284
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4285
                                        if ($this->showExpectedChoice()) {
4286
                                            $data = $options[$real_list[$s_user_answer] - 1];
4287
                                            $user_answer = $data['answer'];
4288
                                        }
4289
                                    }
4290
                                } else {
4291
                                    if ($s_user_answer == $i_answer_correct_answer) {
4292
                                        $questionScore += $i_answerWeighting;
4293
                                        $totalScore += $i_answerWeighting;
4294
                                        $status = Display::label(get_lang('Correct'), 'success');
4295
4296
                                        // Try with id
4297
                                        if (isset($real_list[$i_answer_id])) {
4298
                                            $user_answer = Display::span(
4299
                                                $real_list[$i_answer_id],
4300
                                                ['style' => 'color: #008000; font-weight: bold;']
4301
                                            );
4302
                                        }
4303
4304
                                        // Try with $i_answer_id_auto
4305
                                        if (empty($user_answer)) {
4306
                                            if (isset($real_list[$i_answer_id_auto])) {
4307
                                                $user_answer = Display::span(
4308
                                                    $real_list[$i_answer_id_auto],
4309
                                                    ['style' => 'color: #008000; font-weight: bold;']
4310
                                                );
4311
                                            }
4312
                                        }
4313
4314
                                        if (isset($real_list[$i_answer_correct_answer])) {
4315
                                            $user_answer = Display::span(
4316
                                                $real_list[$i_answer_correct_answer],
4317
                                                ['style' => 'color: #008000; font-weight: bold;']
4318
                                            );
4319
                                        }
4320
                                    } else {
4321
                                        $user_answer = Display::span(
4322
                                            $real_list[$s_user_answer],
4323
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4324
                                        );
4325
                                        if ($this->showExpectedChoice()) {
4326
                                            if (isset($real_list[$s_user_answer])) {
4327
                                                $user_answer = Display::span($real_list[$s_user_answer]);
4328
                                            }
4329
                                        }
4330
                                    }
4331
                                }
4332
                            } elseif ($answerType == DRAGGABLE) {
4333
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4334
                                if ($this->showExpectedChoice()) {
4335
                                    $user_answer = '';
4336
                                }
4337
                            } else {
4338
                                $user_answer = Display::span(
4339
                                    get_lang('Incorrect').' &nbsp;',
4340
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4341
                                );
4342
                                if ($this->showExpectedChoice()) {
4343
                                    $user_answer = '';
4344
                                }
4345
                            }
4346
4347
                            if ($show_result) {
4348
                                if ($this->showExpectedChoice() == false &&
4349
                                    $showTotalScoreAndUserChoicesInLastAttempt === false
4350
                                ) {
4351
                                    $user_answer = '';
4352
                                }
4353
                                switch ($answerType) {
4354
                                    case MATCHING:
4355
                                    case MATCHING_DRAGGABLE:
4356
                                        echo '<tr>';
4357
                                        if (!in_array($this->results_disabled, [
4358
                                            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4359
                                            //RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4360
                                            ])
4361
                                        ) {
4362
                                            echo '<td>'.$s_answer_label.'</td>';
4363
                                            echo '<td>'.$user_answer.'</td>';
4364
                                        } else {
4365
                                            echo '<td>'.$s_answer_label.'</td>';
4366
                                            $status = Display::label(get_lang('Correct'), 'success');
4367
                                        }
4368
4369
                                        if ($this->showExpectedChoice()) {
4370
                                            echo '<td>';
4371
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4372
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4373
                                                    $showTotalScoreAndUserChoicesInLastAttempt == true
4374
                                                ) {
4375
                                                    echo Display::span(
4376
                                                        $real_list[$i_answer_correct_answer]
4377
                                                    );
4378
                                                }
4379
                                            }
4380
                                            echo '</td>';
4381
                                            echo '<td>'.$status.'</td>';
4382
                                        } else {
4383
                                            echo '<td>';
4384
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4385
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4386
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4387
                                                ) {
4388
                                                    echo Display::span(
4389
                                                        $real_list[$i_answer_correct_answer],
4390
                                                        ['style' => 'color: #008000; font-weight: bold;']
4391
                                                    );
4392
                                                }
4393
                                            }
4394
                                            echo '</td>';
4395
                                        }
4396
                                        echo '</tr>';
4397
                                        break;
4398
                                    case DRAGGABLE:
4399
                                        if ($showTotalScoreAndUserChoicesInLastAttempt == false) {
4400
                                            $s_answer_label = '';
4401
                                        }
4402
                                        echo '<tr>';
4403
                                        if ($this->showExpectedChoice()) {
4404
                                            if (!in_array($this->results_disabled, [
4405
                                                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4406
                                                //RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4407
                                            ])
4408
                                            ) {
4409
                                                echo '<td>'.$user_answer.'</td>';
4410
                                            } else {
4411
                                                $status = Display::label(get_lang('Correct'), 'success');
4412
                                            }
4413
                                            echo '<td>'.$s_answer_label.'</td>';
4414
                                            echo '<td>'.$status.'</td>';
4415
                                        } else {
4416
                                            echo '<td>'.$s_answer_label.'</td>';
4417
                                            echo '<td>'.$user_answer.'</td>';
4418
                                            echo '<td>';
4419
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4420
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4421
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4422
                                                ) {
4423
                                                    echo Display::span(
4424
                                                        $real_list[$i_answer_correct_answer],
4425
                                                        ['style' => 'color: #008000; font-weight: bold;']
4426
                                                    );
4427
                                                }
4428
                                            }
4429
                                            echo '</td>';
4430
                                        }
4431
                                        echo '</tr>';
4432
                                        break;
4433
                                }
4434
                            }
4435
                            $counterAnswer++;
4436
                        }
4437
                        break 2; // break the switch and the "for" condition
4438
                    } else {
4439
                        if ($answerCorrect) {
4440
                            if (isset($choice[$answerAutoId]) &&
4441
                                $answerCorrect == $choice[$answerAutoId]
4442
                            ) {
4443
                                $questionScore += $answerWeighting;
4444
                                $totalScore += $answerWeighting;
4445
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4446
                            } else {
4447
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4448
                                    $user_answer = Display::span(
4449
                                        $answerMatching[$choice[$answerAutoId]],
4450
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4451
                                    );
4452
                                }
4453
                            }
4454
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4455
                        }
4456
                    }
4457
                    break;
4458
                case HOT_SPOT:
4459
                    if ($from_database) {
4460
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4461
                        // Check auto id
4462
                        $sql = "SELECT hotspot_correct
4463
                                FROM $TBL_TRACK_HOTSPOT
4464
                                WHERE
4465
                                    hotspot_exe_id = $exeId AND
4466
                                    hotspot_question_id= $questionId AND
4467
                                    hotspot_answer_id = ".intval($answerAutoId)."
4468
                                ORDER BY hotspot_id ASC";
4469
                        $result = Database::query($sql);
4470
                        if (Database::num_rows($result)) {
4471
                            $studentChoice = Database::result(
4472
                                $result,
4473
                                0,
4474
                                'hotspot_correct'
4475
                            );
4476
4477
                            if ($studentChoice) {
4478
                                $questionScore += $answerWeighting;
4479
                                $totalScore += $answerWeighting;
4480
                            }
4481
                        } else {
4482
                            // If answer.id is different:
4483
                            $sql = "SELECT hotspot_correct
4484
                                FROM $TBL_TRACK_HOTSPOT
4485
                                WHERE
4486
                                    hotspot_exe_id = $exeId AND
4487
                                    hotspot_question_id= $questionId AND
4488
                                    hotspot_answer_id = ".intval($answerId)."
4489
                                ORDER BY hotspot_id ASC";
4490
                            $result = Database::query($sql);
4491
4492
                            if (Database::num_rows($result)) {
4493
                                $studentChoice = Database::result(
4494
                                    $result,
4495
                                    0,
4496
                                    'hotspot_correct'
4497
                                );
4498
4499
                                if ($studentChoice) {
4500
                                    $questionScore += $answerWeighting;
4501
                                    $totalScore += $answerWeighting;
4502
                                }
4503
                            } else {
4504
                                // check answer.iid
4505
                                if (!empty($answerIid)) {
4506
                                    $sql = "SELECT hotspot_correct
4507
                                            FROM $TBL_TRACK_HOTSPOT
4508
                                            WHERE
4509
                                                hotspot_exe_id = $exeId AND
4510
                                                hotspot_question_id= $questionId AND
4511
                                                hotspot_answer_id = ".intval($answerIid)."
4512
                                            ORDER BY hotspot_id ASC";
4513
                                    $result = Database::query($sql);
4514
4515
                                    $studentChoice = Database::result(
4516
                                        $result,
4517
                                        0,
4518
                                        'hotspot_correct'
4519
                                    );
4520
4521
                                    if ($studentChoice) {
4522
                                        $questionScore += $answerWeighting;
4523
                                        $totalScore += $answerWeighting;
4524
                                    }
4525
                                }
4526
                            }
4527
                        }
4528
                    } else {
4529
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4530
                            $choice[$answerAutoId] = 0;
4531
                            $choice[$answerIid] = 0;
4532
                        } else {
4533
                            $studentChoice = $choice[$answerAutoId];
4534
                            if (empty($studentChoice)) {
4535
                                $studentChoice = $choice[$answerIid];
4536
                            }
4537
                            $choiceIsValid = false;
4538
                            if (!empty($studentChoice)) {
4539
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4540
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4541
                                $choicePoint = Geometry::decodePoint($studentChoice);
4542
4543
                                switch ($hotspotType) {
4544
                                    case 'square':
4545
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4546
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4547
                                        break;
4548
                                    case 'circle':
4549
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4550
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4551
                                        break;
4552
                                    case 'poly':
4553
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4554
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4555
                                        break;
4556
                                }
4557
                            }
4558
4559
                            $choice[$answerAutoId] = 0;
4560
                            if ($choiceIsValid) {
4561
                                $questionScore += $answerWeighting;
4562
                                $totalScore += $answerWeighting;
4563
                                $choice[$answerAutoId] = 1;
4564
                                $choice[$answerIid] = 1;
4565
                            }
4566
                        }
4567
                    }
4568
                    break;
4569
                case HOT_SPOT_ORDER:
4570
                    // @todo never added to chamilo
4571
                    // for hotspot with fixed order
4572
                    $studentChoice = $choice['order'][$answerId];
4573
                    if ($studentChoice == $answerId) {
4574
                        $questionScore += $answerWeighting;
4575
                        $totalScore += $answerWeighting;
4576
                        $studentChoice = true;
4577
                    } else {
4578
                        $studentChoice = false;
4579
                    }
4580
                    break;
4581
                case HOT_SPOT_DELINEATION:
4582
                    // for hotspot with delineation
4583
                    if ($from_database) {
4584
                        // getting the user answer
4585
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4586
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4587
                                    FROM $TBL_TRACK_HOTSPOT
4588
                                    WHERE
4589
                                        hotspot_exe_id = '".$exeId."' AND
4590
                                        hotspot_question_id= '".$questionId."' AND
4591
                                        hotspot_answer_id='1'";
4592
                        //by default we take 1 because it's a delineation
4593
                        $resq = Database::query($query);
4594
                        $row = Database::fetch_array($resq, 'ASSOC');
4595
4596
                        $choice = $row['hotspot_correct'];
4597
                        $user_answer = $row['hotspot_coordinate'];
4598
4599
                        // THIS is very important otherwise the poly_compile will throw an error!!
4600
                        // round-up the coordinates
4601
                        $coords = explode('/', $user_answer);
4602
                        $user_array = '';
4603
                        foreach ($coords as $coord) {
4604
                            list($x, $y) = explode(';', $coord);
4605
                            $user_array .= round($x).';'.round($y).'/';
4606
                        }
4607
                        $user_array = substr($user_array, 0, -1);
4608
                    } else {
4609
                        if (!empty($studentChoice)) {
4610
                            $newquestionList[] = $questionId;
4611
                        }
4612
4613
                        if ($answerId === 1) {
4614
                            $studentChoice = $choice[$answerId];
4615
                            $questionScore += $answerWeighting;
4616
4617
                            if ($hotspot_delineation_result[1] == 1) {
4618
                                $totalScore += $answerWeighting; //adding the total
4619
                            }
4620
                        }
4621
                    }
4622
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4623
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4624
                    break;
4625
                case ANNOTATION:
4626
                    if ($from_database) {
4627
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4628
                                WHERE 
4629
                                  exe_id = $exeId AND 
4630
                                  question_id= ".$questionId;
4631
                        $resq = Database::query($sql);
4632
                        $data = Database::fetch_array($resq);
4633
4634
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4635
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4636
4637
                        $arrques = $questionName;
4638
                        break;
4639
                    }
4640
                    $studentChoice = $choice;
4641
                    if ($studentChoice) {
4642
                        $questionScore = 0;
4643
                        $totalScore += 0;
4644
                    }
4645
                    break;
4646
            } // end switch Answertype
4647
4648
            if ($show_result) {
4649
                if ($debug) {
4650
                    error_log('Showing questions $from '.$from);
4651
                }
4652
4653
                if ($from === 'exercise_result') {
4654
                    //display answers (if not matching type, or if the answer is correct)
4655
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4656
                        $answerCorrect
4657
                    ) {
4658
                        if (in_array(
4659
                            $answerType,
4660
                            [
4661
                                UNIQUE_ANSWER,
4662
                                UNIQUE_ANSWER_IMAGE,
4663
                                UNIQUE_ANSWER_NO_OPTION,
4664
                                MULTIPLE_ANSWER,
4665
                                MULTIPLE_ANSWER_COMBINATION,
4666
                                GLOBAL_MULTIPLE_ANSWER,
4667
                                READING_COMPREHENSION,
4668
                            ]
4669
                        )) {
4670
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4671
                                $this,
4672
                                $feedback_type,
4673
                                $answerType,
4674
                                $studentChoice,
4675
                                $answer,
4676
                                $answerComment,
4677
                                $answerCorrect,
4678
                                0,
4679
                                0,
4680
                                0,
4681
                                $results_disabled,
4682
                                $showTotalScoreAndUserChoicesInLastAttempt,
4683
                                $this->export
4684
                            );
4685
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4686
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4687
                                $this,
4688
                                $feedback_type,
4689
                                $answerType,
4690
                                $studentChoice,
4691
                                $answer,
4692
                                $answerComment,
4693
                                $answerCorrect,
4694
                                0,
4695
                                $questionId,
4696
                                0,
4697
                                $results_disabled,
4698
                                $showTotalScoreAndUserChoicesInLastAttempt
4699
                            );
4700
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4701
                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
4702
                                $feedback_type,
4703
                                $studentChoice,
4704
                                $studentChoiceDegree,
4705
                                $answer,
4706
                                $answerComment,
4707
                                $answerCorrect,
4708
                                $questionId,
4709
                                $results_disabled
4710
                            );
4711
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4712
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4713
                                $this,
4714
                                $feedback_type,
4715
                                $answerType,
4716
                                $studentChoice,
4717
                                $answer,
4718
                                $answerComment,
4719
                                $answerCorrect,
4720
                                0,
4721
                                0,
4722
                                0,
4723
                                $results_disabled,
4724
                                $showTotalScoreAndUserChoicesInLastAttempt
4725
                            );
4726
                        } elseif ($answerType == FILL_IN_BLANKS) {
4727
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4728
                                $feedback_type,
4729
                                $answer,
4730
                                0,
4731
                                0,
4732
                                $results_disabled,
4733
                                '',
4734
                                $showTotalScoreAndUserChoicesInLastAttempt
4735
                            );
4736
                        } elseif ($answerType == CALCULATED_ANSWER) {
4737
                            ExerciseShowFunctions::display_calculated_answer(
4738
                                $this,
4739
                                $feedback_type,
4740
                                $answer,
4741
                                0,
4742
                                0,
4743
                                $results_disabled,
4744
                                $showTotalScoreAndUserChoicesInLastAttempt,
4745
                                $expectedAnswer,
4746
                                $calculatedChoice,
4747
                                $calculatedStatus
4748
                            );
4749
                        } elseif ($answerType == FREE_ANSWER) {
4750
                            ExerciseShowFunctions::display_free_answer(
4751
                                $feedback_type,
4752
                                $choice,
4753
                                $exeId,
4754
                                $questionId,
4755
                                $questionScore,
4756
                                $results_disabled
4757
                            );
4758
                        } elseif ($answerType == ORAL_EXPRESSION) {
4759
                            // to store the details of open questions in an array to be used in mail
4760
                            /** @var OralExpression $objQuestionTmp */
4761
                            ExerciseShowFunctions::display_oral_expression_answer(
4762
                                $feedback_type,
4763
                                $choice,
4764
                                0,
4765
                                0,
4766
                                $objQuestionTmp->getFileUrl(true),
4767
                                $results_disabled,
4768
                                $questionScore
4769
                            );
4770
                        } elseif ($answerType == HOT_SPOT) {
4771
                            $correctAnswerId = 0;
4772
                            /**
4773
                             * @var int
4774
                             * @var TrackEHotspot $hotspot
4775
                             */
4776
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4777
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4778
                                    break;
4779
                                }
4780
                            }
4781
4782
                            // force to show whether the choice is correct or not
4783
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4784
                            ExerciseShowFunctions::display_hotspot_answer(
4785
                                $feedback_type,
4786
                                ++$correctAnswerId,
4787
                                $answer,
4788
                                $studentChoice,
4789
                                $answerComment,
4790
                                $results_disabled,
4791
                                $correctAnswerId,
4792
                                $showTotalScoreAndUserChoicesInLastAttempt
4793
                            );
4794
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4795
                            ExerciseShowFunctions::display_hotspot_order_answer(
4796
                                $feedback_type,
4797
                                $answerId,
4798
                                $answer,
4799
                                $studentChoice,
4800
                                $answerComment
4801
                            );
4802
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4803
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4804
4805
                            //round-up the coordinates
4806
                            $coords = explode('/', $user_answer);
4807
                            $user_array = '';
4808
                            foreach ($coords as $coord) {
4809
                                list($x, $y) = explode(';', $coord);
4810
                                $user_array .= round($x).';'.round($y).'/';
4811
                            }
4812
                            $user_array = substr($user_array, 0, -1);
4813
4814
                            if ($next) {
4815
                                $user_answer = $user_array;
4816
                                // we compare only the delineation not the other points
4817
                                $answer_question = $_SESSION['hotspot_coord'][1];
4818
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4819
4820
                                //calculating the area
4821
                                $poly_user = convert_coordinates($user_answer, '/');
4822
                                $poly_answer = convert_coordinates($answer_question, '|');
4823
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4824
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4825
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4826
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4827
4828
                                $overlap = $poly_results['both'];
4829
                                $poly_answer_area = $poly_results['s1'];
4830
                                $poly_user_area = $poly_results['s2'];
4831
                                $missing = $poly_results['s1Only'];
4832
                                $excess = $poly_results['s2Only'];
4833
4834
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4835
                                // //this is an area in pixels
4836
                                if ($debug > 0) {
4837
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4838
                                }
4839
4840
                                if ($overlap < 1) {
4841
                                    //shortcut to avoid complicated calculations
4842
                                    $final_overlap = 0;
4843
                                    $final_missing = 100;
4844
                                    $final_excess = 100;
4845
                                } else {
4846
                                    // the final overlap is the percentage of the initial polygon
4847
                                    // that is overlapped by the user's polygon
4848
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4849
                                    if ($debug > 1) {
4850
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4851
                                    }
4852
                                    // the final missing area is the percentage of the initial polygon
4853
                                    // that is not overlapped by the user's polygon
4854
                                    $final_missing = 100 - $final_overlap;
4855
                                    if ($debug > 1) {
4856
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4857
                                    }
4858
                                    // the final excess area is the percentage of the initial polygon's size
4859
                                    // that is covered by the user's polygon outside of the initial polygon
4860
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4861
                                    if ($debug > 1) {
4862
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4863
                                    }
4864
                                }
4865
4866
                                //checking the destination parameters parsing the "@@"
4867
                                $destination_items = explode(
4868
                                    '@@',
4869
                                    $answerDestination
4870
                                );
4871
                                $threadhold_total = $destination_items[0];
4872
                                $threadhold_items = explode(
4873
                                    ';',
4874
                                    $threadhold_total
4875
                                );
4876
                                $threadhold1 = $threadhold_items[0]; // overlap
4877
                                $threadhold2 = $threadhold_items[1]; // excess
4878
                                $threadhold3 = $threadhold_items[2]; //missing
4879
4880
                                // if is delineation
4881
                                if ($answerId === 1) {
4882
                                    //setting colors
4883
                                    if ($final_overlap >= $threadhold1) {
4884
                                        $overlap_color = true; //echo 'a';
4885
                                    }
4886
                                    //echo $excess.'-'.$threadhold2;
4887
                                    if ($final_excess <= $threadhold2) {
4888
                                        $excess_color = true; //echo 'b';
4889
                                    }
4890
                                    //echo '--------'.$missing.'-'.$threadhold3;
4891
                                    if ($final_missing <= $threadhold3) {
4892
                                        $missing_color = true; //echo 'c';
4893
                                    }
4894
4895
                                    // if pass
4896
                                    if ($final_overlap >= $threadhold1 &&
4897
                                        $final_missing <= $threadhold3 &&
4898
                                        $final_excess <= $threadhold2
4899
                                    ) {
4900
                                        $next = 1; //go to the oars
4901
                                        $result_comment = get_lang('Acceptable');
4902
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4903
                                    } else {
4904
                                        $next = 0;
4905
                                        $result_comment = get_lang('Unacceptable');
4906
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4907
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4908
                                        // checking the destination parameters parsing the "@@"
4909
                                        $destination_items = explode('@@', $answerDestination);
4910
                                    }
4911
                                } elseif ($answerId > 1) {
4912
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4913
                                        if ($debug > 0) {
4914
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4915
                                        }
4916
                                        //type no error shouldn't be treated
4917
                                        $next = 1;
4918
                                        continue;
4919
                                    }
4920
                                    if ($debug > 0) {
4921
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4922
                                    }
4923
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4924
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4925
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4926
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4927
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4928
4929
                                    if ($overlap == false) {
4930
                                        //all good, no overlap
4931
                                        $next = 1;
4932
                                        continue;
4933
                                    } else {
4934
                                        if ($debug > 0) {
4935
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4936
                                        }
4937
                                        $organs_at_risk_hit++;
4938
                                        //show the feedback
4939
                                        $next = 0;
4940
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4941
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4942
4943
                                        $destination_items = explode('@@', $answerDestination);
4944
                                        $try_hotspot = $destination_items[1];
4945
                                        $lp_hotspot = $destination_items[2];
4946
                                        $select_question_hotspot = $destination_items[3];
4947
                                        $url_hotspot = $destination_items[4];
4948
                                    }
4949
                                }
4950
                            } else {
4951
                                // the first delineation feedback
4952
                                if ($debug > 0) {
4953
                                    error_log(__LINE__.' first', 0);
4954
                                }
4955
                            }
4956
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4957
                            echo '<tr>';
4958
                            echo Display::tag('td', $answerMatching[$answerId]);
4959
                            echo Display::tag(
4960
                                'td',
4961
                                "$user_answer / ".Display::tag(
4962
                                    'strong',
4963
                                    $answerMatching[$answerCorrect],
4964
                                    ['style' => 'color: #008000; font-weight: bold;']
4965
                                )
4966
                            );
4967
                            echo '</tr>';
4968
                        } elseif ($answerType == ANNOTATION) {
4969
                            ExerciseShowFunctions::displayAnnotationAnswer(
4970
                                $feedback_type,
4971
                                $exeId,
4972
                                $questionId,
4973
                                $questionScore,
4974
                                $results_disabled
4975
                            );
4976
                        }
4977
                    }
4978
                } else {
4979
                    if ($debug) {
4980
                        error_log('Showing questions $from '.$from);
4981
                    }
4982
4983
                    switch ($answerType) {
4984
                        case UNIQUE_ANSWER:
4985
                        case UNIQUE_ANSWER_IMAGE:
4986
                        case UNIQUE_ANSWER_NO_OPTION:
4987
                        case MULTIPLE_ANSWER:
4988
                        case GLOBAL_MULTIPLE_ANSWER:
4989
                        case MULTIPLE_ANSWER_COMBINATION:
4990
                        case READING_COMPREHENSION:
4991
                            if ($answerId == 1) {
4992
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4993
                                    $this,
4994
                                    $feedback_type,
4995
                                    $answerType,
4996
                                    $studentChoice,
4997
                                    $answer,
4998
                                    $answerComment,
4999
                                    $answerCorrect,
5000
                                    $exeId,
5001
                                    $questionId,
5002
                                    $answerId,
5003
                                    $results_disabled,
5004
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5005
                                    $this->export
5006
                                );
5007
                            } else {
5008
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
5009
                                    $this,
5010
                                    $feedback_type,
5011
                                    $answerType,
5012
                                    $studentChoice,
5013
                                    $answer,
5014
                                    $answerComment,
5015
                                    $answerCorrect,
5016
                                    $exeId,
5017
                                    $questionId,
5018
                                    '',
5019
                                    $results_disabled,
5020
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5021
                                    $this->export
5022
                                );
5023
                            }
5024
                            break;
5025
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
5026
                            if ($answerId == 1) {
5027
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5028
                                    $this,
5029
                                    $feedback_type,
5030
                                    $answerType,
5031
                                    $studentChoice,
5032
                                    $answer,
5033
                                    $answerComment,
5034
                                    $answerCorrect,
5035
                                    $exeId,
5036
                                    $questionId,
5037
                                    $answerId,
5038
                                    $results_disabled,
5039
                                    $showTotalScoreAndUserChoicesInLastAttempt
5040
                                );
5041
                            } else {
5042
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5043
                                    $this,
5044
                                    $feedback_type,
5045
                                    $answerType,
5046
                                    $studentChoice,
5047
                                    $answer,
5048
                                    $answerComment,
5049
                                    $answerCorrect,
5050
                                    $exeId,
5051
                                    $questionId,
5052
                                    '',
5053
                                    $results_disabled,
5054
                                    $showTotalScoreAndUserChoicesInLastAttempt
5055
                                );
5056
                            }
5057
                            break;
5058
                        case MULTIPLE_ANSWER_TRUE_FALSE:
5059
                            if ($answerId == 1) {
5060
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5061
                                    $this,
5062
                                    $feedback_type,
5063
                                    $answerType,
5064
                                    $studentChoice,
5065
                                    $answer,
5066
                                    $answerComment,
5067
                                    $answerCorrect,
5068
                                    $exeId,
5069
                                    $questionId,
5070
                                    $answerId,
5071
                                    $results_disabled,
5072
                                    $showTotalScoreAndUserChoicesInLastAttempt
5073
                                );
5074
                            } else {
5075
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5076
                                    $this,
5077
                                    $feedback_type,
5078
                                    $answerType,
5079
                                    $studentChoice,
5080
                                    $answer,
5081
                                    $answerComment,
5082
                                    $answerCorrect,
5083
                                    $exeId,
5084
                                    $questionId,
5085
                                    '',
5086
                                    $results_disabled,
5087
                                    $showTotalScoreAndUserChoicesInLastAttempt
5088
                                );
5089
                            }
5090
                            break;
5091
                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
5092
                            if ($answerId == 1) {
5093
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5094
                                    $feedback_type,
5095
                                    $studentChoice,
5096
                                    $studentChoiceDegree,
5097
                                    $answer,
5098
                                    $answerComment,
5099
                                    $answerCorrect,
5100
                                    $questionId,
5101
                                    $results_disabled
5102
                                );
5103
                            } else {
5104
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5105
                                    $feedback_type,
5106
                                    $studentChoice,
5107
                                    $studentChoiceDegree,
5108
                                    $answer,
5109
                                    $answerComment,
5110
                                    $answerCorrect,
5111
                                    $questionId,
5112
                                    $results_disabled
5113
                                );
5114
                            }
5115
                            break;
5116
                        case FILL_IN_BLANKS:
5117
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5118
                                $feedback_type,
5119
                                $answer,
5120
                                $exeId,
5121
                                $questionId,
5122
                                $results_disabled,
5123
                                $str,
5124
                                $showTotalScoreAndUserChoicesInLastAttempt
5125
                            );
5126
                            break;
5127
                        case CALCULATED_ANSWER:
5128
                            ExerciseShowFunctions::display_calculated_answer(
5129
                                $this,
5130
                                $feedback_type,
5131
                                $answer,
5132
                                $exeId,
5133
                                $questionId,
5134
                                $results_disabled,
5135
                                '',
5136
                                $showTotalScoreAndUserChoicesInLastAttempt
5137
                            );
5138
                            break;
5139
                        case FREE_ANSWER:
5140
                            echo ExerciseShowFunctions::display_free_answer(
5141
                                $feedback_type,
5142
                                $choice,
5143
                                $exeId,
5144
                                $questionId,
5145
                                $questionScore,
5146
                                $results_disabled
5147
                            );
5148
                            break;
5149
                        case ORAL_EXPRESSION:
5150
                            echo '<tr>
5151
                                <td valign="top">'.
5152
                                ExerciseShowFunctions::display_oral_expression_answer(
5153
                                    $feedback_type,
5154
                                    $choice,
5155
                                    $exeId,
5156
                                    $questionId,
5157
                                    $objQuestionTmp->getFileUrl(),
5158
                                    $results_disabled,
5159
                                    $questionScore
5160
                                ).'</td>
5161
                                </tr>
5162
                                </table>';
5163
                            break;
5164
                        case HOT_SPOT:
5165
                            ExerciseShowFunctions::display_hotspot_answer(
5166
                                $feedback_type,
5167
                                $answerId,
5168
                                $answer,
5169
                                $studentChoice,
5170
                                $answerComment,
5171
                                $results_disabled,
5172
                                $answerId,
5173
                                $showTotalScoreAndUserChoicesInLastAttempt
5174
                            );
5175
                            break;
5176
                        case HOT_SPOT_DELINEATION:
5177
                            $user_answer = $user_array;
5178
                            if ($next) {
5179
                                $user_answer = $user_array;
5180
                                // we compare only the delineation not the other points
5181
                                $answer_question = $_SESSION['hotspot_coord'][1];
5182
                                $answerDestination = $_SESSION['hotspot_dest'][1];
5183
5184
                                // calculating the area
5185
                                $poly_user = convert_coordinates($user_answer, '/');
5186
                                $poly_answer = convert_coordinates($answer_question, '|');
5187
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5188
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5189
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5190
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5191
5192
                                $overlap = $poly_results['both'];
5193
                                $poly_answer_area = $poly_results['s1'];
5194
                                $poly_user_area = $poly_results['s2'];
5195
                                $missing = $poly_results['s1Only'];
5196
                                $excess = $poly_results['s2Only'];
5197
                                if ($debug > 0) {
5198
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5199
                                }
5200
                                if ($overlap < 1) {
5201
                                    //shortcut to avoid complicated calculations
5202
                                    $final_overlap = 0;
5203
                                    $final_missing = 100;
5204
                                    $final_excess = 100;
5205
                                } else {
5206
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
5207
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5208
                                    if ($debug > 1) {
5209
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5210
                                    }
5211
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
5212
                                    $final_missing = 100 - $final_overlap;
5213
                                    if ($debug > 1) {
5214
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5215
                                    }
5216
                                    // the final excess area is the percentage of the initial polygon's size that is covered by the user's polygon outside of the initial polygon
5217
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5218
                                    if ($debug > 1) {
5219
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5220
                                    }
5221
                                }
5222
5223
                                // Checking the destination parameters parsing the "@@"
5224
                                $destination_items = explode('@@', $answerDestination);
5225
                                $threadhold_total = $destination_items[0];
5226
                                $threadhold_items = explode(';', $threadhold_total);
5227
                                $threadhold1 = $threadhold_items[0]; // overlap
5228
                                $threadhold2 = $threadhold_items[1]; // excess
5229
                                $threadhold3 = $threadhold_items[2]; //missing
5230
                                // if is delineation
5231
                                if ($answerId === 1) {
5232
                                    //setting colors
5233
                                    if ($final_overlap >= $threadhold1) {
5234
                                        $overlap_color = true; //echo 'a';
5235
                                    }
5236
                                    if ($final_excess <= $threadhold2) {
5237
                                        $excess_color = true; //echo 'b';
5238
                                    }
5239
                                    if ($final_missing <= $threadhold3) {
5240
                                        $missing_color = true; //echo 'c';
5241
                                    }
5242
5243
                                    // if pass
5244
                                    if ($final_overlap >= $threadhold1 &&
5245
                                        $final_missing <= $threadhold3 &&
5246
                                        $final_excess <= $threadhold2
5247
                                    ) {
5248
                                        $next = 1; //go to the oars
5249
                                        $result_comment = get_lang('Acceptable');
5250
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5251
                                    } else {
5252
                                        $next = 0;
5253
                                        $result_comment = get_lang('Unacceptable');
5254
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5255
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5256
                                        //checking the destination parameters parsing the "@@"
5257
                                        $destination_items = explode('@@', $answerDestination);
5258
                                    }
5259
                                } elseif ($answerId > 1) {
5260
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5261
                                        if ($debug > 0) {
5262
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5263
                                        }
5264
                                        //type no error shouldn't be treated
5265
                                        $next = 1;
5266
                                        break;
5267
                                    }
5268
                                    if ($debug > 0) {
5269
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5270
                                    }
5271
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5272
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5273
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5274
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5275
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5276
5277
                                    if ($overlap == false) {
5278
                                        //all good, no overlap
5279
                                        $next = 1;
5280
                                        break;
5281
                                    } else {
5282
                                        if ($debug > 0) {
5283
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5284
                                        }
5285
                                        $organs_at_risk_hit++;
5286
                                        //show the feedback
5287
                                        $next = 0;
5288
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5289
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5290
5291
                                        $destination_items = explode('@@', $answerDestination);
5292
                                        $try_hotspot = $destination_items[1];
5293
                                        $lp_hotspot = $destination_items[2];
5294
                                        $select_question_hotspot = $destination_items[3];
5295
                                        $url_hotspot = $destination_items[4];
5296
                                    }
5297
                                }
5298
                            } else {
5299
                                // the first delineation feedback
5300
                                if ($debug > 0) {
5301
                                    error_log(__LINE__.' first', 0);
5302
                                }
5303
                            }
5304
                            break;
5305
                        case HOT_SPOT_ORDER:
5306
                            ExerciseShowFunctions::display_hotspot_order_answer(
5307
                                $feedback_type,
5308
                                $answerId,
5309
                                $answer,
5310
                                $studentChoice,
5311
                                $answerComment
5312
                            );
5313
                            break;
5314
                        case DRAGGABLE:
5315
                        case MATCHING_DRAGGABLE:
5316
                        case MATCHING:
5317
                            echo '<tr>';
5318
                            echo Display::tag('td', $answerMatching[$answerId]);
5319
                            echo Display::tag(
5320
                                'td',
5321
                                "$user_answer / ".Display::tag(
5322
                                    'strong',
5323
                                    $answerMatching[$answerCorrect],
5324
                                    ['style' => 'color: #008000; font-weight: bold;']
5325
                                )
5326
                            );
5327
                            echo '</tr>';
5328
                            break;
5329
                        case ANNOTATION:
5330
                            ExerciseShowFunctions::displayAnnotationAnswer(
5331
                                $feedback_type,
5332
                                $exeId,
5333
                                $questionId,
5334
                                $questionScore,
5335
                                $results_disabled
5336
                            );
5337
                            break;
5338
                    }
5339
                }
5340
            }
5341
            if ($debug) {
5342
                error_log(' ------ ');
5343
            }
5344
        } // end for that loops over all answers of the current question
5345
5346
        if ($debug) {
5347
            error_log('-- end answer loop --');
5348
        }
5349
5350
        $final_answer = true;
5351
5352
        foreach ($real_answers as $my_answer) {
5353
            if (!$my_answer) {
5354
                $final_answer = false;
5355
            }
5356
        }
5357
5358
        //we add the total score after dealing with the answers
5359
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5360
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5361
        ) {
5362
            if ($final_answer) {
5363
                //getting only the first score where we save the weight of all the question
5364
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5365
                $questionScore += $answerWeighting;
5366
                $totalScore += $answerWeighting;
5367
            }
5368
        }
5369
5370
        //Fixes multiple answer question in order to be exact
5371
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5372
        /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
5373
             $diff = @array_diff($answer_correct_array, $real_answers);
5374
5375
             // All good answers or nothing works like exact
5376
5377
             $counter = 1;
5378
             $correct_answer = true;
5379
             foreach ($real_answers as $my_answer) {
5380
                 if ($debug)
5381
                     error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
5382
                 if ($my_answer != $answer_correct_array[$counter]) {
5383
                     $correct_answer = false;
5384
                     break;
5385
                 }
5386
                 $counter++;
5387
             }
5388
5389
             if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
5390
             if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
5391
             if ($debug) error_log(" correct_answer: ".$correct_answer);
5392
5393
             if ($correct_answer == false) {
5394
                 $questionScore = 0;
5395
             }
5396
5397
             // This makes the result non exact
5398
             if (!empty($diff)) {
5399
                 $questionScore = 0;
5400
             }
5401
         }*/
5402
5403
        $extra_data = [
5404
            'final_overlap' => $final_overlap,
5405
            'final_missing' => $final_missing,
5406
            'final_excess' => $final_excess,
5407
            'overlap_color' => $overlap_color,
5408
            'missing_color' => $missing_color,
5409
            'excess_color' => $excess_color,
5410
            'threadhold1' => $threadhold1,
5411
            'threadhold2' => $threadhold2,
5412
            'threadhold3' => $threadhold3,
5413
        ];
5414
5415
        if ($from == 'exercise_result') {
5416
            // if answer is hotspot. To the difference of exercise_show.php,
5417
            //  we use the results from the session (from_db=0)
5418
            // TODO Change this, because it is wrong to show the user
5419
            //  some results that haven't been stored in the database yet
5420
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5421
                if ($debug) {
5422
                    error_log('$from AND this is a hotspot kind of question ');
5423
                }
5424
                $my_exe_id = 0;
5425
                $from_database = 0;
5426
                if ($answerType == HOT_SPOT_DELINEATION) {
5427
                    if (0) {
5428
                        if ($overlap_color) {
5429
                            $overlap_color = 'green';
5430
                        } else {
5431
                            $overlap_color = 'red';
5432
                        }
5433
                        if ($missing_color) {
5434
                            $missing_color = 'green';
5435
                        } else {
5436
                            $missing_color = 'red';
5437
                        }
5438
                        if ($excess_color) {
5439
                            $excess_color = 'green';
5440
                        } else {
5441
                            $excess_color = 'red';
5442
                        }
5443
                        if (!is_numeric($final_overlap)) {
5444
                            $final_overlap = 0;
5445
                        }
5446
                        if (!is_numeric($final_missing)) {
5447
                            $final_missing = 0;
5448
                        }
5449
                        if (!is_numeric($final_excess)) {
5450
                            $final_excess = 0;
5451
                        }
5452
5453
                        if ($final_overlap > 100) {
5454
                            $final_overlap = 100;
5455
                        }
5456
5457
                        $table_resume = '<table class="data_table">
5458
                                <tr class="row_odd" >
5459
                                    <td></td>
5460
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5461
                                    <td><b>'.get_lang('YourAnswer').'</b></td>
5462
                                </tr>
5463
                                <tr class="row_even">
5464
                                    <td><b>'.get_lang('Overlap').'</b></td>
5465
                                    <td>'.get_lang('Min').' '.$threadhold1.'</td>
5466
                                    <td><div style="color:'.$overlap_color.'">'
5467
                            .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5468
                                </tr>
5469
                                <tr>
5470
                                    <td><b>'.get_lang('Excess').'</b></td>
5471
                                    <td>'.get_lang('Max').' '.$threadhold2.'</td>
5472
                                    <td><div style="color:'.$excess_color.'">'
5473
                            .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5474
                                </tr>
5475
                                <tr class="row_even">
5476
                                    <td><b>'.get_lang('Missing').'</b></td>
5477
                                    <td>'.get_lang('Max').' '.$threadhold3.'</td>
5478
                                    <td><div style="color:'.$missing_color.'">'
5479
                            .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5480
                                </tr>
5481
                            </table>';
5482
                        if ($next == 0) {
5483
                            $try = $try_hotspot;
5484
                            $lp = $lp_hotspot;
5485
                            $destinationid = $select_question_hotspot;
5486
                            $url = $url_hotspot;
5487
                        } else {
5488
                            //show if no error
5489
                            //echo 'no error';
5490
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5491
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5492
                        }
5493
5494
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5495
                            <p style="text-align:center">';
5496
5497
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5498
                        $message .= $table_resume;
5499
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5500
                        if ($organs_at_risk_hit > 0) {
5501
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5502
                        }
5503
                        $message .= '<p>'.$comment.'</p>';
5504
                        echo $message;
5505
                    } else {
5506
                        echo $hotspot_delineation_result[0]; //prints message
5507
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5508
                    }
5509
5510
                    //save the score attempts
5511
                    if (1) {
5512
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5513
                        $final_answer = $hotspot_delineation_result[1];
5514
                        if ($final_answer == 0) {
5515
                            $questionScore = 0;
5516
                        }
5517
                        // we always insert the answer_id 1 = delineation
5518
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5519
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5520
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5521
                        Event::saveExerciseAttemptHotspot(
5522
                            $exeId,
5523
                            $quesId,
5524
                            1,
5525
                            $hotspotValue,
5526
                            $exerciseResultCoordinates[$quesId]
5527
                        );
5528
                    } else {
5529
                        if ($final_answer == 0) {
5530
                            $questionScore = 0;
5531
                            $answer = 0;
5532
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5533
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5534
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5535
                                    Event::saveExerciseAttemptHotspot(
5536
                                        $exeId,
5537
                                        $quesId,
5538
                                        $idx,
5539
                                        0,
5540
                                        $val
5541
                                    );
5542
                                }
5543
                            }
5544
                        } else {
5545
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5546
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5547
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5548
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5549
                                    Event::saveExerciseAttemptHotspot(
5550
                                        $exeId,
5551
                                        $quesId,
5552
                                        $idx,
5553
                                        $hotspotValue,
5554
                                        $val
5555
                                    );
5556
                                }
5557
                            }
5558
                        }
5559
                    }
5560
                    $my_exe_id = $exeId;
5561
                }
5562
            }
5563
5564
            $relPath = api_get_path(WEB_CODE_PATH);
5565
5566
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5567
                // We made an extra table for the answers
5568
                if ($show_result) {
5569
                    echo '</table></td></tr>';
5570
                    echo "
5571
                        <tr>
5572
                            <td colspan=\"2\">
5573
                                <p><em>".get_lang('HotSpot')."</em></p>
5574
                                <div id=\"hotspot-solution-$questionId\"></div>
5575
                                <script>
5576
                                    $(function() {
5577
                                        new HotspotQuestion({
5578
                                            questionId: $questionId,
5579
                                            exerciseId: {$this->id},
5580
                                            exeId: $exeId,
5581
                                            selector: '#hotspot-solution-$questionId',
5582
                                            for: 'solution',
5583
                                            relPath: '$relPath'
5584
                                        });
5585
                                    });
5586
                                </script>
5587
                            </td>
5588
                        </tr>
5589
                    ";
5590
                }
5591
            } elseif ($answerType == ANNOTATION) {
5592
                if ($show_result) {
5593
                    echo '
5594
                        <p><em>'.get_lang('Annotation').'</em></p>
5595
                        <div id="annotation-canvas-'.$questionId.'"></div>
5596
                        <script>
5597
                            AnnotationQuestion({
5598
                                questionId: parseInt('.$questionId.'),
5599
                                exerciseId: parseInt('.$exeId.'),
5600
                                relPath: \''.$relPath.'\',
5601
                                courseId: parseInt('.$course_id.')
5602
                            });
5603
                        </script>
5604
                    ';
5605
                }
5606
            }
5607
5608
            //if ($origin != 'learnpath') {
5609
            if ($show_result && $answerType != ANNOTATION) {
5610
                echo '</table>';
5611
            }
5612
            //	}
5613
        }
5614
        unset($objAnswerTmp);
5615
5616
        $totalWeighting += $questionWeighting;
5617
        // Store results directly in the database
5618
        // For all in one page exercises, the results will be
5619
        // stored by exercise_results.php (using the session)
5620
        if ($saved_results) {
5621
            if ($debug) {
5622
                error_log("Save question results $saved_results");
5623
                error_log('choice: ');
5624
                error_log(print_r($choice, 1));
5625
            }
5626
5627
            if (empty($choice)) {
5628
                $choice = 0;
5629
            }
5630
            // with certainty degree
5631
            if (empty($choiceDegreeCertainty)) {
5632
                $choiceDegreeCertainty = 0;
5633
            }
5634
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
5635
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ||
5636
                $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
5637
            ) {
5638
                if ($choice != 0) {
5639
                    $reply = array_keys($choice);
5640
                    $countReply = count($reply);
5641
                    for ($i = 0; $i < $countReply; $i++) {
5642
                        $chosenAnswer = $reply[$i];
5643
                        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5644
                            if ($choiceDegreeCertainty != 0) {
5645
                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
5646
                                $answerDegreeCertainty = isset($replyDegreeCertainty[$i]) ? $replyDegreeCertainty[$i] : '';
5647
                                $answerValue = isset($choiceDegreeCertainty[$answerDegreeCertainty]) ? $choiceDegreeCertainty[$answerDegreeCertainty] : '';
5648
                                Event::saveQuestionAttempt(
5649
                                    $questionScore,
5650
                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.$answerValue,
5651
                                    $quesId,
5652
                                    $exeId,
5653
                                    $i,
5654
                                    $this->id,
5655
                                    $updateResults
5656
                                );
5657
                            }
5658
                        } else {
5659
                            Event::saveQuestionAttempt(
5660
                                $questionScore,
5661
                                $chosenAnswer.':'.$choice[$chosenAnswer],
5662
                                $quesId,
5663
                                $exeId,
5664
                                $i,
5665
                                $this->id,
5666
                                $updateResults
5667
                            );
5668
                        }
5669
                        if ($debug) {
5670
                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
5671
                        }
5672
                    }
5673
                } else {
5674
                    Event::saveQuestionAttempt(
5675
                        $questionScore,
5676
                        0,
5677
                        $quesId,
5678
                        $exeId,
5679
                        0,
5680
                        $this->id
5681
                    );
5682
                }
5683
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5684
                if ($choice != 0) {
5685
                    $reply = array_keys($choice);
5686
5687
                    if ($debug) {
5688
                        error_log("reply ".print_r($reply, 1)."");
5689
                    }
5690
                    for ($i = 0; $i < sizeof($reply); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5691
                        $ans = $reply[$i];
5692
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5693
                    }
5694
                } else {
5695
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5696
                }
5697
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5698
                if ($choice != 0) {
5699
                    $reply = array_keys($choice);
5700
                    for ($i = 0; $i < sizeof($reply); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5701
                        $ans = $reply[$i];
5702
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5703
                    }
5704
                } else {
5705
                    Event::saveQuestionAttempt(
5706
                        $questionScore,
5707
                        0,
5708
                        $quesId,
5709
                        $exeId,
5710
                        0,
5711
                        $this->id
5712
                    );
5713
                }
5714
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5715
                if (isset($matching)) {
5716
                    foreach ($matching as $j => $val) {
5717
                        Event::saveQuestionAttempt(
5718
                            $questionScore,
5719
                            $val,
5720
                            $quesId,
5721
                            $exeId,
5722
                            $j,
5723
                            $this->id
5724
                        );
5725
                    }
5726
                }
5727
            } elseif ($answerType == FREE_ANSWER) {
5728
                $answer = $choice;
5729
                Event::saveQuestionAttempt(
5730
                    $questionScore,
5731
                    $answer,
5732
                    $quesId,
5733
                    $exeId,
5734
                    0,
5735
                    $this->id
5736
                );
5737
            } elseif ($answerType == ORAL_EXPRESSION) {
5738
                $answer = $choice;
5739
                Event::saveQuestionAttempt(
5740
                    $questionScore,
5741
                    $answer,
5742
                    $quesId,
5743
                    $exeId,
5744
                    0,
5745
                    $this->id,
5746
                    false,
5747
                    $objQuestionTmp->getAbsoluteFilePath()
5748
                );
5749
            } elseif (
5750
                in_array(
5751
                    $answerType,
5752
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
5753
                )
5754
            ) {
5755
                $answer = $choice;
5756
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5757
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5758
                $answer = [];
5759
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5760
                    if ($debug) {
5761
                        error_log('Checking result coordinates');
5762
                    }
5763
                    Database::delete(
5764
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5765
                        [
5766
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5767
                                $exeId,
5768
                                $questionId,
5769
                                api_get_course_int_id(),
5770
                            ],
5771
                        ]
5772
                    );
5773
5774
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5775
                        $answer[] = $val;
5776
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5777
                        if ($debug) {
5778
                            error_log('Hotspot value: '.$hotspotValue);
5779
                        }
5780
                        Event::saveExerciseAttemptHotspot(
5781
                            $exeId,
5782
                            $quesId,
5783
                            $idx,
5784
                            $hotspotValue,
5785
                            $val,
5786
                            false,
5787
                            $this->id
5788
                        );
5789
                    }
5790
                } else {
5791
                    if ($debug) {
5792
                        error_log('Empty: exerciseResultCoordinates');
5793
                    }
5794
                }
5795
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5796
            } else {
5797
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5798
            }
5799
        }
5800
5801
        if ($propagate_neg == 0 && $questionScore < 0) {
5802
            $questionScore = 0;
5803
        }
5804
5805
        if ($saved_results) {
5806
            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5807
            $sql = "UPDATE $statsTable SET
5808
                        score = score + ".floatval($questionScore)."
5809
                    WHERE exe_id = $exeId";
5810
            Database::query($sql);
5811
            if ($debug) {
5812
                error_log($sql);
5813
            }
5814
        }
5815
5816
        $return = [
5817
            'score' => $questionScore,
5818
            'weight' => $questionWeighting,
5819
            'extra' => $extra_data,
5820
            'open_question' => $arrques,
5821
            'open_answer' => $arrans,
5822
            'answer_type' => $answerType,
5823
            'generated_oral_file' => $generatedFile,
5824
            'user_answered' => $userAnsweredQuestion,
5825
        ];
5826
5827
        return $return;
5828
    }
5829
5830
    /**
5831
     * Sends a notification when a user ends an examn.
5832
     *
5833
     * @param string $type                  'start' or 'end' of an exercise
5834
     * @param array  $question_list_answers
5835
     * @param string $origin
5836
     * @param int    $exe_id
5837
     * @param float  $score
5838
     * @param float  $weight
5839
     *
5840
     * @return bool
5841
     */
5842
    public function send_mail_notification_for_exam(
5843
        $type = 'end',
5844
        $question_list_answers,
5845
        $origin,
5846
        $exe_id,
5847
        $score = null,
5848
        $weight = null
5849
    ) {
5850
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5851
5852
        if (empty($setting) && empty($this->getNotifications())) {
5853
            return false;
5854
        }
5855
5856
        $settingFromExercise = $this->getNotifications();
5857
        if (!empty($settingFromExercise)) {
5858
            $setting = $settingFromExercise;
5859
        }
5860
5861
        // Email configuration settings
5862
        $courseCode = api_get_course_id();
5863
        $courseInfo = api_get_course_info($courseCode);
5864
5865
        if (empty($courseInfo)) {
5866
            return false;
5867
        }
5868
5869
        $sessionId = api_get_session_id();
5870
5871
        $sessionData = '';
5872
        if (!empty($sessionId)) {
5873
            $sessionInfo = api_get_session_info($sessionId);
5874
            if (!empty($sessionInfo)) {
5875
                $sessionData = '<tr>'
5876
                    .'<td>'.get_lang('SessionName').'</td>'
5877
                    .'<td>'.$sessionInfo['name'].'</td>'
5878
                    .'</tr>';
5879
            }
5880
        }
5881
5882
        $sendStart = false;
5883
        $sendEnd = false;
5884
        $sendEndOpenQuestion = false;
5885
        $sendEndOralQuestion = false;
5886
5887
        foreach ($setting as $option) {
5888
            switch ($option) {
5889
                case 0:
5890
                    return false;
5891
                    break;
5892
                case 1: // End
5893
                    if ($type == 'end') {
5894
                        $sendEnd = true;
5895
                    }
5896
                    break;
5897
                case 2: // start
5898
                    if ($type == 'start') {
5899
                        $sendStart = true;
5900
                    }
5901
                    break;
5902
                case 3: // end + open
5903
                    if ($type == 'end') {
5904
                        $sendEndOpenQuestion = true;
5905
                    }
5906
                    break;
5907
                case 4: // end + oral
5908
                    if ($type == 'end') {
5909
                        $sendEndOralQuestion = true;
5910
                    }
5911
                    break;
5912
            }
5913
        }
5914
5915
        $user_info = api_get_user_info(api_get_user_id());
5916
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
5917
            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
5918
5919
        if (!empty($sessionId)) {
5920
            $addGeneralCoach = true;
5921
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5922
            if ($setting === true) {
5923
                $addGeneralCoach = false;
5924
            }
5925
            $teachers = CourseManager::get_coach_list_from_course_code(
5926
                $courseCode,
5927
                $sessionId,
5928
                $addGeneralCoach
5929
            );
5930
        } else {
5931
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5932
        }
5933
5934
        if ($sendEndOpenQuestion) {
5935
            $this->sendNotificationForOpenQuestions(
5936
                $question_list_answers,
5937
                $origin,
5938
                $user_info,
5939
                $url,
5940
                $teachers
5941
            );
5942
        }
5943
5944
        if ($sendEndOralQuestion) {
5945
            $this->sendNotificationForOralQuestions(
5946
                $question_list_answers,
5947
                $origin,
5948
                $exe_id,
5949
                $user_info,
5950
                $url,
5951
                $teachers
5952
            );
5953
        }
5954
5955
        if (!$sendEnd && !$sendStart) {
5956
            return false;
5957
        }
5958
5959
        $scoreLabel = '';
5960
        if ($sendEnd &&
5961
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
5962
        ) {
5963
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
5964
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
5965
            $scoreLabel = "<tr>
5966
                            <td>".get_lang('Score')."</td>
5967
                            <td>&nbsp;$scoreLabel</td>
5968
                        </tr>";
5969
        }
5970
5971
        if ($sendEnd) {
5972
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5973
        } else {
5974
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5975
        }
5976
5977
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5978
                    <table>
5979
                        <tr>
5980
                            <td>'.get_lang('CourseName').'</td>
5981
                            <td>#course#</td>
5982
                        </tr>
5983
                        '.$sessionData.'
5984
                        <tr>
5985
                            <td>'.get_lang('Exercise').'</td>
5986
                            <td>&nbsp;#exercise#</td>
5987
                        </tr>
5988
                        <tr>
5989
                            <td>'.get_lang('StudentName').'</td>
5990
                            <td>&nbsp;#student_complete_name#</td>
5991
                        </tr>
5992
                        <tr>
5993
                            <td>'.get_lang('StudentEmail').'</td>
5994
                            <td>&nbsp;#email#</td>
5995
                        </tr>
5996
                        '.$scoreLabel.'
5997
                    </table>';
5998
5999
        $variables = [
6000
            '#email#' => $user_info['email'],
6001
            '#exercise#' => $this->exercise,
6002
            '#student_complete_name#' => $user_info['complete_name'],
6003
            '#course#' => Display::url(
6004
                $courseInfo['title'],
6005
                $courseInfo['course_public_url'].'?id_session='.$sessionId
6006
            ),
6007
        ];
6008
6009
        if ($sendEnd) {
6010
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
6011
            $variables['#url#'] = $url;
6012
        }
6013
6014
        $content = str_replace(array_keys($variables), array_values($variables), $msg);
6015
6016
        if ($sendEnd) {
6017
            $subject = get_lang('ExerciseAttempted');
6018
        } else {
6019
            $subject = get_lang('StudentStartExercise');
6020
        }
6021
6022
        if (!empty($teachers)) {
6023
            foreach ($teachers as $user_id => $teacher_data) {
6024
                MessageManager::send_message_simple(
6025
                    $user_id,
6026
                    $subject,
6027
                    $content
6028
                );
6029
            }
6030
        }
6031
    }
6032
6033
    /**
6034
     * @param array $user_data         result of api_get_user_info()
6035
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
6036
     *
6037
     * @return string
6038
     */
6039
    public function showExerciseResultHeader(
6040
        $user_data,
6041
        $trackExerciseInfo
6042
    ) {
6043
        if (api_get_configuration_value('hide_user_info_in_quiz_result')) {
6044
            return '';
6045
        }
6046
6047
        $start_date = null;
6048
6049
        if (isset($trackExerciseInfo['start_date'])) {
6050
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
6051
        }
6052
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
6053
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
6054
6055
        if (!empty($user_data)) {
6056
            $userFullName = $user_data['complete_name'];
6057
            if (api_is_teacher() || api_is_platform_admin(true, true)) {
6058
                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.
6059
                    $user_data['complete_name'].'</a>';
6060
            }
6061
6062
            $data = [
6063
                'name_url' => $userFullName,
6064
                'complete_name' => $user_data['complete_name'],
6065
                'username' => $user_data['username'],
6066
                'avatar' => $user_data['avatar_medium'],
6067
                'url' => $user_data['profile_url'],
6068
            ];
6069
6070
            if (!empty($user_data['official_code'])) {
6071
                $data['code'] = $user_data['official_code'];
6072
            }
6073
        }
6074
        // Description can be very long and is generally meant to explain
6075
        //   rules *before* the exam. Leaving here to make display easier if
6076
        //   necessary
6077
        /*
6078
        if (!empty($this->description)) {
6079
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
6080
        }
6081
        */
6082
        if (!empty($start_date)) {
6083
            $data['start_date'] = $start_date;
6084
        }
6085
6086
        if (!empty($duration)) {
6087
            $data['duration'] = $duration;
6088
        }
6089
6090
        if (!empty($ip)) {
6091
            $data['ip'] = $ip;
6092
        }
6093
6094
        if (api_get_configuration_value('save_titles_as_html')) {
6095
            $data['title'] = $this->get_formated_title().get_lang('Result');
6096
        } else {
6097
            $data['title'] = PHP_EOL.$this->exercise.' : '.get_lang('Result');
6098
        }
6099
6100
        $tpl = new Template(null, false, false, false, false, false, false);
6101
        $tpl->assign('data', $data);
6102
        $layoutTemplate = $tpl->get_template('exercise/partials/result_exercise.tpl');
6103
        $content = $tpl->fetch($layoutTemplate);
6104
6105
        return $content;
6106
    }
6107
6108
    /**
6109
     * Returns the exercise result.
6110
     *
6111
     * @param 	int		attempt id
6112
     *
6113
     * @return array
6114
     */
6115
    public function get_exercise_result($exe_id)
6116
    {
6117
        $result = [];
6118
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
6119
6120
        if (!empty($track_exercise_info)) {
6121
            $totalScore = 0;
6122
            $objExercise = new Exercise();
6123
            $objExercise->read($track_exercise_info['exe_exo_id']);
6124
            if (!empty($track_exercise_info['data_tracking'])) {
6125
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6126
            }
6127
            foreach ($question_list as $questionId) {
6128
                $question_result = $objExercise->manage_answer(
6129
                    $exe_id,
6130
                    $questionId,
6131
                    '',
6132
                    'exercise_show',
6133
                    [],
6134
                    false,
6135
                    true,
6136
                    false,
6137
                    $objExercise->selectPropagateNeg()
6138
                );
6139
                $totalScore += $question_result['score'];
6140
            }
6141
6142
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
6143
                $totalScore = 0;
6144
            }
6145
            $result = [
6146
                'score' => $totalScore,
6147
                'weight' => $track_exercise_info['max_score'],
6148
            ];
6149
        }
6150
6151
        return $result;
6152
    }
6153
6154
    /**
6155
     * Checks if the exercise is visible due a lot of conditions
6156
     * visibility, time limits, student attempts
6157
     * Return associative array
6158
     * value : true if exercise visible
6159
     * message : HTML formatted message
6160
     * rawMessage : text message.
6161
     *
6162
     * @param int  $lpId
6163
     * @param int  $lpItemId
6164
     * @param int  $lpItemViewId
6165
     * @param bool $filterByAdmin
6166
     *
6167
     * @return array
6168
     */
6169
    public function is_visible(
6170
        $lpId = 0,
6171
        $lpItemId = 0,
6172
        $lpItemViewId = 0,
6173
        $filterByAdmin = true
6174
    ) {
6175
        // 1. By default the exercise is visible
6176
        $isVisible = true;
6177
        $message = null;
6178
6179
        // 1.1 Admins and teachers can access to the exercise
6180
        if ($filterByAdmin) {
6181
            if (api_is_platform_admin() || api_is_course_admin()) {
6182
                return ['value' => true, 'message' => ''];
6183
            }
6184
        }
6185
6186
        // Deleted exercise.
6187
        if ($this->active == -1) {
6188
            return [
6189
                'value' => false,
6190
                'message' => Display::return_message(
6191
                    get_lang('ExerciseNotFound'),
6192
                    'warning',
6193
                    false
6194
                ),
6195
                'rawMessage' => get_lang('ExerciseNotFound'),
6196
            ];
6197
        }
6198
6199
        // Checking visibility in the item_property table.
6200
        $visibility = api_get_item_visibility(
6201
            api_get_course_info(),
6202
            TOOL_QUIZ,
6203
            $this->id,
6204
            api_get_session_id()
6205
        );
6206
6207
        if ($visibility == 0 || $visibility == 2) {
6208
            $this->active = 0;
6209
        }
6210
6211
        // 2. If the exercise is not active.
6212
        if (empty($lpId)) {
6213
            // 2.1 LP is OFF
6214
            if ($this->active == 0) {
6215
                return [
6216
                    'value' => false,
6217
                    'message' => Display::return_message(
6218
                        get_lang('ExerciseNotFound'),
6219
                        'warning',
6220
                        false
6221
                    ),
6222
                    'rawMessage' => get_lang('ExerciseNotFound'),
6223
                ];
6224
            }
6225
        } else {
6226
            // 2.1 LP is loaded
6227
            if ($this->active == 0 &&
6228
                !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())
6229
            ) {
6230
                return [
6231
                    'value' => false,
6232
                    'message' => Display::return_message(
6233
                        get_lang('ExerciseNotFound'),
6234
                        'warning',
6235
                        false
6236
                    ),
6237
                    'rawMessage' => get_lang('ExerciseNotFound'),
6238
                ];
6239
            }
6240
        }
6241
6242
        // 3. We check if the time limits are on
6243
        $limitTimeExists = false;
6244
        if (!empty($this->start_time) || !empty($this->end_time)) {
6245
            $limitTimeExists = true;
6246
        }
6247
6248
        if ($limitTimeExists) {
6249
            $timeNow = time();
6250
            $existsStartDate = false;
6251
            $nowIsAfterStartDate = true;
6252
            $existsEndDate = false;
6253
            $nowIsBeforeEndDate = true;
6254
6255
            if (!empty($this->start_time)) {
6256
                $existsStartDate = true;
6257
            }
6258
6259
            if (!empty($this->end_time)) {
6260
                $existsEndDate = true;
6261
            }
6262
6263
            // check if we are before-or-after end-or-start date
6264
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6265
                $nowIsAfterStartDate = false;
6266
            }
6267
6268
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6269
                $nowIsBeforeEndDate = false;
6270
            }
6271
6272
            // lets check all cases
6273
            if ($existsStartDate && !$existsEndDate) {
6274
                // exists start date and dont exists end date
6275
                if ($nowIsAfterStartDate) {
6276
                    // after start date, no end date
6277
                    $isVisible = true;
6278
                    $message = sprintf(
6279
                        get_lang('ExerciseAvailableSinceX'),
6280
                        api_convert_and_format_date($this->start_time)
6281
                    );
6282
                } else {
6283
                    // before start date, no end date
6284
                    $isVisible = false;
6285
                    $message = sprintf(
6286
                        get_lang('ExerciseAvailableFromX'),
6287
                        api_convert_and_format_date($this->start_time)
6288
                    );
6289
                }
6290
            } elseif (!$existsStartDate && $existsEndDate) {
6291
                // doesnt exist start date, exists end date
6292
                if ($nowIsBeforeEndDate) {
6293
                    // before end date, no start date
6294
                    $isVisible = true;
6295
                    $message = sprintf(
6296
                        get_lang('ExerciseAvailableUntilX'),
6297
                        api_convert_and_format_date($this->end_time)
6298
                    );
6299
                } else {
6300
                    // after end date, no start date
6301
                    $isVisible = false;
6302
                    $message = sprintf(
6303
                        get_lang('ExerciseAvailableUntilX'),
6304
                        api_convert_and_format_date($this->end_time)
6305
                    );
6306
                }
6307
            } elseif ($existsStartDate && $existsEndDate) {
6308
                // exists start date and end date
6309
                if ($nowIsAfterStartDate) {
6310
                    if ($nowIsBeforeEndDate) {
6311
                        // after start date and before end date
6312
                        $isVisible = true;
6313
                        $message = sprintf(
6314
                            get_lang('ExerciseIsActivatedFromXToY'),
6315
                            api_convert_and_format_date($this->start_time),
6316
                            api_convert_and_format_date($this->end_time)
6317
                        );
6318
                    } else {
6319
                        // after start date and after end date
6320
                        $isVisible = false;
6321
                        $message = sprintf(
6322
                            get_lang('ExerciseWasActivatedFromXToY'),
6323
                            api_convert_and_format_date($this->start_time),
6324
                            api_convert_and_format_date($this->end_time)
6325
                        );
6326
                    }
6327
                } else {
6328
                    if ($nowIsBeforeEndDate) {
6329
                        // before start date and before end date
6330
                        $isVisible = false;
6331
                        $message = sprintf(
6332
                            get_lang('ExerciseWillBeActivatedFromXToY'),
6333
                            api_convert_and_format_date($this->start_time),
6334
                            api_convert_and_format_date($this->end_time)
6335
                        );
6336
                    }
6337
                    // case before start date and after end date is impossible
6338
                }
6339
            } elseif (!$existsStartDate && !$existsEndDate) {
6340
                // doesnt exist start date nor end date
6341
                $isVisible = true;
6342
                $message = '';
6343
            }
6344
        }
6345
6346
        // 4. We check if the student have attempts
6347
        if ($isVisible) {
6348
            $exerciseAttempts = $this->selectAttempts();
6349
6350
            if ($exerciseAttempts > 0) {
6351
                $attemptCount = Event::get_attempt_count_not_finished(
6352
                    api_get_user_id(),
6353
                    $this->id,
6354
                    $lpId,
6355
                    $lpItemId,
6356
                    $lpItemViewId
6357
                );
6358
6359
                if ($attemptCount >= $exerciseAttempts) {
6360
                    $message = sprintf(
6361
                        get_lang('ReachedMaxAttempts'),
6362
                        $this->name,
6363
                        $exerciseAttempts
6364
                    );
6365
                    $isVisible = false;
6366
                }
6367
            }
6368
        }
6369
6370
        $rawMessage = '';
6371
        if (!empty($message)) {
6372
            $rawMessage = $message;
6373
            $message = Display::return_message($message, 'warning', false);
6374
        }
6375
6376
        return [
6377
            'value' => $isVisible,
6378
            'message' => $message,
6379
            'rawMessage' => $rawMessage,
6380
        ];
6381
    }
6382
6383
    /**
6384
     * @return bool
6385
     */
6386
    public function added_in_lp()
6387
    {
6388
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6389
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6390
                WHERE 
6391
                    c_id = {$this->course_id} AND 
6392
                    item_type = '".TOOL_QUIZ."' AND 
6393
                    path = '{$this->id}'";
6394
        $result = Database::query($sql);
6395
        if (Database::num_rows($result) > 0) {
6396
            return true;
6397
        }
6398
6399
        return false;
6400
    }
6401
6402
    /**
6403
     * Returns an array with this form.
6404
     *
6405
     * @example
6406
     * <code>
6407
     * array (size=3)
6408
     * 999 =>
6409
     * array (size=3)
6410
     * 0 => int 3422
6411
     * 1 => int 3423
6412
     * 2 => int 3424
6413
     * 100 =>
6414
     * array (size=2)
6415
     * 0 => int 3469
6416
     * 1 => int 3470
6417
     * 101 =>
6418
     * array (size=1)
6419
     * 0 => int 3482
6420
     * </code>
6421
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6422
     * this case is special because 999 means "no media".
6423
     *
6424
     * @return array
6425
     */
6426
    public function getMediaList()
6427
    {
6428
        return $this->mediaList;
6429
    }
6430
6431
    /**
6432
     * Is media question activated?
6433
     *
6434
     * @return bool
6435
     */
6436
    public function mediaIsActivated()
6437
    {
6438
        $mediaQuestions = $this->getMediaList();
6439
        $active = false;
6440
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6441
            $media_count = count($mediaQuestions);
6442
            if ($media_count > 1) {
6443
                return true;
6444
            } elseif ($media_count == 1) {
6445
                if (isset($mediaQuestions[999])) {
6446
                    return false;
6447
                } else {
6448
                    return true;
6449
                }
6450
            }
6451
        }
6452
6453
        return $active;
6454
    }
6455
6456
    /**
6457
     * Gets question list from the exercise.
6458
     *
6459
     * @return array
6460
     */
6461
    public function getQuestionList()
6462
    {
6463
        return $this->questionList;
6464
    }
6465
6466
    /**
6467
     * Question list with medias compressed like this.
6468
     *
6469
     * @example
6470
     * <code>
6471
     * array(
6472
     *      question_id_1,
6473
     *      question_id_2,
6474
     *      media_id, <- this media id contains question ids
6475
     *      question_id_3,
6476
     * )
6477
     * </code>
6478
     *
6479
     * @return array
6480
     */
6481
    public function getQuestionListWithMediasCompressed()
6482
    {
6483
        return $this->questionList;
6484
    }
6485
6486
    /**
6487
     * Question list with medias uncompressed like this.
6488
     *
6489
     * @example
6490
     * <code>
6491
     * array(
6492
     *      question_id,
6493
     *      question_id,
6494
     *      question_id, <- belongs to a media id
6495
     *      question_id, <- belongs to a media id
6496
     *      question_id,
6497
     * )
6498
     * </code>
6499
     *
6500
     * @return array
6501
     */
6502
    public function getQuestionListWithMediasUncompressed()
6503
    {
6504
        return $this->questionListUncompressed;
6505
    }
6506
6507
    /**
6508
     * Sets the question list when the exercise->read() is executed.
6509
     *
6510
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
6511
     */
6512
    public function setQuestionList($adminView = false)
6513
    {
6514
        // Getting question list.
6515
        $questionList = $this->selectQuestionList(true, $adminView);
6516
        $this->setMediaList($questionList);
6517
        $this->questionList = $this->transformQuestionListWithMedias(
6518
            $questionList,
6519
            false
6520
        );
6521
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6522
            $questionList,
6523
            true
6524
        );
6525
    }
6526
6527
    /**
6528
     * @params array question list
6529
     * @params bool expand or not question list (true show all questions,
6530
     * false show media question id instead of the question ids)
6531
     */
6532
    public function transformQuestionListWithMedias(
6533
        $question_list,
6534
        $expand_media_questions = false
6535
    ) {
6536
        $new_question_list = [];
6537
        if (!empty($question_list)) {
6538
            $media_questions = $this->getMediaList();
6539
            $media_active = $this->mediaIsActivated($media_questions);
6540
6541
            if ($media_active) {
6542
                $counter = 1;
6543
                foreach ($question_list as $question_id) {
6544
                    $add_question = true;
6545
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6546
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6547
                            $add_question = false;
6548
                            if (!in_array($media_id, $new_question_list)) {
6549
                                $new_question_list[$counter] = $media_id;
6550
                                $counter++;
6551
                            }
6552
                            break;
6553
                        }
6554
                    }
6555
                    if ($add_question) {
6556
                        $new_question_list[$counter] = $question_id;
6557
                        $counter++;
6558
                    }
6559
                }
6560
                if ($expand_media_questions) {
6561
                    $media_key_list = array_keys($media_questions);
6562
                    foreach ($new_question_list as &$question_id) {
6563
                        if (in_array($question_id, $media_key_list)) {
6564
                            $question_id = $media_questions[$question_id];
6565
                        }
6566
                    }
6567
                    $new_question_list = array_flatten($new_question_list);
6568
                }
6569
            } else {
6570
                $new_question_list = $question_list;
6571
            }
6572
        }
6573
6574
        return $new_question_list;
6575
    }
6576
6577
    /**
6578
     * Get question list depend on the random settings.
6579
     *
6580
     * @return array
6581
     */
6582
    public function get_validated_question_list()
6583
    {
6584
        $isRandomByCategory = $this->isRandomByCat();
6585
        if ($isRandomByCategory == 0) {
6586
            if ($this->isRandom()) {
6587
                return $this->getRandomList();
6588
            }
6589
6590
            return $this->selectQuestionList();
6591
        }
6592
6593
        if ($this->isRandom()) {
6594
            // USE question categories
6595
            // get questions by category for this exercise
6596
            // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6597
            // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6598
            // value is the array of question id of this category
6599
            $questionList = [];
6600
            $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6601
            $isRandomByCategory = $this->getRandomByCategory();
6602
            // We sort categories based on the term between [] in the head
6603
            // of the category's description
6604
            /* examples of categories :
6605
             * [biologie] Maitriser les mecanismes de base de la genetique
6606
             * [biologie] Relier les moyens de depenses et les agents infectieux
6607
             * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6608
             * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6609
             * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6610
             * [chimie] Connaître les charges des particules
6611
             * We want that in the order of the groups defined by the term
6612
             * between brackets at the beginning of the category title
6613
            */
6614
            // If test option is Grouped By Categories
6615
            if ($isRandomByCategory == 2) {
6616
                $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6617
            }
6618
            foreach ($tabCategoryQuestions as $tabquestion) {
6619
                $number_of_random_question = $this->random;
6620
                if ($this->random == -1) {
6621
                    $number_of_random_question = count($this->questionList);
6622
                }
6623
                $questionList = array_merge(
6624
                    $questionList,
6625
                    TestCategory::getNElementsFromArray(
6626
                        $tabquestion,
6627
                        $number_of_random_question
6628
                    )
6629
                );
6630
            }
6631
            // shuffle the question list if test is not grouped by categories
6632
            if ($isRandomByCategory == 1) {
6633
                shuffle($questionList); // or not
6634
            }
6635
6636
            return $questionList;
6637
        }
6638
6639
        // Problem, random by category has been selected and
6640
        // we have no $this->isRandom number of question selected
6641
        // Should not happened
6642
6643
        return [];
6644
    }
6645
6646
    public function get_question_list($expand_media_questions = false)
6647
    {
6648
        $question_list = $this->get_validated_question_list();
6649
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6650
6651
        return $question_list;
6652
    }
6653
6654
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6655
    {
6656
        $new_question_list = [];
6657
        if (!empty($question_list)) {
6658
            $media_questions = $this->getMediaList();
6659
            $media_active = $this->mediaIsActivated($media_questions);
6660
6661
            if ($media_active) {
6662
                $counter = 1;
6663
                foreach ($question_list as $question_id) {
6664
                    $add_question = true;
6665
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6666
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6667
                            $add_question = false;
6668
                            if (!in_array($media_id, $new_question_list)) {
6669
                                $new_question_list[$counter] = $media_id;
6670
                                $counter++;
6671
                            }
6672
                            break;
6673
                        }
6674
                    }
6675
                    if ($add_question) {
6676
                        $new_question_list[$counter] = $question_id;
6677
                        $counter++;
6678
                    }
6679
                }
6680
                if ($expand_media_questions) {
6681
                    $media_key_list = array_keys($media_questions);
6682
                    foreach ($new_question_list as &$question_id) {
6683
                        if (in_array($question_id, $media_key_list)) {
6684
                            $question_id = $media_questions[$question_id];
6685
                        }
6686
                    }
6687
                    $new_question_list = array_flatten($new_question_list);
6688
                }
6689
            } else {
6690
                $new_question_list = $question_list;
6691
            }
6692
        }
6693
6694
        return $new_question_list;
6695
    }
6696
6697
    /**
6698
     * @param int $exe_id
6699
     *
6700
     * @return array
6701
     */
6702
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6703
    {
6704
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6705
        $exe_id = (int) $exe_id;
6706
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
6707
        $result = Database::query($sql_track);
6708
        $new_array = [];
6709
        if (Database::num_rows($result) > 0) {
6710
            $new_array = Database::fetch_array($result, 'ASSOC');
6711
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6712
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6713
            $new_array['duration_formatted'] = '';
6714
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
6715
                $time = api_format_time($new_array['exe_duration'], 'js');
6716
                $new_array['duration_formatted'] = $time;
6717
            }
6718
        }
6719
6720
        return $new_array;
6721
    }
6722
6723
    /**
6724
     * @param int $exeId
6725
     *
6726
     * @return bool
6727
     */
6728
    public function removeAllQuestionToRemind($exeId)
6729
    {
6730
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6731
        $exeId = (int) $exeId;
6732
        if (empty($exeId)) {
6733
            return false;
6734
        }
6735
        $sql = "UPDATE $table 
6736
                SET questions_to_check = '' 
6737
                WHERE exe_id = $exeId ";
6738
        Database::query($sql);
6739
6740
        return true;
6741
    }
6742
6743
    /**
6744
     * @param int   $exeId
6745
     * @param array $questionList
6746
     *
6747
     * @return bool
6748
     */
6749
    public function addAllQuestionToRemind($exeId, $questionList = [])
6750
    {
6751
        $exeId = (int) $exeId;
6752
        if (empty($questionList)) {
6753
            return false;
6754
        }
6755
6756
        $questionListToString = implode(',', $questionList);
6757
        $questionListToString = Database::escape_string($questionListToString);
6758
6759
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6760
        $sql = "UPDATE $table 
6761
                SET questions_to_check = '$questionListToString' 
6762
                WHERE exe_id = $exeId";
6763
        Database::query($sql);
6764
6765
        return true;
6766
    }
6767
6768
    /**
6769
     * @param int    $exe_id
6770
     * @param int    $question_id
6771
     * @param string $action
6772
     */
6773
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6774
    {
6775
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6776
        $question_id = (int) $question_id;
6777
        $exe_id = (int) $exe_id;
6778
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6779
        if ($exercise_info) {
6780
            if (empty($exercise_info['questions_to_check'])) {
6781
                if ($action == 'add') {
6782
                    $sql = "UPDATE $track_exercises 
6783
                            SET questions_to_check = '$question_id' 
6784
                            WHERE exe_id = $exe_id ";
6785
                    Database::query($sql);
6786
                }
6787
            } else {
6788
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6789
                $remind_list_string = '';
6790
                if ($action == 'add') {
6791
                    if (!in_array($question_id, $remind_list)) {
6792
                        $newRemindList = [];
6793
                        $remind_list[] = $question_id;
6794
                        $questionListInSession = Session::read('questionList');
6795
                        if (!empty($questionListInSession)) {
6796
                            foreach ($questionListInSession as $originalQuestionId) {
6797
                                if (in_array($originalQuestionId, $remind_list)) {
6798
                                    $newRemindList[] = $originalQuestionId;
6799
                                }
6800
                            }
6801
                        }
6802
                        $remind_list_string = implode(',', $newRemindList);
6803
                    }
6804
                } elseif ($action == 'delete') {
6805
                    if (!empty($remind_list)) {
6806
                        if (in_array($question_id, $remind_list)) {
6807
                            $remind_list = array_flip($remind_list);
6808
                            unset($remind_list[$question_id]);
6809
                            $remind_list = array_flip($remind_list);
6810
6811
                            if (!empty($remind_list)) {
6812
                                sort($remind_list);
6813
                                array_filter($remind_list);
6814
                                $remind_list_string = implode(',', $remind_list);
6815
                            }
6816
                        }
6817
                    }
6818
                }
6819
                $value = Database::escape_string($remind_list_string);
6820
                $sql = "UPDATE $track_exercises 
6821
                        SET questions_to_check = '$value' 
6822
                        WHERE exe_id = $exe_id ";
6823
                Database::query($sql);
6824
            }
6825
        }
6826
    }
6827
6828
    /**
6829
     * @param string $answer
6830
     *
6831
     * @return mixed
6832
     */
6833
    public function fill_in_blank_answer_to_array($answer)
6834
    {
6835
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6836
        $teacher_answer_list = $teacher_answer_list[0];
6837
6838
        return $teacher_answer_list;
6839
    }
6840
6841
    /**
6842
     * @param string $answer
6843
     *
6844
     * @return string
6845
     */
6846
    public function fill_in_blank_answer_to_string($answer)
6847
    {
6848
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6849
        $result = '';
6850
        if (!empty($teacher_answer_list)) {
6851
            $i = 0;
6852
            foreach ($teacher_answer_list as $teacher_item) {
6853
                $value = null;
6854
                //Cleaning student answer list
6855
                $value = strip_tags($teacher_item);
6856
                $value = api_substr($value, 1, api_strlen($value) - 2);
6857
                $value = explode('/', $value);
6858
                if (!empty($value[0])) {
6859
                    $value = trim($value[0]);
6860
                    $value = str_replace('&nbsp;', '', $value);
6861
                    $result .= $value;
6862
                }
6863
            }
6864
        }
6865
6866
        return $result;
6867
    }
6868
6869
    /**
6870
     * @return string
6871
     */
6872
    public function return_time_left_div()
6873
    {
6874
        $html = '<div id="clock_warning" style="display:none">';
6875
        $html .= Display::return_message(
6876
            get_lang('ReachedTimeLimit'),
6877
            'warning'
6878
        );
6879
        $html .= ' ';
6880
        $html .= sprintf(
6881
            get_lang('YouWillBeRedirectedInXSeconds'),
6882
            '<span id="counter_to_redirect" class="red_alert"></span>'
6883
        );
6884
        $html .= '</div>';
6885
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6886
6887
        return $html;
6888
    }
6889
6890
    /**
6891
     * Get categories added in the exercise--category matrix.
6892
     *
6893
     * @return array
6894
     */
6895
    public function getCategoriesInExercise()
6896
    {
6897
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6898
        if (!empty($this->id)) {
6899
            $sql = "SELECT * FROM $table
6900
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6901
            $result = Database::query($sql);
6902
            $list = [];
6903
            if (Database::num_rows($result)) {
6904
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6905
                    $list[$row['category_id']] = $row;
6906
                }
6907
6908
                return $list;
6909
            }
6910
        }
6911
6912
        return [];
6913
    }
6914
6915
    /**
6916
     * Get total number of question that will be parsed when using the category/exercise.
6917
     *
6918
     * @return int
6919
     */
6920
    public function getNumberQuestionExerciseCategory()
6921
    {
6922
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6923
        if (!empty($this->id)) {
6924
            $sql = "SELECT SUM(count_questions) count_questions
6925
                    FROM $table
6926
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6927
            $result = Database::query($sql);
6928
            if (Database::num_rows($result)) {
6929
                $row = Database::fetch_array($result);
6930
6931
                return $row['count_questions'];
6932
            }
6933
        }
6934
6935
        return 0;
6936
    }
6937
6938
    /**
6939
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
6940
     *
6941
     * @param array $categories
6942
     */
6943
    public function save_categories_in_exercise($categories)
6944
    {
6945
        if (!empty($categories) && !empty($this->id)) {
6946
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6947
            $sql = "DELETE FROM $table
6948
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6949
            Database::query($sql);
6950
            if (!empty($categories)) {
6951
                foreach ($categories as $categoryId => $countQuestions) {
6952
                    $params = [
6953
                        'c_id' => $this->course_id,
6954
                        'exercise_id' => $this->id,
6955
                        'category_id' => $categoryId,
6956
                        'count_questions' => $countQuestions,
6957
                    ];
6958
                    Database::insert($table, $params);
6959
                }
6960
            }
6961
        }
6962
    }
6963
6964
    /**
6965
     * @param array  $questionList
6966
     * @param int    $currentQuestion
6967
     * @param array  $conditions
6968
     * @param string $link
6969
     *
6970
     * @return string
6971
     */
6972
    public function progressExercisePaginationBar(
6973
        $questionList,
6974
        $currentQuestion,
6975
        $conditions,
6976
        $link
6977
    ) {
6978
        $mediaQuestions = $this->getMediaList();
6979
6980
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6981
        $counter = 0;
6982
        $nextValue = 0;
6983
        $wasMedia = false;
6984
        $before = 0;
6985
        $counterNoMedias = 0;
6986
        foreach ($questionList as $questionId) {
6987
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6988
6989
            if (!empty($nextValue)) {
6990
                if ($wasMedia) {
6991
                    $nextValue = $nextValue - $before + 1;
6992
                }
6993
            }
6994
6995
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6996
                $fixedValue = $counterNoMedias;
6997
6998
                $html .= Display::progressPaginationBar(
6999
                    $nextValue,
7000
                    $mediaQuestions[$questionId],
7001
                    $currentQuestion,
7002
                    $fixedValue,
7003
                    $conditions,
7004
                    $link,
7005
                    true,
7006
                    true
7007
                );
7008
7009
                $counter += count($mediaQuestions[$questionId]) - 1;
7010
                $before = count($questionList);
7011
                $wasMedia = true;
7012
                $nextValue += count($questionList);
7013
            } else {
7014
                $html .= Display::parsePaginationItem(
7015
                    $questionId,
7016
                    $isCurrent,
7017
                    $conditions,
7018
                    $link,
7019
                    $counter
7020
                );
7021
                $counter++;
7022
                $nextValue++;
7023
                $wasMedia = false;
7024
            }
7025
            $counterNoMedias++;
7026
        }
7027
        $html .= '</ul></div>';
7028
7029
        return $html;
7030
    }
7031
7032
    /**
7033
     *  Shows a list of numbers that represents the question to answer in a exercise.
7034
     *
7035
     * @param array  $categories
7036
     * @param int    $current
7037
     * @param array  $conditions
7038
     * @param string $link
7039
     *
7040
     * @return string
7041
     */
7042
    public function progressExercisePaginationBarWithCategories(
7043
        $categories,
7044
        $current,
7045
        $conditions = [],
7046
        $link = null
7047
    ) {
7048
        $html = null;
7049
        $counterNoMedias = 0;
7050
        $nextValue = 0;
7051
        $wasMedia = false;
7052
        $before = 0;
7053
7054
        if (!empty($categories)) {
7055
            $selectionType = $this->getQuestionSelectionType();
7056
            $useRootAsCategoryTitle = false;
7057
7058
            // Grouping questions per parent category see BT#6540
7059
            if (in_array(
7060
                $selectionType,
7061
                [
7062
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
7063
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
7064
                ]
7065
            )) {
7066
                $useRootAsCategoryTitle = true;
7067
            }
7068
7069
            // If the exercise is set to only show the titles of the categories
7070
            // at the root of the tree, then pre-order the categories tree by
7071
            // removing children and summing their questions into the parent
7072
            // categories
7073
            if ($useRootAsCategoryTitle) {
7074
                // The new categories list starts empty
7075
                $newCategoryList = [];
7076
                foreach ($categories as $category) {
7077
                    $rootElement = $category['root'];
7078
7079
                    if (isset($category['parent_info'])) {
7080
                        $rootElement = $category['parent_info']['id'];
7081
                    }
7082
7083
                    //$rootElement = $category['id'];
7084
                    // If the current category's ancestor was never seen
7085
                    // before, then declare it and assign the current
7086
                    // category to it.
7087
                    if (!isset($newCategoryList[$rootElement])) {
7088
                        $newCategoryList[$rootElement] = $category;
7089
                    } else {
7090
                        // If it was already seen, then merge the previous with
7091
                        // the current category
7092
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7093
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7094
                        $newCategoryList[$rootElement] = $category;
7095
                    }
7096
                }
7097
                // Now use the newly built categories list, with only parents
7098
                $categories = $newCategoryList;
7099
            }
7100
7101
            foreach ($categories as $category) {
7102
                $questionList = $category['question_list'];
7103
                // Check if in this category there questions added in a media
7104
                $mediaQuestionId = $category['media_question'];
7105
                $isMedia = false;
7106
                $fixedValue = null;
7107
7108
                // Media exists!
7109
                if ($mediaQuestionId != 999) {
7110
                    $isMedia = true;
7111
                    $fixedValue = $counterNoMedias;
7112
                }
7113
7114
                //$categoryName = $category['path']; << show the path
7115
                $categoryName = $category['name'];
7116
7117
                if ($useRootAsCategoryTitle) {
7118
                    if (isset($category['parent_info'])) {
7119
                        $categoryName = $category['parent_info']['title'];
7120
                    }
7121
                }
7122
                $html .= '<div class="row">';
7123
                $html .= '<div class="span2">'.$categoryName.'</div>';
7124
                $html .= '<div class="span8">';
7125
7126
                if (!empty($nextValue)) {
7127
                    if ($wasMedia) {
7128
                        $nextValue = $nextValue - $before + 1;
7129
                    }
7130
                }
7131
                $html .= Display::progressPaginationBar(
7132
                    $nextValue,
7133
                    $questionList,
7134
                    $current,
7135
                    $fixedValue,
7136
                    $conditions,
7137
                    $link,
7138
                    $isMedia,
7139
                    true
7140
                );
7141
                $html .= '</div>';
7142
                $html .= '</div>';
7143
7144
                if ($mediaQuestionId == 999) {
7145
                    $counterNoMedias += count($questionList);
7146
                } else {
7147
                    $counterNoMedias++;
7148
                }
7149
7150
                $nextValue += count($questionList);
7151
                $before = count($questionList);
7152
7153
                if ($mediaQuestionId != 999) {
7154
                    $wasMedia = true;
7155
                } else {
7156
                    $wasMedia = false;
7157
                }
7158
            }
7159
        }
7160
7161
        return $html;
7162
    }
7163
7164
    /**
7165
     * Renders a question list.
7166
     *
7167
     * @param array $questionList    (with media questions compressed)
7168
     * @param int   $currentQuestion
7169
     * @param array $exerciseResult
7170
     * @param array $attemptList
7171
     * @param array $remindList
7172
     */
7173
    public function renderQuestionList(
7174
        $questionList,
7175
        $currentQuestion,
7176
        $exerciseResult,
7177
        $attemptList,
7178
        $remindList
7179
    ) {
7180
        $mediaQuestions = $this->getMediaList();
7181
        $i = 0;
7182
7183
        // Normal question list render (medias compressed)
7184
        foreach ($questionList as $questionId) {
7185
            $i++;
7186
            // For sequential exercises
7187
7188
            if ($this->type == ONE_PER_PAGE) {
7189
                // If it is not the right question, goes to the next loop iteration
7190
                if ($currentQuestion != $i) {
7191
                    continue;
7192
                } else {
7193
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
7194
                        // if the user has already answered this question
7195
                        if (isset($exerciseResult[$questionId])) {
7196
                            echo Display::return_message(
7197
                                get_lang('AlreadyAnswered'),
7198
                                'normal'
7199
                            );
7200
                            break;
7201
                        }
7202
                    }
7203
                }
7204
            }
7205
7206
            // The $questionList contains the media id we check
7207
            // if this questionId is a media question type
7208
            if (isset($mediaQuestions[$questionId]) &&
7209
                $mediaQuestions[$questionId] != 999
7210
            ) {
7211
                // The question belongs to a media
7212
                $mediaQuestionList = $mediaQuestions[$questionId];
7213
                $objQuestionTmp = Question::read($questionId);
7214
7215
                $counter = 1;
7216
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7217
                    echo $objQuestionTmp->show_media_content();
7218
7219
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7220
7221
                    // Show questions that belongs to a media
7222
                    if (!empty($mediaQuestionList)) {
7223
                        // In order to parse media questions we use letters a, b, c, etc.
7224
                        $letterCounter = 97;
7225
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7226
                            $isLastQuestionInMedia = false;
7227
                            if ($counter == $countQuestionsInsideMedia) {
7228
                                $isLastQuestionInMedia = true;
7229
                            }
7230
                            $this->renderQuestion(
7231
                                $questionIdInsideMedia,
7232
                                $attemptList,
7233
                                $remindList,
7234
                                chr($letterCounter),
7235
                                $currentQuestion,
7236
                                $mediaQuestionList,
7237
                                $isLastQuestionInMedia,
7238
                                $questionList
7239
                            );
7240
                            $letterCounter++;
7241
                            $counter++;
7242
                        }
7243
                    }
7244
                } else {
7245
                    $this->renderQuestion(
7246
                        $questionId,
7247
                        $attemptList,
7248
                        $remindList,
7249
                        $i,
7250
                        $currentQuestion,
7251
                        null,
7252
                        null,
7253
                        $questionList
7254
                    );
7255
                    $i++;
7256
                }
7257
            } else {
7258
                // Normal question render.
7259
                $this->renderQuestion(
7260
                    $questionId,
7261
                    $attemptList,
7262
                    $remindList,
7263
                    $i,
7264
                    $currentQuestion,
7265
                    null,
7266
                    null,
7267
                    $questionList
7268
                );
7269
            }
7270
7271
            // For sequential exercises.
7272
            if ($this->type == ONE_PER_PAGE) {
7273
                // quits the loop
7274
                break;
7275
            }
7276
        }
7277
        // end foreach()
7278
7279
        if ($this->type == ALL_ON_ONE_PAGE) {
7280
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7281
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7282
        }
7283
    }
7284
7285
    /**
7286
     * @param int   $questionId
7287
     * @param array $attemptList
7288
     * @param array $remindList
7289
     * @param int   $i
7290
     * @param int   $current_question
7291
     * @param array $questions_in_media
7292
     * @param bool  $last_question_in_media
7293
     * @param array $realQuestionList
7294
     * @param bool  $generateJS
7295
     */
7296
    public function renderQuestion(
7297
        $questionId,
7298
        $attemptList,
7299
        $remindList,
7300
        $i,
7301
        $current_question,
7302
        $questions_in_media = [],
7303
        $last_question_in_media = false,
7304
        $realQuestionList,
7305
        $generateJS = true
7306
    ) {
7307
        // With this option on the question is loaded via AJAX
7308
        //$generateJS = true;
7309
        //$this->loadQuestionAJAX = true;
7310
7311
        if ($generateJS && $this->loadQuestionAJAX) {
7312
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7313
            $params = [
7314
                'questionId' => $questionId,
7315
                'attemptList' => $attemptList,
7316
                'remindList' => $remindList,
7317
                'i' => $i,
7318
                'current_question' => $current_question,
7319
                'questions_in_media' => $questions_in_media,
7320
                'last_question_in_media' => $last_question_in_media,
7321
            ];
7322
            $params = json_encode($params);
7323
7324
            $script = '<script>
7325
            $(function(){
7326
                var params = '.$params.';
7327
                $.ajax({
7328
                    type: "GET",
7329
                    async: false,
7330
                    data: params,
7331
                    url: "'.$url.'",
7332
                    success: function(return_value) {
7333
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7334
                    }
7335
                });
7336
            });
7337
            </script>
7338
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7339
            echo $script;
7340
        } else {
7341
            global $origin;
7342
            $question_obj = Question::read($questionId);
7343
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7344
            $remind_highlight = null;
7345
7346
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7347
            // see #4542 no_remind_highlight class hide with jquery
7348
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7349
                $remind_highlight = 'no_remind_highlight';
7350
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7351
                    return null;
7352
                }
7353
            }
7354
7355
            $attributes = ['id' => 'remind_list['.$questionId.']'];
7356
7357
            // Showing the question
7358
            $exercise_actions = null;
7359
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7360
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7361
7362
            // Shows the question + possible answers
7363
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7364
            echo $this->showQuestion(
7365
                $question_obj,
7366
                false,
7367
                $origin,
7368
                $i,
7369
                $showTitle,
7370
                false,
7371
                $user_choice,
7372
                false,
7373
                null,
7374
                false,
7375
                $this->getModelType(),
7376
                $this->categoryMinusOne
7377
            );
7378
7379
            // Button save and continue
7380
            switch ($this->type) {
7381
                case ONE_PER_PAGE:
7382
                    $exercise_actions .= $this->show_button(
7383
                        $questionId,
7384
                        $current_question,
7385
                        null,
7386
                        $remindList
7387
                    );
7388
                    break;
7389
                case ALL_ON_ONE_PAGE:
7390
                    if (api_is_allowed_to_session_edit()) {
7391
                        $button = [
7392
                            Display::button(
7393
                                'save_now',
7394
                                get_lang('SaveForNow'),
7395
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7396
                            ),
7397
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7398
                        ];
7399
                        $exercise_actions .= Display::div(
7400
                            implode(PHP_EOL, $button),
7401
                            ['class' => 'exercise_save_now_button']
7402
                        );
7403
                    }
7404
                    break;
7405
            }
7406
7407
            if (!empty($questions_in_media)) {
7408
                $count_of_questions_inside_media = count($questions_in_media);
7409
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7410
                    $button = [
7411
                        Display::button(
7412
                            'save_now',
7413
                            get_lang('SaveForNow'),
7414
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7415
                        ),
7416
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7417
                    ];
7418
                    $exercise_actions = Display::div(
7419
                        implode(PHP_EOL, $button),
7420
                        ['class' => 'exercise_save_now_button']
7421
                    );
7422
                }
7423
7424
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7425
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7426
                }
7427
            }
7428
7429
            // Checkbox review answers
7430
            if ($this->review_answers &&
7431
                !in_array($question_obj->type, Question::question_type_no_review())
7432
            ) {
7433
                $remind_question_div = Display::tag(
7434
                    'label',
7435
                    Display::input(
7436
                        'checkbox',
7437
                        'remind_list['.$questionId.']',
7438
                        '',
7439
                        $attributes
7440
                    ).get_lang('ReviewQuestionLater'),
7441
                    [
7442
                        'class' => 'checkbox',
7443
                        'for' => 'remind_list['.$questionId.']',
7444
                    ]
7445
                );
7446
                $exercise_actions .= Display::div(
7447
                    $remind_question_div,
7448
                    ['class' => 'exercise_save_now_button']
7449
                );
7450
            }
7451
7452
            echo Display::div(' ', ['class' => 'clear']);
7453
7454
            $paginationCounter = null;
7455
            if ($this->type == ONE_PER_PAGE) {
7456
                if (empty($questions_in_media)) {
7457
                    $paginationCounter = Display::paginationIndicator(
7458
                        $current_question,
7459
                        count($realQuestionList)
7460
                    );
7461
                } else {
7462
                    if ($last_question_in_media) {
7463
                        $paginationCounter = Display::paginationIndicator(
7464
                            $current_question,
7465
                            count($realQuestionList)
7466
                        );
7467
                    }
7468
                }
7469
            }
7470
7471
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7472
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7473
            echo '</div>';
7474
        }
7475
    }
7476
7477
    /**
7478
     * Returns an array of categories details for the questions of the current
7479
     * exercise.
7480
     *
7481
     * @return array
7482
     */
7483
    public function getQuestionWithCategories()
7484
    {
7485
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7486
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7487
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7488
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7489
        $sql = "SELECT DISTINCT cat.*
7490
                FROM $TBL_EXERCICE_QUESTION e
7491
                INNER JOIN $TBL_QUESTIONS q
7492
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7493
                INNER JOIN $categoryRelTable catRel
7494
                ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id)
7495
                INNER JOIN $categoryTable cat
7496
                ON (cat.id = catRel.category_id AND cat.c_id = e.c_id)
7497
                WHERE
7498
                  e.c_id = {$this->course_id} AND
7499
                  e.exercice_id	= ".intval($this->id);
7500
7501
        $result = Database::query($sql);
7502
        $categoriesInExercise = [];
7503
        if (Database::num_rows($result)) {
7504
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7505
        }
7506
7507
        return $categoriesInExercise;
7508
    }
7509
7510
    /**
7511
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
7512
     */
7513
    public function get_max_score()
7514
    {
7515
        $out_max_score = 0;
7516
        // list of question's id !!! the array key start at 1 !!!
7517
        $questionList = $this->selectQuestionList(true);
7518
7519
        // test is randomQuestions - see field random of test
7520
        if ($this->random > 0 && $this->randomByCat == 0) {
7521
            $numberRandomQuestions = $this->random;
7522
            $questionScoreList = [];
7523
            foreach ($questionList as $questionId) {
7524
                $tmpobj_question = Question::read($questionId);
7525
                if (is_object($tmpobj_question)) {
7526
                    $questionScoreList[] = $tmpobj_question->weighting;
7527
                }
7528
            }
7529
7530
            rsort($questionScoreList);
7531
            // add the first $numberRandomQuestions value of score array to get max_score
7532
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7533
                $out_max_score += $questionScoreList[$i];
7534
            }
7535
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7536
            // test is random by category
7537
            // get the $numberRandomQuestions best score question of each category
7538
            $numberRandomQuestions = $this->random;
7539
            $tab_categories_scores = [];
7540
            foreach ($questionList as $questionId) {
7541
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7542
                if (!is_array($tab_categories_scores[$question_category_id])) {
7543
                    $tab_categories_scores[$question_category_id] = [];
7544
                }
7545
                $tmpobj_question = Question::read($questionId);
7546
                if (is_object($tmpobj_question)) {
7547
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7548
                }
7549
            }
7550
7551
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7552
            foreach ($tab_categories_scores as $tab_scores) {
7553
                rsort($tab_scores);
7554
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7555
                    $out_max_score += $tab_scores[$i];
7556
                }
7557
            }
7558
        } else {
7559
            // standard test, just add each question score
7560
            foreach ($questionList as $questionId) {
7561
                $question = Question::read($questionId, $this->course);
7562
                $out_max_score += $question->weighting;
7563
            }
7564
        }
7565
7566
        return $out_max_score;
7567
    }
7568
7569
    /**
7570
     * @return string
7571
     */
7572
    public function get_formated_title()
7573
    {
7574
        if (api_get_configuration_value('save_titles_as_html')) {
7575
        }
7576
7577
        return api_html_entity_decode($this->selectTitle());
7578
    }
7579
7580
    /**
7581
     * @param string $title
7582
     *
7583
     * @return string
7584
     */
7585
    public static function get_formated_title_variable($title)
7586
    {
7587
        return api_html_entity_decode($title);
7588
    }
7589
7590
    /**
7591
     * @return string
7592
     */
7593
    public function format_title()
7594
    {
7595
        return api_htmlentities($this->title);
7596
    }
7597
7598
    /**
7599
     * @param string $title
7600
     *
7601
     * @return string
7602
     */
7603
    public static function format_title_variable($title)
7604
    {
7605
        return api_htmlentities($title);
7606
    }
7607
7608
    /**
7609
     * @param int $courseId
7610
     * @param int $sessionId
7611
     *
7612
     * @return array exercises
7613
     */
7614
    public function getExercisesByCourseSession($courseId, $sessionId)
7615
    {
7616
        $courseId = (int) $courseId;
7617
        $sessionId = (int) $sessionId;
7618
7619
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7620
        $sql = "SELECT * FROM $tbl_quiz cq
7621
                WHERE
7622
                    cq.c_id = %s AND
7623
                    (cq.session_id = %s OR cq.session_id = 0) AND
7624
                    cq.active = 0
7625
                ORDER BY cq.id";
7626
        $sql = sprintf($sql, $courseId, $sessionId);
7627
7628
        $result = Database::query($sql);
7629
7630
        $rows = [];
7631
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7632
            $rows[] = $row;
7633
        }
7634
7635
        return $rows;
7636
    }
7637
7638
    /**
7639
     * @param int   $courseId
7640
     * @param int   $sessionId
7641
     * @param array $quizId
7642
     *
7643
     * @return array exercises
7644
     */
7645
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7646
    {
7647
        if (empty($quizId)) {
7648
            return [];
7649
        }
7650
7651
        $sessionId = (int) $sessionId;
7652
        $courseId = (int) $courseId;
7653
7654
        $ids = is_array($quizId) ? $quizId : [$quizId];
7655
        $ids = array_map('intval', $ids);
7656
        $ids = implode(',', $ids);
7657
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7658
        if ($sessionId != 0) {
7659
            $sql = "SELECT * FROM $track_exercises te
7660
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7661
              WHERE
7662
              te.id = %s AND
7663
              te.session_id = %s AND
7664
              cq.id IN (%s)
7665
              ORDER BY cq.id";
7666
7667
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7668
        } else {
7669
            $sql = "SELECT * FROM $track_exercises te
7670
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7671
              WHERE
7672
              te.id = %s AND
7673
              cq.id IN (%s)
7674
              ORDER BY cq.id";
7675
            $sql = sprintf($sql, $courseId, $ids);
7676
        }
7677
        $result = Database::query($sql);
7678
        $rows = [];
7679
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7680
            $rows[] = $row;
7681
        }
7682
7683
        return $rows;
7684
    }
7685
7686
    /**
7687
     * @param $exeId
7688
     * @param $exercise_stat_info
7689
     * @param $remindList
7690
     * @param $currentQuestion
7691
     *
7692
     * @return int|null
7693
     */
7694
    public static function getNextQuestionId(
7695
        $exeId,
7696
        $exercise_stat_info,
7697
        $remindList,
7698
        $currentQuestion
7699
    ) {
7700
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7701
7702
        if (isset($result[$exeId])) {
7703
            $result = $result[$exeId];
7704
        } else {
7705
            return null;
7706
        }
7707
7708
        $data_tracking = $exercise_stat_info['data_tracking'];
7709
        $data_tracking = explode(',', $data_tracking);
7710
7711
        // if this is the final question do nothing.
7712
        if ($currentQuestion == count($data_tracking)) {
7713
            return null;
7714
        }
7715
7716
        $currentQuestion = $currentQuestion - 1;
7717
7718
        if (!empty($result['question_list'])) {
7719
            $answeredQuestions = [];
7720
            foreach ($result['question_list'] as $question) {
7721
                if (!empty($question['answer'])) {
7722
                    $answeredQuestions[] = $question['question_id'];
7723
                }
7724
            }
7725
7726
            // Checking answered questions
7727
            $counterAnsweredQuestions = 0;
7728
            foreach ($data_tracking as $questionId) {
7729
                if (!in_array($questionId, $answeredQuestions)) {
7730
                    if ($currentQuestion != $counterAnsweredQuestions) {
7731
                        break;
7732
                    }
7733
                }
7734
                $counterAnsweredQuestions++;
7735
            }
7736
7737
            $counterRemindListQuestions = 0;
7738
            // Checking questions saved in the reminder list
7739
            if (!empty($remindList)) {
7740
                foreach ($data_tracking as $questionId) {
7741
                    if (in_array($questionId, $remindList)) {
7742
                        // Skip the current question
7743
                        if ($currentQuestion != $counterRemindListQuestions) {
7744
                            break;
7745
                        }
7746
                    }
7747
                    $counterRemindListQuestions++;
7748
                }
7749
7750
                if ($counterRemindListQuestions < $currentQuestion) {
7751
                    return null;
7752
                }
7753
7754
                if (!empty($counterRemindListQuestions)) {
7755
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7756
                        return $counterAnsweredQuestions;
7757
                    } else {
7758
                        return $counterRemindListQuestions;
7759
                    }
7760
                }
7761
            }
7762
7763
            return $counterAnsweredQuestions;
7764
        }
7765
    }
7766
7767
    /**
7768
     * Gets the position of a questionId in the question list.
7769
     *
7770
     * @param $questionId
7771
     *
7772
     * @return int
7773
     */
7774
    public function getPositionInCompressedQuestionList($questionId)
7775
    {
7776
        $questionList = $this->getQuestionListWithMediasCompressed();
7777
        $mediaQuestions = $this->getMediaList();
7778
        $position = 1;
7779
        foreach ($questionList as $id) {
7780
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7781
                $mediaQuestionList = $mediaQuestions[$id];
7782
                if (in_array($questionId, $mediaQuestionList)) {
7783
                    return $position;
7784
                } else {
7785
                    $position++;
7786
                }
7787
            } else {
7788
                if ($id == $questionId) {
7789
                    return $position;
7790
                } else {
7791
                    $position++;
7792
                }
7793
            }
7794
        }
7795
7796
        return 1;
7797
    }
7798
7799
    /**
7800
     * Get the correct answers in all attempts.
7801
     *
7802
     * @param int $learnPathId
7803
     * @param int $learnPathItemId
7804
     *
7805
     * @return array
7806
     */
7807
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7808
    {
7809
        $attempts = Event::getExerciseResultsByUser(
7810
            api_get_user_id(),
7811
            $this->id,
7812
            api_get_course_int_id(),
7813
            api_get_session_id(),
7814
            $learnPathId,
7815
            $learnPathItemId,
7816
            'asc'
7817
        );
7818
7819
        $corrects = [];
7820
        foreach ($attempts as $attempt) {
7821
            foreach ($attempt['question_list'] as $answers) {
7822
                foreach ($answers as $answer) {
7823
                    $objAnswer = new Answer($answer['question_id']);
7824
7825
                    switch ($objAnswer->getQuestionType()) {
7826
                        case FILL_IN_BLANKS:
7827
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7828
                            break;
7829
                        case MATCHING:
7830
                        case DRAGGABLE:
7831
                        case MATCHING_DRAGGABLE:
7832
                            $isCorrect = Matching::isCorrect(
7833
                                $answer['position'],
7834
                                $answer['answer'],
7835
                                $answer['question_id']
7836
                            );
7837
                            break;
7838
                        case ORAL_EXPRESSION:
7839
                            $isCorrect = false;
7840
                            break;
7841
                        default:
7842
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7843
                    }
7844
7845
                    if ($isCorrect) {
7846
                        $corrects[$answer['question_id']][] = $answer;
7847
                    }
7848
                }
7849
            }
7850
        }
7851
7852
        return $corrects;
7853
    }
7854
7855
    /**
7856
     * @return bool
7857
     */
7858
    public function showPreviousButton()
7859
    {
7860
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
7861
        if ($allow === false) {
7862
            return true;
7863
        }
7864
7865
        return $this->showPreviousButton;
7866
    }
7867
7868
    /**
7869
     * @return int
7870
     */
7871
    public function getExerciseCategoryId()
7872
    {
7873
        return (int) $this->exerciseCategoryId;
7874
    }
7875
7876
    /**
7877
     * @param int $value
7878
     */
7879
    public function setExerciseCategoryId($value)
7880
    {
7881
        $this->exerciseCategoryId = (int) $value;
7882
    }
7883
7884
    /**
7885
     * @param bool $showPreviousButton
7886
     *
7887
     * @return Exercise
7888
     */
7889
    public function setShowPreviousButton($showPreviousButton)
7890
    {
7891
        $this->showPreviousButton = $showPreviousButton;
7892
7893
        return $this;
7894
    }
7895
7896
    /**
7897
     * @param array $notifications
7898
     */
7899
    public function setNotifications($notifications)
7900
    {
7901
        $this->notifications = $notifications;
7902
    }
7903
7904
    /**
7905
     * @return array
7906
     */
7907
    public function getNotifications()
7908
    {
7909
        return $this->notifications;
7910
    }
7911
7912
    /**
7913
     * @return bool
7914
     */
7915
    public function showExpectedChoice()
7916
    {
7917
        return api_get_configuration_value('show_exercise_expected_choice');
7918
    }
7919
7920
    /**
7921
     * @param string $class
7922
     * @param string $scoreLabel
7923
     * @param string $result
7924
     * @param array
7925
     *
7926
     * @return string
7927
     */
7928
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
7929
    {
7930
        if ($this->showExpectedChoice()) {
7931
            $html = null;
7932
            $hideLabel = api_get_configuration_value('exercise_hide_label');
7933
            $label = '<div class="rib rib-'.$class.'">
7934
                        <h3>'.$scoreLabel.'</h3>
7935
                      </div>';
7936
            if (!empty($result)) {
7937
                $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
7938
            }
7939
            if ($hideLabel === true) {
7940
                $answerUsed = (int) $array['used'];
7941
                $answerMissing = (int) $array['missing'] - $answerUsed;
7942
                for ($i = 1; $i <= $answerUsed; $i++) {
7943
                    $html .= '<span class="score-img">'.
7944
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
7945
                        '</span>';
7946
                }
7947
                for ($i = 1; $i <= $answerMissing; $i++) {
7948
                    $html .= '<span class="score-img">'.
7949
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
7950
                        '</span>';
7951
                }
7952
                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
7953
                $label .= '<div class="score-limits">';
7954
                $label .= $html;
7955
                $label .= '</div>';
7956
            }
7957
7958
            return '<div class="ribbon">
7959
                '.$label.'
7960
                </div>'
7961
                ;
7962
        } else {
7963
            $html = '<div class="ribbon">
7964
                        <div class="rib rib-'.$class.'">
7965
                            <h3>'.$scoreLabel.'</h3>
7966
                        </div>';
7967
            if (!empty($result)) {
7968
                $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
7969
            }
7970
            $html .= '</div>';
7971
7972
            return $html;
7973
        }
7974
    }
7975
7976
    /**
7977
     * @return int
7978
     */
7979
    public function getAutoLaunch()
7980
    {
7981
        return $this->autolaunch;
7982
    }
7983
7984
    /**
7985
     * Clean auto launch settings for all exercise in course/course-session.
7986
     */
7987
    public function enableAutoLaunch()
7988
    {
7989
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7990
        $sql = "UPDATE $table SET autolaunch = 1
7991
                WHERE iid = ".$this->iId;
7992
        Database::query($sql);
7993
    }
7994
7995
    /**
7996
     * Clean auto launch settings for all exercise in course/course-session.
7997
     */
7998
    public function cleanCourseLaunchSettings()
7999
    {
8000
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
8001
        $sql = "UPDATE $table SET autolaunch = 0  
8002
                WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId;
8003
        Database::query($sql);
8004
    }
8005
8006
    /**
8007
     * Get the title without HTML tags.
8008
     *
8009
     * @return string
8010
     */
8011
    public function getUnformattedTitle()
8012
    {
8013
        return strip_tags(api_html_entity_decode($this->title));
8014
    }
8015
8016
    /**
8017
     * @param int $start
8018
     * @param int $length
8019
     *
8020
     * @return array
8021
     */
8022
    public function getQuestionForTeacher($start = 0, $length = 10)
8023
    {
8024
        $start = (int) $start;
8025
        if ($start < 0) {
8026
            $start = 0;
8027
        }
8028
8029
        $length = (int) $length;
8030
8031
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8032
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
8033
        $sql = "SELECT DISTINCT e.question_id
8034
                FROM $quizRelQuestion e
8035
                INNER JOIN $question q
8036
                ON (e.question_id = q.iid AND e.c_id = q.c_id)
8037
                WHERE
8038
                    e.c_id = {$this->course_id} AND
8039
                    e.exercice_id = '".$this->id."'
8040
                ORDER BY question_order
8041
                LIMIT $start, $length
8042
            ";
8043
        $result = Database::query($sql);
8044
        $questionList = [];
8045
        while ($object = Database::fetch_object($result)) {
8046
            $questionList[] = $object->question_id;
8047
        }
8048
8049
        return $questionList;
8050
    }
8051
8052
    /**
8053
     * @param int   $exerciseId
8054
     * @param array $courseInfo
8055
     * @param int   $sessionId
8056
     *
8057
     * @throws \Doctrine\ORM\OptimisticLockException
8058
     *
8059
     * @return bool
8060
     */
8061
    public function generateStats($exerciseId, $courseInfo, $sessionId)
8062
    {
8063
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
8064
        if (!$allowStats) {
8065
            return false;
8066
        }
8067
8068
        if (empty($courseInfo)) {
8069
            return false;
8070
        }
8071
8072
        $courseId = $courseInfo['real_id'];
8073
8074
        $sessionId = (int) $sessionId;
8075
        $result = $this->read($exerciseId);
8076
8077
        if (empty($result)) {
8078
            api_not_allowed(true);
8079
        }
8080
8081
        $statusToFilter = empty($sessionId) ? STUDENT : 0;
8082
8083
        $studentList = CourseManager::get_user_list_from_course_code(
8084
            api_get_course_id(),
8085
            $sessionId,
8086
            null,
8087
            null,
8088
            $statusToFilter
8089
        );
8090
8091
        if (empty($studentList)) {
8092
            Display::addFlash(Display::return_message(get_lang('NoUsersInCourse')));
8093
            header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
8094
            exit;
8095
        }
8096
8097
        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8098
8099
        $studentIdList = [];
8100
        if (!empty($studentList)) {
8101
            $studentIdList = array_column($studentList, 'user_id');
8102
        }
8103
8104
        if ($this->exercise_was_added_in_lp == false) {
8105
            $sql = "SELECT * FROM $tblStats
8106
                        WHERE
8107
                            exe_exo_id = $exerciseId AND
8108
                            orig_lp_id = 0 AND
8109
                            orig_lp_item_id = 0 AND
8110
                            status <> 'incomplete' AND
8111
                            session_id = $sessionId AND
8112
                            c_id = $courseId
8113
                        ";
8114
        } else {
8115
            $lpId = null;
8116
            if (!empty($this->lpList)) {
8117
                // Taking only the first LP
8118
                $lpId = current($this->lpList);
8119
                $lpId = $lpId['lp_id'];
8120
            }
8121
8122
            $sql = "SELECT * 
8123
                        FROM $tblStats
8124
                        WHERE
8125
                            exe_exo_id = $exerciseId AND
8126
                            orig_lp_id = $lpId AND
8127
                            status <> 'incomplete' AND
8128
                            session_id = $sessionId AND
8129
                            c_id = $courseId ";
8130
        }
8131
8132
        $sql .= ' ORDER BY exe_id DESC';
8133
8134
        $studentCount = 0;
8135
        $sum = 0;
8136
        $bestResult = 0;
8137
        $weight = 0;
8138
        $sumResult = 0;
8139
        $result = Database::query($sql);
8140
        while ($data = Database::fetch_array($result, 'ASSOC')) {
8141
            // Only take into account users in the current student list.
8142
            if (!empty($studentIdList)) {
8143
                if (!in_array($data['exe_user_id'], $studentIdList)) {
8144
                    continue;
8145
                }
8146
            }
8147
8148
            if (!isset($students[$data['exe_user_id']])) {
8149
                if ($data['exe_weighting'] != 0) {
8150
                    $students[$data['exe_user_id']] = $data['exe_result'];
8151
                    $studentCount++;
8152
                    if ($data['exe_result'] > $bestResult) {
8153
                        $bestResult = $data['exe_result'];
8154
                    }
8155
                    $sum += $data['exe_result'] / $data['exe_weighting'];
8156
                    $sumResult += $data['exe_result'];
8157
                    $weight = $data['exe_weighting'];
8158
                }
8159
            }
8160
        }
8161
8162
        $count = count($studentList);
8163
        $average = $sumResult / $count;
8164
        $em = Database::getManager();
8165
8166
        $links = AbstractLink::getGradebookLinksFromItem(
8167
            $this->selectId(),
8168
            LINK_EXERCISE,
8169
            api_get_course_id(),
8170
            api_get_session_id()
8171
        );
8172
8173
        if (!empty($links)) {
8174
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
8175
8176
            foreach ($links as $link) {
8177
                $linkId = $link['id'];
8178
                /** @var \Chamilo\CoreBundle\Entity\GradebookLink $exerciseLink */
8179
                $exerciseLink = $repo->find($linkId);
8180
                if ($exerciseLink) {
8181
                    $exerciseLink
8182
                        ->setUserScoreList($students)
8183
                        ->setBestScore($bestResult)
8184
                        ->setAverageScore($average)
8185
                        ->setScoreWeight($this->get_max_score());
8186
                    $em->persist($exerciseLink);
8187
                    $em->flush();
8188
                }
8189
            }
8190
        }
8191
    }
8192
8193
    /**
8194
     * @param int    $categoryId
8195
     * @param int    $page
8196
     * @param int    $from
8197
     * @param int    $limit
8198
     * @param string $keyword
8199
     *
8200
     * @throws \Doctrine\ORM\Query\QueryException
8201
     *
8202
     * @return string
8203
     */
8204
    public static function exerciseGrid($categoryId, $page, $from, $limit, $keyword = '')
8205
    {
8206
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8207
        $TBL_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
8208
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8209
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
8210
        $TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8211
8212
        $page = (int) $page;
8213
        $from = (int) $from;
8214
        $limit = (int) $limit;
8215
8216
        $autoLaunchAvailable = false;
8217
        if (api_get_course_setting('enable_exercise_auto_launch') == 1 &&
8218
            api_get_configuration_value('allow_exercise_auto_launch')
8219
        ) {
8220
            $autoLaunchAvailable = true;
8221
        }
8222
8223
        $is_allowedToEdit = api_is_allowed_to_edit(null, true);
8224
        $courseInfo = api_get_course_info();
8225
        $sessionId = api_get_session_id();
8226
        $courseId = $courseInfo['real_id'];
8227
        $tableRows = [];
8228
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
8229
        $exercisePath = api_get_self();
8230
        $origin = api_get_origin();
8231
        $userInfo = api_get_user_info();
8232
        $charset = 'utf-8';
8233
        $token = Security::get_token();
8234
        $userId = api_get_user_id();
8235
        $isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(
8236
            $userId,
8237
            $courseInfo
8238
        );
8239
        $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
8240
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
8241
8242
        $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : null;
8243
        $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : null;
8244
8245
        // Condition for the session
8246
        $condition_session = api_get_session_condition($sessionId, true, true);
8247
        $content = '';
8248
8249
        $categoryCondition = '';
8250
        $categoryId = (int) $categoryId;
8251
        if (api_get_configuration_value('allow_exercise_categories')) {
8252
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8253
        }
8254
8255
        $keywordCondition = '';
8256
        if (!empty($keyword)) {
8257
            $keyword = Database::escape_string($keyword);
8258
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8259
        }
8260
8261
        // Only for administrators
8262
        if ($is_allowedToEdit) {
8263
            $total_sql = "SELECT count(iid) as count 
8264
                          FROM $TBL_EXERCISES
8265
                          WHERE 
8266
                                c_id = $courseId AND 
8267
                                active<>'-1' 
8268
                                $condition_session 
8269
                                $categoryCondition
8270
                                $keywordCondition
8271
                                ";
8272
            $sql = "SELECT * FROM $TBL_EXERCISES
8273
                    WHERE 
8274
                        c_id = $courseId AND 
8275
                        active <> '-1' 
8276
                        $condition_session 
8277
                        $categoryCondition
8278
                        $keywordCondition
8279
                    ORDER BY title
8280
                    LIMIT $from , $limit";
8281
        } else {
8282
            // Only for students
8283
            $total_sql = "SELECT count(iid) as count 
8284
                          FROM $TBL_EXERCISES
8285
                          WHERE 
8286
                                c_id = $courseId AND 
8287
                                active = '1' 
8288
                                $condition_session 
8289
                                $categoryCondition
8290
                                $keywordCondition
8291
                          ";
8292
            $sql = "SELECT * FROM $TBL_EXERCISES
8293
                    WHERE c_id = $courseId AND
8294
                          active='1' $condition_session
8295
                          $categoryCondition
8296
                          $keywordCondition
8297
                    ORDER BY title LIMIT $from , $limit";
8298
        }
8299
        $result = Database::query($sql);
8300
        $result_total = Database::query($total_sql);
8301
8302
        $total_exercises = 0;
8303
        if (Database :: num_rows($result_total)) {
8304
            $result_total = Database::fetch_array($result_total);
8305
            $total_exercises = $result_total['count'];
8306
        }
8307
8308
        //get HotPotatoes files (active and inactive)
8309
        if ($is_allowedToEdit) {
8310
            $sql = "SELECT * FROM $TBL_DOCUMENT
8311
                    WHERE
8312
                        c_id = $courseId AND
8313
                        path LIKE '".Database::escape_string($uploadPath.'/%/%')."'";
8314
            $res = Database::query($sql);
8315
            $hp_count = Database :: num_rows($res);
8316
        } else {
8317
            $sql = "SELECT * FROM $TBL_DOCUMENT d 
8318
                    INNER JOIN $TBL_ITEM_PROPERTY ip
8319
                    ON (d.id = ip.ref AND d.c_id = ip.c_id) 
8320
                    WHERE                
8321
                        ip.tool = '".TOOL_DOCUMENT."' AND
8322
                        d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
8323
                        ip.visibility ='1' AND
8324
                        d.c_id = $courseId AND
8325
                        ip.c_id  = $courseId";
8326
            $res = Database::query($sql);
8327
            $hp_count = Database::num_rows($res);
8328
        }
8329
8330
        $total = $total_exercises + $hp_count;
8331
        $exerciseList = [];
8332
        $list_ordered = null;
8333
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8334
            $exerciseList[$row['iid']] = $row;
8335
        }
8336
8337
        if (!empty($exerciseList) &&
8338
            api_get_setting('exercise_invisible_in_session') === 'true'
8339
        ) {
8340
            if (!empty($sessionId)) {
8341
                $changeDefaultVisibility = true;
8342
                if (api_get_setting('configure_exercise_visibility_in_course') === 'true') {
8343
                    $changeDefaultVisibility = false;
8344
                    if (api_get_course_setting('exercise_invisible_in_session') == 1) {
8345
                        $changeDefaultVisibility = true;
8346
                    }
8347
                }
8348
8349
                if ($changeDefaultVisibility) {
8350
                    // Check exercise
8351
                    foreach ($exerciseList as $exercise) {
8352
                        if ($exercise['session_id'] == 0) {
8353
                            $visibilityInfo = api_get_item_property_info(
8354
                                $courseId,
8355
                                TOOL_QUIZ,
8356
                                $exercise['iid'],
8357
                                $sessionId
8358
                            );
8359
8360
                            if (empty($visibilityInfo)) {
8361
                                // Create a record for this
8362
                                api_item_property_update(
8363
                                    $courseInfo,
8364
                                    TOOL_QUIZ,
8365
                                    $exercise['iid'],
8366
                                    'invisible',
8367
                                    api_get_user_id(),
8368
                                    0,
8369
                                    null,
8370
                                    '',
8371
                                    '',
8372
                                    $sessionId
8373
                                );
8374
                            }
8375
                        }
8376
                    }
8377
                }
8378
            }
8379
        }
8380
8381
        if (isset($list_ordered) && !empty($list_ordered)) {
8382
            $new_question_list = [];
8383
            foreach ($list_ordered as $exercise_id) {
8384
                if (isset($exerciseList[$exercise_id])) {
8385
                    $new_question_list[] = $exerciseList[$exercise_id];
8386
                }
8387
            }
8388
            $exerciseList = $new_question_list;
8389
        }
8390
8391
        if (!empty($exerciseList)) {
8392
            if ($origin !== 'learnpath') {
8393
                //avoid sending empty parameters
8394
                $mylpid = empty($learnpath_id) ? '' : '&learnpath_id='.$learnpath_id;
8395
                $mylpitemid = empty($learnpath_item_id) ? '' : '&learnpath_item_id='.$learnpath_item_id;
8396
                foreach ($exerciseList as $row) {
8397
                    $currentRow = [];
8398
                    $my_exercise_id = $row['id'];
8399
                    $attempt_text = '';
8400
                    $actions = '';
8401
                    $exercise = new Exercise();
8402
                    $exercise->read($my_exercise_id, false);
8403
8404
                    if (empty($exercise->id)) {
8405
                        continue;
8406
                    }
8407
8408
                    $locked = $exercise->is_gradebook_locked;
8409
                    // Validation when belongs to a session
8410
                    $session_img = api_get_session_image($row['session_id'], $userInfo['status']);
8411
8412
                    $time_limits = false;
8413
                    if (!empty($row['start_time']) || !empty($row['end_time'])) {
8414
                        $time_limits = true;
8415
                    }
8416
8417
                    $is_actived_time = false;
8418
                    if ($time_limits) {
8419
                        // check if start time
8420
                        $start_time = false;
8421
                        if (!empty($row['start_time'])) {
8422
                            $start_time = api_strtotime($row['start_time'], 'UTC');
8423
                        }
8424
                        $end_time = false;
8425
                        if (!empty($row['end_time'])) {
8426
                            $end_time = api_strtotime($row['end_time'], 'UTC');
8427
                        }
8428
                        $now = time();
8429
8430
                        //If both "clocks" are enable
8431
                        if ($start_time && $end_time) {
8432
                            if ($now > $start_time && $end_time > $now) {
8433
                                $is_actived_time = true;
8434
                            }
8435
                        } else {
8436
                            //we check the start and end
8437
                            if ($start_time) {
8438
                                if ($now > $start_time) {
8439
                                    $is_actived_time = true;
8440
                                }
8441
                            }
8442
                            if ($end_time) {
8443
                                if ($end_time > $now) {
8444
                                    $is_actived_time = true;
8445
                                }
8446
                            }
8447
                        }
8448
                    }
8449
8450
                    // Blocking empty start times see BT#2800
8451
                    global $_custom;
8452
                    if (isset($_custom['exercises_hidden_when_no_start_date']) &&
8453
                        $_custom['exercises_hidden_when_no_start_date']
8454
                    ) {
8455
                        if (empty($row['start_time'])) {
8456
                            $time_limits = true;
8457
                            $is_actived_time = false;
8458
                        }
8459
                    }
8460
8461
                    $cut_title = $exercise->getCutTitle();
8462
                    $alt_title = '';
8463
                    if ($cut_title != $row['title']) {
8464
                        $alt_title = ' title = "'.$exercise->getUnformattedTitle().'" ';
8465
                    }
8466
8467
                    // Teacher only
8468
                    if ($is_allowedToEdit) {
8469
                        $lp_blocked = null;
8470
                        if ($exercise->exercise_was_added_in_lp == true) {
8471
                            $lp_blocked = Display::div(
8472
                                get_lang('AddedToLPCannotBeAccessed'),
8473
                                ['class' => 'lp_content_type_label']
8474
                            );
8475
                        }
8476
8477
                        $visibility = api_get_item_visibility(
8478
                            $courseInfo,
8479
                            TOOL_QUIZ,
8480
                            $my_exercise_id,
8481
                            0
8482
                        );
8483
8484
                        if (!empty($sessionId)) {
8485
                            $setting = api_get_configuration_value('show_hidden_exercise_added_to_lp');
8486
                            if ($setting) {
8487
                                if ($exercise->exercise_was_added_in_lp == false) {
8488
                                    if ($visibility == 0) {
8489
                                        continue;
8490
                                    }
8491
                                }
8492
                            } else {
8493
                                if ($visibility == 0) {
8494
                                    continue;
8495
                                }
8496
                            }
8497
8498
                            $visibility = api_get_item_visibility(
8499
                                $courseInfo,
8500
                                TOOL_QUIZ,
8501
                                $my_exercise_id,
8502
                                $sessionId
8503
                            );
8504
                        }
8505
8506
                        if ($row['active'] == 0 || $visibility == 0) {
8507
                            $title = Display::tag('font', $cut_title, ['style' => 'color:grey']);
8508
                        } else {
8509
                            $title = $cut_title;
8510
                        }
8511
8512
                        $count_exercise_not_validated = (int) Event::count_exercise_result_not_validated(
8513
                            $my_exercise_id,
8514
                            $courseId,
8515
                            $sessionId
8516
                        );
8517
8518
                        /*$move = Display::return_icon(
8519
                            'all_directions.png',
8520
                            get_lang('Move'),
8521
                            ['class' => 'moved', 'style' => 'margin-bottom:-0.5em;']
8522
                        );*/
8523
                        $move = null;
8524
                        $class_tip = '';
8525
                        if (!empty($count_exercise_not_validated)) {
8526
                            $results_text = $count_exercise_not_validated == 1 ? get_lang('ResultNotRevised') : get_lang('ResultsNotRevised');
8527
                            $title .= '<span class="exercise_tooltip" style="display: none;">'.$count_exercise_not_validated.' '.$results_text.' </span>';
8528
                            $class_tip = 'link_tooltip';
8529
                        }
8530
8531
                        $url = $move.'<a '.$alt_title.' class="'.$class_tip.'" id="tooltip_'.$row['id'].'" href="overview.php?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">
8532
                             '.Display::return_icon('quiz.png', $row['title']).'
8533
                             '.$title.' </a>'.PHP_EOL;
8534
8535
                        if (ExerciseLib::isQuizEmbeddable($row)) {
8536
                            $embeddableIcon = Display::return_icon('om_integration.png', get_lang('ThisQuizCanBeEmbeddable'));
8537
                            $url .= Display::div($embeddableIcon, ['class' => 'pull-right']);
8538
                        }
8539
8540
                        $currentRow['title'] = $url.' '.$session_img.$lp_blocked;
8541
8542
                        // Count number exercise - teacher
8543
                        $sql = "SELECT count(*) count FROM $TBL_EXERCISE_QUESTION
8544
                                WHERE c_id = $courseId AND exercice_id = $my_exercise_id";
8545
                        $sqlresult = Database::query($sql);
8546
                        $rowi = (int) Database::result($sqlresult, 0, 0);
8547
8548
                        if ($sessionId == $row['session_id']) {
8549
                            // Questions list
8550
                            $actions = Display::url(
8551
                                Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL),
8552
                                'admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
8553
                            );
8554
8555
                            // Test settings
8556
                            $settings = Display::url(
8557
                                Display::return_icon('settings.png', get_lang('Configure'), '', ICON_SIZE_SMALL),
8558
                                'exercise_admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
8559
                            );
8560
8561
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
8562
                                $settings = '';
8563
                            }
8564
                            $actions .= $settings;
8565
8566
                            // Exercise results
8567
                            $resultsLink = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
8568
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
8569
8570
                            if ($limitTeacherAccess) {
8571
                                if (api_is_platform_admin()) {
8572
                                    $actions .= $resultsLink;
8573
                                }
8574
                            } else {
8575
                                // Exercise results
8576
                                $actions .= $resultsLink;
8577
                            }
8578
8579
                            // Auto launch
8580
                            if ($autoLaunchAvailable) {
8581
                                $autoLaunch = $exercise->getAutoLaunch();
8582
                                if (empty($autoLaunch)) {
8583
                                    $actions .= Display::url(
8584
                                        Display::return_icon(
8585
                                            'launch_na.png',
8586
                                            get_lang('Enable'),
8587
                                            '',
8588
                                            ICON_SIZE_SMALL
8589
                                        ),
8590
                                        'exercise.php?'.api_get_cidreq().'&choice=enable_launch&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8591
                                    );
8592
                                } else {
8593
                                    $actions .= Display::url(
8594
                                        Display::return_icon(
8595
                                            'launch.png',
8596
                                            get_lang('Disable'),
8597
                                            '',
8598
                                            ICON_SIZE_SMALL
8599
                                        ),
8600
                                        'exercise.php?'.api_get_cidreq().'&choice=disable_launch&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8601
                                    );
8602
                                }
8603
                            }
8604
8605
                            // Export
8606
                            $actions .= Display::url(
8607
                                Display::return_icon('cd.png', get_lang('CopyExercise')),
8608
                                '',
8609
                                [
8610
                                    'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
8611
                                    'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'],
8612
                                ]
8613
                            );
8614
8615
                            // Clean exercise
8616
                            if ($locked == false) {
8617
                                $clean = Display::url(
8618
                                    Display::return_icon(
8619
                                        'clean.png',
8620
                                        get_lang('CleanStudentResults'),
8621
                                        '',
8622
                                        ICON_SIZE_SMALL
8623
                                    ),
8624
                                    '',
8625
                                    [
8626
                                        'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteResults'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
8627
                                        'href' => 'exercise.php?'.api_get_cidreq().'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['id'],
8628
                                    ]
8629
                                );
8630
                            } else {
8631
                                $clean = Display::return_icon(
8632
                                    'clean_na.png',
8633
                                    get_lang('ResourceLockedByGradebook'),
8634
                                    '',
8635
                                    ICON_SIZE_SMALL
8636
                                );
8637
                            }
8638
8639
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
8640
                                $clean = '';
8641
                            }
8642
                            $actions .= $clean;
8643
                            // Visible / invisible
8644
                            // Check if this exercise was added in a LP
8645
                            if ($exercise->exercise_was_added_in_lp == true) {
8646
                                $visibility = Display::return_icon(
8647
                                    'invisible.png',
8648
                                    get_lang('AddedToLPCannotBeAccessed'),
8649
                                    '',
8650
                                    ICON_SIZE_SMALL
8651
                                );
8652
                            } else {
8653
                                if ($row['active'] == 0 || $visibility == 0) {
8654
                                    $visibility = Display::url(
8655
                                        Display::return_icon(
8656
                                            'invisible.png',
8657
                                            get_lang('Activate'),
8658
                                            '',
8659
                                            ICON_SIZE_SMALL
8660
                                        ),
8661
                                        'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8662
                                    );
8663
                                } else {
8664
                                    // else if not active
8665
                                    $visibility = Display::url(
8666
                                        Display::return_icon(
8667
                                            'visible.png',
8668
                                            get_lang('Deactivate'),
8669
                                            '',
8670
                                            ICON_SIZE_SMALL
8671
                                        ),
8672
                                        'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8673
                                    );
8674
                                }
8675
                            }
8676
8677
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
8678
                                $visibility = '';
8679
                            }
8680
8681
                            $actions .= $visibility;
8682
8683
                            // Export qti ...
8684
                            $export = Display::url(
8685
                                Display::return_icon(
8686
                                    'export_qti2.png',
8687
                                    'IMS/QTI',
8688
                                    '',
8689
                                    ICON_SIZE_SMALL
8690
                                ),
8691
                                'exercise.php?action=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq()
8692
                            );
8693
8694
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
8695
                                $export = '';
8696
                            }
8697
8698
                            $actions .= $export;
8699
                        } else {
8700
                            // not session
8701
                            $actions = Display::return_icon(
8702
                                'edit_na.png',
8703
                                get_lang('ExerciseEditionNotAvailableInSession')
8704
                            );
8705
8706
                            // Check if this exercise was added in a LP
8707
                            if ($exercise->exercise_was_added_in_lp == true) {
8708
                                $visibility = Display::return_icon(
8709
                                    'invisible.png',
8710
                                    get_lang('AddedToLPCannotBeAccessed'),
8711
                                    '',
8712
                                    ICON_SIZE_SMALL
8713
                                );
8714
                            } else {
8715
                                if ($row['active'] == 0 || $visibility == 0) {
8716
                                    $visibility = Display::url(
8717
                                        Display::return_icon(
8718
                                            'invisible.png',
8719
                                            get_lang('Activate'),
8720
                                            '',
8721
                                            ICON_SIZE_SMALL
8722
                                        ),
8723
                                        'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8724
                                    );
8725
                                } else {
8726
                                    // else if not active
8727
                                    $visibility = Display::url(
8728
                                        Display::return_icon(
8729
                                            'visible.png',
8730
                                            get_lang('Deactivate'),
8731
                                            '',
8732
                                            ICON_SIZE_SMALL
8733
                                        ),
8734
                                        'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
8735
                                    );
8736
                                }
8737
                            }
8738
8739
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
8740
                                $visibility = '';
8741
                            }
8742
8743
                            $actions .= $visibility;
8744
                            $actions .= '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
8745
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
8746
                            $actions .= Display::url(
8747
                                Display::return_icon('cd.gif', get_lang('CopyExercise')),
8748
                                '',
8749
                                [
8750
                                    'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
8751
                                    'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'],
8752
                                ]
8753
                            );
8754
                        }
8755
8756
                        // Delete
8757
                        $delete = '';
8758
                        if ($sessionId == $row['session_id']) {
8759
                            if ($locked == false) {
8760
                                $delete = Display::url(
8761
                                    Display::return_icon(
8762
                                        'delete.png',
8763
                                        get_lang('Delete'),
8764
                                        '',
8765
                                        ICON_SIZE_SMALL
8766
                                    ),
8767
                                    '',
8768
                                    [
8769
                                        'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset))." ".addslashes($exercise->getUnformattedTitle())."?"."')) return false;",
8770
                                        'href' => 'exercise.php?'.api_get_cidreq().'&choice=delete&sec_token='.$token.'&exerciseId='.$row['id'],
8771
                                    ]
8772
                                );
8773
                            } else {
8774
                                $delete = Display::return_icon(
8775
                                    'delete_na.png',
8776
                                    get_lang('ResourceLockedByGradebook'),
8777
                                    '',
8778
                                    ICON_SIZE_SMALL
8779
                                );
8780
                            }
8781
                        }
8782
8783
                        if ($limitTeacherAccess && !api_is_platform_admin()) {
8784
                            $delete = '';
8785
                        }
8786
8787
                        $actions .= $delete;
8788
8789
                        // Number of questions
8790
                        $random_label = null;
8791
                        if ($row['random'] > 0 || $row['random'] == -1) {
8792
                            // if random == -1 means use random questions with all questions
8793
                            $random_number_of_question = $row['random'];
8794
                            if ($random_number_of_question == -1) {
8795
                                $random_number_of_question = $rowi;
8796
                            }
8797
                            if ($row['random_by_category'] > 0) {
8798
                                $nbQuestionsTotal = TestCategory::getNumberOfQuestionRandomByCategory(
8799
                                    $my_exercise_id,
8800
                                    $random_number_of_question
8801
                                );
8802
                                $number_of_questions = $nbQuestionsTotal.' ';
8803
                                $number_of_questions .= ($nbQuestionsTotal > 1) ? get_lang('QuestionsLowerCase') : get_lang('QuestionLowerCase');
8804
                                $number_of_questions .= ' - ';
8805
                                $number_of_questions .= min(
8806
                                        TestCategory::getNumberMaxQuestionByCat($my_exercise_id), $random_number_of_question
8807
                                    ).' '.get_lang('QuestionByCategory');
8808
                            } else {
8809
                                $random_label = ' ('.get_lang('Random').') ';
8810
                                $number_of_questions = $random_number_of_question.' '.$random_label.' / '.$rowi;
8811
                                // Bug if we set a random value bigger than the real number of questions
8812
                                if ($random_number_of_question > $rowi) {
8813
                                    $number_of_questions = $rowi.' '.$random_label;
8814
                                }
8815
                            }
8816
                        } else {
8817
                            $number_of_questions = $rowi;
8818
                        }
8819
8820
                        $currentRow['count_questions'] = $number_of_questions;
8821
                    } else {
8822
                        // Student only.
8823
                        $visibility = api_get_item_visibility(
8824
                            $courseInfo,
8825
                            TOOL_QUIZ,
8826
                            $my_exercise_id,
8827
                            $sessionId
8828
                        );
8829
8830
                        if ($visibility == 0) {
8831
                            continue;
8832
                        }
8833
8834
                        $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">'.
8835
                            $cut_title.'</a>';
8836
8837
                        // Link of the exercise.
8838
                        $currentRow['title'] = $url.' '.$session_img;
8839
8840
                        // This query might be improved later on by ordering by the new "tms" field rather than by exe_id
8841
                        // Don't remove this marker: note-query-exe-results
8842
                        $sql = "SELECT * FROM $TBL_TRACK_EXERCISES
8843
                                WHERE
8844
                                    exe_exo_id = ".$row['id']." AND
8845
                                    exe_user_id = $userId AND
8846
                                    c_id = ".api_get_course_int_id()." AND
8847
                                    status <> 'incomplete' AND
8848
                                    orig_lp_id = 0 AND
8849
                                    orig_lp_item_id = 0 AND
8850
                                    session_id =  '".api_get_session_id()."'
8851
                                ORDER BY exe_id DESC";
8852
8853
                        $qryres = Database::query($sql);
8854
                        $num = Database :: num_rows($qryres);
8855
8856
                        // Hide the results.
8857
                        $my_result_disabled = $row['results_disabled'];
8858
8859
                        $attempt_text = '-';
8860
                        // Time limits are on
8861
                        if ($time_limits) {
8862
                            // Exam is ready to be taken
8863
                            if ($is_actived_time) {
8864
                                // Show results
8865
                                if (
8866
                                in_array(
8867
                                    $my_result_disabled,
8868
                                    [
8869
                                        RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
8870
                                        RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
8871
                                        RESULT_DISABLE_SHOW_SCORE_ONLY,
8872
                                        RESULT_DISABLE_RANKING,
8873
                                    ]
8874
                                )
8875
                                ) {
8876
                                    // More than one attempt
8877
                                    if ($num > 0) {
8878
                                        $row_track = Database :: fetch_array($qryres);
8879
                                        $attempt_text = get_lang('LatestAttempt').' : ';
8880
                                        $attempt_text .= ExerciseLib::show_score(
8881
                                            $row_track['exe_result'],
8882
                                            $row_track['exe_weighting']
8883
                                        );
8884
                                    } else {
8885
                                        //No attempts
8886
                                        $attempt_text = get_lang('NotAttempted');
8887
                                    }
8888
                                } else {
8889
                                    $attempt_text = '-';
8890
                                }
8891
                            } else {
8892
                                // Quiz not ready due to time limits
8893
                                //@todo use the is_visible function
8894
                                if (!empty($row['start_time']) && !empty($row['end_time'])) {
8895
                                    $today = time();
8896
                                    $start_time = api_strtotime($row['start_time'], 'UTC');
8897
                                    $end_time = api_strtotime($row['end_time'], 'UTC');
8898
                                    if ($today < $start_time) {
8899
                                        $attempt_text = sprintf(
8900
                                            get_lang('ExerciseWillBeActivatedFromXToY'),
8901
                                            api_convert_and_format_date($row['start_time']),
8902
                                            api_convert_and_format_date($row['end_time'])
8903
                                        );
8904
                                    } else {
8905
                                        if ($today > $end_time) {
8906
                                            $attempt_text = sprintf(
8907
                                                get_lang('ExerciseWasActivatedFromXToY'),
8908
                                                api_convert_and_format_date($row['start_time']),
8909
                                                api_convert_and_format_date($row['end_time'])
8910
                                            );
8911
                                        }
8912
                                    }
8913
                                } else {
8914
                                    //$attempt_text = get_lang('ExamNotAvailableAtThisTime');
8915
                                    if (!empty($row['start_time'])) {
8916
                                        $attempt_text = sprintf(
8917
                                            get_lang('ExerciseAvailableFromX'),
8918
                                            api_convert_and_format_date($row['start_time'])
8919
                                        );
8920
                                    }
8921
                                    if (!empty($row['end_time'])) {
8922
                                        $attempt_text = sprintf(
8923
                                            get_lang('ExerciseAvailableUntilX'),
8924
                                            api_convert_and_format_date($row['end_time'])
8925
                                        );
8926
                                    }
8927
                                }
8928
                            }
8929
                        } else {
8930
                            // Normal behaviour.
8931
                            // Show results.
8932
                            if (
8933
                            in_array(
8934
                                $my_result_disabled,
8935
                                [
8936
                                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
8937
                                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
8938
                                    RESULT_DISABLE_SHOW_SCORE_ONLY,
8939
                                    RESULT_DISABLE_RANKING,
8940
                                ]
8941
                            )
8942
                            ) {
8943
                                if ($num > 0) {
8944
                                    $row_track = Database :: fetch_array($qryres);
8945
                                    $attempt_text = get_lang('LatestAttempt').' : ';
8946
                                    $attempt_text .= ExerciseLib::show_score(
8947
                                        $row_track['exe_result'],
8948
                                        $row_track['exe_weighting']
8949
                                    );
8950
                                } else {
8951
                                    $attempt_text = get_lang('NotAttempted');
8952
                                }
8953
                            }
8954
                        }
8955
                    }
8956
8957
                    $currentRow['attempt'] = $attempt_text;
8958
8959
                    if ($is_allowedToEdit) {
8960
                        $additionalActions = ExerciseLib::getAdditionalTeacherActions($row['id']);
8961
8962
                        if (!empty($additionalActions)) {
8963
                            $actions .= $additionalActions.PHP_EOL;
8964
                        }
8965
8966
                        $currentRow = [
8967
                            $row['iid'],
8968
                            $currentRow['title'],
8969
                            $currentRow['count_questions'],
8970
                            $actions,
8971
                        ];
8972
                    } else {
8973
                        $currentRow = [
8974
                            $currentRow['title'],
8975
                            $currentRow['attempt'],
8976
                        ];
8977
8978
                        if ($isDrhOfCourse) {
8979
                            $currentRow[] = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
8980
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
8981
                        }
8982
                    }
8983
8984
                    $tableRows[] = $currentRow;
8985
                }
8986
            }
8987
        }
8988
8989
        // end exercise list
8990
        // Hotpotatoes results
8991
        if ($is_allowedToEdit) {
8992
            $sql = "SELECT d.iid, d.path as path, d.comment as comment
8993
                    FROM $TBL_DOCUMENT d
8994
                    WHERE
8995
                        d.c_id = $courseId AND
8996
                        (d.path LIKE '%htm%') AND
8997
                        d.path  LIKE '".Database :: escape_string($uploadPath.'/%/%')."'
8998
                    LIMIT $from , $limit"; // only .htm or .html files listed
8999
        } else {
9000
            $sql = "SELECT d.iid, d.path as path, d.comment as comment
9001
                    FROM $TBL_DOCUMENT d
9002
                    WHERE
9003
                        d.c_id = $courseId AND
9004
                        (d.path LIKE '%htm%') AND
9005
                        d.path  LIKE '".Database :: escape_string($uploadPath.'/%/%')."'
9006
                    LIMIT $from , $limit";
9007
        }
9008
9009
        $result = Database::query($sql);
9010
        $attributes = [];
9011
        while ($row = Database :: fetch_array($result, 'ASSOC')) {
9012
            $attributes[$row['iid']] = $row;
9013
        }
9014
9015
        $nbrActiveTests = 0;
9016
        if (!empty($attributes)) {
9017
            foreach ($attributes as $item) {
9018
                $id = $item['iid'];
9019
                $path = $item['path'];
9020
9021
                $title = GetQuizName($path, $documentPath);
9022
                if ($title == '') {
9023
                    $title = basename($path);
9024
                }
9025
9026
                // prof only
9027
                if ($is_allowedToEdit) {
9028
                    $visibility = api_get_item_visibility(
9029
                        ['real_id' => $courseId],
9030
                        TOOL_DOCUMENT,
9031
                        $id,
9032
                        0
9033
                    );
9034
9035
                    if (!empty($sessionId)) {
9036
                        if (0 == $visibility) {
9037
                            continue;
9038
                        }
9039
9040
                        $visibility = api_get_item_visibility(
9041
                            ['real_id' => $courseId],
9042
                            TOOL_DOCUMENT,
9043
                            $id,
9044
                            $sessionId
9045
                        );
9046
                    }
9047
9048
                    $title =
9049
                        implode(PHP_EOL, [
9050
                            Display::return_icon('hotpotatoes_s.png', 'HotPotatoes'),
9051
                            Display::url(
9052
                                $title,
9053
                                'showinframes.php?'.api_get_cidreq().'&'.http_build_query([
9054
                                    'file' => $path,
9055
                                    'uid' => $userId,
9056
                                ]),
9057
                                ['class' => $visibility == 0 ? 'text-muted' : null]
9058
                            ),
9059
                        ]);
9060
9061
                    $actions = Display::url(
9062
                        Display::return_icon(
9063
                            'edit.png',
9064
                            get_lang('Edit'),
9065
                            '',
9066
                            ICON_SIZE_SMALL
9067
                        ),
9068
                        'adminhp.php?'.api_get_cidreq().'&hotpotatoesName='.$path
9069
                    );
9070
9071
                    $actions .= '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'.
9072
                        Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).
9073
                        '</a>';
9074
9075
                    // if active
9076
                    if ($visibility != 0) {
9077
                        $nbrActiveTests = $nbrActiveTests + 1;
9078
                        $actions .= '      <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=disable&page='.$page.'&file='.$path.'">'.
9079
                            Display::return_icon('visible.png', get_lang('Deactivate'), '', ICON_SIZE_SMALL).'</a>';
9080
                    } else { // else if not active
9081
                        $actions .= '    <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=enable&page='.$page.'&file='.$path.'">'.
9082
                            Display::return_icon('invisible.png', get_lang('Activate'), '', ICON_SIZE_SMALL).'</a>';
9083
                    }
9084
                    $actions .= '<a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=delete&file='.$path.'" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset)).'\')) return false;">'.
9085
                        Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>';
9086
9087
                    $currentRow = [
9088
                        '',
9089
                        $title,
9090
                        '',
9091
                        $actions,
9092
                    ];
9093
                } else {
9094
                    $visibility = api_get_item_visibility(
9095
                        ['real_id' => $courseId],
9096
                        TOOL_DOCUMENT,
9097
                        $id,
9098
                        $sessionId
9099
                    );
9100
9101
                    if (0 == $visibility) {
9102
                        continue;
9103
                    }
9104
9105
                    // Student only
9106
                    $attempt = ExerciseLib::getLatestHotPotatoResult(
9107
                        $path,
9108
                        $userId,
9109
                        api_get_course_int_id(),
9110
                        api_get_session_id()
9111
                    );
9112
9113
                    $nbrActiveTests = $nbrActiveTests + 1;
9114
                    $title = Display::url(
9115
                        $title,
9116
                        'showinframes.php?'.api_get_cidreq().'&'.http_build_query(
9117
                            [
9118
                                'file' => $path,
9119
                                'cid' => api_get_course_id(),
9120
                                'uid' => $userId,
9121
                            ]
9122
                        )
9123
                    );
9124
9125
                    $actions = '';
9126
                    if (!empty($attempt)) {
9127
                        $actions = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'&filter_by_user='.$userId.'">'.Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9128
                        $attemptText = get_lang('LatestAttempt').' : ';
9129
                        $attemptText .= ExerciseLib::show_score(
9130
                                $attempt['exe_result'],
9131
                                $attempt['exe_weighting']
9132
                            ).' ';
9133
                        $attemptText .= $actions;
9134
                    } else {
9135
                        // No attempts.
9136
                        $attemptText = get_lang('NotAttempted').' ';
9137
                    }
9138
9139
                    $currentRow = [
9140
                        $title,
9141
                        $attemptText,
9142
                    ];
9143
9144
                    if ($isDrhOfCourse) {
9145
                        $currentRow[] = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'.
9146
                            Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9147
                    }
9148
                }
9149
9150
                $tableRows[] = $currentRow;
9151
            }
9152
        }
9153
9154
        if (empty($tableRows) && empty($categoryId)) {
9155
            if ($is_allowedToEdit && $origin != 'learnpath') {
9156
                $content .= '<div id="no-data-view">';
9157
                $content .= '<h3>'.get_lang('Quiz').'</h3>';
9158
                $content .= Display::return_icon('quiz.png', '', [], 64);
9159
                $content .= '<div class="controls">';
9160
                $content .= Display::url(
9161
                    '<em class="fa fa-plus"></em> '.get_lang('NewEx'),
9162
                    'exercise_admin.php?'.api_get_cidreq(),
9163
                    ['class' => 'btn btn-primary']
9164
                );
9165
                $content .= '</div>';
9166
                $content .= '</div>';
9167
            }
9168
        } else {
9169
            if (empty($tableRows)) {
9170
                return '';
9171
            }
9172
            $table = new SortableTableFromArrayConfig(
9173
                $tableRows,
9174
                0,
9175
                20,
9176
                'exercises_cat'.$categoryId,
9177
                [],
9178
                []
9179
            );
9180
9181
            $table->setTotalNumberOfItems($total);
9182
9183
            $table->set_additional_parameters([
9184
                'cidReq' => api_get_course_id(),
9185
                'id_session' => api_get_session_id(),
9186
                'category_id' => $categoryId,
9187
            ]);
9188
9189
            if ($is_allowedToEdit) {
9190
                $formActions = [];
9191
                $formActions['visible'] = get_lang('Activate');
9192
                $formActions['invisible'] = get_lang('Deactivate');
9193
                $formActions['delete'] = get_lang('Delete');
9194
                $table->set_form_actions($formActions);
9195
            }
9196
9197
            $i = 0;
9198
            if ($is_allowedToEdit) {
9199
                $table->set_header($i++, '', false, 'width="18px"');
9200
            }
9201
            $table->set_header($i++, get_lang('ExerciseName'), false);
9202
9203
            if ($is_allowedToEdit) {
9204
                $table->set_header($i++, get_lang('QuantityQuestions'), false);
9205
                $table->set_header($i++, get_lang('Actions'), false);
9206
            } else {
9207
                $table->set_header($i++, get_lang('Status'), false);
9208
                if ($isDrhOfCourse) {
9209
                    $table->set_header($i++, get_lang('Actions'), false);
9210
                }
9211
            }
9212
9213
            //$content .= '<div class="table-responsive">';
9214
            $content .= $table->return_table();
9215
            //$content .= '</div>';
9216
        }
9217
9218
        return $content;
9219
    }
9220
9221
    /**
9222
     * Gets the question list ordered by the question_order setting (drag and drop).
9223
     *
9224
     * @return array
9225
     */
9226
    private function getQuestionOrderedList()
9227
    {
9228
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
9229
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
9230
9231
        // Getting question_order to verify that the question
9232
        // list is correct and all question_order's were set
9233
        $sql = "SELECT DISTINCT count(e.question_order) as count
9234
                FROM $TBL_EXERCICE_QUESTION e
9235
                INNER JOIN $TBL_QUESTIONS q
9236
                ON (e.question_id = q.id AND e.c_id = q.c_id)
9237
                WHERE
9238
                  e.c_id = {$this->course_id} AND
9239
                  e.exercice_id	= ".$this->id;
9240
9241
        $result = Database::query($sql);
9242
        $row = Database::fetch_array($result);
9243
        $count_question_orders = $row['count'];
9244
9245
        // Getting question list from the order (question list drag n drop interface).
9246
        $sql = "SELECT DISTINCT e.question_id, e.question_order
9247
                FROM $TBL_EXERCICE_QUESTION e
9248
                INNER JOIN $TBL_QUESTIONS q
9249
                ON (e.question_id = q.id AND e.c_id = q.c_id)
9250
                WHERE
9251
                    e.c_id = {$this->course_id} AND
9252
                    e.exercice_id = '".$this->id."'
9253
                ORDER BY question_order";
9254
        $result = Database::query($sql);
9255
9256
        // Fills the array with the question ID for this exercise
9257
        // the key of the array is the question position
9258
        $temp_question_list = [];
9259
        $counter = 1;
9260
        $questionList = [];
9261
        while ($new_object = Database::fetch_object($result)) {
9262
            // Correct order.
9263
            $questionList[$new_object->question_order] = $new_object->question_id;
9264
            // Just in case we save the order in other array
9265
            $temp_question_list[$counter] = $new_object->question_id;
9266
            $counter++;
9267
        }
9268
9269
        if (!empty($temp_question_list)) {
9270
            /* If both array don't match it means that question_order was not correctly set
9271
               for all questions using the default mysql order */
9272
            if (count($temp_question_list) != $count_question_orders) {
9273
                $questionList = $temp_question_list;
9274
            }
9275
        }
9276
9277
        return $questionList;
9278
    }
9279
9280
    /**
9281
     * Select N values from the questions per category array.
9282
     *
9283
     * @param array $categoriesAddedInExercise
9284
     * @param array $question_list
9285
     * @param array $questions_by_category     per category
9286
     * @param bool  $flatResult
9287
     * @param bool  $randomizeQuestions
9288
     *
9289
     * @return array
9290
     */
9291
    private function pickQuestionsPerCategory(
9292
        $categoriesAddedInExercise,
9293
        $question_list,
9294
        &$questions_by_category,
9295
        $flatResult = true,
9296
        $randomizeQuestions = false
9297
    ) {
9298
        $addAll = true;
9299
        $categoryCountArray = [];
9300
9301
        // Getting how many questions will be selected per category.
9302
        if (!empty($categoriesAddedInExercise)) {
9303
            $addAll = false;
9304
            // Parsing question according the category rel exercise settings
9305
            foreach ($categoriesAddedInExercise as $category_info) {
9306
                $category_id = $category_info['category_id'];
9307
                if (isset($questions_by_category[$category_id])) {
9308
                    // How many question will be picked from this category.
9309
                    $count = $category_info['count_questions'];
9310
                    // -1 means all questions
9311
                    $categoryCountArray[$category_id] = $count;
9312
                    if ($count == -1) {
9313
                        $categoryCountArray[$category_id] = 999;
9314
                    }
9315
                }
9316
            }
9317
        }
9318
9319
        if (!empty($questions_by_category)) {
9320
            $temp_question_list = [];
9321
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
9322
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
9323
                    $numberOfQuestions = 0;
9324
                    if (isset($categoryCountArray[$category_id])) {
9325
                        $numberOfQuestions = $categoryCountArray[$category_id];
9326
                    }
9327
                }
9328
9329
                if ($addAll) {
9330
                    $numberOfQuestions = 999;
9331
                }
9332
9333
                if (!empty($numberOfQuestions)) {
9334
                    $elements = TestCategory::getNElementsFromArray(
9335
                        $categoryQuestionList,
9336
                        $numberOfQuestions,
9337
                        $randomizeQuestions
9338
                    );
9339
9340
                    if (!empty($elements)) {
9341
                        $temp_question_list[$category_id] = $elements;
9342
                        $categoryQuestionList = $elements;
9343
                    }
9344
                }
9345
            }
9346
9347
            if (!empty($temp_question_list)) {
9348
                if ($flatResult) {
9349
                    $temp_question_list = array_flatten($temp_question_list);
9350
                }
9351
                $question_list = $temp_question_list;
9352
            }
9353
        }
9354
9355
        return $question_list;
9356
    }
9357
9358
    /**
9359
     * Changes the exercise id.
9360
     *
9361
     * @param int $id - exercise id
9362
     */
9363
    private function updateId($id)
9364
    {
9365
        $this->id = $id;
9366
    }
9367
9368
    /**
9369
     * Sends a notification when a user ends an examn.
9370
     *
9371
     * @param array  $question_list_answers
9372
     * @param string $origin
9373
     * @param array  $user_info
9374
     * @param string $url_email
9375
     * @param array  $teachers
9376
     */
9377
    private function sendNotificationForOpenQuestions(
9378
        $question_list_answers,
9379
        $origin,
9380
        $user_info,
9381
        $url_email,
9382
        $teachers
9383
    ) {
9384
        // Email configuration settings
9385
        $courseCode = api_get_course_id();
9386
        $courseInfo = api_get_course_info($courseCode);
9387
        $sessionId = api_get_session_id();
9388
        $sessionData = '';
9389
        if (!empty($sessionId)) {
9390
            $sessionInfo = api_get_session_info($sessionId);
9391
            if (!empty($sessionInfo)) {
9392
                $sessionData = '<tr>'
9393
                    .'<td><em>'.get_lang('SessionName').'</em></td>'
9394
                    .'<td>&nbsp;<b>'.$sessionInfo['name'].'</b></td>'
9395
                    .'</tr>';
9396
            }
9397
        }
9398
9399
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
9400
            .get_lang('AttemptDetails').' : <br /><br />'
9401
            .'<table>'
9402
            .'<tr>'
9403
            .'<td><em>'.get_lang('CourseName').'</em></td>'
9404
            .'<td>&nbsp;<b>#course#</b></td>'
9405
            .'</tr>'
9406
            .$sessionData
9407
            .'<tr>'
9408
            .'<td>'.get_lang('TestAttempted').'</td>'
9409
            .'<td>&nbsp;#exercise#</td>'
9410
            .'</tr>'
9411
            .'<tr>'
9412
            .'<td>'.get_lang('StudentName').'</td>'
9413
            .'<td>&nbsp;#firstName# #lastName#</td>'
9414
            .'</tr>'
9415
            .'<tr>'
9416
            .'<td>'.get_lang('StudentEmail').'</td>'
9417
            .'<td>&nbsp;#mail#</td>'
9418
            .'</tr>'
9419
            .'</table>';
9420
9421
        $open_question_list = null;
9422
        foreach ($question_list_answers as $item) {
9423
            $question = $item['question'];
9424
            $answer = $item['answer'];
9425
            $answer_type = $item['answer_type'];
9426
9427
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
9428
                $open_question_list .=
9429
                    '<tr>'
9430
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
9431
                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
9432
                    .'</tr>'
9433
                    .'<tr>'
9434
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
9435
                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
9436
                    .'</tr>';
9437
            }
9438
        }
9439
9440
        if (!empty($open_question_list)) {
9441
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
9442
                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
9443
            $msg .= $open_question_list;
9444
            $msg .= '</table><br />';
9445
9446
            $msg = str_replace('#exercise#', $this->exercise, $msg);
9447
            $msg = str_replace('#firstName#', $user_info['firstname'], $msg);
9448
            $msg = str_replace('#lastName#', $user_info['lastname'], $msg);
9449
            $msg = str_replace('#mail#', $user_info['email'], $msg);
9450
            $msg = str_replace(
9451
                '#course#',
9452
                Display::url($courseInfo['title'], $courseInfo['course_public_url'].'?id_session='.$sessionId),
9453
                $msg
9454
            );
9455
9456
            if ($origin != 'learnpath') {
9457
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
9458
            }
9459
            $msg = str_replace('#url#', $url_email, $msg);
9460
            $subject = get_lang('OpenQuestionsAttempted');
9461
9462
            if (!empty($teachers)) {
9463
                foreach ($teachers as $user_id => $teacher_data) {
9464
                    MessageManager::send_message_simple(
9465
                        $user_id,
9466
                        $subject,
9467
                        $msg
9468
                    );
9469
                }
9470
            }
9471
        }
9472
    }
9473
9474
    /**
9475
     * Send notification for oral questions.
9476
     *
9477
     * @param array  $question_list_answers
9478
     * @param string $origin
9479
     * @param int    $exe_id
9480
     * @param array  $user_info
9481
     * @param string $url_email
9482
     * @param array  $teachers
9483
     */
9484
    private function sendNotificationForOralQuestions(
9485
        $question_list_answers,
9486
        $origin,
9487
        $exe_id,
9488
        $user_info,
9489
        $url_email,
9490
        $teachers
9491
    ) {
9492
        // Email configuration settings
9493
        $courseCode = api_get_course_id();
9494
        $courseInfo = api_get_course_info($courseCode);
9495
        $oral_question_list = null;
9496
        foreach ($question_list_answers as $item) {
9497
            $question = $item['question'];
9498
            $file = $item['generated_oral_file'];
9499
            $answer = $item['answer'];
9500
            if ($answer == 0) {
9501
                $answer = '';
9502
            }
9503
            $answer_type = $item['answer_type'];
9504
            if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) {
9505
                if (!empty($file)) {
9506
                    $file = Display::url($file, $file);
9507
                }
9508
                $oral_question_list .= '<br />
9509
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
9510
                    <tr>
9511
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
9512
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
9513
                    </tr>
9514
                    <tr>
9515
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
9516
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
9517
                    </tr></table>';
9518
            }
9519
        }
9520
9521
        if (!empty($oral_question_list)) {
9522
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
9523
                    '.get_lang('AttemptDetails').' : <br /><br />
9524
                    <table>
9525
                        <tr>
9526
                            <td><em>'.get_lang('CourseName').'</em></td>
9527
                            <td>&nbsp;<b>#course#</b></td>
9528
                        </tr>
9529
                        <tr>
9530
                            <td>'.get_lang('TestAttempted').'</td>
9531
                            <td>&nbsp;#exercise#</td>
9532
                        </tr>
9533
                        <tr>
9534
                            <td>'.get_lang('StudentName').'</td>
9535
                            <td>&nbsp;#firstName# #lastName#</td>
9536
                        </tr>
9537
                        <tr>
9538
                            <td>'.get_lang('StudentEmail').'</td>
9539
                            <td>&nbsp;#mail#</td>
9540
                        </tr>
9541
                    </table>';
9542
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
9543
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
9544
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
9545
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
9546
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
9547
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
9548
9549
            if (!in_array($origin, ['learnpath', 'embeddable'])) {
9550
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
9551
            }
9552
            $msg1 = str_replace("#url#", $url_email, $msg);
9553
            $mail_content = $msg1;
9554
            $subject = get_lang('OralQuestionsAttempted');
9555
9556
            if (!empty($teachers)) {
9557
                foreach ($teachers as $user_id => $teacher_data) {
9558
                    MessageManager::send_message_simple(
9559
                        $user_id,
9560
                        $subject,
9561
                        $mail_content
9562
                    );
9563
                }
9564
            }
9565
        }
9566
    }
9567
9568
    /**
9569
     * Returns an array with the media list.
9570
     *
9571
     * @param array question list
9572
     *
9573
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
9574
     * <code>
9575
     * array (size=2)
9576
     *  999 =>
9577
     *    array (size=3)
9578
     *      0 => int 7
9579
     *      1 => int 6
9580
     *      2 => int 3254
9581
     *  100 =>
9582
     *   array (size=1)
9583
     *      0 => int 5
9584
     *  </code>
9585
     */
9586
    private function setMediaList($questionList)
9587
    {
9588
        $mediaList = [];
9589
        /*
9590
         * Media feature is not activated in 1.11.x
9591
        if (!empty($questionList)) {
9592
            foreach ($questionList as $questionId) {
9593
                $objQuestionTmp = Question::read($questionId, $this->course_id);
9594
                // If a media question exists
9595
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
9596
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
9597
                } else {
9598
                    // Always the last item
9599
                    $mediaList[999][] = $objQuestionTmp->id;
9600
                }
9601
            }
9602
        }*/
9603
9604
        $this->mediaList = $mediaList;
9605
    }
9606
9607
    /**
9608
     * @param FormValidator $form
9609
     *
9610
     * @return HTML_QuickForm_group
9611
     */
9612
    private function setResultDisabledGroup(FormValidator $form)
9613
    {
9614
        $resultDisabledGroup = [];
9615
9616
        $resultDisabledGroup[] = $form->createElement(
9617
            'radio',
9618
            'results_disabled',
9619
            null,
9620
            get_lang('ShowScoreAndRightAnswer'),
9621
            '0',
9622
            ['id' => 'result_disabled_0']
9623
        );
9624
9625
        $resultDisabledGroup[] = $form->createElement(
9626
            'radio',
9627
            'results_disabled',
9628
            null,
9629
            get_lang('DoNotShowScoreNorRightAnswer'),
9630
            '1',
9631
            ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
9632
        );
9633
9634
        $resultDisabledGroup[] = $form->createElement(
9635
            'radio',
9636
            'results_disabled',
9637
            null,
9638
            get_lang('OnlyShowScore'),
9639
            '2',
9640
            ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
9641
        );
9642
9643
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
9644
            $group = $form->addGroup(
9645
                $resultDisabledGroup,
9646
                null,
9647
                get_lang('ShowResultsToStudents')
9648
            );
9649
9650
            return $group;
9651
        }
9652
9653
        $resultDisabledGroup[] = $form->createElement(
9654
            'radio',
9655
            'results_disabled',
9656
            null,
9657
            get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
9658
            '4',
9659
            ['id' => 'result_disabled_4']
9660
        );
9661
9662
        $resultDisabledGroup[] = $form->createElement(
9663
            'radio',
9664
            'results_disabled',
9665
            null,
9666
            get_lang('DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt'),
9667
            '5',
9668
            ['id' => 'result_disabled_5', 'onclick' => 'check_results_disabled()']
9669
        );
9670
9671
        $resultDisabledGroup[] = $form->createElement(
9672
            'radio',
9673
            'results_disabled',
9674
            null,
9675
            get_lang('ExerciseRankingMode'),
9676
            RESULT_DISABLE_RANKING,
9677
            ['id' => 'result_disabled_6']
9678
        );
9679
9680
        $resultDisabledGroup[] = $form->createElement(
9681
            'radio',
9682
            'results_disabled',
9683
            null,
9684
            get_lang('ExerciseShowOnlyGlobalScoreAndCorrectAnswers'),
9685
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
9686
            ['id' => 'result_disabled_7']
9687
        );
9688
9689
        $resultDisabledGroup[] = $form->createElement(
9690
            'radio',
9691
            'results_disabled',
9692
            null,
9693
            get_lang('ExerciseAutoEvaluationAndRankingMode'),
9694
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
9695
            ['id' => 'result_disabled_8']
9696
        );
9697
9698
        $group = $form->addGroup(
9699
            $resultDisabledGroup,
9700
            null,
9701
            get_lang('ShowResultsToStudents')
9702
        );
9703
9704
        return $group;
9705
    }
9706
}
9707