Passed
Push — master ( 4c9a82...f78b85 )
by Julito
12:50 queued 04:06
created

Exercise::setQuestionSelectionType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
87
    /**
88
     * Constructor of the class.
89
     *
90
     * @param int $courseId
91
     *
92
     * @author Olivier Brouckaert
93
     */
94
    public function __construct($courseId = 0)
95
    {
96
        $this->iId = 0;
97
        $this->id = 0;
98
        $this->exercise = '';
99
        $this->description = '';
100
        $this->sound = '';
101
        $this->type = ALL_ON_ONE_PAGE;
102
        $this->random = 0;
103
        $this->random_answers = 0;
104
        $this->active = 1;
105
        $this->questionList = [];
106
        $this->timeLimit = 0;
107
        $this->end_time = '';
108
        $this->start_time = '';
109
        $this->results_disabled = 1;
110
        $this->expired_time = 0;
111
        $this->propagate_neg = 0;
112
        $this->saveCorrectAnswers = 0;
113
        $this->review_answers = false;
114
        $this->randomByCat = 0;
115
        $this->text_when_finished = '';
116
        $this->display_category_name = 0;
117
        $this->pass_percentage = 0;
118
        $this->modelType = 1;
119
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
120
        $this->endButton = 0;
121
        $this->scoreTypeModel = 0;
122
        $this->globalCategoryId = null;
123
        $this->notifications = [];
124
125
        if (!empty($courseId)) {
126
            $courseInfo = api_get_course_info_by_id($courseId);
127
        } else {
128
            $courseInfo = api_get_course_info();
129
        }
130
        $this->course_id = $courseInfo['real_id'];
131
        $this->course = $courseInfo;
132
        $this->sessionId = api_get_session_id();
133
134
        // ALTER TABLE c_quiz_question ADD COLUMN feedback text;
135
        $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback');
136
        $this->showPreviousButton = true;
137
    }
138
139
    /**
140
     * Reads exercise information from the data base.
141
     *
142
     * @author Olivier Brouckaert
143
     *
144
     * @param int  $id                - exercise Id
145
     * @param bool $parseQuestionList
146
     *
147
     * @return bool - true if exercise exists, otherwise false
148
     */
149
    public function read($id, $parseQuestionList = true)
150
    {
151
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
152
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
153
154
        $id = (int) $id;
155
        if (empty($this->course_id)) {
156
            return false;
157
        }
158
159
        $sql = "SELECT * FROM $table 
160
                WHERE c_id = ".$this->course_id." AND id = ".$id;
161
        $result = Database::query($sql);
162
163
        // if the exercise has been found
164
        if ($object = Database::fetch_object($result)) {
165
            $this->iId = $object->iid;
166
            $this->id = $id;
167
            $this->exercise = $object->title;
168
            $this->name = $object->title;
169
            $this->title = $object->title;
170
            $this->description = $object->description;
171
            $this->sound = $object->sound;
172
            $this->type = $object->type;
173
            if (empty($this->type)) {
174
                $this->type = ONE_PER_PAGE;
175
            }
176
            $this->random = $object->random;
177
            $this->random_answers = $object->random_answers;
178
            $this->active = $object->active;
179
            $this->results_disabled = $object->results_disabled;
180
            $this->attempts = $object->max_attempt;
181
            $this->feedback_type = $object->feedback_type;
182
            $this->sessionId = $object->session_id;
183
            $this->propagate_neg = $object->propagate_neg;
184
            $this->saveCorrectAnswers = $object->save_correct_answers;
185
            $this->randomByCat = $object->random_by_category;
186
            $this->text_when_finished = $object->text_when_finished;
187
            $this->display_category_name = $object->display_category_name;
188
            $this->pass_percentage = $object->pass_percentage;
189
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
190
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
191
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
192
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
193
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0;
194
            $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0;
195
196
            $this->notifications = [];
197
            if (!empty($object->notifications)) {
198
                $this->notifications = explode(',', $object->notifications);
199
            }
200
201
            if (isset($object->show_previous_button)) {
202
                $this->showPreviousButton = $object->show_previous_button == 1 ? true : false;
203
            }
204
205
            $sql = "SELECT lp_id, max_score
206
                    FROM $tableLpItem
207
                    WHERE   
208
                        c_id = {$this->course_id} AND
209
                        item_type = '".TOOL_QUIZ."' AND
210
                        path = '".$id."'";
211
            $result = Database::query($sql);
212
213
            if (Database::num_rows($result) > 0) {
214
                $this->exercise_was_added_in_lp = true;
215
                $this->lpList = Database::store_result($result, 'ASSOC');
216
            }
217
218
            $this->force_edit_exercise_in_lp = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
219
220
            $this->edit_exercise_in_lp = true;
221
            if ($this->exercise_was_added_in_lp) {
222
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
223
            }
224
225
            if (!empty($object->end_time)) {
226
                $this->end_time = $object->end_time;
227
            }
228
            if (!empty($object->start_time)) {
229
                $this->start_time = $object->start_time;
230
            }
231
232
            // Control time
233
            $this->expired_time = $object->expired_time;
234
235
            // Checking if question_order is correctly set
236
            if ($parseQuestionList) {
237
                $this->setQuestionList(true);
238
            }
239
240
            //overload questions list with recorded questions list
241
            //load questions only for exercises of type 'one question per page'
242
            //this is needed only is there is no questions
243
244
            // @todo not sure were in the code this is used somebody mess with the exercise tool
245
            // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
246
            /*global $_configuration, $questionList;
247
            if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST'
248
                && defined('QUESTION_LIST_ALREADY_LOGGED') &&
249
                isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']
250
            ) {
251
                $this->questionList = $questionList;
252
            }*/
253
            return true;
254
        }
255
256
        return false;
257
    }
258
259
    /**
260
     * @return string
261
     */
262
    public function getCutTitle()
263
    {
264
        $title = $this->getUnformattedTitle();
265
266
        return cut($title, EXERCISE_MAX_NAME_SIZE);
267
    }
268
269
    /**
270
     * returns the exercise ID.
271
     *
272
     * @author Olivier Brouckaert
273
     *
274
     * @return int - exercise ID
275
     */
276
    public function selectId()
277
    {
278
        return $this->id;
279
    }
280
281
    /**
282
     * returns the exercise title.
283
     *
284
     * @author Olivier Brouckaert
285
     *
286
     * @param bool $unformattedText Optional. Get the title without HTML tags
287
     *
288
     * @return string - exercise title
289
     */
290
    public function selectTitle($unformattedText = false)
291
    {
292
        if ($unformattedText) {
293
            return $this->getUnformattedTitle();
294
        }
295
296
        return $this->exercise;
297
    }
298
299
    /**
300
     * returns the number of attempts setted.
301
     *
302
     * @return int - exercise attempts
303
     */
304
    public function selectAttempts()
305
    {
306
        return $this->attempts;
307
    }
308
309
    /** returns the number of FeedbackType  *
310
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback.
311
     *
312
     * @return int - exercise attempts
313
     */
314
    public function selectFeedbackType()
315
    {
316
        return $this->feedback_type;
317
    }
318
319
    /**
320
     * returns the time limit.
321
     *
322
     * @return int
323
     */
324
    public function selectTimeLimit()
325
    {
326
        return $this->timeLimit;
327
    }
328
329
    /**
330
     * returns the exercise description.
331
     *
332
     * @author Olivier Brouckaert
333
     *
334
     * @return string - exercise description
335
     */
336
    public function selectDescription()
337
    {
338
        return $this->description;
339
    }
340
341
    /**
342
     * returns the exercise sound file.
343
     *
344
     * @author Olivier Brouckaert
345
     *
346
     * @return string - exercise description
347
     */
348
    public function selectSound()
349
    {
350
        return $this->sound;
351
    }
352
353
    /**
354
     * returns the exercise type.
355
     *
356
     * @author Olivier Brouckaert
357
     *
358
     * @return int - exercise type
359
     */
360
    public function selectType()
361
    {
362
        return $this->type;
363
    }
364
365
    /**
366
     * @return int
367
     */
368
    public function getModelType()
369
    {
370
        return $this->modelType;
371
    }
372
373
    /**
374
     * @return int
375
     */
376
    public function selectEndButton()
377
    {
378
        return $this->endButton;
379
    }
380
381
    /**
382
     * @return string
383
     */
384
    public function getOnSuccessMessage()
385
    {
386
        return $this->onSuccessMessage;
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function getOnFailedMessage()
393
    {
394
        return $this->onFailedMessage;
395
    }
396
397
    /**
398
     * @author hubert borderiou 30-11-11
399
     *
400
     * @return int : do we display the question category name for students
401
     */
402
    public function selectDisplayCategoryName()
403
    {
404
        return $this->display_category_name;
405
    }
406
407
    /**
408
     * @return int
409
     */
410
    public function selectPassPercentage()
411
    {
412
        return $this->pass_percentage;
413
    }
414
415
    /**
416
     * Modify object to update the switch display_category_name.
417
     *
418
     * @author hubert borderiou 30-11-11
419
     *
420
     * @param int $value is an integer 0 or 1
421
     */
422
    public function updateDisplayCategoryName($value)
423
    {
424
        $this->display_category_name = $value;
425
    }
426
427
    /**
428
     * @author hubert borderiou 28-11-11
429
     *
430
     * @return string html text : the text to display ay the end of the test
431
     */
432
    public function selectTextWhenFinished()
433
    {
434
        return $this->text_when_finished;
435
    }
436
437
    /**
438
     * @param string $text
439
     *
440
     * @author hubert borderiou 28-11-11
441
     */
442
    public function updateTextWhenFinished($text)
443
    {
444
        $this->text_when_finished = $text;
445
    }
446
447
    /**
448
     * return 1 or 2 if randomByCat.
449
     *
450
     * @author hubert borderiou
451
     *
452
     * @return int - quiz random by category
453
     */
454
    public function getRandomByCategory()
455
    {
456
        return $this->randomByCat;
457
    }
458
459
    /**
460
     * return 0 if no random by cat
461
     * return 1 if random by cat, categories shuffled
462
     * return 2 if random by cat, categories sorted by alphabetic order.
463
     *
464
     * @author hubert borderiou
465
     *
466
     * @return int - quiz random by category
467
     */
468
    public function isRandomByCat()
469
    {
470
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
471
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
472
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
473
        } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
474
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
475
        }
476
477
        return $res;
478
    }
479
480
    /**
481
     * return nothing
482
     * update randomByCat value for object.
483
     *
484
     * @param int $random
485
     *
486
     * @author hubert borderiou
487
     */
488
    public function updateRandomByCat($random)
489
    {
490
        $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
491
        if (in_array(
492
            $random,
493
            [
494
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
495
                EXERCISE_CATEGORY_RANDOM_ORDERED,
496
                EXERCISE_CATEGORY_RANDOM_DISABLED,
497
            ]
498
        )) {
499
            $this->randomByCat = $random;
500
        }
501
    }
502
503
    /**
504
     * Tells if questions are selected randomly, and if so returns the draws.
505
     *
506
     * @author Carlos Vargas
507
     *
508
     * @return int - results disabled exercise
509
     */
510
    public function selectResultsDisabled()
511
    {
512
        return $this->results_disabled;
513
    }
514
515
    /**
516
     * tells if questions are selected randomly, and if so returns the draws.
517
     *
518
     * @author Olivier Brouckaert
519
     *
520
     * @return bool
521
     */
522
    public function isRandom()
523
    {
524
        $isRandom = false;
525
        // "-1" means all questions will be random
526
        if ($this->random > 0 || $this->random == -1) {
527
            $isRandom = true;
528
        }
529
530
        return $isRandom;
531
    }
532
533
    /**
534
     * returns random answers status.
535
     *
536
     * @author Juan Carlos Rana
537
     */
538
    public function getRandomAnswers()
539
    {
540
        return $this->random_answers;
541
    }
542
543
    /**
544
     * Same as isRandom() but has a name applied to values different than 0 or 1.
545
     *
546
     * @return int
547
     */
548
    public function getShuffle()
549
    {
550
        return $this->random;
551
    }
552
553
    /**
554
     * returns the exercise status (1 = enabled ; 0 = disabled).
555
     *
556
     * @author Olivier Brouckaert
557
     *
558
     * @return int - 1 if enabled, otherwise 0
559
     */
560
    public function selectStatus()
561
    {
562
        return $this->active;
563
    }
564
565
    /**
566
     * If false the question list will be managed as always if true
567
     * the question will be filtered
568
     * depending of the exercise settings (table c_quiz_rel_category).
569
     *
570
     * @param bool $status active or inactive grouping
571
     */
572
    public function setCategoriesGrouping($status)
573
    {
574
        $this->categories_grouping = (bool) $status;
575
    }
576
577
    /**
578
     * @return int
579
     */
580
    public function getHideQuestionTitle()
581
    {
582
        return $this->hideQuestionTitle;
583
    }
584
585
    /**
586
     * @param $value
587
     */
588
    public function setHideQuestionTitle($value)
589
    {
590
        $this->hideQuestionTitle = (int) $value;
591
    }
592
593
    /**
594
     * @return int
595
     */
596
    public function getScoreTypeModel()
597
    {
598
        return $this->scoreTypeModel;
599
    }
600
601
    /**
602
     * @param int $value
603
     */
604
    public function setScoreTypeModel($value)
605
    {
606
        $this->scoreTypeModel = (int) $value;
607
    }
608
609
    /**
610
     * @return int
611
     */
612
    public function getGlobalCategoryId()
613
    {
614
        return $this->globalCategoryId;
615
    }
616
617
    /**
618
     * @param int $value
619
     */
620
    public function setGlobalCategoryId($value)
621
    {
622
        if (is_array($value) && isset($value[0])) {
623
            $value = $value[0];
624
        }
625
        $this->globalCategoryId = (int) $value;
626
    }
627
628
    /**
629
     * @param int    $start
630
     * @param int    $limit
631
     * @param int    $sidx
632
     * @param string $sord
633
     * @param array  $whereCondition
634
     * @param array  $extraFields
635
     *
636
     * @return array
637
     */
638
    public function getQuestionListPagination(
639
        $start,
640
        $limit,
641
        $sidx,
642
        $sord,
643
        $whereCondition = [],
644
        $extraFields = []
645
    ) {
646
        if (!empty($this->id)) {
647
            $category_list = TestCategory::getListOfCategoriesNameForTest(
648
                $this->id,
649
                false
650
            );
651
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
652
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
653
654
            $sql = "SELECT q.iid
655
                    FROM $TBL_EXERCICE_QUESTION e 
656
                    INNER JOIN $TBL_QUESTIONS  q
657
                    ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
658
					WHERE e.exercice_id	= '".$this->id."' ";
659
660
            $orderCondition = "ORDER BY question_order";
661
662
            if (!empty($sidx) && !empty($sord)) {
663
                if ($sidx == 'question') {
664
                    if (in_array(strtolower($sord), ['desc', 'asc'])) {
665
                        $orderCondition = " ORDER BY q.$sidx $sord";
666
                    }
667
                }
668
            }
669
670
            $sql .= $orderCondition;
671
            $limitCondition = null;
672
            if (isset($start) && isset($limit)) {
673
                $start = intval($start);
674
                $limit = intval($limit);
675
                $limitCondition = " LIMIT $start, $limit";
676
            }
677
            $sql .= $limitCondition;
678
            $result = Database::query($sql);
679
            $questions = [];
680
            if (Database::num_rows($result)) {
681
                if (!empty($extraFields)) {
682
                    $extraFieldValue = new ExtraFieldValue('question');
683
                }
684
                while ($question = Database::fetch_array($result, 'ASSOC')) {
685
                    /** @var Question $objQuestionTmp */
686
                    $objQuestionTmp = Question::read($question['iid']);
687
                    $category_labels = TestCategory::return_category_labels(
688
                        $objQuestionTmp->category_list,
689
                        $category_list
690
                    );
691
692
                    if (empty($category_labels)) {
693
                        $category_labels = "-";
694
                    }
695
696
                    // Question type
697
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
698
699
                    $question_media = null;
700
                    if (!empty($objQuestionTmp->parent_id)) {
701
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
702
                        $question_media = Question::getMediaLabel($objQuestionMedia->question);
703
                    }
704
705
                    $questionType = Display::tag(
706
                        'div',
707
                        Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media
708
                    );
709
710
                    $question = [
711
                        'id' => $question['iid'],
712
                        'question' => $objQuestionTmp->selectTitle(),
713
                        'type' => $questionType,
714
                        'category' => Display::tag(
715
                            'div',
716
                            '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'
717
                        ),
718
                        'score' => $objQuestionTmp->selectWeighting(),
719
                        'level' => $objQuestionTmp->level,
720
                    ];
721
722
                    if (!empty($extraFields)) {
723
                        foreach ($extraFields as $extraField) {
724
                            $value = $extraFieldValue->get_values_by_handler_and_field_id(
725
                                $question['id'],
726
                                $extraField['id']
727
                            );
728
                            $stringValue = null;
729
                            if ($value) {
730
                                $stringValue = $value['field_value'];
731
                            }
732
                            $question[$extraField['field_variable']] = $stringValue;
733
                        }
734
                    }
735
                    $questions[] = $question;
736
                }
737
            }
738
739
            return $questions;
740
        }
741
    }
742
743
    /**
744
     * Get question count per exercise from DB (any special treatment).
745
     *
746
     * @return int
747
     */
748
    public function getQuestionCount()
749
    {
750
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
751
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
752
        $sql = "SELECT count(q.id) as count
753
                FROM $TBL_EXERCICE_QUESTION e 
754
                INNER JOIN $TBL_QUESTIONS q
755
                ON (e.question_id = q.id AND e.c_id = q.c_id)
756
                WHERE 
757
                    e.c_id = {$this->course_id} AND 
758
                    e.exercice_id = ".$this->id;
759
        $result = Database::query($sql);
760
761
        $count = 0;
762
        if (Database::num_rows($result)) {
763
            $row = Database::fetch_array($result);
764
            $count = (int) $row['count'];
765
        }
766
767
        return $count;
768
    }
769
770
    /**
771
     * @return array
772
     */
773
    public function getQuestionOrderedListByName()
774
    {
775
        $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
776
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
777
778
        // Getting question list from the order (question list drag n drop interface ).
779
        $sql = "SELECT e.question_id
780
                FROM $exerciseQuestionTable e 
781
                INNER JOIN $questionTable q
782
                ON (e.question_id= q.id AND e.c_id = q.c_id)
783
                WHERE 
784
                    e.c_id = {$this->course_id} AND 
785
                    e.exercice_id = '".$this->id."'
786
                ORDER BY q.question";
787
        $result = Database::query($sql);
788
        $list = [];
789
        if (Database::num_rows($result)) {
790
            $list = Database::store_result($result, 'ASSOC');
791
        }
792
793
        return $list;
794
    }
795
796
    /**
797
     * Selecting question list depending in the exercise-category
798
     * relationship (category table in exercise settings).
799
     *
800
     * @param array $question_list
801
     * @param int   $questionSelectionType
802
     *
803
     * @return array
804
     */
805
    public function getQuestionListWithCategoryListFilteredByCategorySettings(
806
        $question_list,
807
        $questionSelectionType
808
    ) {
809
        $result = [
810
            'question_list' => [],
811
            'category_with_questions_list' => [],
812
        ];
813
814
        // Order/random categories
815
        $cat = new TestCategory();
816
817
        // Setting category order.
818
        switch ($questionSelectionType) {
819
            case EX_Q_SELECTION_ORDERED: // 1
820
            case EX_Q_SELECTION_RANDOM:  // 2
821
                // This options are not allowed here.
822
                break;
823
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
824
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
825
                    $this,
826
                    $this->course['real_id'],
827
                    'title ASC',
828
                    false,
829
                    true
830
                );
831
832
                $questions_by_category = TestCategory::getQuestionsByCat(
833
                    $this->id,
834
                    $question_list,
835
                    $categoriesAddedInExercise
836
                );
837
838
                $question_list = $this->pickQuestionsPerCategory(
839
                    $categoriesAddedInExercise,
840
                    $question_list,
841
                    $questions_by_category,
842
                    true,
843
                    false
844
                );
845
                break;
846
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
847
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
848
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
849
                    $this,
850
                    $this->course['real_id'],
851
                    null,
852
                    true,
853
                    true
854
                );
855
                $questions_by_category = TestCategory::getQuestionsByCat(
856
                    $this->id,
857
                    $question_list,
858
                    $categoriesAddedInExercise
859
                );
860
                $question_list = $this->pickQuestionsPerCategory(
861
                    $categoriesAddedInExercise,
862
                    $question_list,
863
                    $questions_by_category,
864
                    true,
865
                    false
866
                );
867
                break;
868
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
869
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
870
                    $this,
871
                    $this->course['real_id'],
872
                    'title ASC',
873
                    false,
874
                    true
875
                );
876
                $questions_by_category = TestCategory::getQuestionsByCat(
877
                    $this->id,
878
                    $question_list,
879
                    $categoriesAddedInExercise
880
                );
881
                $question_list = $this->pickQuestionsPerCategory(
882
                    $categoriesAddedInExercise,
883
                    $question_list,
884
                    $questions_by_category,
885
                    true,
886
                    true
887
                );
888
                break;
889
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
890
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
891
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
892
                    $this,
893
                    $this->course['real_id'],
894
                    null,
895
                    true,
896
                    true
897
                );
898
899
                $questions_by_category = TestCategory::getQuestionsByCat(
900
                    $this->id,
901
                    $question_list,
902
                    $categoriesAddedInExercise
903
                );
904
905
                $question_list = $this->pickQuestionsPerCategory(
906
                    $categoriesAddedInExercise,
907
                    $question_list,
908
                    $questions_by_category,
909
                    true,
910
                    true
911
                );
912
                break;
913
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
914
                break;
915
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
916
                break;
917
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
918
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
919
                    $this,
920
                    $this->course['real_id'],
921
                    'root ASC, lft ASC',
922
                    false,
923
                    true
924
                );
925
                $questions_by_category = TestCategory::getQuestionsByCat(
926
                    $this->id,
927
                    $question_list,
928
                    $categoriesAddedInExercise
929
                );
930
                $question_list = $this->pickQuestionsPerCategory(
931
                    $categoriesAddedInExercise,
932
                    $question_list,
933
                    $questions_by_category,
934
                    true,
935
                    false
936
                );
937
                break;
938
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
939
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
940
                    $this,
941
                    $this->course['real_id'],
942
                    'root, lft ASC',
943
                    false,
944
                    true
945
                );
946
                $questions_by_category = TestCategory::getQuestionsByCat(
947
                    $this->id,
948
                    $question_list,
949
                    $categoriesAddedInExercise
950
                );
951
                $question_list = $this->pickQuestionsPerCategory(
952
                    $categoriesAddedInExercise,
953
                    $question_list,
954
                    $questions_by_category,
955
                    true,
956
                    true
957
                );
958
                break;
959
        }
960
961
        $result['question_list'] = isset($question_list) ? $question_list : [];
962
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : [];
963
        $parentsLoaded = [];
964
        // Adding category info in the category list with question list:
965
        if (!empty($questions_by_category)) {
966
            $newCategoryList = [];
967
            $em = Database::getManager();
968
969
            foreach ($questions_by_category as $categoryId => $questionList) {
970
                $cat = new TestCategory();
971
                $cat = $cat->getCategory($categoryId);
972
                if ($cat) {
973
                    $cat = (array) $cat;
974
                    $cat['iid'] = $cat['id'];
975
                }
976
977
                $categoryParentInfo = null;
978
                // Parent is not set no loop here
979
                if (isset($cat['parent_id']) && !empty($cat['parent_id'])) {
980
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryEntity */
981
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
982
                        $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
983
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
984
                    } else {
985
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
986
                    }
987
                    $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');
988
                    $path = $repo->getPath($categoryEntity);
989
990
                    $index = 0;
991
                    if ($this->categoryMinusOne) {
992
                        //$index = 1;
993
                    }
994
995
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent */
996
                    foreach ($path as $categoryParent) {
997
                        $visibility = $categoryParent->getVisibility();
998
                        if ($visibility == 0) {
999
                            $categoryParentId = $categoryId;
1000
                            $categoryTitle = $cat['title'];
1001
                            if (count($path) > 1) {
1002
                                continue;
1003
                            }
1004
                        } else {
1005
                            $categoryParentId = $categoryParent->getIid();
1006
                            $categoryTitle = $categoryParent->getTitle();
1007
                        }
1008
1009
                        $categoryParentInfo['id'] = $categoryParentId;
1010
                        $categoryParentInfo['iid'] = $categoryParentId;
1011
                        $categoryParentInfo['parent_path'] = null;
1012
                        $categoryParentInfo['title'] = $categoryTitle;
1013
                        $categoryParentInfo['name'] = $categoryTitle;
1014
                        $categoryParentInfo['parent_id'] = null;
1015
                        break;
1016
                    }
1017
                }
1018
                $cat['parent_info'] = $categoryParentInfo;
1019
                $newCategoryList[$categoryId] = [
1020
                    'category' => $cat,
1021
                    'question_list' => $questionList,
1022
                ];
1023
            }
1024
1025
            $result['category_with_questions_list'] = $newCategoryList;
1026
        }
1027
1028
        return $result;
1029
    }
1030
1031
    /**
1032
     * returns the array with the question ID list.
1033
     *
1034
     * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory
1035
     * @param bool $adminView Whether we should return all questions (admin view) or
1036
     *                        just a list limited by the max number of random questions
1037
     *
1038
     * @author Olivier Brouckaert
1039
     *
1040
     * @return array - question ID list
1041
     */
1042
    public function selectQuestionList($fromDatabase = false, $adminView = false)
1043
    {
1044
        if ($fromDatabase && !empty($this->id)) {
1045
            $nbQuestions = $this->getQuestionCount();
1046
            $questionSelectionType = $this->getQuestionSelectionType();
1047
1048
            switch ($questionSelectionType) {
1049
                case EX_Q_SELECTION_ORDERED:
1050
                    $questionList = $this->getQuestionOrderedList();
1051
                    break;
1052
                case EX_Q_SELECTION_RANDOM:
1053
                    // Not a random exercise, or if there are not at least 2 questions
1054
                    if ($this->random == 0 || $nbQuestions < 2) {
1055
                        $questionList = $this->getQuestionOrderedList();
1056
                    } else {
1057
                        $questionList = $this->getRandomList($adminView);
1058
                    }
1059
                    break;
1060
                default:
1061
                    $questionList = $this->getQuestionOrderedList();
1062
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1063
                        $questionList,
1064
                        $questionSelectionType
1065
                    );
1066
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1067
                    $questionList = $result['question_list'];
1068
                    break;
1069
            }
1070
1071
            return $questionList;
1072
        }
1073
1074
        return $this->questionList;
1075
    }
1076
1077
    /**
1078
     * returns the number of questions in this exercise.
1079
     *
1080
     * @author Olivier Brouckaert
1081
     *
1082
     * @return int - number of questions
1083
     */
1084
    public function selectNbrQuestions()
1085
    {
1086
        return count($this->questionList);
1087
    }
1088
1089
    /**
1090
     * @return int
1091
     */
1092
    public function selectPropagateNeg()
1093
    {
1094
        return $this->propagate_neg;
1095
    }
1096
1097
    /**
1098
     * @return int
1099
     */
1100
    public function selectSaveCorrectAnswers()
1101
    {
1102
        return $this->saveCorrectAnswers;
1103
    }
1104
1105
    /**
1106
     * Selects questions randomly in the question list.
1107
     *
1108
     * @author Olivier Brouckaert
1109
     * @author Hubert Borderiou 15 nov 2011
1110
     *
1111
     * @param bool $adminView Whether we should return all
1112
     *                        questions (admin view) or just a list limited by the max number of random questions
1113
     *
1114
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1115
     *               without randomizing, otherwise, returns the list with questions selected randomly
1116
     */
1117
    public function getRandomList($adminView = false)
1118
    {
1119
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1120
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1121
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1122
1123
        // Random with limit
1124
        $randomLimit = "ORDER BY RAND() LIMIT $random";
1125
1126
        // Random with no limit
1127
        if ($random == -1) {
1128
            $randomLimit = ' ORDER BY RAND() ';
1129
        }
1130
1131
        // Admin see the list in default order
1132
        if ($adminView === true) {
1133
            // If viewing it as admin for edition, don't show it randomly, use title + id
1134
            $randomLimit = 'ORDER BY e.question_order';
1135
        }
1136
1137
        $sql = "SELECT e.question_id
1138
                FROM $quizRelQuestion e 
1139
                INNER JOIN $question q
1140
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1141
                WHERE 
1142
                    e.c_id = {$this->course_id} AND 
1143
                    e.exercice_id = '".Database::escape_string($this->id)."'
1144
                    $randomLimit ";
1145
        $result = Database::query($sql);
1146
        $questionList = [];
1147
        while ($row = Database::fetch_object($result)) {
1148
            $questionList[] = $row->question_id;
1149
        }
1150
1151
        return $questionList;
1152
    }
1153
1154
    /**
1155
     * returns 'true' if the question ID is in the question list.
1156
     *
1157
     * @author Olivier Brouckaert
1158
     *
1159
     * @param int $questionId - question ID
1160
     *
1161
     * @return bool - true if in the list, otherwise false
1162
     */
1163
    public function isInList($questionId)
1164
    {
1165
        $inList = false;
1166
        if (is_array($this->questionList)) {
1167
            $inList = in_array($questionId, $this->questionList);
1168
        }
1169
1170
        return $inList;
1171
    }
1172
1173
    /**
1174
     * changes the exercise title.
1175
     *
1176
     * @author Olivier Brouckaert
1177
     *
1178
     * @param string $title - exercise title
1179
     */
1180
    public function updateTitle($title)
1181
    {
1182
        $this->title = $this->exercise = $title;
1183
    }
1184
1185
    /**
1186
     * changes the exercise max attempts.
1187
     *
1188
     * @param int $attempts - exercise max attempts
1189
     */
1190
    public function updateAttempts($attempts)
1191
    {
1192
        $this->attempts = $attempts;
1193
    }
1194
1195
    /**
1196
     * changes the exercise feedback type.
1197
     *
1198
     * @param int $feedback_type
1199
     */
1200
    public function updateFeedbackType($feedback_type)
1201
    {
1202
        $this->feedback_type = $feedback_type;
1203
    }
1204
1205
    /**
1206
     * changes the exercise description.
1207
     *
1208
     * @author Olivier Brouckaert
1209
     *
1210
     * @param string $description - exercise description
1211
     */
1212
    public function updateDescription($description)
1213
    {
1214
        $this->description = $description;
1215
    }
1216
1217
    /**
1218
     * changes the exercise expired_time.
1219
     *
1220
     * @author Isaac flores
1221
     *
1222
     * @param int $expired_time The expired time of the quiz
1223
     */
1224
    public function updateExpiredTime($expired_time)
1225
    {
1226
        $this->expired_time = $expired_time;
1227
    }
1228
1229
    /**
1230
     * @param $value
1231
     */
1232
    public function updatePropagateNegative($value)
1233
    {
1234
        $this->propagate_neg = $value;
1235
    }
1236
1237
    /**
1238
     * @param $value int
1239
     */
1240
    public function updateSaveCorrectAnswers($value)
1241
    {
1242
        $this->saveCorrectAnswers = $value;
1243
    }
1244
1245
    /**
1246
     * @param $value
1247
     */
1248
    public function updateReviewAnswers($value)
1249
    {
1250
        $this->review_answers = isset($value) && $value ? true : false;
1251
    }
1252
1253
    /**
1254
     * @param $value
1255
     */
1256
    public function updatePassPercentage($value)
1257
    {
1258
        $this->pass_percentage = $value;
1259
    }
1260
1261
    /**
1262
     * @param string $text
1263
     */
1264
    public function updateEmailNotificationTemplate($text)
1265
    {
1266
        $this->emailNotificationTemplate = $text;
1267
    }
1268
1269
    /**
1270
     * @param string $text
1271
     */
1272
    public function setEmailNotificationTemplateToUser($text)
1273
    {
1274
        $this->emailNotificationTemplateToUser = $text;
1275
    }
1276
1277
    /**
1278
     * @param string $value
1279
     */
1280
    public function setNotifyUserByEmail($value)
1281
    {
1282
        $this->notifyUserByEmail = $value;
1283
    }
1284
1285
    /**
1286
     * @param int $value
1287
     */
1288
    public function updateEndButton($value)
1289
    {
1290
        $this->endButton = (int) $value;
1291
    }
1292
1293
    /**
1294
     * @param string $value
1295
     */
1296
    public function setOnSuccessMessage($value)
1297
    {
1298
        $this->onSuccessMessage = $value;
1299
    }
1300
1301
    /**
1302
     * @param string $value
1303
     */
1304
    public function setOnFailedMessage($value)
1305
    {
1306
        $this->onFailedMessage = $value;
1307
    }
1308
1309
    /**
1310
     * @param $value
1311
     */
1312
    public function setModelType($value)
1313
    {
1314
        $this->modelType = (int) $value;
1315
    }
1316
1317
    /**
1318
     * @param int $value
1319
     */
1320
    public function setQuestionSelectionType($value)
1321
    {
1322
        $this->questionSelectionType = (int) $value;
1323
    }
1324
1325
    /**
1326
     * @return int
1327
     */
1328
    public function getQuestionSelectionType()
1329
    {
1330
        return $this->questionSelectionType;
1331
    }
1332
1333
    /**
1334
     * @param array $categories
1335
     */
1336
    public function updateCategories($categories)
1337
    {
1338
        if (!empty($categories)) {
1339
            $categories = array_map('intval', $categories);
1340
            $this->categories = $categories;
1341
        }
1342
    }
1343
1344
    /**
1345
     * changes the exercise sound file.
1346
     *
1347
     * @author Olivier Brouckaert
1348
     *
1349
     * @param string $sound  - exercise sound file
1350
     * @param string $delete - ask to delete the file
1351
     */
1352
    public function updateSound($sound, $delete)
1353
    {
1354
        global $audioPath, $documentPath;
1355
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1356
1357
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1358
            $this->sound = $sound['name'];
1359
1360
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1361
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1362
                        WHERE 
1363
                            c_id = ".$this->course_id." AND 
1364
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1365
                $result = Database::query($sql);
1366
1367
                if (!Database::num_rows($result)) {
1368
                    DocumentManager::addDocument(
1369
                        $this->course,
1370
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1371
                        'file',
1372
                        $sound['size'],
1373
                        $sound['name']
1374
                    );
1375
                }
1376
            }
1377
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1378
            $this->sound = '';
1379
        }
1380
    }
1381
1382
    /**
1383
     * changes the exercise type.
1384
     *
1385
     * @author Olivier Brouckaert
1386
     *
1387
     * @param int $type - exercise type
1388
     */
1389
    public function updateType($type)
1390
    {
1391
        $this->type = $type;
1392
    }
1393
1394
    /**
1395
     * sets to 0 if questions are not selected randomly
1396
     * if questions are selected randomly, sets the draws.
1397
     *
1398
     * @author Olivier Brouckaert
1399
     *
1400
     * @param int $random - 0 if not random, otherwise the draws
1401
     */
1402
    public function setRandom($random)
1403
    {
1404
        $this->random = $random;
1405
    }
1406
1407
    /**
1408
     * sets to 0 if answers are not selected randomly
1409
     * if answers are selected randomly.
1410
     *
1411
     * @author Juan Carlos Rana
1412
     *
1413
     * @param int $random_answers - random answers
1414
     */
1415
    public function updateRandomAnswers($random_answers)
1416
    {
1417
        $this->random_answers = $random_answers;
1418
    }
1419
1420
    /**
1421
     * enables the exercise.
1422
     *
1423
     * @author Olivier Brouckaert
1424
     */
1425
    public function enable()
1426
    {
1427
        $this->active = 1;
1428
    }
1429
1430
    /**
1431
     * disables the exercise.
1432
     *
1433
     * @author Olivier Brouckaert
1434
     */
1435
    public function disable()
1436
    {
1437
        $this->active = 0;
1438
    }
1439
1440
    /**
1441
     * Set disable results.
1442
     */
1443
    public function disable_results()
1444
    {
1445
        $this->results_disabled = true;
1446
    }
1447
1448
    /**
1449
     * Enable results.
1450
     */
1451
    public function enable_results()
1452
    {
1453
        $this->results_disabled = false;
1454
    }
1455
1456
    /**
1457
     * @param int $results_disabled
1458
     */
1459
    public function updateResultsDisabled($results_disabled)
1460
    {
1461
        $this->results_disabled = (int) $results_disabled;
1462
    }
1463
1464
    /**
1465
     * updates the exercise in the data base.
1466
     *
1467
     * @param string $type_e
1468
     *
1469
     * @author Olivier Brouckaert
1470
     */
1471
    public function save($type_e = '')
1472
    {
1473
        $_course = $this->course;
1474
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1475
1476
        $id = $this->id;
1477
        $exercise = $this->exercise;
1478
        $description = $this->description;
1479
        $sound = $this->sound;
1480
        $type = $this->type;
1481
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1482
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1483
        $random = $this->random;
1484
        $random_answers = $this->random_answers;
1485
        $active = $this->active;
1486
        $propagate_neg = (int) $this->propagate_neg;
1487
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1488
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1489
        $randomByCat = (int) $this->randomByCat;
1490
        $text_when_finished = $this->text_when_finished;
1491
        $display_category_name = (int) $this->display_category_name;
1492
        $pass_percentage = (int) $this->pass_percentage;
1493
        $session_id = $this->sessionId;
1494
1495
        // If direct we do not show results
1496
        $results_disabled = (int) $this->results_disabled;
1497
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1498
            $results_disabled = 0;
1499
        }
1500
        $expired_time = (int) $this->expired_time;
1501
1502
        // Exercise already exists
1503
        if ($id) {
1504
            // we prepare date in the database using the api_get_utc_datetime() function
1505
            $start_time = null;
1506
            if (!empty($this->start_time)) {
1507
                $start_time = $this->start_time;
1508
            }
1509
1510
            $end_time = null;
1511
            if (!empty($this->end_time)) {
1512
                $end_time = $this->end_time;
1513
            }
1514
1515
            $params = [
1516
                'title' => $exercise,
1517
                'description' => $description,
1518
            ];
1519
1520
            $paramsExtra = [];
1521
            if ($type_e != 'simple') {
1522
                $paramsExtra = [
1523
                    'sound' => $sound,
1524
                    'type' => $type,
1525
                    'random' => $random,
1526
                    'random_answers' => $random_answers,
1527
                    'active' => $active,
1528
                    'feedback_type' => $feedback_type,
1529
                    'start_time' => $start_time,
1530
                    'end_time' => $end_time,
1531
                    'max_attempt' => $attempts,
1532
                    'expired_time' => $expired_time,
1533
                    'propagate_neg' => $propagate_neg,
1534
                    'save_correct_answers' => $saveCorrectAnswers,
1535
                    'review_answers' => $review_answers,
1536
                    'random_by_category' => $randomByCat,
1537
                    'text_when_finished' => $text_when_finished,
1538
                    'display_category_name' => $display_category_name,
1539
                    'pass_percentage' => $pass_percentage,
1540
                    'results_disabled' => $results_disabled,
1541
                    'question_selection_type' => $this->getQuestionSelectionType(),
1542
                    'hide_question_title' => $this->getHideQuestionTitle(),
1543
                ];
1544
1545
                $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1546
                if ($allow === true) {
1547
                    $paramsExtra['show_previous_button'] = $this->showPreviousButton();
1548
                }
1549
1550
                $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1551
                if ($allow === true) {
1552
                    $notifications = $this->getNotifications();
1553
                    $notifications = implode(',', $notifications);
1554
                    $paramsExtra['notifications'] = $notifications;
1555
                }
1556
            }
1557
1558
            $params = array_merge($params, $paramsExtra);
1559
1560
            Database::update(
1561
                $TBL_EXERCISES,
1562
                $params,
1563
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1564
            );
1565
1566
            // update into the item_property table
1567
            api_item_property_update(
1568
                $_course,
1569
                TOOL_QUIZ,
1570
                $id,
1571
                'QuizUpdated',
1572
                api_get_user_id()
1573
            );
1574
1575
            if (api_get_setting('search_enabled') == 'true') {
1576
                $this->search_engine_edit();
1577
            }
1578
        } else {
1579
            // Creates a new exercise
1580
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1581
            // for date because, bellow, we call function api_set_default_visibility()
1582
            // In this function, api_set_default_visibility,
1583
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1584
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1585
            $start_time = null;
1586
            if (!empty($this->start_time)) {
1587
                $start_time = $this->start_time;
1588
            }
1589
1590
            $end_time = null;
1591
            if (!empty($this->end_time)) {
1592
                $end_time = $this->end_time;
1593
            }
1594
1595
            $params = [
1596
                'c_id' => $this->course_id,
1597
                'start_time' => $start_time,
1598
                'end_time' => $end_time,
1599
                'title' => $exercise,
1600
                'description' => $description,
1601
                'sound' => $sound,
1602
                'type' => $type,
1603
                'random' => $random,
1604
                'random_answers' => $random_answers,
1605
                'active' => $active,
1606
                'results_disabled' => $results_disabled,
1607
                'max_attempt' => $attempts,
1608
                'feedback_type' => $feedback_type,
1609
                'expired_time' => $expired_time,
1610
                'session_id' => $session_id,
1611
                'review_answers' => $review_answers,
1612
                'random_by_category' => $randomByCat,
1613
                'text_when_finished' => $text_when_finished,
1614
                'display_category_name' => $display_category_name,
1615
                'pass_percentage' => $pass_percentage,
1616
                'save_correct_answers' => (int) $saveCorrectAnswers,
1617
                'propagate_neg' => $propagate_neg,
1618
                'hide_question_title' => $this->getHideQuestionTitle(),
1619
            ];
1620
1621
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1622
            if ($allow === true) {
1623
                $params['show_previous_button'] = $this->showPreviousButton();
1624
            }
1625
1626
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1627
            if ($allow === true) {
1628
                $notifications = $this->getNotifications();
1629
                $params['notifications'] = '';
1630
                if (!empty($notifications)) {
1631
                    $notifications = implode(',', $notifications);
1632
                    $params['notifications'] = $notifications;
1633
                }
1634
            }
1635
1636
            $this->id = $this->iId = Database::insert($TBL_EXERCISES, $params);
1637
1638
            if ($this->id) {
1639
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1640
                Database::query($sql);
1641
1642
                $sql = "UPDATE $TBL_EXERCISES
1643
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1644
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1645
                Database::query($sql);
1646
1647
                // insert into the item_property table
1648
                api_item_property_update(
1649
                    $this->course,
1650
                    TOOL_QUIZ,
1651
                    $this->id,
1652
                    'QuizAdded',
1653
                    api_get_user_id()
1654
                );
1655
1656
                // This function save the quiz again, carefull about start_time
1657
                // and end_time if you remove this line (see above)
1658
                api_set_default_visibility(
1659
                    $this->id,
1660
                    TOOL_QUIZ,
1661
                    null,
1662
                    $this->course
1663
                );
1664
1665
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1666
                    $this->search_engine_save();
1667
                }
1668
            }
1669
        }
1670
1671
        $this->save_categories_in_exercise($this->categories);
1672
1673
        // Updates the question position
1674
        $this->update_question_positions();
1675
1676
        return $this->iId;
1677
    }
1678
1679
    /**
1680
     * Updates question position.
1681
     */
1682
    public function update_question_positions()
1683
    {
1684
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1685
        //Fixes #3483 when updating order
1686
        $question_list = $this->selectQuestionList(true);
1687
        if (!empty($question_list)) {
1688
            foreach ($question_list as $position => $questionId) {
1689
                $sql = "UPDATE $table SET
1690
                            question_order ='".intval($position)."'
1691
                        WHERE
1692
                            c_id = ".$this->course_id." AND
1693
                            question_id = ".intval($questionId)." AND
1694
                            exercice_id=".intval($this->id);
1695
                Database::query($sql);
1696
            }
1697
        }
1698
    }
1699
1700
    /**
1701
     * Adds a question into the question list.
1702
     *
1703
     * @author Olivier Brouckaert
1704
     *
1705
     * @param int $questionId - question ID
1706
     *
1707
     * @return bool - true if the question has been added, otherwise false
1708
     */
1709
    public function addToList($questionId)
1710
    {
1711
        // checks if the question ID is not in the list
1712
        if (!$this->isInList($questionId)) {
1713
            // selects the max position
1714
            if (!$this->selectNbrQuestions()) {
1715
                $pos = 1;
1716
            } else {
1717
                if (is_array($this->questionList)) {
1718
                    $pos = max(array_keys($this->questionList)) + 1;
1719
                }
1720
            }
1721
            $this->questionList[$pos] = $questionId;
1722
1723
            return true;
1724
        }
1725
1726
        return false;
1727
    }
1728
1729
    /**
1730
     * removes a question from the question list.
1731
     *
1732
     * @author Olivier Brouckaert
1733
     *
1734
     * @param int $questionId - question ID
1735
     *
1736
     * @return bool - true if the question has been removed, otherwise false
1737
     */
1738
    public function removeFromList($questionId)
1739
    {
1740
        // searches the position of the question ID in the list
1741
        $pos = array_search($questionId, $this->questionList);
1742
        // question not found
1743
        if ($pos === false) {
1744
            return false;
1745
        } else {
1746
            // dont reduce the number of random question if we use random by category option, or if
1747
            // random all questions
1748
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1749
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1750
                    $this->random--;
1751
                    $this->save();
1752
                }
1753
            }
1754
            // deletes the position from the array containing the wanted question ID
1755
            unset($this->questionList[$pos]);
1756
1757
            return true;
1758
        }
1759
    }
1760
1761
    /**
1762
     * deletes the exercise from the database
1763
     * Notice : leaves the question in the data base.
1764
     *
1765
     * @author Olivier Brouckaert
1766
     */
1767
    public function delete()
1768
    {
1769
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1770
        $sql = "UPDATE $table SET active='-1'
1771
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1772
        Database::query($sql);
1773
1774
        api_item_property_update(
1775
            $this->course,
1776
            TOOL_QUIZ,
1777
            $this->id,
1778
            'QuizDeleted',
1779
            api_get_user_id()
1780
        );
1781
        api_item_property_update(
1782
            $this->course,
1783
            TOOL_QUIZ,
1784
            $this->id,
1785
            'delete',
1786
            api_get_user_id()
1787
        );
1788
1789
        Skill::deleteSkillsFromItem($this->iId, ITEM_TYPE_EXERCISE);
1790
1791
        if (api_get_setting('search_enabled') == 'true' &&
1792
            extension_loaded('xapian')
1793
        ) {
1794
            $this->search_engine_delete();
1795
        }
1796
    }
1797
1798
    /**
1799
     * Creates the form to create / edit an exercise.
1800
     *
1801
     * @param FormValidator $form
1802
     * @param string        $type
1803
     */
1804
    public function createForm($form, $type = 'full')
1805
    {
1806
        if (empty($type)) {
1807
            $type = 'full';
1808
        }
1809
1810
        // form title
1811
        if (!empty($_GET['exerciseId'])) {
1812
            $form_title = get_lang('ModifyExercise');
1813
        } else {
1814
            $form_title = get_lang('NewEx');
1815
        }
1816
1817
        $form->addElement('header', $form_title);
1818
1819
        // Title.
1820
        if (api_get_configuration_value('save_titles_as_html')) {
1821
            $form->addHtmlEditor(
1822
                'exerciseTitle',
1823
                get_lang('ExerciseName'),
1824
                false,
1825
                false,
1826
                ['ToolbarSet' => 'Minimal']
1827
            );
1828
        } else {
1829
            $form->addElement(
1830
                'text',
1831
                'exerciseTitle',
1832
                get_lang('ExerciseName'),
1833
                ['id' => 'exercise_title']
1834
            );
1835
        }
1836
1837
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1838
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1839
1840
        $editor_config = [
1841
            'ToolbarSet' => 'TestQuestionDescription',
1842
            'Width' => '100%',
1843
            'Height' => '150',
1844
        ];
1845
        if (is_array($type)) {
1846
            $editor_config = array_merge($editor_config, $type);
1847
        }
1848
1849
        $form->addHtmlEditor(
1850
            'exerciseDescription',
1851
            get_lang('ExerciseDescription'),
1852
            false,
1853
            false,
1854
            $editor_config
1855
        );
1856
1857
        $skillList = [];
1858
        if ($type == 'full') {
1859
            //Can't modify a DirectFeedback question
1860
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1861
                // feedback type
1862
                $radios_feedback = [];
1863
                $radios_feedback[] = $form->createElement(
1864
                    'radio',
1865
                    'exerciseFeedbackType',
1866
                    null,
1867
                    get_lang('ExerciseAtTheEndOfTheTest'),
1868
                    '0',
1869
                    [
1870
                        'id' => 'exerciseType_0',
1871
                        'onclick' => 'check_feedback()',
1872
                    ]
1873
                );
1874
1875
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1876
                    // Can't convert a question from one feedback to another
1877
                    // if there is more than 1 question already added
1878
                    if ($this->selectNbrQuestions() == 0) {
1879
                        $radios_feedback[] = $form->createElement(
1880
                            'radio',
1881
                            'exerciseFeedbackType',
1882
                            null,
1883
                            get_lang('DirectFeedback'),
1884
                            '1',
1885
                            [
1886
                                'id' => 'exerciseType_1',
1887
                                'onclick' => 'check_direct_feedback()',
1888
                            ]
1889
                        );
1890
                    }
1891
                }
1892
1893
                $radios_feedback[] = $form->createElement(
1894
                    'radio',
1895
                    'exerciseFeedbackType',
1896
                    null,
1897
                    get_lang('NoFeedback'),
1898
                    '2',
1899
                    ['id' => 'exerciseType_2']
1900
                );
1901
                $form->addGroup(
1902
                    $radios_feedback,
1903
                    null,
1904
                    [
1905
                        get_lang('FeedbackType'),
1906
                        get_lang('FeedbackDisplayOptions'),
1907
                    ]
1908
                );
1909
1910
                // Type of results display on the final page
1911
                $radios_results_disabled = [];
1912
                $radios_results_disabled[] = $form->createElement(
1913
                    'radio',
1914
                    'results_disabled',
1915
                    null,
1916
                    get_lang('ShowScoreAndRightAnswer'),
1917
                    '0',
1918
                    ['id' => 'result_disabled_0']
1919
                );
1920
                $radios_results_disabled[] = $form->createElement(
1921
                    'radio',
1922
                    'results_disabled',
1923
                    null,
1924
                    get_lang('DoNotShowScoreNorRightAnswer'),
1925
                    '1',
1926
                    ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
1927
                );
1928
                $radios_results_disabled[] = $form->createElement(
1929
                    'radio',
1930
                    'results_disabled',
1931
                    null,
1932
                    get_lang('OnlyShowScore'),
1933
                    '2',
1934
                    ['id' => 'result_disabled_2']
1935
                );
1936
                $radios_results_disabled[] = $form->createElement(
1937
                    'radio',
1938
                    'results_disabled',
1939
                    null,
1940
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1941
                    '4',
1942
                    ['id' => 'result_disabled_4']
1943
                );
1944
                $radios_results_disabled[] = $form->createElement(
1945
                    'radio',
1946
                    'results_disabled',
1947
                    null,
1948
                    get_lang('DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt'),
1949
                    '5',
1950
                    ['id' => 'result_disabled_5', 'onclick' => 'check_results_disabled()']
1951
                );
1952
                $radios_results_disabled[] = $form->createElement(
1953
                    'radio',
1954
                    'results_disabled',
1955
                    null,
1956
                    get_lang('ExerciseRankingMode'),
1957
                    '6',
1958
                    ['id' => 'result_disabled_6']
1959
                );
1960
1961
                $form->addGroup(
1962
                    $radios_results_disabled,
1963
                    null,
1964
                    get_lang('ShowResultsToStudents')
1965
                );
1966
1967
                // Type of questions disposition on page
1968
                $radios = [];
1969
                $radios[] = $form->createElement(
1970
                    'radio',
1971
                    'exerciseType',
1972
                    null,
1973
                    get_lang('SimpleExercise'),
1974
                    '1',
1975
                    [
1976
                        'onclick' => 'check_per_page_all()',
1977
                        'id' => 'option_page_all',
1978
                    ]
1979
                );
1980
                $radios[] = $form->createElement(
1981
                    'radio',
1982
                    'exerciseType',
1983
                    null,
1984
                    get_lang('SequentialExercise'),
1985
                    '2',
1986
                    [
1987
                        'onclick' => 'check_per_page_one()',
1988
                        'id' => 'option_page_one',
1989
                    ]
1990
                );
1991
1992
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1993
            } else {
1994
                // if is Direct feedback but has not questions we can allow to modify the question type
1995
                if ($this->selectNbrQuestions() == 0) {
1996
                    // feedback type
1997
                    $radios_feedback = [];
1998
                    $radios_feedback[] = $form->createElement(
1999
                        'radio',
2000
                        'exerciseFeedbackType',
2001
                        null,
2002
                        get_lang('ExerciseAtTheEndOfTheTest'),
2003
                        '0',
2004
                        ['id' => 'exerciseType_0', 'onclick' => 'check_feedback()']
2005
                    );
2006
2007
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
2008
                        $radios_feedback[] = $form->createElement(
2009
                            'radio',
2010
                            'exerciseFeedbackType',
2011
                            null,
2012
                            get_lang('DirectFeedback'),
2013
                            '1',
2014
                            ['id' => 'exerciseType_1', 'onclick' => 'check_direct_feedback()']
2015
                        );
2016
                    }
2017
                    $radios_feedback[] = $form->createElement(
2018
                        'radio',
2019
                        'exerciseFeedbackType',
2020
                        null,
2021
                        get_lang('NoFeedback'),
2022
                        '2',
2023
                        ['id' => 'exerciseType_2']
2024
                    );
2025
                    $form->addGroup(
2026
                        $radios_feedback,
2027
                        null,
2028
                        [get_lang('FeedbackType'), get_lang('FeedbackDisplayOptions')]
2029
                    );
2030
2031
                    $radios_results_disabled = [];
2032
                    $radios_results_disabled[] = $form->createElement(
2033
                        'radio',
2034
                        'results_disabled',
2035
                        null,
2036
                        get_lang('ShowScoreAndRightAnswer'),
2037
                        '0',
2038
                        ['id' => 'result_disabled_0']
2039
                    );
2040
                    $radios_results_disabled[] = $form->createElement(
2041
                        'radio',
2042
                        'results_disabled',
2043
                        null,
2044
                        get_lang('DoNotShowScoreNorRightAnswer'),
2045
                        '1',
2046
                        ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
2047
                    );
2048
                    $radios_results_disabled[] = $form->createElement(
2049
                        'radio',
2050
                        'results_disabled',
2051
                        null,
2052
                        get_lang('OnlyShowScore'),
2053
                        '2',
2054
                        ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
2055
                    );
2056
2057
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
2058
2059
                    // Type of questions disposition on page
2060
                    $radios = [];
2061
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
2062
                    $radios[] = $form->createElement(
2063
                        'radio',
2064
                        'exerciseType',
2065
                        null,
2066
                        get_lang('SequentialExercise'),
2067
                        '2'
2068
                    );
2069
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2070
                } else {
2071
                    // Show options freeze
2072
                    $radios_results_disabled[] = $form->createElement(
2073
                        'radio',
2074
                        'results_disabled',
2075
                        null,
2076
                        get_lang('ShowScoreAndRightAnswer'),
2077
                        '0',
2078
                        ['id' => 'result_disabled_0']
2079
                    );
2080
                    $radios_results_disabled[] = $form->createElement(
2081
                        'radio',
2082
                        'results_disabled',
2083
                        null,
2084
                        get_lang('DoNotShowScoreNorRightAnswer'),
2085
                        '1',
2086
                        ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
2087
                    );
2088
                    $radios_results_disabled[] = $form->createElement(
2089
                        'radio',
2090
                        'results_disabled',
2091
                        null,
2092
                        get_lang('OnlyShowScore'),
2093
                        '2',
2094
                        ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
2095
                    );
2096
2097
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
2098
2099
                    $result_disable_group = $form->addGroup(
2100
                        $radios_results_disabled,
2101
                        null,
2102
                        get_lang('ShowResultsToStudents')
2103
                    );
2104
                    $result_disable_group->freeze();
2105
2106
                    // we force the options to the DirectFeedback exercisetype
2107
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2108
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2109
2110
                    // Type of questions disposition on page
2111
                    $radios[] = $form->createElement(
2112
                        'radio',
2113
                        'exerciseType',
2114
                        null,
2115
                        get_lang('SimpleExercise'),
2116
                        '1',
2117
                        [
2118
                            'onclick' => 'check_per_page_all()',
2119
                            'id' => 'option_page_all',
2120
                        ]
2121
                    );
2122
                    $radios[] = $form->createElement(
2123
                        'radio',
2124
                        'exerciseType',
2125
                        null,
2126
                        get_lang('SequentialExercise'),
2127
                        '2',
2128
                        [
2129
                            'onclick' => 'check_per_page_one()',
2130
                            'id' => 'option_page_one',
2131
                        ]
2132
                    );
2133
2134
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2135
                    $type_group->freeze();
2136
                }
2137
            }
2138
2139
            $option = [
2140
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2141
                //  Defined by user
2142
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2143
                // 1-10, All
2144
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2145
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2146
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2147
                // A 123 B 456 C 78 (0, 1, all)
2148
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2149
                // C 78 B 456 A 123
2150
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2151
                // A 321 B 654 C 87
2152
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2153
                // C 87 B 654 A 321
2154
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2155
                /*    B 456 C 78 A 123
2156
                        456 78 123
2157
                        123 456 78
2158
                */
2159
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2160
                /*
2161
                    A 123 B 456 C 78
2162
                    B 456 C 78 A 123
2163
                    B 654 C 87 A 321
2164
                    654 87 321
2165
                    165 842 73
2166
                */
2167
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2168
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2169
            ];
2170
2171
            $form->addElement(
2172
                'select',
2173
                'question_selection_type',
2174
                [get_lang('QuestionSelection')],
2175
                $option,
2176
                [
2177
                    'id' => 'questionSelection',
2178
                    'onchange' => 'checkQuestionSelection()',
2179
                ]
2180
            );
2181
2182
            $displayMatrix = 'none';
2183
            $displayRandom = 'none';
2184
            $selectionType = $this->getQuestionSelectionType();
2185
            switch ($selectionType) {
2186
                case EX_Q_SELECTION_RANDOM:
2187
                    $displayRandom = 'block';
2188
                    break;
2189
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2190
                    $displayMatrix = 'block';
2191
                    break;
2192
            }
2193
2194
            $form->addElement(
2195
                'html',
2196
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2197
            );
2198
            // Number of random question.
2199
            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2200
            $option = range(0, $max);
2201
            $option[0] = get_lang('No');
2202
            $option[-1] = get_lang('AllQuestionsShort');
2203
            $form->addElement(
2204
                'select',
2205
                'randomQuestions',
2206
                [
2207
                    get_lang('RandomQuestions'),
2208
                    get_lang('RandomQuestionsHelp'),
2209
                ],
2210
                $option,
2211
                ['id' => 'randomQuestions']
2212
            );
2213
            $form->addElement('html', '</div>');
2214
2215
            $form->addElement(
2216
                'html',
2217
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2218
            );
2219
2220
            // Category selection.
2221
            $cat = new TestCategory();
2222
            $cat_form = $cat->returnCategoryForm($this);
2223
            if (empty($cat_form)) {
2224
                $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>';
2225
            }
2226
            $form->addElement('label', null, $cat_form);
2227
            $form->addElement('html', '</div>');
2228
2229
            // Category name.
2230
            $radio_display_cat_name = [
2231
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2232
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'),
2233
            ];
2234
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2235
2236
            // Random answers.
2237
            $radios_random_answers = [
2238
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2239
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'),
2240
            ];
2241
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2242
2243
            // Hide question title.
2244
            $group = [
2245
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2246
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'),
2247
            ];
2248
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2249
2250
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
2251
2252
            if ($allow === true) {
2253
                // Hide question title.
2254
                $group = [
2255
                    $form->createElement(
2256
                        'radio',
2257
                        'show_previous_button',
2258
                        null,
2259
                        get_lang('Yes'),
2260
                        '1'
2261
                    ),
2262
                    $form->createElement(
2263
                        'radio',
2264
                        'show_previous_button',
2265
                        null,
2266
                        get_lang('No'),
2267
                        '0'
2268
                    ),
2269
                ];
2270
                $form->addGroup($group, null, get_lang('ShowPreviousButton'));
2271
            }
2272
2273
            // Attempts
2274
            $attempt_option = range(0, 10);
2275
            $attempt_option[0] = get_lang('Infinite');
2276
2277
            $form->addElement(
2278
                'select',
2279
                'exerciseAttempts',
2280
                get_lang('ExerciseAttempts'),
2281
                $attempt_option,
2282
                ['id' => 'exerciseAttempts']
2283
            );
2284
2285
            // Exercise time limit
2286
            $form->addElement(
2287
                'checkbox',
2288
                'activate_start_date_check',
2289
                null,
2290
                get_lang('EnableStartTime'),
2291
                ['onclick' => 'activate_start_date()']
2292
            );
2293
2294
            $var = self::selectTimeLimit();
2295
2296
            if (!empty($this->start_time)) {
2297
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2298
            } else {
2299
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2300
            }
2301
2302
            $form->addElement('date_time_picker', 'start_time');
2303
            $form->addElement('html', '</div>');
2304
            $form->addElement(
2305
                'checkbox',
2306
                'activate_end_date_check',
2307
                null,
2308
                get_lang('EnableEndTime'),
2309
                ['onclick' => 'activate_end_date()']
2310
            );
2311
2312
            if (!empty($this->end_time)) {
2313
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2314
            } else {
2315
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2316
            }
2317
2318
            $form->addElement('date_time_picker', 'end_time');
2319
            $form->addElement('html', '</div>');
2320
2321
            $display = 'block';
2322
            $form->addElement(
2323
                'checkbox',
2324
                'propagate_neg',
2325
                null,
2326
                get_lang('PropagateNegativeResults')
2327
            );
2328
            $form->addCheckBox(
2329
                'save_correct_answers',
2330
                null,
2331
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2332
            );
2333
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2334
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2335
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2336
2337
            // Timer control
2338
            $form->addElement(
2339
                'checkbox',
2340
                'enabletimercontrol',
2341
                null,
2342
                get_lang('EnableTimerControl'),
2343
                [
2344
                    'onclick' => 'option_time_expired()',
2345
                    'id' => 'enabletimercontrol',
2346
                    'onload' => 'check_load_time()',
2347
                ]
2348
            );
2349
2350
            $expired_date = (int) $this->selectExpiredTime();
2351
2352
            if (($expired_date != '0')) {
2353
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2354
            } else {
2355
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2356
            }
2357
            $form->addText(
2358
                'enabletimercontroltotalminutes',
2359
                get_lang('ExerciseTotalDurationInMinutes'),
2360
                false,
2361
                [
2362
                    'id' => 'enabletimercontroltotalminutes',
2363
                    'cols-size' => [2, 2, 8],
2364
                ]
2365
            );
2366
            $form->addElement('html', '</div>');
2367
            $form->addElement(
2368
                'text',
2369
                'pass_percentage',
2370
                [get_lang('PassPercentage'), null, '%'],
2371
                ['id' => 'pass_percentage']
2372
            );
2373
2374
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2375
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2376
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2377
2378
            // add the text_when_finished textbox
2379
            $form->addHtmlEditor(
2380
                'text_when_finished',
2381
                get_lang('TextWhenFinished'),
2382
                false,
2383
                false,
2384
                $editor_config
2385
            );
2386
2387
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
2388
            if ($allow === true) {
2389
                $settings = ExerciseLib::getNotificationSettings();
2390
                $group = [];
2391
                foreach ($settings as $itemId => $label) {
2392
                    $group[] = $form->createElement(
2393
                        'checkbox',
2394
                        'notifications[]',
2395
                        null,
2396
                        $label,
2397
                        ['value' => $itemId]
2398
                    );
2399
                }
2400
                $form->addGroup($group, '', [get_lang('EmailNotifications')]);
2401
            }
2402
2403
            $form->addCheckBox(
2404
                'update_title_in_lps',
2405
                null,
2406
                get_lang('UpdateTitleInLps')
2407
            );
2408
2409
            $defaults = [];
2410
            if (api_get_setting('search_enabled') === 'true') {
2411
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2412
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2413
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2414
                $specific_fields = get_specific_field_list();
2415
2416
                foreach ($specific_fields as $specific_field) {
2417
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2418
                    $filter = [
2419
                        'c_id' => api_get_course_int_id(),
2420
                        'field_id' => $specific_field['id'],
2421
                        'ref_id' => $this->id,
2422
                        'tool_id' => "'".TOOL_QUIZ."'",
2423
                    ];
2424
                    $values = get_specific_field_values_list($filter, ['value']);
2425
                    if (!empty($values)) {
2426
                        $arr_str_values = [];
2427
                        foreach ($values as $value) {
2428
                            $arr_str_values[] = $value['value'];
2429
                        }
2430
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2431
                    }
2432
                }
2433
            }
2434
2435
            $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_EXERCISE, $this->iId);
2436
2437
            $form->addElement('html', '</div>'); //End advanced setting
2438
            $form->addElement('html', '</div>');
2439
        }
2440
2441
        // submit
2442
        if (isset($_GET['exerciseId'])) {
2443
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2444
        } else {
2445
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2446
        }
2447
2448
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2449
2450
        if ($type == 'full') {
2451
            // rules
2452
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2453
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2454
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2455
        }
2456
2457
        // defaults
2458
        if ($type == 'full') {
2459
            if ($this->id > 0) {
2460
                if ($this->random > $this->selectNbrQuestions()) {
2461
                    $defaults['randomQuestions'] = $this->selectNbrQuestions();
2462
                } else {
2463
                    $defaults['randomQuestions'] = $this->random;
2464
                }
2465
2466
                $defaults['randomAnswers'] = $this->getRandomAnswers();
2467
                $defaults['exerciseType'] = $this->selectType();
2468
                $defaults['exerciseTitle'] = $this->get_formated_title();
2469
                $defaults['exerciseDescription'] = $this->selectDescription();
2470
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2471
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2472
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2473
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2474
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2475
                $defaults['review_answers'] = $this->review_answers;
2476
                $defaults['randomByCat'] = $this->getRandomByCategory();
2477
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2478
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2479
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2480
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2481
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2482
                $defaults['show_previous_button'] = $this->showPreviousButton();
2483
2484
                if (!empty($this->start_time)) {
2485
                    $defaults['activate_start_date_check'] = 1;
2486
                }
2487
                if (!empty($this->end_time)) {
2488
                    $defaults['activate_end_date_check'] = 1;
2489
                }
2490
2491
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2492
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2493
2494
                // Get expired time
2495
                if ($this->expired_time != '0') {
2496
                    $defaults['enabletimercontrol'] = 1;
2497
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2498
                } else {
2499
                    $defaults['enabletimercontroltotalminutes'] = 0;
2500
                }
2501
                $defaults['skills'] = array_keys($skillList);
2502
                $defaults['notifications'] = $this->getNotifications();
2503
            } else {
2504
                $defaults['exerciseType'] = 2;
2505
                $defaults['exerciseAttempts'] = 0;
2506
                $defaults['randomQuestions'] = 0;
2507
                $defaults['randomAnswers'] = 0;
2508
                $defaults['exerciseDescription'] = '';
2509
                $defaults['exerciseFeedbackType'] = 0;
2510
                $defaults['results_disabled'] = 0;
2511
                $defaults['randomByCat'] = 0;
2512
                $defaults['text_when_finished'] = '';
2513
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2514
                $defaults['display_category_name'] = 1;
2515
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2516
                $defaults['pass_percentage'] = '';
2517
                $defaults['end_button'] = $this->selectEndButton();
2518
                $defaults['question_selection_type'] = 1;
2519
                $defaults['hide_question_title'] = 0;
2520
                $defaults['show_previous_button'] = 1;
2521
                $defaults['on_success_message'] = null;
2522
                $defaults['on_failed_message'] = null;
2523
            }
2524
        } else {
2525
            $defaults['exerciseTitle'] = $this->selectTitle();
2526
            $defaults['exerciseDescription'] = $this->selectDescription();
2527
        }
2528
2529
        if (api_get_setting('search_enabled') === 'true') {
2530
            $defaults['index_document'] = 'checked="checked"';
2531
        }
2532
        $form->setDefaults($defaults);
2533
2534
        // Freeze some elements.
2535
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2536
            $elementsToFreeze = [
2537
                'randomQuestions',
2538
                //'randomByCat',
2539
                'exerciseAttempts',
2540
                'propagate_neg',
2541
                'enabletimercontrol',
2542
                'review_answers',
2543
            ];
2544
2545
            foreach ($elementsToFreeze as $elementName) {
2546
                /** @var HTML_QuickForm_element $element */
2547
                $element = $form->getElement($elementName);
2548
                $element->freeze();
2549
            }
2550
        }
2551
    }
2552
2553
    /**
2554
     * function which process the creation of exercises.
2555
     *
2556
     * @param FormValidator $form
2557
     * @param string
2558
     *
2559
     * @return int c_quiz.iid
2560
     */
2561
    public function processCreation($form, $type = '')
2562
    {
2563
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2564
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2565
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2566
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2567
        $this->updateType($form->getSubmitValue('exerciseType'));
2568
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2569
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2570
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2571
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2572
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2573
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2574
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2575
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2576
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2577
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2578
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2579
        $this->updateCategories($form->getSubmitValue('category'));
2580
        $this->updateEndButton($form->getSubmitValue('end_button'));
2581
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2582
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2583
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2584
        $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2585
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2586
        $this->setModelType($form->getSubmitValue('model_type'));
2587
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2588
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2589
        $this->sessionId = api_get_session_id();
2590
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2591
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2592
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2593
        $this->setShowPreviousButton($form->getSubmitValue('show_previous_button'));
2594
        $this->setNotifications($form->getSubmitValue('notifications'));
2595
2596
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2597
            $start_time = $form->getSubmitValue('start_time');
2598
            $this->start_time = api_get_utc_datetime($start_time);
2599
        } else {
2600
            $this->start_time = null;
2601
        }
2602
2603
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2604
            $end_time = $form->getSubmitValue('end_time');
2605
            $this->end_time = api_get_utc_datetime($end_time);
2606
        } else {
2607
            $this->end_time = null;
2608
        }
2609
2610
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2611
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2612
            if ($this->expired_time == 0) {
2613
                $this->expired_time = $expired_total_time;
2614
            }
2615
        } else {
2616
            $this->expired_time = 0;
2617
        }
2618
2619
        if ($form->getSubmitValue('randomAnswers') == 1) {
2620
            $this->random_answers = 1;
2621
        } else {
2622
            $this->random_answers = 0;
2623
        }
2624
2625
        // Update title in all LPs that have this quiz added
2626
        if ($form->getSubmitValue('update_title_in_lps') == 1) {
2627
            $courseId = api_get_course_int_id();
2628
            $table = Database::get_course_table(TABLE_LP_ITEM);
2629
            $sql = "SELECT * FROM $table 
2630
                    WHERE 
2631
                        c_id = $courseId AND 
2632
                        item_type = 'quiz' AND 
2633
                        path = '".$this->id."'
2634
                    ";
2635
            $result = Database::query($sql);
2636
            $items = Database::store_result($result);
2637
            if (!empty($items)) {
2638
                foreach ($items as $item) {
2639
                    $itemId = $item['iid'];
2640
                    $sql = "UPDATE $table SET title = '".$this->title."'                             
2641
                            WHERE iid = $itemId AND c_id = $courseId ";
2642
                    Database::query($sql);
2643
                }
2644
            }
2645
        }
2646
2647
        $iId = $this->save($type);
2648
        if (!empty($iId)) {
2649
            Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iId);
2650
        }
2651
    }
2652
2653
    public function search_engine_save()
2654
    {
2655
        if ($_POST['index_document'] != 1) {
2656
            return;
2657
        }
2658
        $course_id = api_get_course_id();
2659
2660
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2661
2662
        $specific_fields = get_specific_field_list();
2663
        $ic_slide = new IndexableChunk();
2664
2665
        $all_specific_terms = '';
2666
        foreach ($specific_fields as $specific_field) {
2667
            if (isset($_REQUEST[$specific_field['code']])) {
2668
                $sterms = trim($_REQUEST[$specific_field['code']]);
2669
                if (!empty($sterms)) {
2670
                    $all_specific_terms .= ' '.$sterms;
2671
                    $sterms = explode(',', $sterms);
2672
                    foreach ($sterms as $sterm) {
2673
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2674
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2675
                    }
2676
                }
2677
            }
2678
        }
2679
2680
        // build the chunk to index
2681
        $ic_slide->addValue("title", $this->exercise);
2682
        $ic_slide->addCourseId($course_id);
2683
        $ic_slide->addToolId(TOOL_QUIZ);
2684
        $xapian_data = [
2685
            SE_COURSE_ID => $course_id,
2686
            SE_TOOL_ID => TOOL_QUIZ,
2687
            SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2688
            SE_USER => (int) api_get_user_id(),
2689
        ];
2690
        $ic_slide->xapian_data = serialize($xapian_data);
2691
        $exercise_description = $all_specific_terms.' '.$this->description;
2692
        $ic_slide->addValue("content", $exercise_description);
2693
2694
        $di = new ChamiloIndexer();
2695
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2696
        $di->connectDb(null, null, $lang);
2697
        $di->addChunk($ic_slide);
2698
2699
        //index and return search engine document id
2700
        $did = $di->index();
2701
        if ($did) {
2702
            // save it to db
2703
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2704
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2705
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2706
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2707
            Database::query($sql);
2708
        }
2709
    }
2710
2711
    public function search_engine_edit()
2712
    {
2713
        // update search enchine and its values table if enabled
2714
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2715
            $course_id = api_get_course_id();
2716
2717
            // actually, it consists on delete terms from db,
2718
            // insert new ones, create a new search engine document, and remove the old one
2719
            // get search_did
2720
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2721
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2722
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2723
            $res = Database::query($sql);
2724
2725
            if (Database::num_rows($res) > 0) {
2726
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2727
2728
                $se_ref = Database::fetch_array($res);
2729
                $specific_fields = get_specific_field_list();
2730
                $ic_slide = new IndexableChunk();
2731
2732
                $all_specific_terms = '';
2733
                foreach ($specific_fields as $specific_field) {
2734
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2735
                    if (isset($_REQUEST[$specific_field['code']])) {
2736
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2737
                        $all_specific_terms .= ' '.$sterms;
2738
                        $sterms = explode(',', $sterms);
2739
                        foreach ($sterms as $sterm) {
2740
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2741
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2742
                        }
2743
                    }
2744
                }
2745
2746
                // build the chunk to index
2747
                $ic_slide->addValue('title', $this->exercise);
2748
                $ic_slide->addCourseId($course_id);
2749
                $ic_slide->addToolId(TOOL_QUIZ);
2750
                $xapian_data = [
2751
                    SE_COURSE_ID => $course_id,
2752
                    SE_TOOL_ID => TOOL_QUIZ,
2753
                    SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2754
                    SE_USER => (int) api_get_user_id(),
2755
                ];
2756
                $ic_slide->xapian_data = serialize($xapian_data);
2757
                $exercise_description = $all_specific_terms.' '.$this->description;
2758
                $ic_slide->addValue("content", $exercise_description);
2759
2760
                $di = new ChamiloIndexer();
2761
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2762
                $di->connectDb(null, null, $lang);
2763
                $di->remove_document($se_ref['search_did']);
2764
                $di->addChunk($ic_slide);
2765
2766
                //index and return search engine document id
2767
                $did = $di->index();
2768
                if ($did) {
2769
                    // save it to db
2770
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2771
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2772
                    Database::query($sql);
2773
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2774
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2775
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2776
                    Database::query($sql);
2777
                }
2778
            } else {
2779
                $this->search_engine_save();
2780
            }
2781
        }
2782
    }
2783
2784
    public function search_engine_delete()
2785
    {
2786
        // remove from search engine if enabled
2787
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2788
            $course_id = api_get_course_id();
2789
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2790
            $sql = 'SELECT * FROM %s
2791
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2792
                    LIMIT 1';
2793
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2794
            $res = Database::query($sql);
2795
            if (Database::num_rows($res) > 0) {
2796
                $row = Database::fetch_array($res);
2797
                $di = new ChamiloIndexer();
2798
                $di->remove_document($row['search_did']);
2799
                unset($di);
2800
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2801
                foreach ($this->questionList as $question_i) {
2802
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2803
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2804
                    $qres = Database::query($sql);
2805
                    if (Database::num_rows($qres) > 0) {
2806
                        $qrow = Database::fetch_array($qres);
2807
                        $objQuestion = Question::getInstance($qrow['type']);
2808
                        $objQuestion = Question::read((int) $question_i);
2809
                        $objQuestion->search_engine_edit($this->id, false, true);
2810
                        unset($objQuestion);
2811
                    }
2812
                }
2813
            }
2814
            $sql = 'DELETE FROM %s 
2815
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2816
                    LIMIT 1';
2817
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2818
            Database::query($sql);
2819
2820
            // remove terms from db
2821
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2822
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2823
        }
2824
    }
2825
2826
    public function selectExpiredTime()
2827
    {
2828
        return $this->expired_time;
2829
    }
2830
2831
    /**
2832
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2833
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2834
     * Works with exercises in sessions.
2835
     *
2836
     * @param bool   $cleanLpTests
2837
     * @param string $cleanResultBeforeDate
2838
     *
2839
     * @return int quantity of user's exercises deleted
2840
     */
2841
    public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
2842
    {
2843
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2844
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2845
2846
        $sql_where = '  AND
2847
                        orig_lp_id = 0 AND
2848
                        orig_lp_item_id = 0';
2849
2850
        // if we want to delete results from LP too
2851
        if ($cleanLpTests) {
2852
            $sql_where = '';
2853
        }
2854
2855
        // if we want to delete attempts before date $cleanResultBeforeDate
2856
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2857
2858
        if (!empty($cleanResultBeforeDate)) {
2859
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2860
            if (api_is_valid_date($cleanResultBeforeDate)) {
2861
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2862
            } else {
2863
                return 0;
2864
            }
2865
        }
2866
2867
        $sql = "SELECT exe_id
2868
            FROM $table_track_e_exercises
2869
            WHERE
2870
                c_id = ".api_get_course_int_id()." AND
2871
                exe_exo_id = ".$this->id." AND
2872
                session_id = ".api_get_session_id()." ".
2873
                $sql_where;
2874
2875
        $result = Database::query($sql);
2876
        $exe_list = Database::store_result($result);
2877
2878
        // deleting TRACK_E_ATTEMPT table
2879
        // check if exe in learning path or not
2880
        $i = 0;
2881
        if (is_array($exe_list) && count($exe_list) > 0) {
2882
            foreach ($exe_list as $item) {
2883
                $sql = "DELETE FROM $table_track_e_attempt
2884
                        WHERE exe_id = '".$item['exe_id']."'";
2885
                Database::query($sql);
2886
                $i++;
2887
            }
2888
        }
2889
2890
        $session_id = api_get_session_id();
2891
        // delete TRACK_E_EXERCISES table
2892
        $sql = "DELETE FROM $table_track_e_exercises
2893
                WHERE 
2894
                  c_id = ".api_get_course_int_id()." AND 
2895
                  exe_exo_id = ".$this->id." $sql_where AND 
2896
                  session_id = ".$session_id;
2897
        Database::query($sql);
2898
2899
        Event::addEvent(
2900
            LOG_EXERCISE_RESULT_DELETE,
2901
            LOG_EXERCISE_ID,
2902
            $this->id,
2903
            null,
2904
            null,
2905
            api_get_course_int_id(),
2906
            $session_id
2907
        );
2908
2909
        return $i;
2910
    }
2911
2912
    /**
2913
     * Copies an exercise (duplicate all questions and answers).
2914
     */
2915
    public function copyExercise()
2916
    {
2917
        $exerciseObject = $this;
2918
        $categories = $exerciseObject->getCategoriesInExercise();
2919
        // Get all questions no matter the order/category settings
2920
        $questionList = $exerciseObject->getQuestionOrderedList();
2921
        // Force the creation of a new exercise
2922
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
2923
        // Hides the new exercise
2924
        $exerciseObject->updateStatus(false);
2925
        $exerciseObject->updateId(0);
2926
        $exerciseObject->save();
2927
        $newId = $exerciseObject->selectId();
2928
        if ($newId && !empty($questionList)) {
2929
            // Question creation
2930
            foreach ($questionList as $oldQuestionId) {
2931
                $oldQuestionObj = Question::read($oldQuestionId);
2932
                $newQuestionId = $oldQuestionObj->duplicate();
2933
                if ($newQuestionId) {
2934
                    $newQuestionObj = Question::read($newQuestionId);
2935
                    if (isset($newQuestionObj) && $newQuestionObj) {
2936
                        $newQuestionObj->addToList($newId);
2937
                        if (!empty($oldQuestionObj->category)) {
2938
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
2939
                        }
2940
2941
                        // This should be moved to the duplicate function
2942
                        $newAnswerObj = new Answer($oldQuestionId);
2943
                        $newAnswerObj->read();
2944
                        $newAnswerObj->duplicate($newQuestionObj);
2945
                    }
2946
                }
2947
            }
2948
            if (!empty($categories)) {
2949
                $newCategoryList = [];
2950
                foreach ($categories as $category) {
2951
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
2952
                }
2953
                $exerciseObject->save_categories_in_exercise($newCategoryList);
2954
            }
2955
        }
2956
    }
2957
2958
    /**
2959
     * Changes the exercise status.
2960
     *
2961
     * @param string $status - exercise status
2962
     */
2963
    public function updateStatus($status)
2964
    {
2965
        $this->active = $status;
2966
    }
2967
2968
    /**
2969
     * @param int    $lp_id
2970
     * @param int    $lp_item_id
2971
     * @param int    $lp_item_view_id
2972
     * @param string $status
2973
     *
2974
     * @return array
2975
     */
2976
    public function get_stat_track_exercise_info(
2977
        $lp_id = 0,
2978
        $lp_item_id = 0,
2979
        $lp_item_view_id = 0,
2980
        $status = 'incomplete'
2981
    ) {
2982
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2983
        if (empty($lp_id)) {
2984
            $lp_id = 0;
2985
        }
2986
        if (empty($lp_item_id)) {
2987
            $lp_item_id = 0;
2988
        }
2989
        if (empty($lp_item_view_id)) {
2990
            $lp_item_view_id = 0;
2991
        }
2992
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
2993
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
2994
					   c_id                 = '.api_get_course_int_id().' AND
2995
					   status 				= '."'".Database::escape_string($status)."'".' AND
2996
					   orig_lp_id 			= '."'".$lp_id."'".' AND
2997
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
2998
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
2999
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
3000
3001
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
3002
3003
        $result = Database::query($sql_track);
3004
        $new_array = [];
3005
        if (Database::num_rows($result) > 0) {
3006
            $new_array = Database::fetch_array($result, 'ASSOC');
3007
            $new_array['num_exe'] = Database::num_rows($result);
3008
        }
3009
3010
        return $new_array;
3011
    }
3012
3013
    /**
3014
     * Saves a test attempt.
3015
     *
3016
     * @param int $clock_expired_time clock_expired_time
3017
     * @param int  int lp id
3018
     * @param int  int lp item id
3019
     * @param int  int lp item_view id
3020
     * @param array $questionList
3021
     * @param float $weight
3022
     *
3023
     * @return int
3024
     */
3025
    public function save_stat_track_exercise_info(
3026
        $clock_expired_time = 0,
3027
        $safe_lp_id = 0,
3028
        $safe_lp_item_id = 0,
3029
        $safe_lp_item_view_id = 0,
3030
        $questionList = [],
3031
        $weight = 0
3032
    ) {
3033
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3034
        $safe_lp_id = intval($safe_lp_id);
3035
        $safe_lp_item_id = intval($safe_lp_item_id);
3036
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
3037
3038
        if (empty($safe_lp_id)) {
3039
            $safe_lp_id = 0;
3040
        }
3041
        if (empty($safe_lp_item_id)) {
3042
            $safe_lp_item_id = 0;
3043
        }
3044
        if (empty($clock_expired_time)) {
3045
            $clock_expired_time = null;
3046
        }
3047
3048
        $questionList = array_map('intval', $questionList);
3049
3050
        $params = [
3051
            'exe_exo_id' => $this->id,
3052
            'exe_user_id' => api_get_user_id(),
3053
            'c_id' => api_get_course_int_id(),
3054
            'status' => 'incomplete',
3055
            'session_id' => api_get_session_id(),
3056
            'data_tracking' => implode(',', $questionList),
3057
            'start_date' => api_get_utc_datetime(),
3058
            'orig_lp_id' => $safe_lp_id,
3059
            'orig_lp_item_id' => $safe_lp_item_id,
3060
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
3061
            'max_score' => $weight,
3062
            'user_ip' => Database::escape_string(api_get_real_ip()),
3063
            'exe_date' => api_get_utc_datetime(),
3064
            'score' => 0,
3065
            'steps_counter' => 0,
3066
            'exe_duration' => 0,
3067
            'expired_time_control' => $clock_expired_time,
3068
            'questions_to_check' => '',
3069
        ];
3070
3071
        $id = Database::insert($track_exercises, $params);
3072
3073
        return $id;
3074
    }
3075
3076
    /**
3077
     * @param int    $question_id
3078
     * @param int    $questionNum
3079
     * @param array  $questions_in_media
3080
     * @param string $currentAnswer
3081
     * @param array  $myRemindList
3082
     *
3083
     * @return string
3084
     */
3085
    public function show_button(
3086
        $question_id,
3087
        $questionNum,
3088
        $questions_in_media = [],
3089
        $currentAnswer = '',
3090
        $myRemindList = []
3091
    ) {
3092
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3093
        $nbrQuestions = $this->get_count_question_list();
3094
        $buttonList = [];
3095
        $html = $label = '';
3096
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3097
3098
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
3099
            $urlTitle = get_lang('ContinueTest');
3100
            if ($questionNum == count($this->questionList)) {
3101
                $urlTitle = get_lang('EndTest');
3102
            }
3103
3104
            $html .= Display::url(
3105
                $urlTitle,
3106
                'exercise_submit_modal.php?'.http_build_query([
3107
                    'learnpath_id' => $safe_lp_id,
3108
                    'learnpath_item_id' => $safe_lp_item_id,
3109
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
3110
                    'origin' => $origin,
3111
                    'hotspot' => $hotspot_get,
3112
                    'nbrQuestions' => $nbrQuestions,
3113
                    'num' => $questionNum,
3114
                    'exerciseType' => $this->type,
3115
                    'exerciseId' => $this->id,
3116
                    'reminder' => empty($myRemindList) ? null : 2,
3117
                ]),
3118
                [
3119
                    'class' => 'ajax btn btn-default',
3120
                    'data-title' => $urlTitle,
3121
                    'data-size' => 'md',
3122
                ]
3123
            );
3124
            $html .= '<br />';
3125
        } else {
3126
            // User
3127
            if (api_is_allowed_to_session_edit()) {
3128
                $endReminderValue = false;
3129
                if (!empty($myRemindList)) {
3130
                    $endValue = end($myRemindList);
3131
                    if ($endValue == $question_id) {
3132
                        $endReminderValue = true;
3133
                    }
3134
                }
3135
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3136
                    if ($this->review_answers) {
3137
                        $label = get_lang('ReviewQuestions');
3138
                        $class = 'btn btn-success';
3139
                    } else {
3140
                        $label = get_lang('EndTest');
3141
                        $class = 'btn btn-warning';
3142
                    }
3143
                } else {
3144
                    $label = get_lang('NextQuestion');
3145
                    $class = 'btn btn-primary';
3146
                }
3147
                // used to select it with jquery
3148
                $class .= ' question-validate-btn';
3149
                if ($this->type == ONE_PER_PAGE) {
3150
                    if ($questionNum != 1) {
3151
                        if ($this->showPreviousButton()) {
3152
                            $prev_question = $questionNum - 2;
3153
                            $showPreview = true;
3154
                            if (!empty($myRemindList)) {
3155
                                $beforeId = null;
3156
                                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...
3157
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3158
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3159
                                        break;
3160
                                    }
3161
                                }
3162
3163
                                if (empty($beforeId)) {
3164
                                    $showPreview = false;
3165
                                } else {
3166
                                    $num = 0;
3167
                                    foreach ($this->questionList as $originalQuestionId) {
3168
                                        if ($originalQuestionId == $beforeId) {
3169
                                            break;
3170
                                        }
3171
                                        $num++;
3172
                                    }
3173
                                    $prev_question = $num;
3174
                                    //$question_id = $beforeId;
3175
                                }
3176
                            }
3177
3178
                            if ($showPreview) {
3179
                                $buttonList[] = Display::button(
3180
                                    'previous_question_and_save',
3181
                                    get_lang('PreviousQuestion'),
3182
                                    [
3183
                                        'type' => 'button',
3184
                                        'class' => 'btn btn-default',
3185
                                        'data-prev' => $prev_question,
3186
                                        'data-question' => $question_id,
3187
                                    ]
3188
                                );
3189
                            }
3190
                        }
3191
                    }
3192
3193
                    // Next question
3194
                    if (!empty($questions_in_media)) {
3195
                        $buttonList[] = Display::button(
3196
                            'save_question_list',
3197
                            $label,
3198
                            [
3199
                                'type' => 'button',
3200
                                'class' => $class,
3201
                                'data-list' => implode(",", $questions_in_media),
3202
                            ]
3203
                        );
3204
                    } else {
3205
                        $buttonList[] = Display::button(
3206
                            'save_now',
3207
                            $label,
3208
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3209
                        );
3210
                    }
3211
                    $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3212
3213
                    $html .= implode(PHP_EOL, $buttonList);
3214
                } else {
3215
                    if ($this->review_answers) {
3216
                        $all_label = get_lang('ReviewQuestions');
3217
                        $class = 'btn btn-success';
3218
                    } else {
3219
                        $all_label = get_lang('EndTest');
3220
                        $class = 'btn btn-warning';
3221
                    }
3222
                    // used to select it with jquery
3223
                    $class .= ' question-validate-btn';
3224
                    $buttonList[] = Display::button(
3225
                        'validate_all',
3226
                        $all_label,
3227
                        ['type' => 'button', 'class' => $class]
3228
                    );
3229
                    $buttonList[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_response']);
3230
                    $html .= implode(PHP_EOL, $buttonList);
3231
                }
3232
            }
3233
        }
3234
3235
        return $html;
3236
    }
3237
3238
    /**
3239
     * So the time control will work.
3240
     *
3241
     * @param string $time_left
3242
     *
3243
     * @return string
3244
     */
3245
    public function showTimeControlJS($time_left)
3246
    {
3247
        $time_left = (int) $time_left;
3248
        $script = "redirectExerciseToResult();";
3249
        if ($this->type == ALL_ON_ONE_PAGE) {
3250
            $script = "save_now_all('validate');";
3251
        }
3252
3253
        return "<script>
3254
            function openClockWarning() {
3255
                $('#clock_warning').dialog({
3256
                    modal:true,
3257
                    height:250,
3258
                    closeOnEscape: false,
3259
                    resizable: false,
3260
                    buttons: {
3261
                        '".addslashes(get_lang("EndTest"))."': function() {
3262
                            $('#clock_warning').dialog('close');
3263
                        }
3264
                    },
3265
                    close: function() {
3266
                        send_form();
3267
                    }
3268
                });
3269
                
3270
                $('#clock_warning').dialog('open');
3271
                $('#counter_to_redirect').epiclock({
3272
                    mode: $.epiclock.modes.countdown,
3273
                    offset: {seconds: 5},
3274
                    format: 's'
3275
                }).bind('timer', function () {
3276
                    send_form();
3277
                });
3278
            }
3279
3280
            function send_form() {
3281
                if ($('#exercise_form').length) {
3282
                    $script
3283
                } else {
3284
                    // In exercise_reminder.php
3285
                    final_submit();
3286
                }
3287
            }
3288
3289
            function onExpiredTimeExercise() {
3290
                $('#wrapper-clock').hide();
3291
                $('#expired-message-id').show();
3292
                //Fixes bug #5263
3293
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3294
                openClockWarning();
3295
            }
3296
3297
			$(document).ready(function() {
3298
				// time in seconds when using minutes there are some seconds lost
3299
                var time_left = parseInt(".$time_left.");
3300
                $('#exercise_clock_warning').epiclock({
3301
                    mode: $.epiclock.modes.countdown,
3302
                    offset: {seconds: time_left},
3303
                    format: 'x:i:s',
3304
                    renderer: 'minute'
3305
                }).bind('timer', function () {
3306
                    onExpiredTimeExercise();
3307
                });
3308
	       		$('#submit_save').click(function () {});
3309
	    });
3310
	    </script>";
3311
    }
3312
3313
    /**
3314
     * This function was originally found in the exercise_show.php.
3315
     *
3316
     * @param int    $exeId
3317
     * @param int    $questionId
3318
     * @param mixed  $choice                                    the user-selected option
3319
     * @param string $from                                      function is called from 'exercise_show' or 'exercise_result'
3320
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] = coordinates
3321
     * @param bool   $saved_results                             save results in the DB or just show the reponse
3322
     * @param bool   $from_database                             gets information from DB or from the current selection
3323
     * @param bool   $show_result                               show results or not
3324
     * @param int    $propagate_neg
3325
     * @param array  $hotspot_delineation_result
3326
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3327
     * @param bool   $updateResults
3328
     *
3329
     * @todo    reduce parameters of this function
3330
     *
3331
     * @return string html code
3332
     */
3333
    public function manage_answer(
3334
        $exeId,
3335
        $questionId,
3336
        $choice,
3337
        $from = 'exercise_show',
3338
        $exerciseResultCoordinates = [],
3339
        $saved_results = true,
3340
        $from_database = false,
3341
        $show_result = true,
3342
        $propagate_neg = 0,
3343
        $hotspot_delineation_result = [],
3344
        $showTotalScoreAndUserChoicesInLastAttempt = true,
3345
        $updateResults = false
3346
    ) {
3347
        $debug = false;
3348
        //needed in order to use in the exercise_attempt() for the time
3349
        global $learnpath_id, $learnpath_item_id;
3350
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3351
        $em = Database::getManager();
3352
        $feedback_type = $this->selectFeedbackType();
3353
        $results_disabled = $this->selectResultsDisabled();
3354
3355
        if ($debug) {
3356
            error_log("<------ manage_answer ------> ");
3357
            error_log('exe_id: '.$exeId);
3358
            error_log('$from:  '.$from);
3359
            error_log('$saved_results: '.intval($saved_results));
3360
            error_log('$from_database: '.intval($from_database));
3361
            error_log('$show_result: '.intval($show_result));
3362
            error_log('$propagate_neg: '.$propagate_neg);
3363
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3364
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3365
            error_log('$learnpath_id: '.$learnpath_id);
3366
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3367
            error_log('$choice: '.print_r($choice, 1));
3368
        }
3369
3370
        $final_overlap = 0;
3371
        $final_missing = 0;
3372
        $final_excess = 0;
3373
        $overlap_color = 0;
3374
        $missing_color = 0;
3375
        $excess_color = 0;
3376
        $threadhold1 = 0;
3377
        $threadhold2 = 0;
3378
        $threadhold3 = 0;
3379
        $arrques = null;
3380
        $arrans = null;
3381
        $studentChoice = null;
3382
        $expectedAnswer = '';
3383
        $calculatedChoice = '';
3384
        $calculatedStatus = '';
3385
        $questionId = (int) $questionId;
3386
        $exeId = (int) $exeId;
3387
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3388
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3389
3390
        // Creates a temporary Question object
3391
        $course_id = $this->course_id;
3392
        $objQuestionTmp = Question::read($questionId, $course_id);
3393
3394
        if ($objQuestionTmp === false) {
3395
            return false;
3396
        }
3397
3398
        $questionName = $objQuestionTmp->selectTitle();
3399
        $questionWeighting = $objQuestionTmp->selectWeighting();
3400
        $answerType = $objQuestionTmp->selectType();
3401
        $quesId = $objQuestionTmp->selectId();
3402
        $extra = $objQuestionTmp->extra;
3403
        $next = 1; //not for now
3404
        $totalWeighting = 0;
3405
        $totalScore = 0;
3406
3407
        // Extra information of the question
3408
        if ((
3409
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
3410
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
3411
            )
3412
            && !empty($extra)
3413
        ) {
3414
            $extra = explode(':', $extra);
3415
            if ($debug) {
3416
                error_log(print_r($extra, 1));
3417
            }
3418
            // Fixes problems with negatives values using intval
3419
3420
            $true_score = floatval(trim($extra[0]));
3421
            $false_score = floatval(trim($extra[1]));
3422
            $doubt_score = floatval(trim($extra[2]));
3423
        }
3424
3425
        // Construction of the Answer object
3426
        $objAnswerTmp = new Answer($questionId, $course_id);
3427
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3428
3429
        if ($debug) {
3430
            error_log('Count of answers: '.$nbrAnswers);
3431
            error_log('$answerType: '.$answerType);
3432
        }
3433
3434
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
3435
            $choiceTmp = $choice;
3436
            $choice = isset($choiceTmp['choice']) ? $choiceTmp['choice'] : '';
3437
            $choiceDegreeCertainty = isset($choiceTmp['choiceDegreeCertainty']) ? $choiceTmp['choiceDegreeCertainty'] : '';
3438
        }
3439
3440
        if ($answerType == FREE_ANSWER ||
3441
            $answerType == ORAL_EXPRESSION ||
3442
            $answerType == CALCULATED_ANSWER ||
3443
            $answerType == ANNOTATION
3444
        ) {
3445
            $nbrAnswers = 1;
3446
        }
3447
3448
        $generatedFile = '';
3449
        if ($answerType == ORAL_EXPRESSION) {
3450
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3451
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3452
3453
            $objQuestionTmp->initFile(
3454
                api_get_session_id(),
3455
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3456
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3457
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3458
            );
3459
3460
            //probably this attempt came in an exercise all question by page
3461
            if ($feedback_type == 0) {
3462
                $objQuestionTmp->replaceWithRealExe($exeId);
3463
            }
3464
            $generatedFile = $objQuestionTmp->getFileUrl();
3465
        }
3466
3467
        $user_answer = '';
3468
        // Get answer list for matching
3469
        $sql = "SELECT id_auto, id, answer
3470
                FROM $table_ans
3471
                WHERE c_id = $course_id AND question_id = $questionId";
3472
        $res_answer = Database::query($sql);
3473
3474
        $answerMatching = [];
3475
        while ($real_answer = Database::fetch_array($res_answer)) {
3476
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3477
        }
3478
3479
        $real_answers = [];
3480
        $quiz_question_options = Question::readQuestionOption(
3481
            $questionId,
3482
            $course_id
3483
        );
3484
3485
        $organs_at_risk_hit = 0;
3486
        $questionScore = 0;
3487
        $answer_correct_array = [];
3488
        $orderedHotspots = [];
3489
3490
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3491
            $orderedHotspots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3492
                [
3493
                    'hotspotQuestionId' => $questionId,
3494
                    'cId' => $course_id,
3495
                    'hotspotExeId' => $exeId,
3496
                ],
3497
                ['hotspotAnswerId' => 'ASC']
3498
            );
3499
        }
3500
        if ($debug) {
3501
            error_log('Start answer loop ');
3502
        }
3503
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3504
            $answer = $objAnswerTmp->selectAnswer($answerId);
3505
            $answerComment = $objAnswerTmp->selectComment($answerId);
3506
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3507
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3508
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3509
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3510
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3511
3512
            if ($debug) {
3513
                error_log("answer auto id: $answerAutoId ");
3514
                error_log("answer correct: $answerCorrect ");
3515
            }
3516
3517
            // Delineation
3518
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3519
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3520
3521
            switch ($answerType) {
3522
                case UNIQUE_ANSWER:
3523
                case UNIQUE_ANSWER_IMAGE:
3524
                case UNIQUE_ANSWER_NO_OPTION:
3525
                case READING_COMPREHENSION:
3526
                    if ($from_database) {
3527
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3528
                                WHERE
3529
                                    exe_id = '".$exeId."' AND
3530
                                    question_id= '".$questionId."'";
3531
                        $result = Database::query($sql);
3532
                        $choice = Database::result($result, 0, 'answer');
3533
3534
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3535
                        if ($studentChoice) {
3536
                            $questionScore += $answerWeighting;
3537
                            $totalScore += $answerWeighting;
3538
                        }
3539
                    } else {
3540
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3541
                        if ($studentChoice) {
3542
                            $questionScore += $answerWeighting;
3543
                            $totalScore += $answerWeighting;
3544
                        }
3545
                    }
3546
                    break;
3547
                case MULTIPLE_ANSWER_TRUE_FALSE:
3548
                    if ($from_database) {
3549
                        $choice = [];
3550
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3551
                                WHERE
3552
                                    exe_id = $exeId AND
3553
                                    question_id = ".$questionId;
3554
3555
                        $result = Database::query($sql);
3556
                        while ($row = Database::fetch_array($result)) {
3557
                            $values = explode(':', $row['answer']);
3558
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3559
                            $option = isset($values[1]) ? $values[1] : '';
3560
                            $choice[$my_answer_id] = $option;
3561
                        }
3562
                    }
3563
3564
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3565
                    if (!empty($studentChoice)) {
3566
                        if ($studentChoice == $answerCorrect) {
3567
                            $questionScore += $true_score;
3568
                        } else {
3569
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3570
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3571
                            ) {
3572
                                $questionScore += $doubt_score;
3573
                            } else {
3574
                                $questionScore += $false_score;
3575
                            }
3576
                        }
3577
                    } else {
3578
                        // If no result then the user just hit don't know
3579
                        $studentChoice = 3;
3580
                        $questionScore += $doubt_score;
3581
                    }
3582
                    $totalScore = $questionScore;
3583
                    break;
3584
                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
3585
                    if ($from_database) {
3586
                        $choice = [];
3587
                        $choiceDegreeCertainty = [];
3588
                        $sql = "SELECT answer 
3589
                            FROM $TBL_TRACK_ATTEMPT
3590
                            WHERE 
3591
                            exe_id = $exeId AND question_id = $questionId";
3592
3593
                        $result = Database::query($sql);
3594
                        while ($row = Database::fetch_array($result)) {
3595
                            $ind = $row['answer'];
3596
                            $values = explode(':', $ind);
3597
                            $myAnswerId = $values[0];
3598
                            $option = $values[1];
3599
                            $percent = $values[2];
3600
                            $choice[$myAnswerId] = $option;
3601
                            $choiceDegreeCertainty[$myAnswerId] = $percent;
3602
                        }
3603
                    }
3604
3605
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3606
                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ?
3607
                        $choiceDegreeCertainty[$answerAutoId] : null;
3608
3609
                    // student score update
3610
                    if (!empty($studentChoice)) {
3611
                        if ($studentChoice == $answerCorrect) {
3612
                            // correct answer and student is Unsure or PrettySur
3613
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3614
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3615
                                $questionScore += $true_score;
3616
                            } else {
3617
                                // student ignore correct answer
3618
                                $questionScore += $doubt_score;
3619
                            }
3620
                        } else {
3621
                            // false answer and student is Unsure or PrettySur
3622
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3623
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3624
                                $questionScore += $false_score;
3625
                            } else {
3626
                                // student ignore correct answer
3627
                                $questionScore += $doubt_score;
3628
                            }
3629
                        }
3630
                    }
3631
                    $totalScore = $questionScore;
3632
                    break;
3633
                case MULTIPLE_ANSWER: //2
3634
                    if ($from_database) {
3635
                        $choice = [];
3636
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3637
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3638
                        $resultans = Database::query($sql);
3639
                        while ($row = Database::fetch_array($resultans)) {
3640
                            $choice[$row['answer']] = 1;
3641
                        }
3642
3643
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3644
                        $real_answers[$answerId] = (bool) $studentChoice;
3645
3646
                        if ($studentChoice) {
3647
                            $questionScore += $answerWeighting;
3648
                        }
3649
                    } else {
3650
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3651
                        $real_answers[$answerId] = (bool) $studentChoice;
3652
3653
                        if (isset($studentChoice)) {
3654
                            $questionScore += $answerWeighting;
3655
                        }
3656
                    }
3657
                    $totalScore += $answerWeighting;
3658
3659
                    if ($debug) {
3660
                        error_log("studentChoice: $studentChoice");
3661
                    }
3662
                    break;
3663
                case GLOBAL_MULTIPLE_ANSWER:
3664
                    if ($from_database) {
3665
                        $choice = [];
3666
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3667
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3668
                        $resultans = Database::query($sql);
3669
                        while ($row = Database::fetch_array($resultans)) {
3670
                            $choice[$row['answer']] = 1;
3671
                        }
3672
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3673
                        $real_answers[$answerId] = (bool) $studentChoice;
3674
                        if ($studentChoice) {
3675
                            $questionScore += $answerWeighting;
3676
                        }
3677
                    } else {
3678
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3679
                        if (isset($studentChoice)) {
3680
                            $questionScore += $answerWeighting;
3681
                        }
3682
                        $real_answers[$answerId] = (bool) $studentChoice;
3683
                    }
3684
                    $totalScore += $answerWeighting;
3685
                    if ($debug) {
3686
                        error_log("studentChoice: $studentChoice");
3687
                    }
3688
                    break;
3689
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3690
                    if ($from_database) {
3691
                        $choice = [];
3692
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3693
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3694
                        $resultans = Database::query($sql);
3695
                        while ($row = Database::fetch_array($resultans)) {
3696
                            $result = explode(':', $row['answer']);
3697
                            if (isset($result[0])) {
3698
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3699
                                $option = isset($result[1]) ? $result[1] : '';
3700
                                $choice[$my_answer_id] = $option;
3701
                            }
3702
                        }
3703
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3704
3705
                        if ($answerCorrect == $studentChoice) {
3706
                            //$answerCorrect = 1;
3707
                            $real_answers[$answerId] = true;
3708
                        } else {
3709
                            //$answerCorrect = 0;
3710
                            $real_answers[$answerId] = false;
3711
                        }
3712
                    } else {
3713
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3714
                        if ($answerCorrect == $studentChoice) {
3715
                            //$answerCorrect = 1;
3716
                            $real_answers[$answerId] = true;
3717
                        } else {
3718
                            //$answerCorrect = 0;
3719
                            $real_answers[$answerId] = false;
3720
                        }
3721
                    }
3722
                    break;
3723
                case MULTIPLE_ANSWER_COMBINATION:
3724
                    if ($from_database) {
3725
                        $choice = [];
3726
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3727
                                WHERE exe_id = $exeId AND question_id= $questionId";
3728
                        $resultans = Database::query($sql);
3729
                        while ($row = Database::fetch_array($resultans)) {
3730
                            $choice[$row['answer']] = 1;
3731
                        }
3732
3733
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3734
                        if ($answerCorrect == 1) {
3735
                            if ($studentChoice) {
3736
                                $real_answers[$answerId] = true;
3737
                            } else {
3738
                                $real_answers[$answerId] = false;
3739
                            }
3740
                        } else {
3741
                            if ($studentChoice) {
3742
                                $real_answers[$answerId] = false;
3743
                            } else {
3744
                                $real_answers[$answerId] = true;
3745
                            }
3746
                        }
3747
                    } else {
3748
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3749
                        if ($answerCorrect == 1) {
3750
                            if ($studentChoice) {
3751
                                $real_answers[$answerId] = true;
3752
                            } else {
3753
                                $real_answers[$answerId] = false;
3754
                            }
3755
                        } else {
3756
                            if ($studentChoice) {
3757
                                $real_answers[$answerId] = false;
3758
                            } else {
3759
                                $real_answers[$answerId] = true;
3760
                            }
3761
                        }
3762
                    }
3763
                    break;
3764
                case FILL_IN_BLANKS:
3765
                    $str = '';
3766
                    $answerFromDatabase = '';
3767
                    if ($from_database) {
3768
                        $sql = "SELECT answer
3769
                                FROM $TBL_TRACK_ATTEMPT
3770
                                WHERE
3771
                                    exe_id = $exeId AND
3772
                                    question_id= ".intval($questionId);
3773
                        $result = Database::query($sql);
3774
                        if ($debug) {
3775
                            error_log($sql);
3776
                        }
3777
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3778
                    }
3779
3780
                    // if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3781
                    if (false) {
3782
                        // the question is encoded like this
3783
                        // [A] B [C] D [E] F::10,10,10@1
3784
                        // number 1 before the "@" means that is a switchable fill in blank question
3785
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3786
                        // means that is a normal fill blank question
3787
                        // first we explode the "::"
3788
                        $pre_array = explode('::', $answer);
3789
3790
                        // is switchable fill blank or not
3791
                        $last = count($pre_array) - 1;
3792
                        $is_set_switchable = explode('@', $pre_array[$last]);
3793
                        $switchable_answer_set = false;
3794
                        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3795
                            $switchable_answer_set = true;
3796
                        }
3797
                        $answer = '';
3798
                        for ($k = 0; $k < $last; $k++) {
3799
                            $answer .= $pre_array[$k];
3800
                        }
3801
                        // splits weightings that are joined with a comma
3802
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3803
                        // we save the answer because it will be modified
3804
                        $temp = $answer;
3805
                        $answer = '';
3806
                        $j = 0;
3807
                        //initialise answer tags
3808
                        $user_tags = $correct_tags = $real_text = [];
3809
                        // the loop will stop at the end of the text
3810
                        while (1) {
3811
                            // quits the loop if there are no more blanks (detect '[')
3812
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3813
                                // adds the end of the text
3814
                                $answer = $temp;
3815
                                $real_text[] = $answer;
3816
                                break; //no more "blanks", quit the loop
3817
                            }
3818
                            // adds the piece of text that is before the blank
3819
                            //and ends with '[' into a general storage array
3820
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3821
                            $answer .= api_substr($temp, 0, $pos + 1);
3822
                            //take the string remaining (after the last "[" we found)
3823
                            $temp = api_substr($temp, $pos + 1);
3824
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3825
                            if (($pos = api_strpos($temp, ']')) === false) {
3826
                                // adds the end of the text
3827
                                $answer .= $temp;
3828
                                break;
3829
                            }
3830
                            if ($from_database) {
3831
                                $str = $answerFromDatabase;
3832
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3833
                                $str = str_replace('\r\n', '', $str);
3834
3835
                                $choice = $arr[1];
3836
                                if (isset($choice[$j])) {
3837
                                    $tmp = api_strrpos($choice[$j], ' / ');
3838
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3839
                                    $choice[$j] = trim($choice[$j]);
3840
                                    // Needed to let characters ' and " to work as part of an answer
3841
                                    $choice[$j] = stripslashes($choice[$j]);
3842
                                } else {
3843
                                    $choice[$j] = null;
3844
                                }
3845
                            } else {
3846
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3847
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3848
                            }
3849
3850
                            $user_tags[] = $choice[$j];
3851
                            // Put the contents of the [] answer tag into correct_tags[]
3852
                            $correct_tags[] = api_substr($temp, 0, $pos);
3853
                            $j++;
3854
                            $temp = api_substr($temp, $pos + 1);
3855
                        }
3856
                        $answer = '';
3857
                        $real_correct_tags = $correct_tags;
3858
                        $chosen_list = [];
3859
3860
                        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...
3861
                            if ($i == 0) {
3862
                                $answer .= $real_text[0];
3863
                            }
3864
                            if (!$switchable_answer_set) {
3865
                                // Needed to parse ' and " characters
3866
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3867
                                if ($correct_tags[$i] == $user_tags[$i]) {
3868
                                    // gives the related weighting to the student
3869
                                    $questionScore += $answerWeighting[$i];
3870
                                    // increments total score
3871
                                    $totalScore += $answerWeighting[$i];
3872
                                    // adds the word in green at the end of the string
3873
                                    $answer .= $correct_tags[$i];
3874
                                } elseif (!empty($user_tags[$i])) {
3875
                                    // else if the word entered by the student IS NOT the same as
3876
                                    // the one defined by the professor
3877
                                    // adds the word in red at the end of the string, and strikes it
3878
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3879
                                } else {
3880
                                    // adds a tabulation if no word has been typed by the student
3881
                                    $answer .= ''; // remove &nbsp; that causes issue
3882
                                }
3883
                            } else {
3884
                                // switchable fill in the blanks
3885
                                if (in_array($user_tags[$i], $correct_tags)) {
3886
                                    $chosen_list[] = $user_tags[$i];
3887
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3888
                                    // gives the related weighting to the student
3889
                                    $questionScore += $answerWeighting[$i];
3890
                                    // increments total score
3891
                                    $totalScore += $answerWeighting[$i];
3892
                                    // adds the word in green at the end of the string
3893
                                    $answer .= $user_tags[$i];
3894
                                } elseif (!empty($user_tags[$i])) {
3895
                                    // else if the word entered by the student IS NOT the same
3896
                                    // as the one defined by the professor
3897
                                    // adds the word in red at the end of the string, and strikes it
3898
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3899
                                } else {
3900
                                    // adds a tabulation if no word has been typed by the student
3901
                                    $answer .= ''; // remove &nbsp; that causes issue
3902
                                }
3903
                            }
3904
3905
                            // adds the correct word, followed by ] to close the blank
3906
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3907
                            if (isset($real_text[$i + 1])) {
3908
                                $answer .= $real_text[$i + 1];
3909
                            }
3910
                        }
3911
                    } else {
3912
                        // insert the student result in the track_e_attempt table, field answer
3913
                        // $answer is the answer like in the c_quiz_answer table for the question
3914
                        // student data are choice[]
3915
                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
3916
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3917
                        $answerWeighting = $listCorrectAnswers['weighting'];
3918
                        // user choices is an array $choice
3919
3920
                        // get existing user data in n the BDD
3921
                        if ($from_database) {
3922
                            $listStudentResults = FillBlanks::getAnswerInfo(
3923
                                $answerFromDatabase,
3924
                                true
3925
                            );
3926
                            $choice = $listStudentResults['student_answer'];
3927
                        }
3928
3929
                        // loop other all blanks words
3930
                        if (!$switchableAnswerSet) {
3931
                            // not switchable answer, must be in the same place than teacher order
3932
                            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...
3933
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3934
                                $correctAnswer = $listCorrectAnswers['words'][$i];
3935
3936
                                if ($debug) {
3937
                                    error_log("Student answer: $i");
3938
                                    error_log($studentAnswer);
3939
                                }
3940
3941
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3942
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3943
                                // ENT_QUOTES is used in order to transform ' to &#039;
3944
                                if (!$from_database) {
3945
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3946
                                    if ($debug) {
3947
                                        error_log("Student answer cleaned:");
3948
                                        error_log($studentAnswer);
3949
                                    }
3950
                                }
3951
3952
                                $isAnswerCorrect = 0;
3953
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
3954
                                    // gives the related weighting to the student
3955
                                    $questionScore += $answerWeighting[$i];
3956
                                    // increments total score
3957
                                    $totalScore += $answerWeighting[$i];
3958
                                    $isAnswerCorrect = 1;
3959
                                }
3960
                                if ($debug) {
3961
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
3962
                                }
3963
3964
                                $studentAnswerToShow = $studentAnswer;
3965
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3966
                                if ($debug) {
3967
                                    error_log("Fill in blank type: $type");
3968
                                }
3969
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3970
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3971
                                    if ($studentAnswer != '') {
3972
                                        foreach ($listMenu as $item) {
3973
                                            if (sha1($item) == $studentAnswer) {
3974
                                                $studentAnswerToShow = $item;
3975
                                            }
3976
                                        }
3977
                                    }
3978
                                }
3979
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3980
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
3981
                            }
3982
                        } else {
3983
                            // switchable answer
3984
                            $listStudentAnswerTemp = $choice;
3985
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
3986
3987
                            // for every teacher answer, check if there is a student answer
3988
                            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...
3989
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3990
                                $studentAnswerToShow = $studentAnswer;
3991
3992
                                if ($debug) {
3993
                                    error_log("Student answer: $i");
3994
                                    error_log($studentAnswer);
3995
                                }
3996
3997
                                $found = false;
3998
                                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...
3999
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
4000
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
4001
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
4002
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
4003
                                        if (!empty($studentAnswer)) {
4004
                                            foreach ($listMenu as $key => $item) {
4005
                                                if ($key == $correctAnswer) {
4006
                                                    $studentAnswerToShow = $item;
4007
                                                    break;
4008
                                                }
4009
                                            }
4010
                                        }
4011
                                    }
4012
4013
                                    if (!$found) {
4014
                                        if (FillBlanks::isStudentAnswerGood(
4015
                                            $studentAnswer,
4016
                                            $correctAnswer,
4017
                                            $from_database
4018
                                        )
4019
                                        ) {
4020
                                            $questionScore += $answerWeighting[$i];
4021
                                            $totalScore += $answerWeighting[$i];
4022
                                            $listTeacherAnswerTemp[$j] = '';
4023
                                            $found = true;
4024
                                        }
4025
                                    }
4026
                                }
4027
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4028
                                if (!$found) {
4029
                                    $listCorrectAnswers['student_score'][$i] = 0;
4030
                                } else {
4031
                                    $listCorrectAnswers['student_score'][$i] = 1;
4032
                                }
4033
                            }
4034
                        }
4035
                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
4036
                    }
4037
                    break;
4038
                case CALCULATED_ANSWER:
4039
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
4040
                    if (!empty($calculatedAnswerList)) {
4041
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
4042
                        $preArray = explode('@@', $answer);
4043
                        $last = count($preArray) - 1;
4044
                        $answer = '';
4045
                        for ($k = 0; $k < $last; $k++) {
4046
                            $answer .= $preArray[$k];
4047
                        }
4048
                        $answerWeighting = [$answerWeighting];
4049
                        // we save the answer because it will be modified
4050
                        $temp = $answer;
4051
                        $answer = '';
4052
                        $j = 0;
4053
                        //initialise answer tags
4054
                        $userTags = $correctTags = $realText = [];
4055
                        // the loop will stop at the end of the text
4056
                        while (1) {
4057
                            // quits the loop if there are no more blanks (detect '[')
4058
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4059
                                // adds the end of the text
4060
                                $answer = $temp;
4061
                                $realText[] = $answer;
4062
                                break; //no more "blanks", quit the loop
4063
                            }
4064
                            // adds the piece of text that is before the blank
4065
                            //and ends with '[' into a general storage array
4066
                            $realText[] = api_substr($temp, 0, $pos + 1);
4067
                            $answer .= api_substr($temp, 0, $pos + 1);
4068
                            //take the string remaining (after the last "[" we found)
4069
                            $temp = api_substr($temp, $pos + 1);
4070
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4071
                            if (($pos = api_strpos($temp, ']')) === false) {
4072
                                // adds the end of the text
4073
                                $answer .= $temp;
4074
                                break;
4075
                            }
4076
4077
                            if ($from_database) {
4078
                                $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
4079
                                    WHERE
4080
                                        exe_id = '".$exeId."' AND
4081
                                        question_id = ".intval($questionId);
4082
                                $result = Database::query($sql);
4083
                                $str = Database::result($result, 0, 'answer');
4084
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4085
                                $str = str_replace('\r\n', '', $str);
4086
                                $choice = $arr[1];
4087
                                if (isset($choice[$j])) {
4088
                                    $tmp = api_strrpos($choice[$j], ' / ');
4089
4090
                                    if ($tmp) {
4091
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4092
                                    } else {
4093
                                        $tmp = ltrim($tmp, '[');
4094
                                        $tmp = rtrim($tmp, ']');
4095
                                    }
4096
4097
                                    $choice[$j] = trim($choice[$j]);
4098
                                    // Needed to let characters ' and " to work as part of an answer
4099
                                    $choice[$j] = stripslashes($choice[$j]);
4100
                                } else {
4101
                                    $choice[$j] = null;
4102
                                }
4103
                            } else {
4104
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
4105
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4106
                            }
4107
                            $userTags[] = $choice[$j];
4108
                            //put the contents of the [] answer tag into correct_tags[]
4109
                            $correctTags[] = api_substr($temp, 0, $pos);
4110
                            $j++;
4111
                            $temp = api_substr($temp, $pos + 1);
4112
                        }
4113
                        $answer = '';
4114
                        $realCorrectTags = $correctTags;
4115
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4116
                        $expectedAnswer = '';
4117
                        $calculatedChoice = '';
4118
4119
                        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...
4120
                            if ($i == 0) {
4121
                                $answer .= $realText[0];
4122
                            }
4123
                            // Needed to parse ' and " characters
4124
                            $userTags[$i] = stripslashes($userTags[$i]);
4125
                            if ($correctTags[$i] == $userTags[$i]) {
4126
                                // gives the related weighting to the student
4127
                                $questionScore += $answerWeighting[$i];
4128
                                // increments total score
4129
                                $totalScore += $answerWeighting[$i];
4130
                                // adds the word in green at the end of the string
4131
                                $answer .= $correctTags[$i];
4132
                                $calculatedChoice = $correctTags[$i];
4133
                            } elseif (!empty($userTags[$i])) {
4134
                                // else if the word entered by the student IS NOT the same as
4135
                                // the one defined by the professor
4136
                                // adds the word in red at the end of the string, and strikes it
4137
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4138
                                $calculatedChoice = $userTags[$i];
4139
                            } else {
4140
                                // adds a tabulation if no word has been typed by the student
4141
                                $answer .= ''; // remove &nbsp; that causes issue
4142
                            }
4143
                            // adds the correct word, followed by ] to close the blank
4144
4145
                            if ($this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM) {
4146
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4147
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4148
                                $expectedAnswer = $realCorrectTags[$i];
4149
                            }
4150
                            $answer .= ']';
4151
4152
                            if (isset($realText[$i + 1])) {
4153
                                $answer .= $realText[$i + 1];
4154
                            }
4155
                        }
4156
                    } else {
4157
                        if ($from_database) {
4158
                            $sql = "SELECT *
4159
                                FROM $TBL_TRACK_ATTEMPT
4160
                                WHERE
4161
                                    exe_id = $exeId AND
4162
                                    question_id= ".intval($questionId);
4163
                            $result = Database::query($sql);
4164
                            $resultData = Database::fetch_array($result, 'ASSOC');
4165
                            $answer = $resultData['answer'];
4166
                            $questionScore = $resultData['marks'];
4167
                        }
4168
                    }
4169
                    break;
4170
                case FREE_ANSWER:
4171
                    if ($from_database) {
4172
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4173
                                 WHERE 
4174
                                    exe_id = $exeId AND 
4175
                                    question_id= ".$questionId;
4176
                        $result = Database::query($sql);
4177
                        $data = Database::fetch_array($result);
4178
4179
                        $choice = $data['answer'];
4180
                        $choice = str_replace('\r\n', '', $choice);
4181
                        $choice = stripslashes($choice);
4182
                        $questionScore = $data['marks'];
4183
4184
                        if ($questionScore == -1) {
4185
                            $totalScore += 0;
4186
                        } else {
4187
                            $totalScore += $questionScore;
4188
                        }
4189
                        if ($questionScore == '') {
4190
                            $questionScore = 0;
4191
                        }
4192
                        $arrques = $questionName;
4193
                        $arrans = $choice;
4194
                    } else {
4195
                        $studentChoice = $choice;
4196
                        if ($studentChoice) {
4197
                            //Fixing negative puntation see #2193
4198
                            $questionScore = 0;
4199
                            $totalScore += 0;
4200
                        }
4201
                    }
4202
                    break;
4203
                case ORAL_EXPRESSION:
4204
                    if ($from_database) {
4205
                        $query = "SELECT answer, marks 
4206
                                  FROM $TBL_TRACK_ATTEMPT
4207
                                  WHERE 
4208
                                        exe_id = $exeId AND 
4209
                                        question_id = $questionId
4210
                                 ";
4211
                        $resq = Database::query($query);
4212
                        $row = Database::fetch_assoc($resq);
4213
                        $choice = $row['answer'];
4214
                        $choice = str_replace('\r\n', '', $choice);
4215
                        $choice = stripslashes($choice);
4216
                        $questionScore = $row['marks'];
4217
                        if ($questionScore == -1) {
4218
                            $totalScore += 0;
4219
                        } else {
4220
                            $totalScore += $questionScore;
4221
                        }
4222
                        $arrques = $questionName;
4223
                        $arrans = $choice;
4224
                    } else {
4225
                        $studentChoice = $choice;
4226
                        if ($studentChoice) {
4227
                            //Fixing negative puntation see #2193
4228
                            $questionScore = 0;
4229
                            $totalScore += 0;
4230
                        }
4231
                    }
4232
                    break;
4233
                case DRAGGABLE:
4234
                case MATCHING_DRAGGABLE:
4235
                case MATCHING:
4236
                    if ($from_database) {
4237
                        $sql = "SELECT id, answer, id_auto
4238
                                FROM $table_ans
4239
                                WHERE
4240
                                    c_id = $course_id AND
4241
                                    question_id = $questionId AND
4242
                                    correct = 0
4243
                                ";
4244
                        $result = Database::query($sql);
4245
                        // Getting the real answer
4246
                        $real_list = [];
4247
                        while ($realAnswer = Database::fetch_array($result)) {
4248
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4249
                        }
4250
4251
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4252
                                FROM $table_ans
4253
                                WHERE
4254
                                    c_id = $course_id AND
4255
                                    question_id = $questionId AND
4256
                                    correct <> 0
4257
                                ORDER BY id_auto";
4258
                        $result = Database::query($sql);
4259
                        $options = [];
4260
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4261
                            $options[] = $row;
4262
                        }
4263
4264
                        $questionScore = 0;
4265
                        $counterAnswer = 1;
4266
                        foreach ($options as $a_answers) {
4267
                            $i_answer_id = $a_answers['id']; //3
4268
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4269
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4270
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4271
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4272
                                    WHERE
4273
                                        exe_id = '$exeId' AND
4274
                                        question_id = '$questionId' AND
4275
                                        position = '$i_answer_id_auto'";
4276
                            $result = Database::query($sql);
4277
                            $s_user_answer = 0;
4278
                            if (Database::num_rows($result) > 0) {
4279
                                //  rich - good looking
4280
                                $s_user_answer = Database::result($result, 0, 0);
4281
                            }
4282
                            $i_answerWeighting = $a_answers['ponderation'];
4283
                            $user_answer = '';
4284
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4285
                            if (!empty($s_user_answer)) {
4286
                                if ($answerType == DRAGGABLE) {
4287
                                    if ($s_user_answer == $i_answer_correct_answer) {
4288
                                        $questionScore += $i_answerWeighting;
4289
                                        $totalScore += $i_answerWeighting;
4290
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4291
                                        if ($this->showExpectedChoice()) {
4292
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4293
                                        }
4294
                                        $status = Display::label(get_lang('Correct'), 'success');
4295
                                    } else {
4296
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4297
                                        if ($this->showExpectedChoice()) {
4298
                                            $data = $options[$real_list[$s_user_answer] - 1];
4299
                                            $user_answer = $data['answer'];
4300
                                        }
4301
                                    }
4302
                                } else {
4303
                                    if ($s_user_answer == $i_answer_correct_answer) {
4304
                                        $questionScore += $i_answerWeighting;
4305
                                        $totalScore += $i_answerWeighting;
4306
                                        $status = Display::label(get_lang('Correct'), 'success');
4307
4308
                                        // Try with id
4309
                                        if (isset($real_list[$i_answer_id])) {
4310
                                            $user_answer = Display::span($real_list[$i_answer_id]);
4311
                                        }
4312
4313
                                        // Try with $i_answer_id_auto
4314
                                        if (empty($user_answer)) {
4315
                                            if (isset($real_list[$i_answer_id_auto])) {
4316
                                                $user_answer = Display::span($real_list[$i_answer_id_auto]);
4317
                                            }
4318
                                        }
4319
                                        if ($this->showExpectedChoice()) {
4320
                                            if (isset($real_list[$i_answer_correct_answer])) {
4321
                                                $user_answer = Display::span($real_list[$i_answer_correct_answer]);
4322
                                            }
4323
                                        }
4324
                                    } else {
4325
                                        $user_answer = Display::span(
4326
                                            $real_list[$s_user_answer],
4327
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4328
                                        );
4329
                                        if ($this->showExpectedChoice()) {
4330
                                            if (isset($real_list[$s_user_answer])) {
4331
                                                $user_answer = Display::span($real_list[$s_user_answer]);
4332
                                            }
4333
                                        }
4334
                                    }
4335
                                }
4336
                            } elseif ($answerType == DRAGGABLE) {
4337
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4338
                                if ($this->showExpectedChoice()) {
4339
                                    $user_answer = '';
4340
                                }
4341
                            } else {
4342
                                $user_answer = Display::span(
4343
                                    get_lang('Incorrect').' &nbsp;',
4344
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4345
                                );
4346
                                if ($this->showExpectedChoice()) {
4347
                                    $user_answer = '';
4348
                                }
4349
                            }
4350
4351
                            if ($show_result) {
4352
                                if ($this->showExpectedChoice() == false &&
4353
                                    $showTotalScoreAndUserChoicesInLastAttempt === false
4354
                                ) {
4355
                                    $user_answer = '';
4356
                                }
4357
                                switch ($answerType) {
4358
                                    case MATCHING:
4359
                                    case MATCHING_DRAGGABLE:
4360
                                        echo '<tr>';
4361
                                        if ($this->showExpectedChoice()) {
4362
                                            echo '<td>'.$s_answer_label.'</td>';
4363
                                            echo '<td>'.$user_answer.'</td>';
4364
                                            echo '<td>';
4365
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4366
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4367
                                                    $showTotalScoreAndUserChoicesInLastAttempt == true
4368
                                                ) {
4369
                                                    echo Display::span(
4370
                                                        $real_list[$i_answer_correct_answer]
4371
                                                    );
4372
                                                }
4373
                                            }
4374
                                            echo '</td>';
4375
                                            echo '<td>'.$status.'</td>';
4376
                                        } else {
4377
                                            echo '<td>'.$s_answer_label.'</td>';
4378
                                            //echo '<td>'.$user_answer.'</td>';
4379
                                            echo '<td>';
4380
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4381
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4382
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4383
                                                ) {
4384
                                                    echo Display::span(
4385
                                                        $real_list[$i_answer_correct_answer],
4386
                                                        ['style' => 'color: #008000; font-weight: bold;']
4387
                                                    );
4388
                                                }
4389
                                            }
4390
                                            echo '</td>';
4391
                                        }
4392
                                        echo '</tr>';
4393
                                        break;
4394
                                    case DRAGGABLE:
4395
                                        if ($showTotalScoreAndUserChoicesInLastAttempt == false) {
4396
                                            $s_answer_label = '';
4397
                                        }
4398
                                        echo '<tr>';
4399
                                        if ($this->showExpectedChoice()) {
4400
                                            echo '<td>'.$user_answer.'</td>';
4401
                                            echo '<td>'.$s_answer_label.'</td>';
4402
                                            echo '<td>'.$status.'</td>';
4403
                                        } else {
4404
                                            echo '<td>'.$s_answer_label.'</td>';
4405
                                            echo '<td>'.$user_answer.'</td>';
4406
                                            echo '<td>';
4407
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4408
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4409
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4410
                                                ) {
4411
                                                    echo Display::span(
4412
                                                        $real_list[$i_answer_correct_answer],
4413
                                                        ['style' => 'color: #008000; font-weight: bold;']
4414
                                                    );
4415
                                                }
4416
                                            }
4417
                                            echo '</td>';
4418
                                        }
4419
                                        echo '</tr>';
4420
                                        break;
4421
                                }
4422
                            }
4423
                            $counterAnswer++;
4424
                        }
4425
                        break 2; // break the switch and the "for" condition
4426
                    } else {
4427
                        if ($answerCorrect) {
4428
                            if (isset($choice[$answerAutoId]) &&
4429
                                $answerCorrect == $choice[$answerAutoId]
4430
                            ) {
4431
                                $questionScore += $answerWeighting;
4432
                                $totalScore += $answerWeighting;
4433
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4434
                            } else {
4435
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4436
                                    $user_answer = Display::span(
4437
                                        $answerMatching[$choice[$answerAutoId]],
4438
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4439
                                    );
4440
                                }
4441
                            }
4442
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4443
                        }
4444
                    }
4445
                    break;
4446
                case HOT_SPOT:
4447
                    if ($from_database) {
4448
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4449
                        // Check auto id
4450
                        $sql = "SELECT hotspot_correct
4451
                                FROM $TBL_TRACK_HOTSPOT
4452
                                WHERE
4453
                                    hotspot_exe_id = $exeId AND
4454
                                    hotspot_question_id= $questionId AND
4455
                                    hotspot_answer_id = ".intval($answerAutoId)."
4456
                                ORDER BY hotspot_id ASC";
4457
                        $result = Database::query($sql);
4458
                        if (Database::num_rows($result)) {
4459
                            $studentChoice = Database::result(
4460
                                $result,
4461
                                0,
4462
                                'hotspot_correct'
4463
                            );
4464
4465
                            if ($studentChoice) {
4466
                                $questionScore += $answerWeighting;
4467
                                $totalScore += $answerWeighting;
4468
                            }
4469
                        } else {
4470
                            // If answer.id is different:
4471
                            $sql = "SELECT hotspot_correct
4472
                                FROM $TBL_TRACK_HOTSPOT
4473
                                WHERE
4474
                                    hotspot_exe_id = $exeId AND
4475
                                    hotspot_question_id= $questionId AND
4476
                                    hotspot_answer_id = ".intval($answerId)."
4477
                                ORDER BY hotspot_id ASC";
4478
                            $result = Database::query($sql);
4479
4480
                            if (Database::num_rows($result)) {
4481
                                $studentChoice = Database::result(
4482
                                    $result,
4483
                                    0,
4484
                                    'hotspot_correct'
4485
                                );
4486
4487
                                if ($studentChoice) {
4488
                                    $questionScore += $answerWeighting;
4489
                                    $totalScore += $answerWeighting;
4490
                                }
4491
                            } else {
4492
                                // check answer.iid
4493
                                if (!empty($answerIid)) {
4494
                                    $sql = "SELECT hotspot_correct
4495
                                            FROM $TBL_TRACK_HOTSPOT
4496
                                            WHERE
4497
                                                hotspot_exe_id = $exeId AND
4498
                                                hotspot_question_id= $questionId AND
4499
                                                hotspot_answer_id = ".intval($answerIid)."
4500
                                            ORDER BY hotspot_id ASC";
4501
                                    $result = Database::query($sql);
4502
4503
                                    $studentChoice = Database::result(
4504
                                        $result,
4505
                                        0,
4506
                                        'hotspot_correct'
4507
                                    );
4508
4509
                                    if ($studentChoice) {
4510
                                        $questionScore += $answerWeighting;
4511
                                        $totalScore += $answerWeighting;
4512
                                    }
4513
                                }
4514
                            }
4515
                        }
4516
                    } else {
4517
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4518
                            $choice[$answerAutoId] = 0;
4519
                            $choice[$answerIid] = 0;
4520
                        } else {
4521
                            $studentChoice = $choice[$answerAutoId];
4522
                            if (empty($studentChoice)) {
4523
                                $studentChoice = $choice[$answerIid];
4524
                            }
4525
                            $choiceIsValid = false;
4526
                            if (!empty($studentChoice)) {
4527
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4528
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4529
                                $choicePoint = Geometry::decodePoint($studentChoice);
4530
4531
                                switch ($hotspotType) {
4532
                                    case 'square':
4533
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4534
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4535
                                        break;
4536
                                    case 'circle':
4537
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4538
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4539
                                        break;
4540
                                    case 'poly':
4541
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4542
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4543
                                        break;
4544
                                }
4545
                            }
4546
4547
                            $choice[$answerAutoId] = 0;
4548
                            if ($choiceIsValid) {
4549
                                $questionScore += $answerWeighting;
4550
                                $totalScore += $answerWeighting;
4551
                                $choice[$answerAutoId] = 1;
4552
                                $choice[$answerIid] = 1;
4553
                            }
4554
                        }
4555
                    }
4556
                    break;
4557
                case HOT_SPOT_ORDER:
4558
                    // @todo never added to chamilo
4559
                    // for hotspot with fixed order
4560
                    $studentChoice = $choice['order'][$answerId];
4561
                    if ($studentChoice == $answerId) {
4562
                        $questionScore += $answerWeighting;
4563
                        $totalScore += $answerWeighting;
4564
                        $studentChoice = true;
4565
                    } else {
4566
                        $studentChoice = false;
4567
                    }
4568
                    break;
4569
                case HOT_SPOT_DELINEATION:
4570
                    // for hotspot with delineation
4571
                    if ($from_database) {
4572
                        // getting the user answer
4573
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4574
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4575
                                    FROM $TBL_TRACK_HOTSPOT
4576
                                    WHERE
4577
                                        hotspot_exe_id = '".$exeId."' AND
4578
                                        hotspot_question_id= '".$questionId."' AND
4579
                                        hotspot_answer_id='1'";
4580
                        //by default we take 1 because it's a delineation
4581
                        $resq = Database::query($query);
4582
                        $row = Database::fetch_array($resq, 'ASSOC');
4583
4584
                        $choice = $row['hotspot_correct'];
4585
                        $user_answer = $row['hotspot_coordinate'];
4586
4587
                        // THIS is very important otherwise the poly_compile will throw an error!!
4588
                        // round-up the coordinates
4589
                        $coords = explode('/', $user_answer);
4590
                        $user_array = '';
4591
                        foreach ($coords as $coord) {
4592
                            list($x, $y) = explode(';', $coord);
4593
                            $user_array .= round($x).';'.round($y).'/';
4594
                        }
4595
                        $user_array = substr($user_array, 0, -1);
4596
                    } else {
4597
                        if (!empty($studentChoice)) {
4598
                            $newquestionList[] = $questionId;
4599
                        }
4600
4601
                        if ($answerId === 1) {
4602
                            $studentChoice = $choice[$answerId];
4603
                            $questionScore += $answerWeighting;
4604
4605
                            if ($hotspot_delineation_result[1] == 1) {
4606
                                $totalScore += $answerWeighting; //adding the total
4607
                            }
4608
                        }
4609
                    }
4610
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4611
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4612
                    break;
4613
                case ANNOTATION:
4614
                    if ($from_database) {
4615
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4616
                                WHERE 
4617
                                  exe_id = $exeId AND 
4618
                                  question_id= ".$questionId;
4619
                        $resq = Database::query($sql);
4620
                        $data = Database::fetch_array($resq);
4621
4622
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4623
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4624
4625
                        $arrques = $questionName;
4626
                        break;
4627
                    }
4628
                    $studentChoice = $choice;
4629
                    if ($studentChoice) {
4630
                        $questionScore = 0;
4631
                        $totalScore += 0;
4632
                    }
4633
                    break;
4634
            } // end switch Answertype
4635
4636
            if ($show_result) {
4637
                if ($debug) {
4638
                    error_log('Showing questions $from '.$from);
4639
                }
4640
                if ($from === 'exercise_result') {
4641
                    //display answers (if not matching type, or if the answer is correct)
4642
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4643
                        $answerCorrect
4644
                    ) {
4645
                        if (in_array(
4646
                            $answerType,
4647
                            [
4648
                                UNIQUE_ANSWER,
4649
                                UNIQUE_ANSWER_IMAGE,
4650
                                UNIQUE_ANSWER_NO_OPTION,
4651
                                MULTIPLE_ANSWER,
4652
                                MULTIPLE_ANSWER_COMBINATION,
4653
                                GLOBAL_MULTIPLE_ANSWER,
4654
                                READING_COMPREHENSION,
4655
                            ]
4656
                        )) {
4657
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4658
                                $this,
4659
                                $feedback_type,
4660
                                $answerType,
4661
                                $studentChoice,
4662
                                $answer,
4663
                                $answerComment,
4664
                                $answerCorrect,
4665
                                0,
4666
                                0,
4667
                                0,
4668
                                $results_disabled,
4669
                                $showTotalScoreAndUserChoicesInLastAttempt,
4670
                                $this->export
4671
                            );
4672
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4673
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4674
                                $this,
4675
                                $feedback_type,
4676
                                $answerType,
4677
                                $studentChoice,
4678
                                $answer,
4679
                                $answerComment,
4680
                                $answerCorrect,
4681
                                0,
4682
                                $questionId,
4683
                                0,
4684
                                $results_disabled,
4685
                                $showTotalScoreAndUserChoicesInLastAttempt
4686
                            );
4687
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4688
                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
4689
                                $feedback_type,
4690
                                $studentChoice,
4691
                                $studentChoiceDegree,
4692
                                $answer,
4693
                                $answerComment,
4694
                                $answerCorrect,
4695
                                $questionId,
4696
                                $results_disabled
4697
                            );
4698
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4699
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4700
                                $this,
4701
                                $feedback_type,
4702
                                $answerType,
4703
                                $studentChoice,
4704
                                $answer,
4705
                                $answerComment,
4706
                                $answerCorrect,
4707
                                0,
4708
                                0,
4709
                                0,
4710
                                $results_disabled,
4711
                                $showTotalScoreAndUserChoicesInLastAttempt
4712
                            );
4713
                        } elseif ($answerType == FILL_IN_BLANKS) {
4714
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4715
                                $feedback_type,
4716
                                $answer,
4717
                                0,
4718
                                0,
4719
                                $results_disabled,
4720
                                '',
4721
                                $showTotalScoreAndUserChoicesInLastAttempt
4722
                            );
4723
                        } elseif ($answerType == CALCULATED_ANSWER) {
4724
                            ExerciseShowFunctions::display_calculated_answer(
4725
                                $this,
4726
                                $feedback_type,
4727
                                $answer,
4728
                                0,
4729
                                0,
4730
                                $results_disabled,
4731
                                $showTotalScoreAndUserChoicesInLastAttempt,
4732
                                $expectedAnswer,
4733
                                $calculatedChoice,
4734
                                $calculatedStatus
4735
                            );
4736
                        } elseif ($answerType == FREE_ANSWER) {
4737
                            ExerciseShowFunctions::display_free_answer(
4738
                                $feedback_type,
4739
                                $choice,
4740
                                $exeId,
4741
                                $questionId,
4742
                                $questionScore,
4743
                                $results_disabled
4744
                            );
4745
                        } elseif ($answerType == ORAL_EXPRESSION) {
4746
                            // to store the details of open questions in an array to be used in mail
4747
                            /** @var OralExpression $objQuestionTmp */
4748
                            ExerciseShowFunctions::display_oral_expression_answer(
4749
                                $feedback_type,
4750
                                $choice,
4751
                                0,
4752
                                0,
4753
                                $objQuestionTmp->getFileUrl(true),
4754
                                $results_disabled,
4755
                                $questionScore
4756
                            );
4757
                        } elseif ($answerType == HOT_SPOT) {
4758
                            $correctAnswerId = 0;
4759
                            /**
4760
                             * @var int
4761
                             * @var TrackEHotspot $hotspot
4762
                             */
4763
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4764
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4765
                                    break;
4766
                                }
4767
                            }
4768
4769
                            // force to show whether the choice is correct or not
4770
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4771
                            ExerciseShowFunctions::display_hotspot_answer(
4772
                                $feedback_type,
4773
                                ++$correctAnswerId,
4774
                                $answer,
4775
                                $studentChoice,
4776
                                $answerComment,
4777
                                $results_disabled,
4778
                                $correctAnswerId,
4779
                                $showTotalScoreAndUserChoicesInLastAttempt
4780
                            );
4781
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4782
                            ExerciseShowFunctions::display_hotspot_order_answer(
4783
                                $feedback_type,
4784
                                $answerId,
4785
                                $answer,
4786
                                $studentChoice,
4787
                                $answerComment
4788
                            );
4789
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4790
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4791
4792
                            //round-up the coordinates
4793
                            $coords = explode('/', $user_answer);
4794
                            $user_array = '';
4795
                            foreach ($coords as $coord) {
4796
                                list($x, $y) = explode(';', $coord);
4797
                                $user_array .= round($x).';'.round($y).'/';
4798
                            }
4799
                            $user_array = substr($user_array, 0, -1);
4800
4801
                            if ($next) {
4802
                                $user_answer = $user_array;
4803
                                // we compare only the delineation not the other points
4804
                                $answer_question = $_SESSION['hotspot_coord'][1];
4805
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4806
4807
                                //calculating the area
4808
                                $poly_user = convert_coordinates($user_answer, '/');
4809
                                $poly_answer = convert_coordinates($answer_question, '|');
4810
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4811
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4812
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4813
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4814
4815
                                $overlap = $poly_results['both'];
4816
                                $poly_answer_area = $poly_results['s1'];
4817
                                $poly_user_area = $poly_results['s2'];
4818
                                $missing = $poly_results['s1Only'];
4819
                                $excess = $poly_results['s2Only'];
4820
4821
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4822
                                // //this is an area in pixels
4823
                                if ($debug > 0) {
4824
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4825
                                }
4826
4827
                                if ($overlap < 1) {
4828
                                    //shortcut to avoid complicated calculations
4829
                                    $final_overlap = 0;
4830
                                    $final_missing = 100;
4831
                                    $final_excess = 100;
4832
                                } else {
4833
                                    // the final overlap is the percentage of the initial polygon
4834
                                    // that is overlapped by the user's polygon
4835
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4836
                                    if ($debug > 1) {
4837
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4838
                                    }
4839
                                    // the final missing area is the percentage of the initial polygon
4840
                                    // that is not overlapped by the user's polygon
4841
                                    $final_missing = 100 - $final_overlap;
4842
                                    if ($debug > 1) {
4843
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4844
                                    }
4845
                                    // the final excess area is the percentage of the initial polygon's size
4846
                                    // that is covered by the user's polygon outside of the initial polygon
4847
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4848
                                    if ($debug > 1) {
4849
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4850
                                    }
4851
                                }
4852
4853
                                //checking the destination parameters parsing the "@@"
4854
                                $destination_items = explode(
4855
                                    '@@',
4856
                                    $answerDestination
4857
                                );
4858
                                $threadhold_total = $destination_items[0];
4859
                                $threadhold_items = explode(
4860
                                    ';',
4861
                                    $threadhold_total
4862
                                );
4863
                                $threadhold1 = $threadhold_items[0]; // overlap
4864
                                $threadhold2 = $threadhold_items[1]; // excess
4865
                                $threadhold3 = $threadhold_items[2]; //missing
4866
4867
                                // if is delineation
4868
                                if ($answerId === 1) {
4869
                                    //setting colors
4870
                                    if ($final_overlap >= $threadhold1) {
4871
                                        $overlap_color = true; //echo 'a';
4872
                                    }
4873
                                    //echo $excess.'-'.$threadhold2;
4874
                                    if ($final_excess <= $threadhold2) {
4875
                                        $excess_color = true; //echo 'b';
4876
                                    }
4877
                                    //echo '--------'.$missing.'-'.$threadhold3;
4878
                                    if ($final_missing <= $threadhold3) {
4879
                                        $missing_color = true; //echo 'c';
4880
                                    }
4881
4882
                                    // if pass
4883
                                    if ($final_overlap >= $threadhold1 &&
4884
                                        $final_missing <= $threadhold3 &&
4885
                                        $final_excess <= $threadhold2
4886
                                    ) {
4887
                                        $next = 1; //go to the oars
4888
                                        $result_comment = get_lang('Acceptable');
4889
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4890
                                    } else {
4891
                                        $next = 0;
4892
                                        $result_comment = get_lang('Unacceptable');
4893
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4894
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4895
                                        // checking the destination parameters parsing the "@@"
4896
                                        $destination_items = explode('@@', $answerDestination);
4897
                                    }
4898
                                } elseif ($answerId > 1) {
4899
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4900
                                        if ($debug > 0) {
4901
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4902
                                        }
4903
                                        //type no error shouldn't be treated
4904
                                        $next = 1;
4905
                                        continue;
4906
                                    }
4907
                                    if ($debug > 0) {
4908
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4909
                                    }
4910
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4911
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4912
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4913
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4914
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4915
4916
                                    if ($overlap == false) {
4917
                                        //all good, no overlap
4918
                                        $next = 1;
4919
                                        continue;
4920
                                    } else {
4921
                                        if ($debug > 0) {
4922
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4923
                                        }
4924
                                        $organs_at_risk_hit++;
4925
                                        //show the feedback
4926
                                        $next = 0;
4927
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4928
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4929
4930
                                        $destination_items = explode('@@', $answerDestination);
4931
                                        $try_hotspot = $destination_items[1];
4932
                                        $lp_hotspot = $destination_items[2];
4933
                                        $select_question_hotspot = $destination_items[3];
4934
                                        $url_hotspot = $destination_items[4];
4935
                                    }
4936
                                }
4937
                            } else {
4938
                                // the first delineation feedback
4939
                                if ($debug > 0) {
4940
                                    error_log(__LINE__.' first', 0);
4941
                                }
4942
                            }
4943
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4944
                            echo '<tr>';
4945
                            echo Display::tag('td', $answerMatching[$answerId]);
4946
                            echo Display::tag(
4947
                                'td',
4948
                                "$user_answer / ".Display::tag(
4949
                                    'strong',
4950
                                    $answerMatching[$answerCorrect],
4951
                                    ['style' => 'color: #008000; font-weight: bold;']
4952
                                )
4953
                            );
4954
                            echo '</tr>';
4955
                        } elseif ($answerType == ANNOTATION) {
4956
                            ExerciseShowFunctions::displayAnnotationAnswer(
4957
                                $feedback_type,
4958
                                $exeId,
4959
                                $questionId,
4960
                                $questionScore,
4961
                                $results_disabled
4962
                            );
4963
                        }
4964
                    }
4965
                } else {
4966
                    if ($debug) {
4967
                        error_log('Showing questions $from '.$from);
4968
                    }
4969
                    switch ($answerType) {
4970
                        case UNIQUE_ANSWER:
4971
                        case UNIQUE_ANSWER_IMAGE:
4972
                        case UNIQUE_ANSWER_NO_OPTION:
4973
                        case MULTIPLE_ANSWER:
4974
                        case GLOBAL_MULTIPLE_ANSWER:
4975
                        case MULTIPLE_ANSWER_COMBINATION:
4976
                        case READING_COMPREHENSION:
4977
                            if ($answerId == 1) {
4978
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4979
                                    $this,
4980
                                    $feedback_type,
4981
                                    $answerType,
4982
                                    $studentChoice,
4983
                                    $answer,
4984
                                    $answerComment,
4985
                                    $answerCorrect,
4986
                                    $exeId,
4987
                                    $questionId,
4988
                                    $answerId,
4989
                                    $results_disabled,
4990
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4991
                                    $this->export
4992
                                );
4993
                            } else {
4994
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4995
                                    $this,
4996
                                    $feedback_type,
4997
                                    $answerType,
4998
                                    $studentChoice,
4999
                                    $answer,
5000
                                    $answerComment,
5001
                                    $answerCorrect,
5002
                                    $exeId,
5003
                                    $questionId,
5004
                                    '',
5005
                                    $results_disabled,
5006
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5007
                                    $this->export
5008
                                );
5009
                            }
5010
                            break;
5011
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
5012
                            if ($answerId == 1) {
5013
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5014
                                    $this,
5015
                                    $feedback_type,
5016
                                    $answerType,
5017
                                    $studentChoice,
5018
                                    $answer,
5019
                                    $answerComment,
5020
                                    $answerCorrect,
5021
                                    $exeId,
5022
                                    $questionId,
5023
                                    $answerId,
5024
                                    $results_disabled,
5025
                                    $showTotalScoreAndUserChoicesInLastAttempt
5026
                                );
5027
                            } else {
5028
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5029
                                    $this,
5030
                                    $feedback_type,
5031
                                    $answerType,
5032
                                    $studentChoice,
5033
                                    $answer,
5034
                                    $answerComment,
5035
                                    $answerCorrect,
5036
                                    $exeId,
5037
                                    $questionId,
5038
                                    '',
5039
                                    $results_disabled,
5040
                                    $showTotalScoreAndUserChoicesInLastAttempt
5041
                                );
5042
                            }
5043
                            break;
5044
                        case MULTIPLE_ANSWER_TRUE_FALSE:
5045
                            if ($answerId == 1) {
5046
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5047
                                    $this,
5048
                                    $feedback_type,
5049
                                    $answerType,
5050
                                    $studentChoice,
5051
                                    $answer,
5052
                                    $answerComment,
5053
                                    $answerCorrect,
5054
                                    $exeId,
5055
                                    $questionId,
5056
                                    $answerId,
5057
                                    $results_disabled,
5058
                                    $showTotalScoreAndUserChoicesInLastAttempt
5059
                                );
5060
                            } else {
5061
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5062
                                    $this,
5063
                                    $feedback_type,
5064
                                    $answerType,
5065
                                    $studentChoice,
5066
                                    $answer,
5067
                                    $answerComment,
5068
                                    $answerCorrect,
5069
                                    $exeId,
5070
                                    $questionId,
5071
                                    '',
5072
                                    $results_disabled,
5073
                                    $showTotalScoreAndUserChoicesInLastAttempt
5074
                                );
5075
                            }
5076
                            break;
5077
                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
5078
                            if ($answerId == 1) {
5079
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5080
                                    $feedback_type,
5081
                                    $studentChoice,
5082
                                    $studentChoiceDegree,
5083
                                    $answer,
5084
                                    $answerComment,
5085
                                    $answerCorrect,
5086
                                    $questionId,
5087
                                    $results_disabled
5088
                                );
5089
                            } else {
5090
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5091
                                    $feedback_type,
5092
                                    $studentChoice,
5093
                                    $studentChoiceDegree,
5094
                                    $answer,
5095
                                    $answerComment,
5096
                                    $answerCorrect,
5097
                                    $questionId,
5098
                                    $results_disabled
5099
                                );
5100
                            }
5101
                            break;
5102
                        case FILL_IN_BLANKS:
5103
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5104
                                $feedback_type,
5105
                                $answer,
5106
                                $exeId,
5107
                                $questionId,
5108
                                $results_disabled,
5109
                                $str,
5110
                                $showTotalScoreAndUserChoicesInLastAttempt
5111
                            );
5112
                            break;
5113
                        case CALCULATED_ANSWER:
5114
                            ExerciseShowFunctions::display_calculated_answer(
5115
                                $this,
5116
                                $feedback_type,
5117
                                $answer,
5118
                                $exeId,
5119
                                $questionId,
5120
                                $results_disabled,
5121
                                '',
5122
                                $showTotalScoreAndUserChoicesInLastAttempt
5123
                            );
5124
                            break;
5125
                        case FREE_ANSWER:
5126
                            echo ExerciseShowFunctions::display_free_answer(
5127
                                $feedback_type,
5128
                                $choice,
5129
                                $exeId,
5130
                                $questionId,
5131
                                $questionScore,
5132
                                $results_disabled
5133
                            );
5134
                            break;
5135
                        case ORAL_EXPRESSION:
5136
                            echo '<tr>
5137
                                <td valign="top">'.
5138
                                ExerciseShowFunctions::display_oral_expression_answer(
5139
                                    $feedback_type,
5140
                                    $choice,
5141
                                    $exeId,
5142
                                    $questionId,
5143
                                    $objQuestionTmp->getFileUrl(),
5144
                                    $results_disabled,
5145
                                    $questionScore
5146
                                ).'</td>
5147
                                </tr>
5148
                                </table>';
5149
                            break;
5150
                        case HOT_SPOT:
5151
                            ExerciseShowFunctions::display_hotspot_answer(
5152
                                $feedback_type,
5153
                                $answerId,
5154
                                $answer,
5155
                                $studentChoice,
5156
                                $answerComment,
5157
                                $results_disabled,
5158
                                $answerId,
5159
                                $showTotalScoreAndUserChoicesInLastAttempt
5160
                            );
5161
                            break;
5162
                        case HOT_SPOT_DELINEATION:
5163
                            $user_answer = $user_array;
5164
                            if ($next) {
5165
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
5166
                                // Save into db
5167
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
5168
                                 * hotspot_user_id,
5169
                                 *  hotspot_course_code,
5170
                                 *  hotspot_exe_id,
5171
                                 *  hotspot_question_id,
5172
                                 *  hotspot_answer_id,
5173
                                 *  hotspot_correct,
5174
                                 *  hotspot_coordinate
5175
                                 *  )
5176
                                VALUES (
5177
                                 * '".Database::escape_string($_user['user_id'])."',
5178
                                 *  '".Database::escape_string($_course['id'])."',
5179
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
5180
                                 *  '".Database::escape_string($answerId)."',
5181
                                 *  '".Database::escape_string($studentChoice)."',
5182
                                 *  '".Database::escape_string($user_array)."')";
5183
                                $result = Database::query($sql,__FILE__,__LINE__);
5184
                                 */
5185
                                $user_answer = $user_array;
5186
                                // we compare only the delineation not the other points
5187
                                $answer_question = $_SESSION['hotspot_coord'][1];
5188
                                $answerDestination = $_SESSION['hotspot_dest'][1];
5189
5190
                                // calculating the area
5191
                                $poly_user = convert_coordinates($user_answer, '/');
5192
                                $poly_answer = convert_coordinates($answer_question, '|');
5193
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5194
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5195
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5196
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5197
5198
                                $overlap = $poly_results['both'];
5199
                                $poly_answer_area = $poly_results['s1'];
5200
                                $poly_user_area = $poly_results['s2'];
5201
                                $missing = $poly_results['s1Only'];
5202
                                $excess = $poly_results['s2Only'];
5203
                                if ($debug > 0) {
5204
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5205
                                }
5206
                                if ($overlap < 1) {
5207
                                    //shortcut to avoid complicated calculations
5208
                                    $final_overlap = 0;
5209
                                    $final_missing = 100;
5210
                                    $final_excess = 100;
5211
                                } else {
5212
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
5213
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5214
                                    if ($debug > 1) {
5215
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5216
                                    }
5217
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
5218
                                    $final_missing = 100 - $final_overlap;
5219
                                    if ($debug > 1) {
5220
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5221
                                    }
5222
                                    // 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
5223
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5224
                                    if ($debug > 1) {
5225
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5226
                                    }
5227
                                }
5228
5229
                                // Checking the destination parameters parsing the "@@"
5230
                                $destination_items = explode('@@', $answerDestination);
5231
                                $threadhold_total = $destination_items[0];
5232
                                $threadhold_items = explode(';', $threadhold_total);
5233
                                $threadhold1 = $threadhold_items[0]; // overlap
5234
                                $threadhold2 = $threadhold_items[1]; // excess
5235
                                $threadhold3 = $threadhold_items[2]; //missing
5236
                                // if is delineation
5237
                                if ($answerId === 1) {
5238
                                    //setting colors
5239
                                    if ($final_overlap >= $threadhold1) {
5240
                                        $overlap_color = true; //echo 'a';
5241
                                    }
5242
                                    if ($final_excess <= $threadhold2) {
5243
                                        $excess_color = true; //echo 'b';
5244
                                    }
5245
                                    if ($final_missing <= $threadhold3) {
5246
                                        $missing_color = true; //echo 'c';
5247
                                    }
5248
5249
                                    // if pass
5250
                                    if ($final_overlap >= $threadhold1 &&
5251
                                        $final_missing <= $threadhold3 &&
5252
                                        $final_excess <= $threadhold2
5253
                                    ) {
5254
                                        $next = 1; //go to the oars
5255
                                        $result_comment = get_lang('Acceptable');
5256
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5257
                                    } else {
5258
                                        $next = 0;
5259
                                        $result_comment = get_lang('Unacceptable');
5260
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5261
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5262
                                        //checking the destination parameters parsing the "@@"
5263
                                        $destination_items = explode('@@', $answerDestination);
5264
                                    }
5265
                                } elseif ($answerId > 1) {
5266
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5267
                                        if ($debug > 0) {
5268
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5269
                                        }
5270
                                        //type no error shouldn't be treated
5271
                                        $next = 1;
5272
                                        continue;
5273
                                    }
5274
                                    if ($debug > 0) {
5275
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5276
                                    }
5277
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5278
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5279
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5280
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5281
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5282
5283
                                    if ($overlap == false) {
5284
                                        //all good, no overlap
5285
                                        $next = 1;
5286
                                        continue;
5287
                                    } else {
5288
                                        if ($debug > 0) {
5289
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5290
                                        }
5291
                                        $organs_at_risk_hit++;
5292
                                        //show the feedback
5293
                                        $next = 0;
5294
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5295
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5296
5297
                                        $destination_items = explode('@@', $answerDestination);
5298
                                        $try_hotspot = $destination_items[1];
5299
                                        $lp_hotspot = $destination_items[2];
5300
                                        $select_question_hotspot = $destination_items[3];
5301
                                        $url_hotspot = $destination_items[4];
5302
                                    }
5303
                                }
5304
                            } else {
5305
                                // the first delineation feedback
5306
                                if ($debug > 0) {
5307
                                    error_log(__LINE__.' first', 0);
5308
                                }
5309
                            }
5310
                            break;
5311
                        case HOT_SPOT_ORDER:
5312
                            ExerciseShowFunctions::display_hotspot_order_answer(
5313
                                $feedback_type,
5314
                                $answerId,
5315
                                $answer,
5316
                                $studentChoice,
5317
                                $answerComment
5318
                            );
5319
                            break;
5320
                        case DRAGGABLE:
5321
                        case MATCHING_DRAGGABLE:
5322
                        case MATCHING:
5323
                            echo '<tr>';
5324
                            echo Display::tag('td', $answerMatching[$answerId]);
5325
                            echo Display::tag(
5326
                                'td',
5327
                                "$user_answer / ".Display::tag(
5328
                                    'strong',
5329
                                    $answerMatching[$answerCorrect],
5330
                                    ['style' => 'color: #008000; font-weight: bold;']
5331
                                )
5332
                            );
5333
                            echo '</tr>';
5334
5335
                            break;
5336
                        case ANNOTATION:
5337
                            ExerciseShowFunctions::displayAnnotationAnswer(
5338
                                $feedback_type,
5339
                                $exeId,
5340
                                $questionId,
5341
                                $questionScore,
5342
                                $results_disabled
5343
                            );
5344
                            break;
5345
                    }
5346
                }
5347
            }
5348
            if ($debug) {
5349
                error_log(' ------ ');
5350
            }
5351
        } // end for that loops over all answers of the current question
5352
5353
        if ($debug) {
5354
            error_log('-- end answer loop --');
5355
        }
5356
5357
        $final_answer = true;
5358
5359
        foreach ($real_answers as $my_answer) {
5360
            if (!$my_answer) {
5361
                $final_answer = false;
5362
            }
5363
        }
5364
5365
        //we add the total score after dealing with the answers
5366
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5367
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5368
        ) {
5369
            if ($final_answer) {
5370
                //getting only the first score where we save the weight of all the question
5371
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5372
                $questionScore += $answerWeighting;
5373
                $totalScore += $answerWeighting;
5374
            }
5375
        }
5376
5377
        //Fixes multiple answer question in order to be exact
5378
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5379
        /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
5380
             $diff = @array_diff($answer_correct_array, $real_answers);
5381
5382
             // All good answers or nothing works like exact
5383
5384
             $counter = 1;
5385
             $correct_answer = true;
5386
             foreach ($real_answers as $my_answer) {
5387
                 if ($debug)
5388
                     error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
5389
                 if ($my_answer != $answer_correct_array[$counter]) {
5390
                     $correct_answer = false;
5391
                     break;
5392
                 }
5393
                 $counter++;
5394
             }
5395
5396
             if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
5397
             if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
5398
             if ($debug) error_log(" correct_answer: ".$correct_answer);
5399
5400
             if ($correct_answer == false) {
5401
                 $questionScore = 0;
5402
             }
5403
5404
             // This makes the result non exact
5405
             if (!empty($diff)) {
5406
                 $questionScore = 0;
5407
             }
5408
         }*/
5409
5410
        $extra_data = [
5411
            'final_overlap' => $final_overlap,
5412
            'final_missing' => $final_missing,
5413
            'final_excess' => $final_excess,
5414
            'overlap_color' => $overlap_color,
5415
            'missing_color' => $missing_color,
5416
            'excess_color' => $excess_color,
5417
            'threadhold1' => $threadhold1,
5418
            'threadhold2' => $threadhold2,
5419
            'threadhold3' => $threadhold3,
5420
        ];
5421
        if ($from == 'exercise_result') {
5422
            // if answer is hotspot. To the difference of exercise_show.php,
5423
            //  we use the results from the session (from_db=0)
5424
            // TODO Change this, because it is wrong to show the user
5425
            //  some results that haven't been stored in the database yet
5426
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5427
                if ($debug) {
5428
                    error_log('$from AND this is a hotspot kind of question ');
5429
                }
5430
                $my_exe_id = 0;
5431
                $from_database = 0;
5432
                if ($answerType == HOT_SPOT_DELINEATION) {
5433
                    if (0) {
5434
                        if ($overlap_color) {
5435
                            $overlap_color = 'green';
5436
                        } else {
5437
                            $overlap_color = 'red';
5438
                        }
5439
                        if ($missing_color) {
5440
                            $missing_color = 'green';
5441
                        } else {
5442
                            $missing_color = 'red';
5443
                        }
5444
                        if ($excess_color) {
5445
                            $excess_color = 'green';
5446
                        } else {
5447
                            $excess_color = 'red';
5448
                        }
5449
                        if (!is_numeric($final_overlap)) {
5450
                            $final_overlap = 0;
5451
                        }
5452
                        if (!is_numeric($final_missing)) {
5453
                            $final_missing = 0;
5454
                        }
5455
                        if (!is_numeric($final_excess)) {
5456
                            $final_excess = 0;
5457
                        }
5458
5459
                        if ($final_overlap > 100) {
5460
                            $final_overlap = 100;
5461
                        }
5462
5463
                        $table_resume = '<table class="data_table">
5464
                                <tr class="row_odd" >
5465
                                    <td></td>
5466
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5467
                                    <td><b>'.get_lang('YourAnswer').'</b></td>
5468
                                </tr>
5469
                                <tr class="row_even">
5470
                                    <td><b>'.get_lang('Overlap').'</b></td>
5471
                                    <td>'.get_lang('Min').' '.$threadhold1.'</td>
5472
                                    <td><div style="color:'.$overlap_color.'">'
5473
                            .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5474
                                </tr>
5475
                                <tr>
5476
                                    <td><b>'.get_lang('Excess').'</b></td>
5477
                                    <td>'.get_lang('Max').' '.$threadhold2.'</td>
5478
                                    <td><div style="color:'.$excess_color.'">'
5479
                            .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5480
                                </tr>
5481
                                <tr class="row_even">
5482
                                    <td><b>'.get_lang('Missing').'</b></td>
5483
                                    <td>'.get_lang('Max').' '.$threadhold3.'</td>
5484
                                    <td><div style="color:'.$missing_color.'">'
5485
                            .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5486
                                </tr>
5487
                            </table>';
5488
                        if ($next == 0) {
5489
                            $try = $try_hotspot;
5490
                            $lp = $lp_hotspot;
5491
                            $destinationid = $select_question_hotspot;
5492
                            $url = $url_hotspot;
5493
                        } else {
5494
                            //show if no error
5495
                            //echo 'no error';
5496
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5497
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5498
                        }
5499
5500
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5501
                            <p style="text-align:center">';
5502
5503
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5504
                        $message .= $table_resume;
5505
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5506
                        if ($organs_at_risk_hit > 0) {
5507
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5508
                        }
5509
                        $message .= '<p>'.$comment.'</p>';
5510
                        echo $message;
5511
                    } else {
5512
                        echo $hotspot_delineation_result[0]; //prints message
5513
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5514
                    }
5515
5516
                    //save the score attempts
5517
                    if (1) {
5518
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5519
                        $final_answer = $hotspot_delineation_result[1];
5520
                        if ($final_answer == 0) {
5521
                            $questionScore = 0;
5522
                        }
5523
                        // we always insert the answer_id 1 = delineation
5524
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5525
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5526
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5527
                        Event::saveExerciseAttemptHotspot(
5528
                            $exeId,
5529
                            $quesId,
5530
                            1,
5531
                            $hotspotValue,
5532
                            $exerciseResultCoordinates[$quesId]
5533
                        );
5534
                    } else {
5535
                        if ($final_answer == 0) {
5536
                            $questionScore = 0;
5537
                            $answer = 0;
5538
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5539
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5540
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5541
                                    Event::saveExerciseAttemptHotspot(
5542
                                        $exeId,
5543
                                        $quesId,
5544
                                        $idx,
5545
                                        0,
5546
                                        $val
5547
                                    );
5548
                                }
5549
                            }
5550
                        } else {
5551
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5552
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5553
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5554
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5555
                                    Event::saveExerciseAttemptHotspot(
5556
                                        $exeId,
5557
                                        $quesId,
5558
                                        $idx,
5559
                                        $hotspotValue,
5560
                                        $val
5561
                                    );
5562
                                }
5563
                            }
5564
                        }
5565
                    }
5566
                    $my_exe_id = $exeId;
5567
                }
5568
            }
5569
5570
            $relPath = api_get_path(WEB_CODE_PATH);
5571
5572
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5573
                // We made an extra table for the answers
5574
                if ($show_result) {
5575
                    echo '</table></td></tr>';
5576
                    echo "
5577
                        <tr>
5578
                            <td colspan=\"2\">
5579
                                <p><em>".get_lang('HotSpot')."</em></p>
5580
                                <div id=\"hotspot-solution-$questionId\"></div>
5581
                                <script>
5582
                                    $(document).on('ready', function () {
5583
                                        new HotspotQuestion({
5584
                                            questionId: $questionId,
5585
                                            exerciseId: {$this->id},
5586
                                            exeId: $exeId,
5587
                                            selector: '#hotspot-solution-$questionId',
5588
                                            for: 'solution',
5589
                                            relPath: '$relPath'
5590
                                        });
5591
                                    });
5592
                                </script>
5593
                            </td>
5594
                        </tr>
5595
                    ";
5596
                }
5597
            } elseif ($answerType == ANNOTATION) {
5598
                if ($show_result) {
5599
                    echo '
5600
                        <p><em>'.get_lang('Annotation').'</em></p>
5601
                        <div id="annotation-canvas-'.$questionId.'"></div>
5602
                        <script>
5603
                            AnnotationQuestion({
5604
                                questionId: parseInt('.$questionId.'),
5605
                                exerciseId: parseInt('.$exeId.'),
5606
                                relPath: \''.$relPath.'\',
5607
                                courseId: parseInt('.$course_id.')
5608
                            });
5609
                        </script>
5610
                    ';
5611
                }
5612
            }
5613
5614
            //if ($origin != 'learnpath') {
5615
            if ($show_result && $answerType != ANNOTATION) {
5616
                echo '</table>';
5617
            }
5618
            //	}
5619
        }
5620
        unset($objAnswerTmp);
5621
5622
        $totalWeighting += $questionWeighting;
5623
        // Store results directly in the database
5624
        // For all in one page exercises, the results will be
5625
        // stored by exercise_results.php (using the session)
5626
        if ($saved_results) {
5627
            if ($debug) {
5628
                error_log("Save question results $saved_results");
5629
                error_log('choice: ');
5630
                error_log(print_r($choice, 1));
5631
            }
5632
5633
            if (empty($choice)) {
5634
                $choice = 0;
5635
            }
5636
            // with certainty degree
5637
            if (empty($choiceDegreeCertainty)) {
5638
                $choiceDegreeCertainty = 0;
5639
            }
5640
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
5641
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ||
5642
                $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
5643
            ) {
5644
                if ($choice != 0) {
5645
                    $reply = array_keys($choice);
5646
                    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...
5647
                        $chosenAnswer = $reply[$i];
5648
                        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5649
                            if ($choiceDegreeCertainty != 0) {
5650
                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
5651
                                $answerDegreeCertainty = $replyDegreeCertainty[$i];
5652
                                Event::saveQuestionAttempt(
5653
                                    $questionScore,
5654
                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.
5655
                                    $choiceDegreeCertainty[$answerDegreeCertainty],
5656
                                    $quesId,
5657
                                    $exeId,
5658
                                    $i,
5659
                                    $this->id,
5660
                                    $updateResults
5661
                                );
5662
                            }
5663
                        } else {
5664
                            Event::saveQuestionAttempt(
5665
                                $questionScore,
5666
                                $chosenAnswer.':'.$choice[$chosenAnswer],
5667
                                $quesId,
5668
                                $exeId,
5669
                                $i,
5670
                                $this->id,
5671
                                $updateResults
5672
                            );
5673
                        }
5674
                        if ($debug) {
5675
                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
5676
                        }
5677
                    }
5678
                } else {
5679
                    Event::saveQuestionAttempt(
5680
                        $questionScore,
5681
                        0,
5682
                        $quesId,
5683
                        $exeId,
5684
                        0,
5685
                        $this->id
5686
                    );
5687
                }
5688
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5689
                if ($choice != 0) {
5690
                    $reply = array_keys($choice);
5691
5692
                    if ($debug) {
5693
                        error_log("reply ".print_r($reply, 1)."");
5694
                    }
5695
                    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...
5696
                        $ans = $reply[$i];
5697
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5698
                    }
5699
                } else {
5700
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5701
                }
5702
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5703
                if ($choice != 0) {
5704
                    $reply = array_keys($choice);
5705
                    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...
5706
                        $ans = $reply[$i];
5707
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5708
                    }
5709
                } else {
5710
                    Event::saveQuestionAttempt(
5711
                        $questionScore,
5712
                        0,
5713
                        $quesId,
5714
                        $exeId,
5715
                        0,
5716
                        $this->id
5717
                    );
5718
                }
5719
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5720
                if (isset($matching)) {
5721
                    foreach ($matching as $j => $val) {
5722
                        Event::saveQuestionAttempt(
5723
                            $questionScore,
5724
                            $val,
5725
                            $quesId,
5726
                            $exeId,
5727
                            $j,
5728
                            $this->id
5729
                        );
5730
                    }
5731
                }
5732
            } elseif ($answerType == FREE_ANSWER) {
5733
                $answer = $choice;
5734
                Event::saveQuestionAttempt(
5735
                    $questionScore,
5736
                    $answer,
5737
                    $quesId,
5738
                    $exeId,
5739
                    0,
5740
                    $this->id
5741
                );
5742
            } elseif ($answerType == ORAL_EXPRESSION) {
5743
                $answer = $choice;
5744
                Event::saveQuestionAttempt(
5745
                    $questionScore,
5746
                    $answer,
5747
                    $quesId,
5748
                    $exeId,
5749
                    0,
5750
                    $this->id,
5751
                    false,
5752
                    $objQuestionTmp->getAbsoluteFilePath()
5753
                );
5754
            } elseif (
5755
                in_array(
5756
                    $answerType,
5757
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
5758
                )
5759
            ) {
5760
                $answer = $choice;
5761
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5762
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5763
                $answer = [];
5764
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5765
                    if ($debug) {
5766
                        error_log('Checking result coordinates');
5767
                    }
5768
                    Database::delete(
5769
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5770
                        [
5771
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5772
                                $exeId,
5773
                                $questionId,
5774
                                api_get_course_int_id(),
5775
                            ],
5776
                        ]
5777
                    );
5778
5779
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5780
                        $answer[] = $val;
5781
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5782
                        if ($debug) {
5783
                            error_log('Hotspot value: '.$hotspotValue);
5784
                        }
5785
                        Event::saveExerciseAttemptHotspot(
5786
                            $exeId,
5787
                            $quesId,
5788
                            $idx,
5789
                            $hotspotValue,
5790
                            $val,
5791
                            false,
5792
                            $this->id
5793
                        );
5794
                    }
5795
                } else {
5796
                    if ($debug) {
5797
                        error_log('Empty: exerciseResultCoordinates');
5798
                    }
5799
                }
5800
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5801
            } else {
5802
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5803
            }
5804
        }
5805
5806
        if ($propagate_neg == 0 && $questionScore < 0) {
5807
            $questionScore = 0;
5808
        }
5809
5810
        if ($saved_results) {
5811
            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5812
            $sql = "UPDATE $statsTable SET
5813
                        score = score + ".floatval($questionScore)."
5814
                    WHERE exe_id = $exeId";
5815
            Database::query($sql);
5816
            if ($debug) {
5817
                error_log($sql);
5818
            }
5819
        }
5820
5821
        $return_array = [
5822
            'score' => $questionScore,
5823
            'weight' => $questionWeighting,
5824
            'extra' => $extra_data,
5825
            'open_question' => $arrques,
5826
            'open_answer' => $arrans,
5827
            'answer_type' => $answerType,
5828
            'generated_oral_file' => $generatedFile,
5829
        ];
5830
5831
        return $return_array;
5832
    }
5833
5834
    /**
5835
     * Sends a notification when a user ends an examn.
5836
     *
5837
     * @param string $type                  'start' or 'end' of an exercise
5838
     * @param array  $question_list_answers
5839
     * @param string $origin
5840
     * @param int    $exe_id
5841
     * @param float  $score
5842
     * @param float  $weight
5843
     *
5844
     * @return bool
5845
     */
5846
    public function send_mail_notification_for_exam(
5847
        $type = 'end',
5848
        $question_list_answers,
5849
        $origin,
5850
        $exe_id,
5851
        $score = null,
5852
        $weight = null
5853
    ) {
5854
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5855
5856
        if (empty($setting) && empty($this->getNotifications())) {
5857
            return false;
5858
        }
5859
5860
        $settingFromExercise = $this->getNotifications();
5861
        if (!empty($settingFromExercise)) {
5862
            $setting = $settingFromExercise;
5863
        }
5864
5865
        // Email configuration settings
5866
        $courseCode = api_get_course_id();
5867
        $courseInfo = api_get_course_info($courseCode);
5868
5869
        if (empty($courseInfo)) {
5870
            return false;
5871
        }
5872
5873
        $sessionId = api_get_session_id();
5874
        $sendStart = false;
5875
        $sendEnd = false;
5876
        $sendEndOpenQuestion = false;
5877
        $sendEndOralQuestion = false;
5878
5879
        foreach ($setting as $option) {
5880
            switch ($option) {
5881
                case 0:
5882
                    return false;
5883
                    break;
5884
                case 1: // End
5885
                    if ($type == 'end') {
5886
                        $sendEnd = true;
5887
                    }
5888
                    break;
5889
                case 2: // start
5890
                    if ($type == 'start') {
5891
                        $sendStart = true;
5892
                    }
5893
                    break;
5894
                case 3: // end + open
5895
                    if ($type == 'end') {
5896
                        $sendEndOpenQuestion = true;
5897
                    }
5898
                    break;
5899
                case 4: // end + oral
5900
                    if ($type == 'end') {
5901
                        $sendEndOralQuestion = true;
5902
                    }
5903
                    break;
5904
            }
5905
        }
5906
5907
        $user_info = api_get_user_info(api_get_user_id());
5908
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
5909
            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
5910
5911
        if (!empty($sessionId)) {
5912
            $addGeneralCoach = true;
5913
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5914
            if ($setting === true) {
5915
                $addGeneralCoach = false;
5916
            }
5917
            $teachers = CourseManager::get_coach_list_from_course_code(
5918
                $courseCode,
5919
                $sessionId,
5920
                $addGeneralCoach
5921
            );
5922
        } else {
5923
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5924
        }
5925
5926
        if ($sendEndOpenQuestion) {
5927
            $this->sendNotificationForOpenQuestions(
5928
                $question_list_answers,
5929
                $origin,
5930
                $exe_id,
5931
                $user_info,
5932
                $url,
5933
                $teachers
5934
            );
5935
        }
5936
5937
        if ($sendEndOralQuestion) {
5938
            $this->sendNotificationForOralQuestions(
5939
                $question_list_answers,
5940
                $origin,
5941
                $exe_id,
5942
                $user_info,
5943
                $url,
5944
                $teachers
5945
            );
5946
        }
5947
5948
        if (!$sendEnd && !$sendStart) {
5949
            return false;
5950
        }
5951
5952
        $scoreLabel = '';
5953
        if ($sendEnd &&
5954
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
5955
        ) {
5956
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
5957
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
5958
            $scoreLabel = "<tr>
5959
                            <td>".get_lang('Score')."</td>
5960
                            <td>&nbsp;$scoreLabel</td>
5961
                        </tr>";
5962
        }
5963
5964
        if ($sendEnd) {
5965
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5966
        } else {
5967
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5968
        }
5969
5970
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5971
                    <table>
5972
                        <tr>
5973
                            <td>'.get_lang('CourseName').'</td>
5974
                            <td>#course#</td>
5975
                        </tr>
5976
                        <tr>
5977
                            <td>'.get_lang('Exercise').'</td>
5978
                            <td>&nbsp;#exercise#</td>
5979
                        </tr>
5980
                        <tr>
5981
                            <td>'.get_lang('StudentName').'</td>
5982
                            <td>&nbsp;#student_complete_name#</td>
5983
                        </tr>
5984
                        <tr>
5985
                            <td>'.get_lang('StudentEmail').'</td>
5986
                            <td>&nbsp;#email#</td>
5987
                        </tr>
5988
                        '.$scoreLabel.'
5989
                    </table>';
5990
5991
        $variables = [
5992
            '#email#' => $user_info['email'],
5993
            '#exercise#' => $this->exercise,
5994
            '#student_complete_name#' => $user_info['complete_name'],
5995
            '#course#' => $courseInfo['title'],
5996
        ];
5997
5998
        if ($sendEnd) {
5999
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
6000
            $variables['#url#'] = $url;
6001
        }
6002
6003
        $content = str_replace(array_keys($variables), array_values($variables), $msg);
6004
6005
        if ($sendEnd) {
6006
            $subject = get_lang('ExerciseAttempted');
6007
        } else {
6008
            $subject = get_lang('StudentStartExercise');
6009
        }
6010
6011
        if (!empty($teachers)) {
6012
            foreach ($teachers as $user_id => $teacher_data) {
6013
                MessageManager::send_message_simple(
6014
                    $user_id,
6015
                    $subject,
6016
                    $content
6017
                );
6018
            }
6019
        }
6020
    }
6021
6022
    /**
6023
     * @param array $user_data         result of api_get_user_info()
6024
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
6025
     *
6026
     * @return string
6027
     */
6028
    public function showExerciseResultHeader(
6029
        $user_data,
6030
        $trackExerciseInfo
6031
    ) {
6032
        $start_date = null;
6033
        if (isset($trackExerciseInfo['start_date'])) {
6034
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
6035
        }
6036
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
6037
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
6038
6039
        $array = [];
6040
        if (!empty($user_data)) {
6041
            $userFullName = $user_data['complete_name'];
6042
            if (api_is_teacher() || api_is_platform_admin(true, true)) {
6043
                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.$user_data['complete_name'].'</a>';
6044
            }
6045
            $array[] = [
6046
                'title' => get_lang('Name'),
6047
                'content' => $userFullName,
6048
            ];
6049
            $array[] = [
6050
                'title' => get_lang('Username'),
6051
                'content' => $user_data['username'],
6052
            ];
6053
            if (!empty($user_data['official_code'])) {
6054
                $array[] = [
6055
                    'title' => get_lang('OfficialCode'),
6056
                    'content' => $user_data['official_code'],
6057
                ];
6058
            }
6059
        }
6060
        // Description can be very long and is generally meant to explain
6061
        //   rules *before* the exam. Leaving here to make display easier if
6062
        //   necessary
6063
        /*
6064
        if (!empty($this->description)) {
6065
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
6066
        }
6067
        */
6068
        if (!empty($start_date)) {
6069
            $array[] = ['title' => get_lang('StartDate'), 'content' => $start_date];
6070
        }
6071
6072
        if (!empty($duration)) {
6073
            $array[] = ['title' => get_lang('Duration'), 'content' => $duration];
6074
        }
6075
6076
        if (!empty($ip)) {
6077
            $array[] = ['title' => get_lang('IP'), 'content' => $ip];
6078
        }
6079
6080
        $icon = Display::return_icon(
6081
            'test-quiz.png',
6082
            get_lang('Result'),
6083
            null,
6084
            ICON_SIZE_MEDIUM
6085
        );
6086
6087
        $html = '<div class="question-result">';
6088
        if (api_get_configuration_value('save_titles_as_html')) {
6089
            $html .= $this->get_formated_title();
6090
            $html .= Display::page_header(get_lang('Result'));
6091
        } else {
6092
            $html .= Display::page_header(
6093
                $icon.PHP_EOL.$this->exercise.' : '.get_lang('Result')
6094
            );
6095
        }
6096
6097
        $hide = api_get_configuration_value('hide_user_info_in_quiz_result');
6098
6099
        if ($hide === false) {
6100
            $html .= Display::description($array);
6101
        }
6102
6103
        $html .= "</div>";
6104
6105
        return $html;
6106
    }
6107
6108
    /**
6109
     * Returns the exercise result.
6110
     *
6111
     * @param 	int		attempt id
6112
     *
6113
     * @return float exercise result
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
        $exerciseAttempts = $this->selectAttempts();
6348
6349
        if ($isVisible) {
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
    public function added_in_lp()
6384
    {
6385
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6386
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6387
                WHERE 
6388
                    c_id = {$this->course_id} AND 
6389
                    item_type = '".TOOL_QUIZ."' AND 
6390
                    path = '{$this->id}'";
6391
        $result = Database::query($sql);
6392
        if (Database::num_rows($result) > 0) {
6393
            return true;
6394
        }
6395
6396
        return false;
6397
    }
6398
6399
    /**
6400
     * Returns an array with this form.
6401
     *
6402
     * @example
6403
     * <code>
6404
     * array (size=3)
6405
     * 999 =>
6406
     * array (size=3)
6407
     * 0 => int 3422
6408
     * 1 => int 3423
6409
     * 2 => int 3424
6410
     * 100 =>
6411
     * array (size=2)
6412
     * 0 => int 3469
6413
     * 1 => int 3470
6414
     * 101 =>
6415
     * array (size=1)
6416
     * 0 => int 3482
6417
     * </code>
6418
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6419
     * this case is special because 999 means "no media".
6420
     *
6421
     * @return array
6422
     */
6423
    public function getMediaList()
6424
    {
6425
        return $this->mediaList;
6426
    }
6427
6428
    /**
6429
     * Is media question activated?
6430
     *
6431
     * @return bool
6432
     */
6433
    public function mediaIsActivated()
6434
    {
6435
        $mediaQuestions = $this->getMediaList();
6436
        $active = false;
6437
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6438
            $media_count = count($mediaQuestions);
6439
            if ($media_count > 1) {
6440
                return true;
6441
            } elseif ($media_count == 1) {
6442
                if (isset($mediaQuestions[999])) {
6443
                    return false;
6444
                } else {
6445
                    return true;
6446
                }
6447
            }
6448
        }
6449
6450
        return $active;
6451
    }
6452
6453
    /**
6454
     * Gets question list from the exercise.
6455
     *
6456
     * @return array
6457
     */
6458
    public function getQuestionList()
6459
    {
6460
        return $this->questionList;
6461
    }
6462
6463
    /**
6464
     * Question list with medias compressed like this.
6465
     *
6466
     * @example
6467
     * <code>
6468
     * array(
6469
     *      question_id_1,
6470
     *      question_id_2,
6471
     *      media_id, <- this media id contains question ids
6472
     *      question_id_3,
6473
     * )
6474
     * </code>
6475
     *
6476
     * @return array
6477
     */
6478
    public function getQuestionListWithMediasCompressed()
6479
    {
6480
        return $this->questionList;
6481
    }
6482
6483
    /**
6484
     * Question list with medias uncompressed like this.
6485
     *
6486
     * @example
6487
     * <code>
6488
     * array(
6489
     *      question_id,
6490
     *      question_id,
6491
     *      question_id, <- belongs to a media id
6492
     *      question_id, <- belongs to a media id
6493
     *      question_id,
6494
     * )
6495
     * </code>
6496
     *
6497
     * @return array
6498
     */
6499
    public function getQuestionListWithMediasUncompressed()
6500
    {
6501
        return $this->questionListUncompressed;
6502
    }
6503
6504
    /**
6505
     * Sets the question list when the exercise->read() is executed.
6506
     *
6507
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
6508
     */
6509
    public function setQuestionList($adminView = false)
6510
    {
6511
        // Getting question list.
6512
        $questionList = $this->selectQuestionList(true, $adminView);
6513
        $this->setMediaList($questionList);
6514
        $this->questionList = $this->transformQuestionListWithMedias(
6515
            $questionList,
6516
            false
6517
        );
6518
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6519
            $questionList,
6520
            true
6521
        );
6522
    }
6523
6524
    /**
6525
     * @params array question list
6526
     * @params bool expand or not question list (true show all questions,
6527
     * false show media question id instead of the question ids)
6528
     */
6529
    public function transformQuestionListWithMedias(
6530
        $question_list,
6531
        $expand_media_questions = false
6532
    ) {
6533
        $new_question_list = [];
6534
        if (!empty($question_list)) {
6535
            $media_questions = $this->getMediaList();
6536
6537
            $media_active = $this->mediaIsActivated($media_questions);
6538
6539
            if ($media_active) {
6540
                $counter = 1;
6541
                foreach ($question_list as $question_id) {
6542
                    $add_question = true;
6543
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6544
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6545
                            $add_question = false;
6546
                            if (!in_array($media_id, $new_question_list)) {
6547
                                $new_question_list[$counter] = $media_id;
6548
                                $counter++;
6549
                            }
6550
                            break;
6551
                        }
6552
                    }
6553
                    if ($add_question) {
6554
                        $new_question_list[$counter] = $question_id;
6555
                        $counter++;
6556
                    }
6557
                }
6558
                if ($expand_media_questions) {
6559
                    $media_key_list = array_keys($media_questions);
6560
                    foreach ($new_question_list as &$question_id) {
6561
                        if (in_array($question_id, $media_key_list)) {
6562
                            $question_id = $media_questions[$question_id];
6563
                        }
6564
                    }
6565
                    $new_question_list = array_flatten($new_question_list);
6566
                }
6567
            } else {
6568
                $new_question_list = $question_list;
6569
            }
6570
        }
6571
6572
        return $new_question_list;
6573
    }
6574
6575
    /**
6576
     * Get question list depend on the random settings.
6577
     *
6578
     * @return array
6579
     */
6580
    public function get_validated_question_list()
6581
    {
6582
        $result = [];
6583
        $isRandomByCategory = $this->isRandomByCat();
6584
        if ($isRandomByCategory == 0) {
6585
            if ($this->isRandom()) {
6586
                $result = $this->getRandomList();
6587
            } else {
6588
                $result = $this->selectQuestionList();
6589
            }
6590
        } else {
6591
            if ($this->isRandom()) {
6592
                // USE question categories
6593
                // get questions by category for this exercise
6594
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6595
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6596
                // value is the array of question id of this category
6597
                $questionList = [];
6598
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6599
                $isRandomByCategory = $this->getRandomByCategory();
6600
                // We sort categories based on the term between [] in the head
6601
                // of the category's description
6602
                /* examples of categories :
6603
                 * [biologie] Maitriser les mecanismes de base de la genetique
6604
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6605
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6606
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6607
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6608
                 * [chimie] Connaître les charges des particules
6609
                 * We want that in the order of the groups defined by the term
6610
                 * between brackets at the beginning of the category title
6611
                */
6612
                // If test option is Grouped By Categories
6613
                if ($isRandomByCategory == 2) {
6614
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6615
                }
6616
                foreach ($tabCategoryQuestions as $tabquestion) {
6617
                    $number_of_random_question = $this->random;
6618
                    if ($this->random == -1) {
6619
                        $number_of_random_question = count($this->questionList);
6620
                    }
6621
                    $questionList = array_merge(
6622
                        $questionList,
6623
                        TestCategory::getNElementsFromArray(
6624
                            $tabquestion,
6625
                            $number_of_random_question
6626
                        )
6627
                    );
6628
                }
6629
                // shuffle the question list if test is not grouped by categories
6630
                if ($isRandomByCategory == 1) {
6631
                    shuffle($questionList); // or not
6632
                }
6633
                $result = $questionList;
6634
            } else {
6635
                // Problem, random by category has been selected and
6636
                // we have no $this->isRandom number of question selected
6637
                // Should not happened
6638
            }
6639
        }
6640
6641
        return $result;
6642
    }
6643
6644
    public function get_question_list($expand_media_questions = false)
6645
    {
6646
        $question_list = $this->get_validated_question_list();
6647
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6648
6649
        return $question_list;
6650
    }
6651
6652
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6653
    {
6654
        $new_question_list = [];
6655
        if (!empty($question_list)) {
6656
            $media_questions = $this->getMediaList();
6657
            $media_active = $this->mediaIsActivated($media_questions);
6658
6659
            if ($media_active) {
6660
                $counter = 1;
6661
                foreach ($question_list as $question_id) {
6662
                    $add_question = true;
6663
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6664
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6665
                            $add_question = false;
6666
                            if (!in_array($media_id, $new_question_list)) {
6667
                                $new_question_list[$counter] = $media_id;
6668
                                $counter++;
6669
                            }
6670
                            break;
6671
                        }
6672
                    }
6673
                    if ($add_question) {
6674
                        $new_question_list[$counter] = $question_id;
6675
                        $counter++;
6676
                    }
6677
                }
6678
                if ($expand_media_questions) {
6679
                    $media_key_list = array_keys($media_questions);
6680
                    foreach ($new_question_list as &$question_id) {
6681
                        if (in_array($question_id, $media_key_list)) {
6682
                            $question_id = $media_questions[$question_id];
6683
                        }
6684
                    }
6685
                    $new_question_list = array_flatten($new_question_list);
6686
                }
6687
            } else {
6688
                $new_question_list = $question_list;
6689
            }
6690
        }
6691
6692
        return $new_question_list;
6693
    }
6694
6695
    /**
6696
     * @param int $exe_id
6697
     *
6698
     * @return array
6699
     */
6700
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6701
    {
6702
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6703
        $exe_id = (int) $exe_id;
6704
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
6705
        $result = Database::query($sql_track);
6706
        $new_array = [];
6707
        if (Database::num_rows($result) > 0) {
6708
            $new_array = Database::fetch_array($result, 'ASSOC');
6709
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6710
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6711
            $new_array['duration_formatted'] = '';
6712
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
6713
                $time = api_format_time($new_array['exe_duration'], 'js');
6714
                $new_array['duration_formatted'] = $time;
6715
            }
6716
        }
6717
6718
        return $new_array;
6719
    }
6720
6721
    /**
6722
     * @param int $exeId
6723
     *
6724
     * @return bool
6725
     */
6726
    public function removeAllQuestionToRemind($exeId)
6727
    {
6728
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6729
        $exeId = (int) $exeId;
6730
        if (empty($exeId)) {
6731
            return false;
6732
        }
6733
        $sql = "UPDATE $table 
6734
                SET questions_to_check = '' 
6735
                WHERE exe_id = $exeId ";
6736
        Database::query($sql);
6737
6738
        return true;
6739
    }
6740
6741
    /**
6742
     * @param int   $exeId
6743
     * @param array $questionList
6744
     *
6745
     * @return bool
6746
     */
6747
    public function addAllQuestionToRemind($exeId, $questionList = [])
6748
    {
6749
        $exeId = (int) $exeId;
6750
        if (empty($questionList)) {
6751
            return false;
6752
        }
6753
6754
        $questionListToString = implode(',', $questionList);
6755
        $questionListToString = Database::escape_string($questionListToString);
6756
6757
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6758
        $sql = "UPDATE $table 
6759
                SET questions_to_check = '$questionListToString' 
6760
                WHERE exe_id = $exeId";
6761
        Database::query($sql);
6762
6763
        return true;
6764
    }
6765
6766
    /**
6767
     * @param int    $exe_id
6768
     * @param int    $question_id
6769
     * @param string $action
6770
     */
6771
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6772
    {
6773
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6774
        $question_id = (int) $question_id;
6775
        $exe_id = (int) $exe_id;
6776
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6777
        if ($exercise_info) {
6778
            if (empty($exercise_info['questions_to_check'])) {
6779
                if ($action == 'add') {
6780
                    $sql = "UPDATE $track_exercises 
6781
                            SET questions_to_check = '$question_id' 
6782
                            WHERE exe_id = $exe_id ";
6783
                    Database::query($sql);
6784
                }
6785
            } else {
6786
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6787
                $remind_list_string = '';
6788
                if ($action == 'add') {
6789
                    if (!in_array($question_id, $remind_list)) {
6790
                        $newRemindList = [];
6791
                        $remind_list[] = $question_id;
6792
                        $questionListInSession = Session::read('questionList');
6793
                        if (!empty($questionListInSession)) {
6794
                            foreach ($questionListInSession as $originalQuestionId) {
6795
                                if (in_array($originalQuestionId, $remind_list)) {
6796
                                    $newRemindList[] = $originalQuestionId;
6797
                                }
6798
                            }
6799
                        }
6800
                        $remind_list_string = implode(',', $newRemindList);
6801
                    }
6802
                } elseif ($action == 'delete') {
6803
                    if (!empty($remind_list)) {
6804
                        if (in_array($question_id, $remind_list)) {
6805
                            $remind_list = array_flip($remind_list);
6806
                            unset($remind_list[$question_id]);
6807
                            $remind_list = array_flip($remind_list);
6808
6809
                            if (!empty($remind_list)) {
6810
                                sort($remind_list);
6811
                                array_filter($remind_list);
6812
                                $remind_list_string = implode(',', $remind_list);
6813
                            }
6814
                        }
6815
                    }
6816
                }
6817
                $value = Database::escape_string($remind_list_string);
6818
                $sql = "UPDATE $track_exercises 
6819
                        SET questions_to_check = '$value' 
6820
                        WHERE exe_id = $exe_id ";
6821
                Database::query($sql);
6822
            }
6823
        }
6824
    }
6825
6826
    /**
6827
     * @param string $answer
6828
     *
6829
     * @return mixed
6830
     */
6831
    public function fill_in_blank_answer_to_array($answer)
6832
    {
6833
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6834
        $teacher_answer_list = $teacher_answer_list[0];
6835
6836
        return $teacher_answer_list;
6837
    }
6838
6839
    /**
6840
     * @param string $answer
6841
     *
6842
     * @return string
6843
     */
6844
    public function fill_in_blank_answer_to_string($answer)
6845
    {
6846
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6847
        $result = '';
6848
        if (!empty($teacher_answer_list)) {
6849
            $i = 0;
6850
            foreach ($teacher_answer_list as $teacher_item) {
6851
                $value = null;
6852
                //Cleaning student answer list
6853
                $value = strip_tags($teacher_item);
6854
                $value = api_substr($value, 1, api_strlen($value) - 2);
6855
                $value = explode('/', $value);
6856
                if (!empty($value[0])) {
6857
                    $value = trim($value[0]);
6858
                    $value = str_replace('&nbsp;', '', $value);
6859
                    $result .= $value;
6860
                }
6861
            }
6862
        }
6863
6864
        return $result;
6865
    }
6866
6867
    /**
6868
     * @return string
6869
     */
6870
    public function return_time_left_div()
6871
    {
6872
        $html = '<div id="clock_warning" style="display:none">';
6873
        $html .= Display::return_message(
6874
            get_lang('ReachedTimeLimit'),
6875
            'warning'
6876
        );
6877
        $html .= ' ';
6878
        $html .= sprintf(
6879
            get_lang('YouWillBeRedirectedInXSeconds'),
6880
            '<span id="counter_to_redirect" class="red_alert"></span>'
6881
        );
6882
        $html .= '</div>';
6883
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6884
6885
        return $html;
6886
    }
6887
6888
    /**
6889
     * @return int
6890
     */
6891
    public function get_count_question_list()
6892
    {
6893
        // Real question count
6894
        $question_count = 0;
6895
        $question_list = $this->get_question_list();
6896
        if (!empty($question_list)) {
6897
            $question_count = count($question_list);
6898
        }
6899
6900
        return $question_count;
6901
    }
6902
6903
    /**
6904
     * Get categories added in the exercise--category matrix.
6905
     *
6906
     * @return array
6907
     */
6908
    public function getCategoriesInExercise()
6909
    {
6910
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6911
        if (!empty($this->id)) {
6912
            $sql = "SELECT * FROM $table
6913
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6914
            $result = Database::query($sql);
6915
            $list = [];
6916
            if (Database::num_rows($result)) {
6917
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6918
                    $list[$row['category_id']] = $row;
6919
                }
6920
6921
                return $list;
6922
            }
6923
        }
6924
6925
        return [];
6926
    }
6927
6928
    /**
6929
     * Get total number of question that will be parsed when using the category/exercise.
6930
     *
6931
     * @return int
6932
     */
6933
    public function getNumberQuestionExerciseCategory()
6934
    {
6935
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6936
        if (!empty($this->id)) {
6937
            $sql = "SELECT SUM(count_questions) count_questions
6938
                    FROM $table
6939
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6940
            $result = Database::query($sql);
6941
            if (Database::num_rows($result)) {
6942
                $row = Database::fetch_array($result);
6943
6944
                return $row['count_questions'];
6945
            }
6946
        }
6947
6948
        return 0;
6949
    }
6950
6951
    /**
6952
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
6953
     *
6954
     * @param array $categories
6955
     */
6956
    public function save_categories_in_exercise($categories)
6957
    {
6958
        if (!empty($categories) && !empty($this->id)) {
6959
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6960
            $sql = "DELETE FROM $table
6961
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6962
            Database::query($sql);
6963
            if (!empty($categories)) {
6964
                foreach ($categories as $categoryId => $countQuestions) {
6965
                    $params = [
6966
                        'c_id' => $this->course_id,
6967
                        'exercise_id' => $this->id,
6968
                        'category_id' => $categoryId,
6969
                        'count_questions' => $countQuestions,
6970
                    ];
6971
                    Database::insert($table, $params);
6972
                }
6973
            }
6974
        }
6975
    }
6976
6977
    /**
6978
     * @param array  $questionList
6979
     * @param int    $currentQuestion
6980
     * @param array  $conditions
6981
     * @param string $link
6982
     *
6983
     * @return string
6984
     */
6985
    public function progressExercisePaginationBar(
6986
        $questionList,
6987
        $currentQuestion,
6988
        $conditions,
6989
        $link
6990
    ) {
6991
        $mediaQuestions = $this->getMediaList();
6992
6993
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6994
        $counter = 0;
6995
        $nextValue = 0;
6996
        $wasMedia = false;
6997
        $before = 0;
6998
        $counterNoMedias = 0;
6999
        foreach ($questionList as $questionId) {
7000
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
7001
7002
            if (!empty($nextValue)) {
7003
                if ($wasMedia) {
7004
                    $nextValue = $nextValue - $before + 1;
7005
                }
7006
            }
7007
7008
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
7009
                $fixedValue = $counterNoMedias;
7010
7011
                $html .= Display::progressPaginationBar(
7012
                    $nextValue,
7013
                    $mediaQuestions[$questionId],
7014
                    $currentQuestion,
7015
                    $fixedValue,
7016
                    $conditions,
7017
                    $link,
7018
                    true,
7019
                    true
7020
                );
7021
7022
                $counter += count($mediaQuestions[$questionId]) - 1;
7023
                $before = count($questionList);
7024
                $wasMedia = true;
7025
                $nextValue += count($questionList);
7026
            } else {
7027
                $html .= Display::parsePaginationItem(
7028
                    $questionId,
7029
                    $isCurrent,
7030
                    $conditions,
7031
                    $link,
7032
                    $counter
7033
                );
7034
                $counter++;
7035
                $nextValue++;
7036
                $wasMedia = false;
7037
            }
7038
            $counterNoMedias++;
7039
        }
7040
        $html .= '</ul></div>';
7041
7042
        return $html;
7043
    }
7044
7045
    /**
7046
     *  Shows a list of numbers that represents the question to answer in a exercise.
7047
     *
7048
     * @param array  $categories
7049
     * @param int    $current
7050
     * @param array  $conditions
7051
     * @param string $link
7052
     *
7053
     * @return string
7054
     */
7055
    public function progressExercisePaginationBarWithCategories(
7056
        $categories,
7057
        $current,
7058
        $conditions = [],
7059
        $link = null
7060
    ) {
7061
        $html = null;
7062
        $counterNoMedias = 0;
7063
        $nextValue = 0;
7064
        $wasMedia = false;
7065
        $before = 0;
7066
7067
        if (!empty($categories)) {
7068
            $selectionType = $this->getQuestionSelectionType();
7069
            $useRootAsCategoryTitle = false;
7070
7071
            // Grouping questions per parent category see BT#6540
7072
            if (in_array(
7073
                $selectionType,
7074
                [
7075
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
7076
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
7077
                ]
7078
            )) {
7079
                $useRootAsCategoryTitle = true;
7080
            }
7081
7082
            // If the exercise is set to only show the titles of the categories
7083
            // at the root of the tree, then pre-order the categories tree by
7084
            // removing children and summing their questions into the parent
7085
            // categories
7086
            if ($useRootAsCategoryTitle) {
7087
                // The new categories list starts empty
7088
                $newCategoryList = [];
7089
                foreach ($categories as $category) {
7090
                    $rootElement = $category['root'];
7091
7092
                    if (isset($category['parent_info'])) {
7093
                        $rootElement = $category['parent_info']['id'];
7094
                    }
7095
7096
                    //$rootElement = $category['id'];
7097
                    // If the current category's ancestor was never seen
7098
                    // before, then declare it and assign the current
7099
                    // category to it.
7100
                    if (!isset($newCategoryList[$rootElement])) {
7101
                        $newCategoryList[$rootElement] = $category;
7102
                    } else {
7103
                        // If it was already seen, then merge the previous with
7104
                        // the current category
7105
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7106
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7107
                        $newCategoryList[$rootElement] = $category;
7108
                    }
7109
                }
7110
                // Now use the newly built categories list, with only parents
7111
                $categories = $newCategoryList;
7112
            }
7113
7114
            foreach ($categories as $category) {
7115
                $questionList = $category['question_list'];
7116
                // Check if in this category there questions added in a media
7117
                $mediaQuestionId = $category['media_question'];
7118
                $isMedia = false;
7119
                $fixedValue = null;
7120
7121
                // Media exists!
7122
                if ($mediaQuestionId != 999) {
7123
                    $isMedia = true;
7124
                    $fixedValue = $counterNoMedias;
7125
                }
7126
7127
                //$categoryName = $category['path']; << show the path
7128
                $categoryName = $category['name'];
7129
7130
                if ($useRootAsCategoryTitle) {
7131
                    if (isset($category['parent_info'])) {
7132
                        $categoryName = $category['parent_info']['title'];
7133
                    }
7134
                }
7135
                $html .= '<div class="row">';
7136
                $html .= '<div class="span2">'.$categoryName.'</div>';
7137
                $html .= '<div class="span8">';
7138
7139
                if (!empty($nextValue)) {
7140
                    if ($wasMedia) {
7141
                        $nextValue = $nextValue - $before + 1;
7142
                    }
7143
                }
7144
                $html .= Display::progressPaginationBar(
7145
                    $nextValue,
7146
                    $questionList,
7147
                    $current,
7148
                    $fixedValue,
7149
                    $conditions,
7150
                    $link,
7151
                    $isMedia,
7152
                    true
7153
                );
7154
                $html .= '</div>';
7155
                $html .= '</div>';
7156
7157
                if ($mediaQuestionId == 999) {
7158
                    $counterNoMedias += count($questionList);
7159
                } else {
7160
                    $counterNoMedias++;
7161
                }
7162
7163
                $nextValue += count($questionList);
7164
                $before = count($questionList);
7165
7166
                if ($mediaQuestionId != 999) {
7167
                    $wasMedia = true;
7168
                } else {
7169
                    $wasMedia = false;
7170
                }
7171
            }
7172
        }
7173
7174
        return $html;
7175
    }
7176
7177
    /**
7178
     * Renders a question list.
7179
     *
7180
     * @param array $questionList    (with media questions compressed)
7181
     * @param int   $currentQuestion
7182
     * @param array $exerciseResult
7183
     * @param array $attemptList
7184
     * @param array $remindList
7185
     */
7186
    public function renderQuestionList(
7187
        $questionList,
7188
        $currentQuestion,
7189
        $exerciseResult,
7190
        $attemptList,
7191
        $remindList
7192
    ) {
7193
        $mediaQuestions = $this->getMediaList();
7194
        $i = 0;
7195
7196
        // Normal question list render (medias compressed)
7197
        foreach ($questionList as $questionId) {
7198
            $i++;
7199
            // For sequential exercises
7200
7201
            if ($this->type == ONE_PER_PAGE) {
7202
                // If it is not the right question, goes to the next loop iteration
7203
                if ($currentQuestion != $i) {
7204
                    continue;
7205
                } else {
7206
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
7207
                        // if the user has already answered this question
7208
                        if (isset($exerciseResult[$questionId])) {
7209
                            echo Display::return_message(
7210
                                get_lang('AlreadyAnswered'),
7211
                                'normal'
7212
                            );
7213
                            break;
7214
                        }
7215
                    }
7216
                }
7217
            }
7218
7219
            // The $questionList contains the media id we check
7220
            // if this questionId is a media question type
7221
            if (isset($mediaQuestions[$questionId]) &&
7222
                $mediaQuestions[$questionId] != 999
7223
            ) {
7224
                // The question belongs to a media
7225
                $mediaQuestionList = $mediaQuestions[$questionId];
7226
                $objQuestionTmp = Question::read($questionId);
7227
7228
                $counter = 1;
7229
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7230
                    echo $objQuestionTmp->show_media_content();
7231
7232
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7233
7234
                    // Show questions that belongs to a media
7235
                    if (!empty($mediaQuestionList)) {
7236
                        // In order to parse media questions we use letters a, b, c, etc.
7237
                        $letterCounter = 97;
7238
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7239
                            $isLastQuestionInMedia = false;
7240
                            if ($counter == $countQuestionsInsideMedia) {
7241
                                $isLastQuestionInMedia = true;
7242
                            }
7243
                            $this->renderQuestion(
7244
                                $questionIdInsideMedia,
7245
                                $attemptList,
7246
                                $remindList,
7247
                                chr($letterCounter),
7248
                                $currentQuestion,
7249
                                $mediaQuestionList,
7250
                                $isLastQuestionInMedia,
7251
                                $questionList
7252
                            );
7253
                            $letterCounter++;
7254
                            $counter++;
7255
                        }
7256
                    }
7257
                } else {
7258
                    $this->renderQuestion(
7259
                        $questionId,
7260
                        $attemptList,
7261
                        $remindList,
7262
                        $i,
7263
                        $currentQuestion,
7264
                        null,
7265
                        null,
7266
                        $questionList
7267
                    );
7268
                    $i++;
7269
                }
7270
            } else {
7271
                // Normal question render.
7272
                $this->renderQuestion(
7273
                    $questionId,
7274
                    $attemptList,
7275
                    $remindList,
7276
                    $i,
7277
                    $currentQuestion,
7278
                    null,
7279
                    null,
7280
                    $questionList
7281
                );
7282
            }
7283
7284
            // For sequential exercises.
7285
            if ($this->type == ONE_PER_PAGE) {
7286
                // quits the loop
7287
                break;
7288
            }
7289
        }
7290
        // end foreach()
7291
7292
        if ($this->type == ALL_ON_ONE_PAGE) {
7293
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7294
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7295
        }
7296
    }
7297
7298
    /**
7299
     * @param int   $questionId
7300
     * @param array $attemptList
7301
     * @param array $remindList
7302
     * @param int   $i
7303
     * @param int   $current_question
7304
     * @param array $questions_in_media
7305
     * @param bool  $last_question_in_media
7306
     * @param array $realQuestionList
7307
     * @param bool  $generateJS
7308
     */
7309
    public function renderQuestion(
7310
        $questionId,
7311
        $attemptList,
7312
        $remindList,
7313
        $i,
7314
        $current_question,
7315
        $questions_in_media = [],
7316
        $last_question_in_media = false,
7317
        $realQuestionList,
7318
        $generateJS = true
7319
    ) {
7320
        // With this option on the question is loaded via AJAX
7321
        //$generateJS = true;
7322
        //$this->loadQuestionAJAX = true;
7323
7324
        if ($generateJS && $this->loadQuestionAJAX) {
7325
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7326
            $params = [
7327
                'questionId' => $questionId,
7328
                'attemptList' => $attemptList,
7329
                'remindList' => $remindList,
7330
                'i' => $i,
7331
                'current_question' => $current_question,
7332
                'questions_in_media' => $questions_in_media,
7333
                'last_question_in_media' => $last_question_in_media,
7334
            ];
7335
            $params = json_encode($params);
7336
7337
            $script = '<script>
7338
            $(function(){
7339
                var params = '.$params.';
7340
                $.ajax({
7341
                    type: "GET",
7342
                    async: false,
7343
                    data: params,
7344
                    url: "'.$url.'",
7345
                    success: function(return_value) {
7346
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7347
                    }
7348
                });
7349
            });
7350
            </script>
7351
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7352
            echo $script;
7353
        } else {
7354
            global $origin;
7355
            $question_obj = Question::read($questionId);
7356
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7357
            $remind_highlight = null;
7358
7359
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7360
            // see #4542 no_remind_highlight class hide with jquery
7361
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7362
                $remind_highlight = 'no_remind_highlight';
7363
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7364
                    return null;
7365
                }
7366
            }
7367
7368
            $attributes = ['id' => 'remind_list['.$questionId.']'];
7369
            // Showing the question
7370
7371
            $exercise_actions = null;
7372
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7373
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7374
7375
            // Shows the question + possible answers
7376
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7377
            echo $this->showQuestion(
7378
                $question_obj,
7379
                false,
7380
                $origin,
7381
                $i,
7382
                $showTitle,
7383
                false,
7384
                $user_choice,
7385
                false,
7386
                null,
7387
                false,
7388
                $this->getModelType(),
7389
                $this->categoryMinusOne
7390
            );
7391
7392
            // Button save and continue
7393
            switch ($this->type) {
7394
                case ONE_PER_PAGE:
7395
                    $exercise_actions .= $this->show_button(
7396
                        $questionId,
7397
                        $current_question,
7398
                        null,
7399
                        $remindList
7400
                    );
7401
                    break;
7402
                case ALL_ON_ONE_PAGE:
7403
                    if (api_is_allowed_to_session_edit()) {
7404
                        $button = [
7405
                            Display::button(
7406
                                'save_now',
7407
                                get_lang('SaveForNow'),
7408
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7409
                            ),
7410
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7411
                        ];
7412
                        $exercise_actions .= Display::div(
7413
                            implode(PHP_EOL, $button),
7414
                            ['class' => 'exercise_save_now_button']
7415
                        );
7416
                    }
7417
                    break;
7418
            }
7419
7420
            if (!empty($questions_in_media)) {
7421
                $count_of_questions_inside_media = count($questions_in_media);
7422
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7423
                    $button = [
7424
                        Display::button(
7425
                            'save_now',
7426
                            get_lang('SaveForNow'),
7427
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7428
                        ),
7429
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7430
                    ];
7431
                    $exercise_actions = Display::div(
7432
                        implode(PHP_EOL, $button),
7433
                        ['class' => 'exercise_save_now_button']
7434
                    );
7435
                }
7436
7437
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7438
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7439
                }
7440
            }
7441
7442
            // Checkbox review answers
7443
            if ($this->review_answers &&
7444
                !in_array($question_obj->type, Question::question_type_no_review())
7445
            ) {
7446
                $remind_question_div = Display::tag(
7447
                    'label',
7448
                    Display::input(
7449
                        'checkbox',
7450
                        'remind_list['.$questionId.']',
7451
                        '',
7452
                        $attributes
7453
                    ).get_lang('ReviewQuestionLater'),
7454
                    [
7455
                        'class' => 'checkbox',
7456
                        'for' => 'remind_list['.$questionId.']',
7457
                    ]
7458
                );
7459
                $exercise_actions .= Display::div(
7460
                    $remind_question_div,
7461
                    ['class' => 'exercise_save_now_button']
7462
                );
7463
            }
7464
7465
            echo Display::div(' ', ['class' => 'clear']);
7466
7467
            $paginationCounter = null;
7468
            if ($this->type == ONE_PER_PAGE) {
7469
                if (empty($questions_in_media)) {
7470
                    $paginationCounter = Display::paginationIndicator(
7471
                        $current_question,
7472
                        count($realQuestionList)
7473
                    );
7474
                } else {
7475
                    if ($last_question_in_media) {
7476
                        $paginationCounter = Display::paginationIndicator(
7477
                            $current_question,
7478
                            count($realQuestionList)
7479
                        );
7480
                    }
7481
                }
7482
            }
7483
7484
            echo '<div class="row"><div class="float-right">'.$paginationCounter.'</div></div>';
7485
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7486
            echo '</div>';
7487
        }
7488
    }
7489
7490
    /**
7491
     * Returns an array of categories details for the questions of the current
7492
     * exercise.
7493
     *
7494
     * @return array
7495
     */
7496
    public function getQuestionWithCategories()
7497
    {
7498
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7499
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7500
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7501
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7502
        $sql = "SELECT DISTINCT cat.*
7503
                FROM $TBL_EXERCICE_QUESTION e
7504
                INNER JOIN $TBL_QUESTIONS q
7505
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7506
                INNER JOIN $categoryRelTable catRel
7507
                ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id)
7508
                INNER JOIN $categoryTable cat
7509
                ON (cat.id = catRel.category_id AND cat.c_id = e.c_id)
7510
                WHERE
7511
                  e.c_id = {$this->course_id} AND
7512
                  e.exercice_id	= ".intval($this->id);
7513
7514
        $result = Database::query($sql);
7515
        $categoriesInExercise = [];
7516
        if (Database::num_rows($result)) {
7517
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7518
        }
7519
7520
        return $categoriesInExercise;
7521
    }
7522
7523
    /**
7524
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
7525
     */
7526
    public function get_max_score()
7527
    {
7528
        $out_max_score = 0;
7529
        // list of question's id !!! the array key start at 1 !!!
7530
        $questionList = $this->selectQuestionList(true);
7531
7532
        // test is randomQuestions - see field random of test
7533
        if ($this->random > 0 && $this->randomByCat == 0) {
7534
            $numberRandomQuestions = $this->random;
7535
            $questionScoreList = [];
7536
            foreach ($questionList as $questionId) {
7537
                $tmpobj_question = Question::read($questionId);
7538
                if (is_object($tmpobj_question)) {
7539
                    $questionScoreList[] = $tmpobj_question->weighting;
7540
                }
7541
            }
7542
7543
            rsort($questionScoreList);
7544
            // add the first $numberRandomQuestions value of score array to get max_score
7545
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7546
                $out_max_score += $questionScoreList[$i];
7547
            }
7548
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7549
            // test is random by category
7550
            // get the $numberRandomQuestions best score question of each category
7551
            $numberRandomQuestions = $this->random;
7552
            $tab_categories_scores = [];
7553
            foreach ($questionList as $questionId) {
7554
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7555
                if (!is_array($tab_categories_scores[$question_category_id])) {
7556
                    $tab_categories_scores[$question_category_id] = [];
7557
                }
7558
                $tmpobj_question = Question::read($questionId);
7559
                if (is_object($tmpobj_question)) {
7560
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7561
                }
7562
            }
7563
7564
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7565
            foreach ($tab_categories_scores as $tab_scores) {
7566
                rsort($tab_scores);
7567
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7568
                    $out_max_score += $tab_scores[$i];
7569
                }
7570
            }
7571
        } else {
7572
            // standard test, just add each question score
7573
            foreach ($questionList as $questionId) {
7574
                $question = Question::read($questionId, $this->course_id);
7575
                $out_max_score += $question->weighting;
7576
            }
7577
        }
7578
7579
        return $out_max_score;
7580
    }
7581
7582
    /**
7583
     * @return string
7584
     */
7585
    public function get_formated_title()
7586
    {
7587
        if (api_get_configuration_value('save_titles_as_html')) {
7588
        }
7589
7590
        return api_html_entity_decode($this->selectTitle());
7591
    }
7592
7593
    /**
7594
     * @param string $title
7595
     *
7596
     * @return string
7597
     */
7598
    public static function get_formated_title_variable($title)
7599
    {
7600
        return api_html_entity_decode($title);
7601
    }
7602
7603
    /**
7604
     * @return string
7605
     */
7606
    public function format_title()
7607
    {
7608
        return api_htmlentities($this->title);
7609
    }
7610
7611
    /**
7612
     * @param string $title
7613
     *
7614
     * @return string
7615
     */
7616
    public static function format_title_variable($title)
7617
    {
7618
        return api_htmlentities($title);
7619
    }
7620
7621
    /**
7622
     * @param int $courseId
7623
     * @param int $sessionId
7624
     *
7625
     * @return array exercises
7626
     */
7627
    public function getExercisesByCourseSession($courseId, $sessionId)
7628
    {
7629
        $courseId = (int) $courseId;
7630
        $sessionId = (int) $sessionId;
7631
7632
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7633
        $sql = "SELECT * FROM $tbl_quiz cq
7634
                WHERE
7635
                    cq.c_id = %s AND
7636
                    (cq.session_id = %s OR cq.session_id = 0) AND
7637
                    cq.active = 0
7638
                ORDER BY cq.id";
7639
        $sql = sprintf($sql, $courseId, $sessionId);
7640
7641
        $result = Database::query($sql);
7642
7643
        $rows = [];
7644
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7645
            $rows[] = $row;
7646
        }
7647
7648
        return $rows;
7649
    }
7650
7651
    /**
7652
     * @param int   $courseId
7653
     * @param int   $sessionId
7654
     * @param array $quizId
7655
     *
7656
     * @return array exercises
7657
     */
7658
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7659
    {
7660
        if (empty($quizId)) {
7661
            return [];
7662
        }
7663
7664
        $sessionId = (int) $sessionId;
7665
        $courseId = (int) $courseId;
7666
7667
        $ids = is_array($quizId) ? $quizId : [$quizId];
7668
        $ids = array_map('intval', $ids);
7669
        $ids = implode(',', $ids);
7670
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7671
        if ($sessionId != 0) {
7672
            $sql = "SELECT * FROM $track_exercises te
7673
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7674
              WHERE
7675
              te.id = %s AND
7676
              te.session_id = %s AND
7677
              cq.id IN (%s)
7678
              ORDER BY cq.id";
7679
7680
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7681
        } else {
7682
            $sql = "SELECT * FROM $track_exercises te
7683
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7684
              WHERE
7685
              te.id = %s AND
7686
              cq.id IN (%s)
7687
              ORDER BY cq.id";
7688
            $sql = sprintf($sql, $courseId, $ids);
7689
        }
7690
        $result = Database::query($sql);
7691
        $rows = [];
7692
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7693
            $rows[] = $row;
7694
        }
7695
7696
        return $rows;
7697
    }
7698
7699
    /**
7700
     * @param $exeId
7701
     * @param $exercise_stat_info
7702
     * @param $remindList
7703
     * @param $currentQuestion
7704
     *
7705
     * @return int|null
7706
     */
7707
    public static function getNextQuestionId(
7708
        $exeId,
7709
        $exercise_stat_info,
7710
        $remindList,
7711
        $currentQuestion
7712
    ) {
7713
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7714
7715
        if (isset($result[$exeId])) {
7716
            $result = $result[$exeId];
7717
        } else {
7718
            return null;
7719
        }
7720
7721
        $data_tracking = $exercise_stat_info['data_tracking'];
7722
        $data_tracking = explode(',', $data_tracking);
7723
7724
        // if this is the final question do nothing.
7725
        if ($currentQuestion == count($data_tracking)) {
7726
            return null;
7727
        }
7728
7729
        $currentQuestion = $currentQuestion - 1;
7730
7731
        if (!empty($result['question_list'])) {
7732
            $answeredQuestions = [];
7733
            foreach ($result['question_list'] as $question) {
7734
                if (!empty($question['answer'])) {
7735
                    $answeredQuestions[] = $question['question_id'];
7736
                }
7737
            }
7738
7739
            // Checking answered questions
7740
            $counterAnsweredQuestions = 0;
7741
            foreach ($data_tracking as $questionId) {
7742
                if (!in_array($questionId, $answeredQuestions)) {
7743
                    if ($currentQuestion != $counterAnsweredQuestions) {
7744
                        break;
7745
                    }
7746
                }
7747
                $counterAnsweredQuestions++;
7748
            }
7749
7750
            $counterRemindListQuestions = 0;
7751
            // Checking questions saved in the reminder list
7752
            if (!empty($remindList)) {
7753
                foreach ($data_tracking as $questionId) {
7754
                    if (in_array($questionId, $remindList)) {
7755
                        // Skip the current question
7756
                        if ($currentQuestion != $counterRemindListQuestions) {
7757
                            break;
7758
                        }
7759
                    }
7760
                    $counterRemindListQuestions++;
7761
                }
7762
7763
                if ($counterRemindListQuestions < $currentQuestion) {
7764
                    return null;
7765
                }
7766
7767
                if (!empty($counterRemindListQuestions)) {
7768
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7769
                        return $counterAnsweredQuestions;
7770
                    } else {
7771
                        return $counterRemindListQuestions;
7772
                    }
7773
                }
7774
            }
7775
7776
            return $counterAnsweredQuestions;
7777
        }
7778
    }
7779
7780
    /**
7781
     * Gets the position of a questionId in the question list.
7782
     *
7783
     * @param $questionId
7784
     *
7785
     * @return int
7786
     */
7787
    public function getPositionInCompressedQuestionList($questionId)
7788
    {
7789
        $questionList = $this->getQuestionListWithMediasCompressed();
7790
        $mediaQuestions = $this->getMediaList();
7791
        $position = 1;
7792
        foreach ($questionList as $id) {
7793
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7794
                $mediaQuestionList = $mediaQuestions[$id];
7795
                if (in_array($questionId, $mediaQuestionList)) {
7796
                    return $position;
7797
                } else {
7798
                    $position++;
7799
                }
7800
            } else {
7801
                if ($id == $questionId) {
7802
                    return $position;
7803
                } else {
7804
                    $position++;
7805
                }
7806
            }
7807
        }
7808
7809
        return 1;
7810
    }
7811
7812
    /**
7813
     * Get the correct answers in all attempts.
7814
     *
7815
     * @param int $learnPathId
7816
     * @param int $learnPathItemId
7817
     *
7818
     * @return array
7819
     */
7820
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7821
    {
7822
        $attempts = Event::getExerciseResultsByUser(
7823
            api_get_user_id(),
7824
            $this->id,
7825
            api_get_course_int_id(),
7826
            api_get_session_id(),
7827
            $learnPathId,
7828
            $learnPathItemId,
7829
            'asc'
7830
        );
7831
7832
        $corrects = [];
7833
        foreach ($attempts as $attempt) {
7834
            foreach ($attempt['question_list'] as $answers) {
7835
                foreach ($answers as $answer) {
7836
                    $objAnswer = new Answer($answer['question_id']);
7837
7838
                    switch ($objAnswer->getQuestionType()) {
7839
                        case FILL_IN_BLANKS:
7840
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7841
                            break;
7842
                        case MATCHING:
7843
                        case DRAGGABLE:
7844
                        case MATCHING_DRAGGABLE:
7845
                            $isCorrect = Matching::isCorrect(
7846
                                $answer['position'],
7847
                                $answer['answer'],
7848
                                $answer['question_id']
7849
                            );
7850
                            break;
7851
                        case ORAL_EXPRESSION:
7852
                            $isCorrect = false;
7853
                            break;
7854
                        default:
7855
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7856
                    }
7857
7858
                    if ($isCorrect) {
7859
                        $corrects[$answer['question_id']][] = $answer;
7860
                    }
7861
                }
7862
            }
7863
        }
7864
7865
        return $corrects;
7866
    }
7867
7868
    /**
7869
     * @return bool
7870
     */
7871
    public function showPreviousButton()
7872
    {
7873
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
7874
        if ($allow === false) {
7875
            return true;
7876
        }
7877
7878
        return $this->showPreviousButton;
7879
    }
7880
7881
    /**
7882
     * @param bool $showPreviousButton
7883
     *
7884
     * @return Exercise
7885
     */
7886
    public function setShowPreviousButton($showPreviousButton)
7887
    {
7888
        $this->showPreviousButton = $showPreviousButton;
7889
7890
        return $this;
7891
    }
7892
7893
    /**
7894
     * @param array $notifications
7895
     */
7896
    public function setNotifications($notifications)
7897
    {
7898
        $this->notifications = $notifications;
7899
    }
7900
7901
    /**
7902
     * @return array
7903
     */
7904
    public function getNotifications()
7905
    {
7906
        return $this->notifications;
7907
    }
7908
7909
    /**
7910
     * @return bool
7911
     */
7912
    public function showExpectedChoice()
7913
    {
7914
        return api_get_configuration_value('show_exercise_expected_choice');
7915
    }
7916
7917
    /**
7918
     * @param string $class
7919
     * @param string $scoreLabel
7920
     * @param string $result
7921
     * @param array
7922
     *
7923
     * @return string
7924
     */
7925
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
7926
    {
7927
        if ($this->showExpectedChoice()) {
7928
            $html = null;
7929
            $hideLabel = api_get_configuration_value('exercise_hide_label');
7930
            $label = '<div class="rib rib-'.$class.'">
7931
                        <h3>'.$scoreLabel.'</h3>
7932
                  </div> 
7933
                  <h4>'.get_lang('Score').': '.$result.'</h4>';
7934
            if ($hideLabel === true) {
7935
                $answerUsed = (int) $array['used'];
7936
                $answerMissing = (int) $array['missing'] - $answerUsed;
7937
                for ($i = 1; $i <= $answerUsed; $i++) {
7938
                    $html .= '<span class="score-img">'.
7939
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
7940
                        '</span>';
7941
                }
7942
                for ($i = 1; $i <= $answerMissing; $i++) {
7943
                    $html .= '<span class="score-img">'.
7944
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
7945
                        '</span>';
7946
                }
7947
                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
7948
                $label .= '<div class="score-limits">';
7949
                $label .= $html;
7950
                $label .= '</div>';
7951
            }
7952
7953
            return '<div class="ribbon">
7954
                '.$label.'
7955
                </div>'
7956
                ;
7957
        } else {
7958
            return '<div class="ribbon">
7959
                        <div class="rib rib-'.$class.'">
7960
                            <h3>'.$scoreLabel.'</h3>
7961
                        </div>
7962
                        <h4>'.get_lang('Score').': '.$result.'</h4>
7963
                    </div>'
7964
                ;
7965
        }
7966
    }
7967
7968
    /**
7969
     * @return int
7970
     */
7971
    public function getAutoLaunch()
7972
    {
7973
        return $this->autolaunch;
7974
    }
7975
7976
    /**
7977
     * Clean auto launch settings for all exercise in course/course-session.
7978
     */
7979
    public function enableAutoLaunch()
7980
    {
7981
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7982
        $sql = "UPDATE $table SET autolaunch = 1
7983
                WHERE iid = ".$this->iId;
7984
        Database::query($sql);
7985
    }
7986
7987
    /**
7988
     * Clean auto launch settings for all exercise in course/course-session.
7989
     */
7990
    public function cleanCourseLaunchSettings()
7991
    {
7992
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7993
        $sql = "UPDATE $table SET autolaunch = 0  
7994
                WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId;
7995
        Database::query($sql);
7996
    }
7997
7998
    /**
7999
     * Get the title without HTML tags.
8000
     *
8001
     * @return string
8002
     */
8003
    public function getUnformattedTitle()
8004
    {
8005
        return strip_tags(api_html_entity_decode($this->title));
8006
    }
8007
8008
    /**
8009
     * @param int $start
8010
     * @param int $lenght
8011
     *
8012
     * @return array
8013
     */
8014
    public function getQuestionForTeacher($start = 0, $lenght = 10)
8015
    {
8016
        $start = (int) $start;
8017
        $lenght = (int) $lenght;
8018
8019
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8020
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8021
        $sql = "SELECT DISTINCT e.question_id, e.question_order
8022
                FROM $TBL_EXERCICE_QUESTION e
8023
                INNER JOIN $TBL_QUESTIONS q
8024
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8025
                WHERE
8026
                    e.c_id = {$this->course_id} AND
8027
                    e.exercice_id = '".$this->id."'
8028
                ORDER BY question_order
8029
                LIMIT $start, $lenght
8030
            ";
8031
        $result = Database::query($sql);
8032
        $questionList = [];
8033
        while ($object = Database::fetch_object($result)) {
8034
            $questionList[$object->question_order] = $object->question_id;
8035
        }
8036
8037
        return $questionList;
8038
    }
8039
8040
    /**
8041
     * Gets the question list ordered by the question_order setting (drag and drop).
8042
     *
8043
     * @return array
8044
     */
8045
    private function getQuestionOrderedList()
8046
    {
8047
        $questionList = [];
8048
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8049
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8050
8051
        // Getting question_order to verify that the question
8052
        // list is correct and all question_order's were set
8053
        $sql = "SELECT DISTINCT e.question_order
8054
                FROM $TBL_EXERCICE_QUESTION e
8055
                INNER JOIN $TBL_QUESTIONS q
8056
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8057
                WHERE
8058
                  e.c_id = {$this->course_id} AND
8059
                  e.exercice_id	= ".$this->id;
8060
8061
        $result = Database::query($sql);
8062
        $count_question_orders = Database::num_rows($result);
8063
8064
        // Getting question list from the order (question list drag n drop interface).
8065
        $sql = "SELECT DISTINCT e.question_id, e.question_order
8066
                FROM $TBL_EXERCICE_QUESTION e
8067
                INNER JOIN $TBL_QUESTIONS q
8068
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8069
                WHERE
8070
                    e.c_id = {$this->course_id} AND
8071
                    e.exercice_id = '".$this->id."'
8072
                ORDER BY question_order";
8073
        $result = Database::query($sql);
8074
8075
        // Fills the array with the question ID for this exercise
8076
        // the key of the array is the question position
8077
        $temp_question_list = [];
8078
        $counter = 1;
8079
        while ($new_object = Database::fetch_object($result)) {
8080
            // Correct order.
8081
            $questionList[$new_object->question_order] = $new_object->question_id;
8082
            // Just in case we save the order in other array
8083
            $temp_question_list[$counter] = $new_object->question_id;
8084
            $counter++;
8085
        }
8086
8087
        if (!empty($temp_question_list)) {
8088
            /* If both array don't match it means that question_order was not correctly set
8089
               for all questions using the default mysql order */
8090
            if (count($temp_question_list) != $count_question_orders) {
8091
                $questionList = $temp_question_list;
8092
            }
8093
        }
8094
8095
        return $questionList;
8096
    }
8097
8098
    /**
8099
     * Select N values from the questions per category array.
8100
     *
8101
     * @param array $categoriesAddedInExercise
8102
     * @param array $question_list
8103
     * @param array $questions_by_category     per category
8104
     * @param bool  $flatResult
8105
     * @param bool  $randomizeQuestions
8106
     *
8107
     * @return array
8108
     */
8109
    private function pickQuestionsPerCategory(
8110
        $categoriesAddedInExercise,
8111
        $question_list,
8112
        &$questions_by_category,
8113
        $flatResult = true,
8114
        $randomizeQuestions = false
8115
    ) {
8116
        $addAll = true;
8117
        $categoryCountArray = [];
8118
8119
        // Getting how many questions will be selected per category.
8120
        if (!empty($categoriesAddedInExercise)) {
8121
            $addAll = false;
8122
            // Parsing question according the category rel exercise settings
8123
            foreach ($categoriesAddedInExercise as $category_info) {
8124
                $category_id = $category_info['category_id'];
8125
                if (isset($questions_by_category[$category_id])) {
8126
                    // How many question will be picked from this category.
8127
                    $count = $category_info['count_questions'];
8128
                    // -1 means all questions
8129
                    $categoryCountArray[$category_id] = $count;
8130
                    if ($count == -1) {
8131
                        $categoryCountArray[$category_id] = 999;
8132
                    }
8133
                }
8134
            }
8135
        }
8136
8137
        if (!empty($questions_by_category)) {
8138
            $temp_question_list = [];
8139
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
8140
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
8141
                    $numberOfQuestions = 0;
8142
                    if (isset($categoryCountArray[$category_id])) {
8143
                        $numberOfQuestions = $categoryCountArray[$category_id];
8144
                    }
8145
                }
8146
8147
                if ($addAll) {
8148
                    $numberOfQuestions = 999;
8149
                }
8150
8151
                if (!empty($numberOfQuestions)) {
8152
                    $elements = TestCategory::getNElementsFromArray(
8153
                        $categoryQuestionList,
8154
                        $numberOfQuestions,
8155
                        $randomizeQuestions
8156
                    );
8157
8158
                    if (!empty($elements)) {
8159
                        $temp_question_list[$category_id] = $elements;
8160
                        $categoryQuestionList = $elements;
8161
                    }
8162
                }
8163
            }
8164
8165
            if (!empty($temp_question_list)) {
8166
                if ($flatResult) {
8167
                    $temp_question_list = array_flatten($temp_question_list);
8168
                }
8169
                $question_list = $temp_question_list;
8170
            }
8171
        }
8172
8173
        return $question_list;
8174
    }
8175
8176
    /**
8177
     * Changes the exercise id.
8178
     *
8179
     * @param int $id - exercise id
8180
     */
8181
    private function updateId($id)
8182
    {
8183
        $this->id = $id;
8184
    }
8185
8186
    /**
8187
     * Sends a notification when a user ends an examn.
8188
     *
8189
     * @param array  $question_list_answers
8190
     * @param string $origin
8191
     * @param int    $exe_id
8192
     */
8193
    private function sendNotificationForOpenQuestions(
8194
        $question_list_answers,
8195
        $origin,
8196
        $exe_id,
8197
        $user_info,
8198
        $url_email,
8199
        $teachers
8200
    ) {
8201
        // Email configuration settings
8202
        $courseCode = api_get_course_id();
8203
        $courseInfo = api_get_course_info($courseCode);
8204
8205
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
8206
            .get_lang('AttemptDetails').' : <br /><br />'
8207
            .'<table>'
8208
            .'<tr>'
8209
            .'<td><em>'.get_lang('CourseName').'</em></td>'
8210
            .'<td>&nbsp;<b>#course#</b></td>'
8211
            .'</tr>'
8212
            .'<tr>'
8213
            .'<td>'.get_lang('TestAttempted').'</td>'
8214
            .'<td>&nbsp;#exercise#</td>'
8215
            .'</tr>'
8216
            .'<tr>'
8217
            .'<td>'.get_lang('StudentName').'</td>'
8218
            .'<td>&nbsp;#firstName# #lastName#</td>'
8219
            .'</tr>'
8220
            .'<tr>'
8221
            .'<td>'.get_lang('StudentEmail').'</td>'
8222
            .'<td>&nbsp;#mail#</td>'
8223
            .'</tr>'
8224
            .'</table>';
8225
        $open_question_list = null;
8226
        foreach ($question_list_answers as $item) {
8227
            $question = $item['question'];
8228
            $answer = $item['answer'];
8229
            $answer_type = $item['answer_type'];
8230
8231
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
8232
                $open_question_list .=
8233
                    '<tr>'
8234
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
8235
                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
8236
                    .'</tr>'
8237
                    .'<tr>'
8238
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
8239
                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
8240
                    .'</tr>';
8241
            }
8242
        }
8243
8244
        if (!empty($open_question_list)) {
8245
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
8246
                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
8247
            $msg .= $open_question_list;
8248
            $msg .= '</table><br />';
8249
8250
            $msg = str_replace("#exercise#", $this->exercise, $msg);
8251
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg);
8252
            $msg = str_replace("#lastName#", $user_info['lastname'], $msg);
8253
            $msg = str_replace("#mail#", $user_info['email'], $msg);
8254
            $msg = str_replace("#course#", $courseInfo['name'], $msg);
8255
8256
            if ($origin != 'learnpath') {
8257
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8258
            }
8259
            $msg = str_replace("#url#", $url_email, $msg);
8260
            $subject = get_lang('OpenQuestionsAttempted');
8261
8262
            if (!empty($teachers)) {
8263
                foreach ($teachers as $user_id => $teacher_data) {
8264
                    MessageManager::send_message_simple(
8265
                        $user_id,
8266
                        $subject,
8267
                        $msg
8268
                    );
8269
                }
8270
            }
8271
        }
8272
    }
8273
8274
    /**
8275
     * Send notification for oral questions.
8276
     *
8277
     * @param array  $question_list_answers
8278
     * @param string $origin
8279
     * @param int    $exe_id
8280
     * @param array  $user_info
8281
     * @param string $url_email
8282
     * @param array  $teachers
8283
     */
8284
    private function sendNotificationForOralQuestions(
8285
        $question_list_answers,
8286
        $origin,
8287
        $exe_id,
8288
        $user_info,
8289
        $url_email,
8290
        $teachers
8291
    ) {
8292
        // Email configuration settings
8293
        $courseCode = api_get_course_id();
8294
        $courseInfo = api_get_course_info($courseCode);
8295
        $oral_question_list = null;
8296
        foreach ($question_list_answers as $item) {
8297
            $question = $item['question'];
8298
            $file = $item['generated_oral_file'];
8299
            $answer = $item['answer'];
8300
            if ($answer == 0) {
8301
                $answer = '';
8302
            }
8303
            $answer_type = $item['answer_type'];
8304
            if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) {
8305
                if (!empty($file)) {
8306
                    $file = Display::url($file, $file);
8307
                }
8308
                $oral_question_list .= '<br />
8309
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
8310
                    <tr>
8311
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
8312
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
8313
                    </tr>
8314
                    <tr>
8315
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
8316
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
8317
                    </tr></table>';
8318
            }
8319
        }
8320
8321
        if (!empty($oral_question_list)) {
8322
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
8323
                    '.get_lang('AttemptDetails').' : <br /><br />
8324
                    <table>
8325
                        <tr>
8326
                            <td><em>'.get_lang('CourseName').'</em></td>
8327
                            <td>&nbsp;<b>#course#</b></td>
8328
                        </tr>
8329
                        <tr>
8330
                            <td>'.get_lang('TestAttempted').'</td>
8331
                            <td>&nbsp;#exercise#</td>
8332
                        </tr>
8333
                        <tr>
8334
                            <td>'.get_lang('StudentName').'</td>
8335
                            <td>&nbsp;#firstName# #lastName#</td>
8336
                        </tr>
8337
                        <tr>
8338
                            <td>'.get_lang('StudentEmail').'</td>
8339
                            <td>&nbsp;#mail#</td>
8340
                        </tr>
8341
                    </table>';
8342
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
8343
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
8344
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
8345
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
8346
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
8347
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
8348
8349
            if ($origin != 'learnpath') {
8350
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8351
            }
8352
            $msg1 = str_replace("#url#", $url_email, $msg);
8353
            $mail_content = $msg1;
8354
            $subject = get_lang('OralQuestionsAttempted');
8355
8356
            if (!empty($teachers)) {
8357
                foreach ($teachers as $user_id => $teacher_data) {
8358
                    MessageManager::send_message_simple(
8359
                        $user_id,
8360
                        $subject,
8361
                        $mail_content
8362
                    );
8363
                }
8364
            }
8365
        }
8366
    }
8367
8368
    /**
8369
     * Returns an array with the media list.
8370
     *
8371
     * @param array question list
8372
     *
8373
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
8374
     * <code>
8375
     * array (size=2)
8376
     *  999 =>
8377
     *    array (size=3)
8378
     *      0 => int 7
8379
     *      1 => int 6
8380
     *      2 => int 3254
8381
     *  100 =>
8382
     *   array (size=1)
8383
     *      0 => int 5
8384
     *  </code>
8385
     */
8386
    private function setMediaList($questionList)
8387
    {
8388
        $mediaList = [];
8389
        if (!empty($questionList)) {
8390
            foreach ($questionList as $questionId) {
8391
                $objQuestionTmp = Question::read($questionId, $this->course_id);
8392
                // If a media question exists
8393
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
8394
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
8395
                } else {
8396
                    if ($objQuestionTmp) {
8397
                        // Always the last item
8398
                        $mediaList[999][] = $objQuestionTmp->id;
8399
                    }
8400
                }
8401
            }
8402
        }
8403
        $this->mediaList = $mediaList;
8404
    }
8405
}
8406