Passed
Push — master ( 78cd0a...f41061 )
by Julito
10:08
created

Exercise::updateSound()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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