Passed
Push — master ( f437d8...92f70a )
by Julito
10:14
created

Exercise::getUnformattedTitle()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\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->selectNbrQuestions() : 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
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2760
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2761
2762
        $sql_where = '  AND
2763
                        orig_lp_id = 0 AND
2764
                        orig_lp_item_id = 0';
2765
2766
        // if we want to delete results from LP too
2767
        if ($cleanLpTests) {
2768
            $sql_where = '';
2769
        }
2770
2771
        // if we want to delete attempts before date $cleanResultBeforeDate
2772
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2773
2774
        if (!empty($cleanResultBeforeDate)) {
2775
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2776
            if (api_is_valid_date($cleanResultBeforeDate)) {
2777
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2778
            } else {
2779
                return 0;
2780
            }
2781
        }
2782
2783
        $sql = "SELECT exe_id
2784
            FROM $table_track_e_exercises
2785
            WHERE
2786
                c_id = ".api_get_course_int_id()." AND
2787
                exe_exo_id = ".$this->id." AND
2788
                session_id = ".api_get_session_id()." ".
2789
                $sql_where;
2790
2791
        $result = Database::query($sql);
2792
        $exe_list = Database::store_result($result);
2793
2794
        // deleting TRACK_E_ATTEMPT table
2795
        // check if exe in learning path or not
2796
        $i = 0;
2797
        if (is_array($exe_list) && count($exe_list) > 0) {
2798
            foreach ($exe_list as $item) {
2799
                $sql = "DELETE FROM $table_track_e_attempt
2800
                        WHERE exe_id = '".$item['exe_id']."'";
2801
                Database::query($sql);
2802
                $i++;
2803
            }
2804
        }
2805
2806
        $session_id = api_get_session_id();
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 = ".$session_id;
2813
        Database::query($sql);
2814
2815
        Event::addEvent(
2816
            LOG_EXERCISE_RESULT_DELETE,
2817
            LOG_EXERCISE_ID,
2818
            $this->id,
2819
            null,
2820
            null,
2821
            api_get_course_int_id(),
2822
            $session_id
2823
        );
2824
2825
        return $i;
2826
    }
2827
2828
    /**
2829
     * Copies an exercise (duplicate all questions and answers).
2830
     */
2831
    public function copyExercise()
2832
    {
2833
        $exerciseObject = $this;
2834
        $categories = $exerciseObject->getCategoriesInExercise();
2835
        // Get all questions no matter the order/category settings
2836
        $questionList = $exerciseObject->getQuestionOrderedList();
2837
        // Force the creation of a new exercise
2838
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
2839
        // Hides the new exercise
2840
        $exerciseObject->updateStatus(false);
2841
        $exerciseObject->updateId(0);
2842
        $exerciseObject->save();
2843
        $newId = $exerciseObject->selectId();
2844
        if ($newId && !empty($questionList)) {
2845
            // Question creation
2846
            foreach ($questionList as $oldQuestionId) {
2847
                $oldQuestionObj = Question::read($oldQuestionId);
2848
                $newQuestionId = $oldQuestionObj->duplicate();
2849
                if ($newQuestionId) {
2850
                    $newQuestionObj = Question::read($newQuestionId);
2851
                    if (isset($newQuestionObj) && $newQuestionObj) {
2852
                        $newQuestionObj->addToList($newId);
2853
                        if (!empty($oldQuestionObj->category)) {
2854
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
2855
                        }
2856
2857
                        // This should be moved to the duplicate function
2858
                        $newAnswerObj = new Answer($oldQuestionId);
2859
                        $newAnswerObj->read();
2860
                        $newAnswerObj->duplicate($newQuestionObj);
2861
                    }
2862
                }
2863
            }
2864
            if (!empty($categories)) {
2865
                $newCategoryList = [];
2866
                foreach ($categories as $category) {
2867
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
2868
                }
2869
                $exerciseObject->save_categories_in_exercise($newCategoryList);
2870
            }
2871
        }
2872
    }
2873
2874
    /**
2875
     * Changes the exercise status.
2876
     *
2877
     * @param string $status - exercise status
2878
     */
2879
    public function updateStatus($status)
2880
    {
2881
        $this->active = $status;
2882
    }
2883
2884
    /**
2885
     * @param int    $lp_id
2886
     * @param int    $lp_item_id
2887
     * @param int    $lp_item_view_id
2888
     * @param string $status
2889
     *
2890
     * @return array
2891
     */
2892
    public function get_stat_track_exercise_info(
2893
        $lp_id = 0,
2894
        $lp_item_id = 0,
2895
        $lp_item_view_id = 0,
2896
        $status = 'incomplete'
2897
    ) {
2898
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2899
        if (empty($lp_id)) {
2900
            $lp_id = 0;
2901
        }
2902
        if (empty($lp_item_id)) {
2903
            $lp_item_id = 0;
2904
        }
2905
        if (empty($lp_item_view_id)) {
2906
            $lp_item_view_id = 0;
2907
        }
2908
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
2909
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
2910
					   c_id                 = '.api_get_course_int_id().' AND
2911
					   status 				= '."'".Database::escape_string($status)."'".' AND
2912
					   orig_lp_id 			= '."'".$lp_id."'".' AND
2913
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
2914
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
2915
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
2916
2917
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2918
2919
        $result = Database::query($sql_track);
2920
        $new_array = [];
2921
        if (Database::num_rows($result) > 0) {
2922
            $new_array = Database::fetch_array($result, 'ASSOC');
2923
            $new_array['num_exe'] = Database::num_rows($result);
2924
        }
2925
2926
        return $new_array;
2927
    }
2928
2929
    /**
2930
     * Saves a test attempt.
2931
     *
2932
     * @param int $clock_expired_time clock_expired_time
2933
     * @param int  int lp id
2934
     * @param int  int lp item id
2935
     * @param int  int lp item_view id
2936
     * @param array $questionList
2937
     * @param float $weight
2938
     *
2939
     * @return int
2940
     */
2941
    public function save_stat_track_exercise_info(
2942
        $clock_expired_time = 0,
2943
        $safe_lp_id = 0,
2944
        $safe_lp_item_id = 0,
2945
        $safe_lp_item_view_id = 0,
2946
        $questionList = [],
2947
        $weight = 0
2948
    ) {
2949
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2950
        $safe_lp_id = intval($safe_lp_id);
2951
        $safe_lp_item_id = intval($safe_lp_item_id);
2952
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2953
2954
        if (empty($safe_lp_id)) {
2955
            $safe_lp_id = 0;
2956
        }
2957
        if (empty($safe_lp_item_id)) {
2958
            $safe_lp_item_id = 0;
2959
        }
2960
        if (empty($clock_expired_time)) {
2961
            $clock_expired_time = null;
2962
        }
2963
2964
        $questionList = array_map('intval', $questionList);
2965
2966
        $params = [
2967
            'exe_exo_id' => $this->id,
2968
            'exe_user_id' => api_get_user_id(),
2969
            'c_id' => api_get_course_int_id(),
2970
            'status' => 'incomplete',
2971
            'session_id' => api_get_session_id(),
2972
            'data_tracking' => implode(',', $questionList),
2973
            'start_date' => api_get_utc_datetime(),
2974
            'orig_lp_id' => $safe_lp_id,
2975
            'orig_lp_item_id' => $safe_lp_item_id,
2976
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
2977
            'max_score' => $weight,
2978
            'user_ip' => Database::escape_string(api_get_real_ip()),
2979
            'exe_date' => api_get_utc_datetime(),
2980
            'score' => 0,
2981
            'steps_counter' => 0,
2982
            'exe_duration' => 0,
2983
            'expired_time_control' => $clock_expired_time,
2984
            'questions_to_check' => '',
2985
        ];
2986
2987
        $id = Database::insert($track_exercises, $params);
2988
2989
        return $id;
2990
    }
2991
2992
    /**
2993
     * @param int    $question_id
2994
     * @param int    $questionNum
2995
     * @param array  $questions_in_media
2996
     * @param string $currentAnswer
2997
     * @param array  $myRemindList
2998
     *
2999
     * @return string
3000
     */
3001
    public function show_button(
3002
        $question_id,
3003
        $questionNum,
3004
        $questions_in_media = [],
3005
        $currentAnswer = '',
3006
        $myRemindList = []
3007
    ) {
3008
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3009
        $nbrQuestions = $this->getQuestionCount();
3010
        $buttonList = [];
3011
        $html = $label = '';
3012
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3013
3014
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
3015
            $urlTitle = get_lang('ContinueTest');
3016
            if ($questionNum == count($this->questionList)) {
3017
                $urlTitle = get_lang('EndTest');
3018
            }
3019
3020
            $html .= Display::url(
3021
                $urlTitle,
3022
                'exercise_submit_modal.php?'.http_build_query([
3023
                    'learnpath_id' => $safe_lp_id,
3024
                    'learnpath_item_id' => $safe_lp_item_id,
3025
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
3026
                    'origin' => $origin,
3027
                    'hotspot' => $hotspot_get,
3028
                    'nbrQuestions' => $nbrQuestions,
3029
                    'num' => $questionNum,
3030
                    'exerciseType' => $this->type,
3031
                    'exerciseId' => $this->id,
3032
                    'reminder' => empty($myRemindList) ? null : 2,
3033
                ]),
3034
                [
3035
                    'class' => 'ajax btn btn-default',
3036
                    'data-title' => $urlTitle,
3037
                    'data-size' => 'md',
3038
                ]
3039
            );
3040
            $html .= '<br />';
3041
        } else {
3042
            // User
3043
            if (api_is_allowed_to_session_edit()) {
3044
                $endReminderValue = false;
3045
                if (!empty($myRemindList)) {
3046
                    $endValue = end($myRemindList);
3047
                    if ($endValue == $question_id) {
3048
                        $endReminderValue = true;
3049
                    }
3050
                }
3051
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3052
                    if ($this->review_answers) {
3053
                        $label = get_lang('ReviewQuestions');
3054
                        $class = 'btn btn-success';
3055
                    } else {
3056
                        $label = get_lang('EndTest');
3057
                        $class = 'btn btn-warning';
3058
                    }
3059
                } else {
3060
                    $label = get_lang('NextQuestion');
3061
                    $class = 'btn btn-primary';
3062
                }
3063
                // used to select it with jquery
3064
                $class .= ' question-validate-btn';
3065
                if ($this->type == ONE_PER_PAGE) {
3066
                    if ($questionNum != 1) {
3067
                        if ($this->showPreviousButton()) {
3068
                            $prev_question = $questionNum - 2;
3069
                            $showPreview = true;
3070
                            if (!empty($myRemindList)) {
3071
                                $beforeId = null;
3072
                                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...
3073
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3074
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3075
                                        break;
3076
                                    }
3077
                                }
3078
3079
                                if (empty($beforeId)) {
3080
                                    $showPreview = false;
3081
                                } else {
3082
                                    $num = 0;
3083
                                    foreach ($this->questionList as $originalQuestionId) {
3084
                                        if ($originalQuestionId == $beforeId) {
3085
                                            break;
3086
                                        }
3087
                                        $num++;
3088
                                    }
3089
                                    $prev_question = $num;
3090
                                    //$question_id = $beforeId;
3091
                                }
3092
                            }
3093
3094
                            if ($showPreview) {
3095
                                $buttonList[] = Display::button(
3096
                                    'previous_question_and_save',
3097
                                    get_lang('PreviousQuestion'),
3098
                                    [
3099
                                        'type' => 'button',
3100
                                        'class' => 'btn btn-default',
3101
                                        'data-prev' => $prev_question,
3102
                                        'data-question' => $question_id,
3103
                                    ]
3104
                                );
3105
                            }
3106
                        }
3107
                    }
3108
3109
                    // Next question
3110
                    if (!empty($questions_in_media)) {
3111
                        $buttonList[] = Display::button(
3112
                            'save_question_list',
3113
                            $label,
3114
                            [
3115
                                'type' => 'button',
3116
                                'class' => $class,
3117
                                'data-list' => implode(",", $questions_in_media),
3118
                            ]
3119
                        );
3120
                    } else {
3121
                        $buttonList[] = Display::button(
3122
                            'save_now',
3123
                            $label,
3124
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3125
                        );
3126
                    }
3127
                    $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3128
3129
                    $html .= implode(PHP_EOL, $buttonList);
3130
                } else {
3131
                    if ($this->review_answers) {
3132
                        $all_label = get_lang('ReviewQuestions');
3133
                        $class = 'btn btn-success';
3134
                    } else {
3135
                        $all_label = get_lang('EndTest');
3136
                        $class = 'btn btn-warning';
3137
                    }
3138
                    // used to select it with jquery
3139
                    $class .= ' question-validate-btn';
3140
                    $buttonList[] = Display::button(
3141
                        'validate_all',
3142
                        $all_label,
3143
                        ['type' => 'button', 'class' => $class]
3144
                    );
3145
                    $buttonList[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_response']);
3146
                    $html .= implode(PHP_EOL, $buttonList);
3147
                }
3148
            }
3149
        }
3150
3151
        return $html;
3152
    }
3153
3154
    /**
3155
     * So the time control will work.
3156
     *
3157
     * @param string $time_left
3158
     *
3159
     * @return string
3160
     */
3161
    public function showTimeControlJS($time_left)
3162
    {
3163
        $time_left = (int) $time_left;
3164
        $script = "redirectExerciseToResult();";
3165
        if ($this->type == ALL_ON_ONE_PAGE) {
3166
            $script = "save_now_all('validate');";
3167
        }
3168
3169
        return "<script>
3170
            function openClockWarning() {
3171
                $('#clock_warning').dialog({
3172
                    modal:true,
3173
                    height:250,
3174
                    closeOnEscape: false,
3175
                    resizable: false,
3176
                    buttons: {
3177
                        '".addslashes(get_lang("EndTest"))."': function() {
3178
                            $('#clock_warning').dialog('close');
3179
                        }
3180
                    },
3181
                    close: function() {
3182
                        send_form();
3183
                    }
3184
                });
3185
                
3186
                $('#clock_warning').dialog('open');
3187
                $('#counter_to_redirect').epiclock({
3188
                    mode: $.epiclock.modes.countdown,
3189
                    offset: {seconds: 5},
3190
                    format: 's'
3191
                }).bind('timer', function () {
3192
                    send_form();
3193
                });
3194
            }
3195
3196
            function send_form() {
3197
                if ($('#exercise_form').length) {
3198
                    $script
3199
                } else {
3200
                    // In exercise_reminder.php
3201
                    final_submit();
3202
                }
3203
            }
3204
3205
            function onExpiredTimeExercise() {
3206
                $('#wrapper-clock').hide();
3207
                $('#expired-message-id').show();
3208
                // Fixes bug #5263
3209
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3210
                openClockWarning();
3211
            }
3212
3213
			$(document).ready(function() {
3214
				// time in seconds when using minutes there are some seconds lost
3215
                var time_left = parseInt(".$time_left.");
3216
                $('#exercise_clock_warning').epiclock({
3217
                    mode: $.epiclock.modes.countdown,
3218
                    offset: {seconds: time_left},
3219
                    format: 'x:i:s',
3220
                    renderer: 'minute'
3221
                }).bind('timer', function () {
3222
                    onExpiredTimeExercise();
3223
                });
3224
	       		$('#submit_save').click(function () {});
3225
	    });
3226
	    </script>";
3227
    }
3228
3229
    /**
3230
     * This function was originally found in the exercise_show.php.
3231
     *
3232
     * @param int    $exeId
3233
     * @param int    $questionId
3234
     * @param mixed  $choice                                    the user-selected option
3235
     * @param string $from                                      function is called from 'exercise_show' or 'exercise_result'
3236
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] = coordinates
3237
     * @param bool   $saved_results                             save results in the DB or just show the reponse
3238
     * @param bool   $from_database                             gets information from DB or from the current selection
3239
     * @param bool   $show_result                               show results or not
3240
     * @param int    $propagate_neg
3241
     * @param array  $hotspot_delineation_result
3242
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3243
     * @param bool   $updateResults
3244
     *
3245
     * @todo    reduce parameters of this function
3246
     *
3247
     * @return string html code
3248
     */
3249
    public function manage_answer(
3250
        $exeId,
3251
        $questionId,
3252
        $choice,
3253
        $from = 'exercise_show',
3254
        $exerciseResultCoordinates = [],
3255
        $saved_results = true,
3256
        $from_database = false,
3257
        $show_result = true,
3258
        $propagate_neg = 0,
3259
        $hotspot_delineation_result = [],
3260
        $showTotalScoreAndUserChoicesInLastAttempt = true,
3261
        $updateResults = false
3262
    ) {
3263
        $debug = false;
3264
        //needed in order to use in the exercise_attempt() for the time
3265
        global $learnpath_id, $learnpath_item_id;
3266
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3267
        $em = Database::getManager();
3268
        $feedback_type = $this->selectFeedbackType();
3269
        $results_disabled = $this->selectResultsDisabled();
3270
3271
        if ($debug) {
3272
            error_log("<------ manage_answer ------> ");
3273
            error_log('exe_id: '.$exeId);
3274
            error_log('$from:  '.$from);
3275
            error_log('$saved_results: '.intval($saved_results));
3276
            error_log('$from_database: '.intval($from_database));
3277
            error_log('$show_result: '.intval($show_result));
3278
            error_log('$propagate_neg: '.$propagate_neg);
3279
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3280
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3281
            error_log('$learnpath_id: '.$learnpath_id);
3282
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3283
            error_log('$choice: '.print_r($choice, 1));
3284
        }
3285
3286
        $final_overlap = 0;
3287
        $final_missing = 0;
3288
        $final_excess = 0;
3289
        $overlap_color = 0;
3290
        $missing_color = 0;
3291
        $excess_color = 0;
3292
        $threadhold1 = 0;
3293
        $threadhold2 = 0;
3294
        $threadhold3 = 0;
3295
        $arrques = null;
3296
        $arrans = null;
3297
        $studentChoice = null;
3298
        $expectedAnswer = '';
3299
        $calculatedChoice = '';
3300
        $calculatedStatus = '';
3301
        $questionId = (int) $questionId;
3302
        $exeId = (int) $exeId;
3303
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3304
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3305
3306
        // Creates a temporary Question object
3307
        $course_id = $this->course_id;
3308
        $objQuestionTmp = Question::read($questionId, $course_id);
3309
3310
        if ($objQuestionTmp === false) {
3311
            return false;
3312
        }
3313
3314
        $questionName = $objQuestionTmp->selectTitle();
3315
        $questionWeighting = $objQuestionTmp->selectWeighting();
3316
        $answerType = $objQuestionTmp->selectType();
3317
        $quesId = $objQuestionTmp->selectId();
3318
        $extra = $objQuestionTmp->extra;
3319
        $next = 1; //not for now
3320
        $totalWeighting = 0;
3321
        $totalScore = 0;
3322
3323
        // Extra information of the question
3324
        if ((
3325
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
3326
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
3327
            )
3328
            && !empty($extra)
3329
        ) {
3330
            $extra = explode(':', $extra);
3331
            if ($debug) {
3332
                error_log(print_r($extra, 1));
3333
            }
3334
            // Fixes problems with negatives values using intval
3335
3336
            $true_score = floatval(trim($extra[0]));
3337
            $false_score = floatval(trim($extra[1]));
3338
            $doubt_score = floatval(trim($extra[2]));
3339
        }
3340
3341
        // Construction of the Answer object
3342
        $objAnswerTmp = new Answer($questionId, $course_id);
3343
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3344
3345
        if ($debug) {
3346
            error_log('Count of answers: '.$nbrAnswers);
3347
            error_log('$answerType: '.$answerType);
3348
        }
3349
3350
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
3351
            $choiceTmp = $choice;
3352
            $choice = isset($choiceTmp['choice']) ? $choiceTmp['choice'] : '';
3353
            $choiceDegreeCertainty = isset($choiceTmp['choiceDegreeCertainty']) ? $choiceTmp['choiceDegreeCertainty'] : '';
3354
        }
3355
3356
        if ($answerType == FREE_ANSWER ||
3357
            $answerType == ORAL_EXPRESSION ||
3358
            $answerType == CALCULATED_ANSWER ||
3359
            $answerType == ANNOTATION
3360
        ) {
3361
            $nbrAnswers = 1;
3362
        }
3363
3364
        $generatedFile = '';
3365
        if ($answerType == ORAL_EXPRESSION) {
3366
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3367
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3368
3369
            $objQuestionTmp->initFile(
3370
                api_get_session_id(),
3371
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3372
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3373
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3374
            );
3375
3376
            // Probably this attempt came in an exercise all question by page
3377
            if ($feedback_type == 0) {
3378
                $objQuestionTmp->replaceWithRealExe($exeId);
3379
            }
3380
            $generatedFile = $objQuestionTmp->getFileUrl();
3381
        }
3382
3383
        $user_answer = '';
3384
        // Get answer list for matching
3385
        $sql = "SELECT id_auto, id, answer
3386
                FROM $table_ans
3387
                WHERE c_id = $course_id AND question_id = $questionId";
3388
        $res_answer = Database::query($sql);
3389
3390
        $answerMatching = [];
3391
        while ($real_answer = Database::fetch_array($res_answer)) {
3392
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3393
        }
3394
3395
        $real_answers = [];
3396
        $quiz_question_options = Question::readQuestionOption(
3397
            $questionId,
3398
            $course_id
3399
        );
3400
3401
        $organs_at_risk_hit = 0;
3402
        $questionScore = 0;
3403
        $answer_correct_array = [];
3404
        $orderedHotspots = [];
3405
3406
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3407
            $orderedHotspots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3408
                [
3409
                    'hotspotQuestionId' => $questionId,
3410
                    'course' => $course_id,
3411
                    'hotspotExeId' => $exeId,
3412
                ],
3413
                ['hotspotAnswerId' => 'ASC']
3414
            );
3415
        }
3416
        if ($debug) {
3417
            error_log('Start answer loop ');
3418
        }
3419
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3420
            $answer = $objAnswerTmp->selectAnswer($answerId);
3421
            $answerComment = $objAnswerTmp->selectComment($answerId);
3422
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3423
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3424
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3425
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3426
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3427
3428
            if ($debug) {
3429
                error_log("answer auto id: $answerAutoId ");
3430
                error_log("answer correct: $answerCorrect ");
3431
            }
3432
3433
            // Delineation
3434
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3435
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3436
3437
            switch ($answerType) {
3438
                case UNIQUE_ANSWER:
3439
                case UNIQUE_ANSWER_IMAGE:
3440
                case UNIQUE_ANSWER_NO_OPTION:
3441
                case READING_COMPREHENSION:
3442
                    if ($from_database) {
3443
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3444
                                WHERE
3445
                                    exe_id = '".$exeId."' AND
3446
                                    question_id= '".$questionId."'";
3447
                        $result = Database::query($sql);
3448
                        $choice = Database::result($result, 0, 'answer');
3449
3450
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3451
                        if ($studentChoice) {
3452
                            $questionScore += $answerWeighting;
3453
                            $totalScore += $answerWeighting;
3454
                        }
3455
                    } else {
3456
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3457
                        if ($studentChoice) {
3458
                            $questionScore += $answerWeighting;
3459
                            $totalScore += $answerWeighting;
3460
                        }
3461
                    }
3462
                    break;
3463
                case MULTIPLE_ANSWER_TRUE_FALSE:
3464
                    if ($from_database) {
3465
                        $choice = [];
3466
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3467
                                WHERE
3468
                                    exe_id = $exeId AND
3469
                                    question_id = ".$questionId;
3470
3471
                        $result = Database::query($sql);
3472
                        while ($row = Database::fetch_array($result)) {
3473
                            $values = explode(':', $row['answer']);
3474
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3475
                            $option = isset($values[1]) ? $values[1] : '';
3476
                            $choice[$my_answer_id] = $option;
3477
                        }
3478
                    }
3479
3480
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3481
                    if (!empty($studentChoice)) {
3482
                        if ($studentChoice == $answerCorrect) {
3483
                            $questionScore += $true_score;
3484
                        } else {
3485
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3486
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3487
                            ) {
3488
                                $questionScore += $doubt_score;
3489
                            } else {
3490
                                $questionScore += $false_score;
3491
                            }
3492
                        }
3493
                    } else {
3494
                        // If no result then the user just hit don't know
3495
                        $studentChoice = 3;
3496
                        $questionScore += $doubt_score;
3497
                    }
3498
                    $totalScore = $questionScore;
3499
                    break;
3500
                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
3501
                    if ($from_database) {
3502
                        $choice = [];
3503
                        $choiceDegreeCertainty = [];
3504
                        $sql = "SELECT answer 
3505
                            FROM $TBL_TRACK_ATTEMPT
3506
                            WHERE 
3507
                            exe_id = $exeId AND question_id = $questionId";
3508
3509
                        $result = Database::query($sql);
3510
                        while ($row = Database::fetch_array($result)) {
3511
                            $ind = $row['answer'];
3512
                            $values = explode(':', $ind);
3513
                            $myAnswerId = $values[0];
3514
                            $option = $values[1];
3515
                            $percent = $values[2];
3516
                            $choice[$myAnswerId] = $option;
3517
                            $choiceDegreeCertainty[$myAnswerId] = $percent;
3518
                        }
3519
                    }
3520
3521
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3522
                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ?
3523
                        $choiceDegreeCertainty[$answerAutoId] : null;
3524
3525
                    // student score update
3526
                    if (!empty($studentChoice)) {
3527
                        if ($studentChoice == $answerCorrect) {
3528
                            // correct answer and student is Unsure or PrettySur
3529
                            if (isset($quiz_question_options[$studentChoiceDegree]) &&
3530
                                $quiz_question_options[$studentChoiceDegree]['position'] >= 3 &&
3531
                                $quiz_question_options[$studentChoiceDegree]['position'] < 9
3532
                            ) {
3533
                                $questionScore += $true_score;
3534
                            } else {
3535
                                // student ignore correct answer
3536
                                $questionScore += $doubt_score;
3537
                            }
3538
                        } else {
3539
                            // false answer and student is Unsure or PrettySur
3540
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3541
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3542
                                $questionScore += $false_score;
3543
                            } else {
3544
                                // student ignore correct answer
3545
                                $questionScore += $doubt_score;
3546
                            }
3547
                        }
3548
                    }
3549
                    $totalScore = $questionScore;
3550
                    break;
3551
                case MULTIPLE_ANSWER: //2
3552
                    if ($from_database) {
3553
                        $choice = [];
3554
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3555
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3556
                        $resultans = Database::query($sql);
3557
                        while ($row = Database::fetch_array($resultans)) {
3558
                            $choice[$row['answer']] = 1;
3559
                        }
3560
3561
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3562
                        $real_answers[$answerId] = (bool) $studentChoice;
3563
3564
                        if ($studentChoice) {
3565
                            $questionScore += $answerWeighting;
3566
                        }
3567
                    } else {
3568
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3569
                        $real_answers[$answerId] = (bool) $studentChoice;
3570
3571
                        if (isset($studentChoice)) {
3572
                            $questionScore += $answerWeighting;
3573
                        }
3574
                    }
3575
                    $totalScore += $answerWeighting;
3576
3577
                    if ($debug) {
3578
                        error_log("studentChoice: $studentChoice");
3579
                    }
3580
                    break;
3581
                case GLOBAL_MULTIPLE_ANSWER:
3582
                    if ($from_database) {
3583
                        $choice = [];
3584
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3585
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3586
                        $resultans = Database::query($sql);
3587
                        while ($row = Database::fetch_array($resultans)) {
3588
                            $choice[$row['answer']] = 1;
3589
                        }
3590
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3591
                        $real_answers[$answerId] = (bool) $studentChoice;
3592
                        if ($studentChoice) {
3593
                            $questionScore += $answerWeighting;
3594
                        }
3595
                    } else {
3596
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3597
                        if (isset($studentChoice)) {
3598
                            $questionScore += $answerWeighting;
3599
                        }
3600
                        $real_answers[$answerId] = (bool) $studentChoice;
3601
                    }
3602
                    $totalScore += $answerWeighting;
3603
                    if ($debug) {
3604
                        error_log("studentChoice: $studentChoice");
3605
                    }
3606
                    break;
3607
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3608
                    if ($from_database) {
3609
                        $choice = [];
3610
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3611
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3612
                        $resultans = Database::query($sql);
3613
                        while ($row = Database::fetch_array($resultans)) {
3614
                            $result = explode(':', $row['answer']);
3615
                            if (isset($result[0])) {
3616
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3617
                                $option = isset($result[1]) ? $result[1] : '';
3618
                                $choice[$my_answer_id] = $option;
3619
                            }
3620
                        }
3621
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3622
3623
                        $real_answers[$answerId] = false;
3624
                        if ($answerCorrect == $studentChoice) {
3625
                            $real_answers[$answerId] = true;
3626
                        }
3627
                    } else {
3628
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3629
                        $real_answers[$answerId] = false;
3630
                        if ($answerCorrect == $studentChoice) {
3631
                            $real_answers[$answerId] = true;
3632
                        }
3633
                    }
3634
                    break;
3635
                case MULTIPLE_ANSWER_COMBINATION:
3636
                    if ($from_database) {
3637
                        $choice = [];
3638
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3639
                                WHERE exe_id = $exeId AND question_id= $questionId";
3640
                        $resultans = Database::query($sql);
3641
                        while ($row = Database::fetch_array($resultans)) {
3642
                            $choice[$row['answer']] = 1;
3643
                        }
3644
3645
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3646
                        if ($answerCorrect == 1) {
3647
                            $real_answers[$answerId] = false;
3648
                            if ($studentChoice) {
3649
                                $real_answers[$answerId] = true;
3650
                            }
3651
                        } else {
3652
                            $real_answers[$answerId] = true;
3653
                            if ($studentChoice) {
3654
                                $real_answers[$answerId] = false;
3655
                            }
3656
                        }
3657
                    } else {
3658
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3659
                        if ($answerCorrect == 1) {
3660
                            $real_answers[$answerId] = false;
3661
                            if ($studentChoice) {
3662
                                $real_answers[$answerId] = true;
3663
                            }
3664
                        } else {
3665
                            $real_answers[$answerId] = true;
3666
                            if ($studentChoice) {
3667
                                $real_answers[$answerId] = false;
3668
                            }
3669
                        }
3670
                    }
3671
                    break;
3672
                case FILL_IN_BLANKS:
3673
                    $str = '';
3674
                    $answerFromDatabase = '';
3675
                    if ($from_database) {
3676
                        $sql = "SELECT answer
3677
                                FROM $TBL_TRACK_ATTEMPT
3678
                                WHERE
3679
                                    exe_id = $exeId AND
3680
                                    question_id= ".intval($questionId);
3681
                        $result = Database::query($sql);
3682
                        if ($debug) {
3683
                            error_log($sql);
3684
                        }
3685
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3686
                    }
3687
3688
                    // if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3689
                    if (false) {
3690
                        // the question is encoded like this
3691
                        // [A] B [C] D [E] F::10,10,10@1
3692
                        // number 1 before the "@" means that is a switchable fill in blank question
3693
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3694
                        // means that is a normal fill blank question
3695
                        // first we explode the "::"
3696
                        $pre_array = explode('::', $answer);
3697
3698
                        // is switchable fill blank or not
3699
                        $last = count($pre_array) - 1;
3700
                        $is_set_switchable = explode('@', $pre_array[$last]);
3701
                        $switchable_answer_set = false;
3702
                        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3703
                            $switchable_answer_set = true;
3704
                        }
3705
                        $answer = '';
3706
                        for ($k = 0; $k < $last; $k++) {
3707
                            $answer .= $pre_array[$k];
3708
                        }
3709
                        // splits weightings that are joined with a comma
3710
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3711
                        // we save the answer because it will be modified
3712
                        $temp = $answer;
3713
                        $answer = '';
3714
                        $j = 0;
3715
                        //initialise answer tags
3716
                        $user_tags = $correct_tags = $real_text = [];
3717
                        // the loop will stop at the end of the text
3718
                        while (1) {
3719
                            // quits the loop if there are no more blanks (detect '[')
3720
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3721
                                // adds the end of the text
3722
                                $answer = $temp;
3723
                                $real_text[] = $answer;
3724
                                break; //no more "blanks", quit the loop
3725
                            }
3726
                            // adds the piece of text that is before the blank
3727
                            //and ends with '[' into a general storage array
3728
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3729
                            $answer .= api_substr($temp, 0, $pos + 1);
3730
                            //take the string remaining (after the last "[" we found)
3731
                            $temp = api_substr($temp, $pos + 1);
3732
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3733
                            if (($pos = api_strpos($temp, ']')) === false) {
3734
                                // adds the end of the text
3735
                                $answer .= $temp;
3736
                                break;
3737
                            }
3738
                            if ($from_database) {
3739
                                $str = $answerFromDatabase;
3740
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3741
                                $str = str_replace('\r\n', '', $str);
3742
3743
                                $choice = $arr[1];
3744
                                if (isset($choice[$j])) {
3745
                                    $tmp = api_strrpos($choice[$j], ' / ');
3746
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3747
                                    $choice[$j] = trim($choice[$j]);
3748
                                    // Needed to let characters ' and " to work as part of an answer
3749
                                    $choice[$j] = stripslashes($choice[$j]);
3750
                                } else {
3751
                                    $choice[$j] = null;
3752
                                }
3753
                            } else {
3754
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3755
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3756
                            }
3757
3758
                            $user_tags[] = $choice[$j];
3759
                            // Put the contents of the [] answer tag into correct_tags[]
3760
                            $correct_tags[] = api_substr($temp, 0, $pos);
3761
                            $j++;
3762
                            $temp = api_substr($temp, $pos + 1);
3763
                        }
3764
                        $answer = '';
3765
                        $real_correct_tags = $correct_tags;
3766
                        $chosen_list = [];
3767
3768
                        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...
3769
                            if ($i == 0) {
3770
                                $answer .= $real_text[0];
3771
                            }
3772
                            if (!$switchable_answer_set) {
3773
                                // Needed to parse ' and " characters
3774
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3775
                                if ($correct_tags[$i] == $user_tags[$i]) {
3776
                                    // gives the related weighting to the student
3777
                                    $questionScore += $answerWeighting[$i];
3778
                                    // increments total score
3779
                                    $totalScore += $answerWeighting[$i];
3780
                                    // adds the word in green at the end of the string
3781
                                    $answer .= $correct_tags[$i];
3782
                                } elseif (!empty($user_tags[$i])) {
3783
                                    // else if the word entered by the student IS NOT the same as
3784
                                    // the one defined by the professor
3785
                                    // adds the word in red at the end of the string, and strikes it
3786
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3787
                                } else {
3788
                                    // adds a tabulation if no word has been typed by the student
3789
                                    $answer .= ''; // remove &nbsp; that causes issue
3790
                                }
3791
                            } else {
3792
                                // switchable fill in the blanks
3793
                                if (in_array($user_tags[$i], $correct_tags)) {
3794
                                    $chosen_list[] = $user_tags[$i];
3795
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3796
                                    // gives the related weighting to the student
3797
                                    $questionScore += $answerWeighting[$i];
3798
                                    // increments total score
3799
                                    $totalScore += $answerWeighting[$i];
3800
                                    // adds the word in green at the end of the string
3801
                                    $answer .= $user_tags[$i];
3802
                                } elseif (!empty($user_tags[$i])) {
3803
                                    // else if the word entered by the student IS NOT the same
3804
                                    // as the one defined by the professor
3805
                                    // adds the word in red at the end of the string, and strikes it
3806
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3807
                                } else {
3808
                                    // adds a tabulation if no word has been typed by the student
3809
                                    $answer .= ''; // remove &nbsp; that causes issue
3810
                                }
3811
                            }
3812
3813
                            // adds the correct word, followed by ] to close the blank
3814
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3815
                            if (isset($real_text[$i + 1])) {
3816
                                $answer .= $real_text[$i + 1];
3817
                            }
3818
                        }
3819
                    } else {
3820
                        // insert the student result in the track_e_attempt table, field answer
3821
                        // $answer is the answer like in the c_quiz_answer table for the question
3822
                        // student data are choice[]
3823
                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
3824
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3825
                        $answerWeighting = $listCorrectAnswers['weighting'];
3826
                        // user choices is an array $choice
3827
3828
                        // get existing user data in n the BDD
3829
                        if ($from_database) {
3830
                            $listStudentResults = FillBlanks::getAnswerInfo(
3831
                                $answerFromDatabase,
3832
                                true
3833
                            );
3834
                            $choice = $listStudentResults['student_answer'];
3835
                        }
3836
3837
                        // loop other all blanks words
3838
                        if (!$switchableAnswerSet) {
3839
                            // not switchable answer, must be in the same place than teacher order
3840
                            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...
3841
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3842
                                $correctAnswer = $listCorrectAnswers['words'][$i];
3843
3844
                                if ($debug) {
3845
                                    error_log("Student answer: $i");
3846
                                    error_log($studentAnswer);
3847
                                }
3848
3849
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3850
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3851
                                // ENT_QUOTES is used in order to transform ' to &#039;
3852
                                if (!$from_database) {
3853
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3854
                                    if ($debug) {
3855
                                        error_log("Student answer cleaned:");
3856
                                        error_log($studentAnswer);
3857
                                    }
3858
                                }
3859
3860
                                $isAnswerCorrect = 0;
3861
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
3862
                                    // gives the related weighting to the student
3863
                                    $questionScore += $answerWeighting[$i];
3864
                                    // increments total score
3865
                                    $totalScore += $answerWeighting[$i];
3866
                                    $isAnswerCorrect = 1;
3867
                                }
3868
                                if ($debug) {
3869
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
3870
                                }
3871
3872
                                $studentAnswerToShow = $studentAnswer;
3873
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3874
                                if ($debug) {
3875
                                    error_log("Fill in blank type: $type");
3876
                                }
3877
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3878
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3879
                                    if ($studentAnswer != '') {
3880
                                        foreach ($listMenu as $item) {
3881
                                            if (sha1($item) == $studentAnswer) {
3882
                                                $studentAnswerToShow = $item;
3883
                                            }
3884
                                        }
3885
                                    }
3886
                                }
3887
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3888
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
3889
                            }
3890
                        } else {
3891
                            // switchable answer
3892
                            $listStudentAnswerTemp = $choice;
3893
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
3894
3895
                            // for every teacher answer, check if there is a student answer
3896
                            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...
3897
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3898
                                $studentAnswerToShow = $studentAnswer;
3899
3900
                                if ($debug) {
3901
                                    error_log("Student answer: $i");
3902
                                    error_log($studentAnswer);
3903
                                }
3904
3905
                                $found = false;
3906
                                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...
3907
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3908
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3909
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3910
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3911
                                        if (!empty($studentAnswer)) {
3912
                                            foreach ($listMenu as $key => $item) {
3913
                                                if ($key == $correctAnswer) {
3914
                                                    $studentAnswerToShow = $item;
3915
                                                    break;
3916
                                                }
3917
                                            }
3918
                                        }
3919
                                    }
3920
3921
                                    if (!$found) {
3922
                                        if (FillBlanks::isStudentAnswerGood(
3923
                                            $studentAnswer,
3924
                                            $correctAnswer,
3925
                                            $from_database
3926
                                        )
3927
                                        ) {
3928
                                            $questionScore += $answerWeighting[$i];
3929
                                            $totalScore += $answerWeighting[$i];
3930
                                            $listTeacherAnswerTemp[$j] = '';
3931
                                            $found = true;
3932
                                        }
3933
                                    }
3934
                                }
3935
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3936
                                if (!$found) {
3937
                                    $listCorrectAnswers['student_score'][$i] = 0;
3938
                                } else {
3939
                                    $listCorrectAnswers['student_score'][$i] = 1;
3940
                                }
3941
                            }
3942
                        }
3943
                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
3944
                    }
3945
                    break;
3946
                case CALCULATED_ANSWER:
3947
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
3948
                    if (!empty($calculatedAnswerList)) {
3949
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
3950
                        $preArray = explode('@@', $answer);
3951
                        $last = count($preArray) - 1;
3952
                        $answer = '';
3953
                        for ($k = 0; $k < $last; $k++) {
3954
                            $answer .= $preArray[$k];
3955
                        }
3956
                        $answerWeighting = [$answerWeighting];
3957
                        // we save the answer because it will be modified
3958
                        $temp = $answer;
3959
                        $answer = '';
3960
                        $j = 0;
3961
                        // initialise answer tags
3962
                        $userTags = $correctTags = $realText = [];
3963
                        // the loop will stop at the end of the text
3964
                        while (1) {
3965
                            // quits the loop if there are no more blanks (detect '[')
3966
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3967
                                // adds the end of the text
3968
                                $answer = $temp;
3969
                                $realText[] = $answer;
3970
                                break; //no more "blanks", quit the loop
3971
                            }
3972
                            // adds the piece of text that is before the blank
3973
                            // and ends with '[' into a general storage array
3974
                            $realText[] = api_substr($temp, 0, $pos + 1);
3975
                            $answer .= api_substr($temp, 0, $pos + 1);
3976
                            // take the string remaining (after the last "[" we found)
3977
                            $temp = api_substr($temp, $pos + 1);
3978
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3979
                            if (($pos = api_strpos($temp, ']')) === false) {
3980
                                // adds the end of the text
3981
                                $answer .= $temp;
3982
                                break;
3983
                            }
3984
3985
                            if ($from_database) {
3986
                                $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3987
                                    WHERE
3988
                                        exe_id = '".$exeId."' AND
3989
                                        question_id = ".intval($questionId);
3990
                                $result = Database::query($sql);
3991
                                $str = Database::result($result, 0, 'answer');
3992
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3993
                                $str = str_replace('\r\n', '', $str);
3994
                                $choice = $arr[1];
3995
                                if (isset($choice[$j])) {
3996
                                    $tmp = api_strrpos($choice[$j], ' / ');
3997
3998
                                    if ($tmp) {
3999
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4000
                                    } else {
4001
                                        $tmp = ltrim($tmp, '[');
4002
                                        $tmp = rtrim($tmp, ']');
4003
                                    }
4004
4005
                                    $choice[$j] = trim($choice[$j]);
4006
                                    // Needed to let characters ' and " to work as part of an answer
4007
                                    $choice[$j] = stripslashes($choice[$j]);
4008
                                } else {
4009
                                    $choice[$j] = null;
4010
                                }
4011
                            } else {
4012
                                // This value is the user input not escaped while correct answer is escaped by ckeditor
4013
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4014
                            }
4015
                            $userTags[] = $choice[$j];
4016
                            // put the contents of the [] answer tag into correct_tags[]
4017
                            $correctTags[] = api_substr($temp, 0, $pos);
4018
                            $j++;
4019
                            $temp = api_substr($temp, $pos + 1);
4020
                        }
4021
                        $answer = '';
4022
                        $realCorrectTags = $correctTags;
4023
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4024
                        $expectedAnswer = '';
4025
                        $calculatedChoice = '';
4026
4027
                        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...
4028
                            if ($i == 0) {
4029
                                $answer .= $realText[0];
4030
                            }
4031
                            // Needed to parse ' and " characters
4032
                            $userTags[$i] = stripslashes($userTags[$i]);
4033
                            if ($correctTags[$i] == $userTags[$i]) {
4034
                                // gives the related weighting to the student
4035
                                $questionScore += $answerWeighting[$i];
4036
                                // increments total score
4037
                                $totalScore += $answerWeighting[$i];
4038
                                // adds the word in green at the end of the string
4039
                                $answer .= $correctTags[$i];
4040
                                $calculatedChoice = $correctTags[$i];
4041
                            } elseif (!empty($userTags[$i])) {
4042
                                // else if the word entered by the student IS NOT the same as
4043
                                // the one defined by the professor
4044
                                // adds the word in red at the end of the string, and strikes it
4045
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4046
                                $calculatedChoice = $userTags[$i];
4047
                            } else {
4048
                                // adds a tabulation if no word has been typed by the student
4049
                                $answer .= ''; // remove &nbsp; that causes issue
4050
                            }
4051
                            // adds the correct word, followed by ] to close the blank
4052
4053
                            if ($this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM) {
4054
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4055
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4056
                                $expectedAnswer = $realCorrectTags[$i];
4057
                            }
4058
                            $answer .= ']';
4059
4060
                            if (isset($realText[$i + 1])) {
4061
                                $answer .= $realText[$i + 1];
4062
                            }
4063
                        }
4064
                    } else {
4065
                        if ($from_database) {
4066
                            $sql = "SELECT *
4067
                                FROM $TBL_TRACK_ATTEMPT
4068
                                WHERE
4069
                                    exe_id = $exeId AND
4070
                                    question_id= ".intval($questionId);
4071
                            $result = Database::query($sql);
4072
                            $resultData = Database::fetch_array($result, 'ASSOC');
4073
                            $answer = $resultData['answer'];
4074
                            $questionScore = $resultData['marks'];
4075
                        }
4076
                    }
4077
                    break;
4078
                case FREE_ANSWER:
4079
                    if ($from_database) {
4080
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4081
                                 WHERE 
4082
                                    exe_id = $exeId AND 
4083
                                    question_id= ".$questionId;
4084
                        $result = Database::query($sql);
4085
                        $data = Database::fetch_array($result);
4086
4087
                        $choice = $data['answer'];
4088
                        $choice = str_replace('\r\n', '', $choice);
4089
                        $choice = stripslashes($choice);
4090
                        $questionScore = $data['marks'];
4091
4092
                        if ($questionScore == -1) {
4093
                            $totalScore += 0;
4094
                        } else {
4095
                            $totalScore += $questionScore;
4096
                        }
4097
                        if ($questionScore == '') {
4098
                            $questionScore = 0;
4099
                        }
4100
                        $arrques = $questionName;
4101
                        $arrans = $choice;
4102
                    } else {
4103
                        $studentChoice = $choice;
4104
                        if ($studentChoice) {
4105
                            //Fixing negative puntation see #2193
4106
                            $questionScore = 0;
4107
                            $totalScore += 0;
4108
                        }
4109
                    }
4110
                    break;
4111
                case ORAL_EXPRESSION:
4112
                    if ($from_database) {
4113
                        $query = "SELECT answer, marks 
4114
                                  FROM $TBL_TRACK_ATTEMPT
4115
                                  WHERE 
4116
                                        exe_id = $exeId AND 
4117
                                        question_id = $questionId
4118
                                 ";
4119
                        $resq = Database::query($query);
4120
                        $row = Database::fetch_assoc($resq);
4121
                        $choice = $row['answer'];
4122
                        $choice = str_replace('\r\n', '', $choice);
4123
                        $choice = stripslashes($choice);
4124
                        $questionScore = $row['marks'];
4125
                        if ($questionScore == -1) {
4126
                            $totalScore += 0;
4127
                        } else {
4128
                            $totalScore += $questionScore;
4129
                        }
4130
                        $arrques = $questionName;
4131
                        $arrans = $choice;
4132
                    } else {
4133
                        $studentChoice = $choice;
4134
                        if ($studentChoice) {
4135
                            //Fixing negative puntation see #2193
4136
                            $questionScore = 0;
4137
                            $totalScore += 0;
4138
                        }
4139
                    }
4140
                    break;
4141
                case DRAGGABLE:
4142
                case MATCHING_DRAGGABLE:
4143
                case MATCHING:
4144
                    if ($from_database) {
4145
                        $sql = "SELECT id, answer, id_auto
4146
                                FROM $table_ans
4147
                                WHERE
4148
                                    c_id = $course_id AND
4149
                                    question_id = $questionId AND
4150
                                    correct = 0
4151
                                ";
4152
                        $result = Database::query($sql);
4153
                        // Getting the real answer
4154
                        $real_list = [];
4155
                        while ($realAnswer = Database::fetch_array($result)) {
4156
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4157
                        }
4158
4159
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4160
                                FROM $table_ans
4161
                                WHERE
4162
                                    c_id = $course_id AND
4163
                                    question_id = $questionId AND
4164
                                    correct <> 0
4165
                                ORDER BY id_auto";
4166
                        $result = Database::query($sql);
4167
                        $options = [];
4168
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4169
                            $options[] = $row;
4170
                        }
4171
4172
                        $questionScore = 0;
4173
                        $counterAnswer = 1;
4174
                        foreach ($options as $a_answers) {
4175
                            $i_answer_id = $a_answers['id']; //3
4176
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4177
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4178
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4179
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4180
                                    WHERE
4181
                                        exe_id = '$exeId' AND
4182
                                        question_id = '$questionId' AND
4183
                                        position = '$i_answer_id_auto'";
4184
                            $result = Database::query($sql);
4185
                            $s_user_answer = 0;
4186
                            if (Database::num_rows($result) > 0) {
4187
                                //  rich - good looking
4188
                                $s_user_answer = Database::result($result, 0, 0);
4189
                            }
4190
                            $i_answerWeighting = $a_answers['ponderation'];
4191
                            $user_answer = '';
4192
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4193
                            if (!empty($s_user_answer)) {
4194
                                if ($answerType == DRAGGABLE) {
4195
                                    if ($s_user_answer == $i_answer_correct_answer) {
4196
                                        $questionScore += $i_answerWeighting;
4197
                                        $totalScore += $i_answerWeighting;
4198
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4199
                                        if ($this->showExpectedChoice()) {
4200
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4201
                                        }
4202
                                        $status = Display::label(get_lang('Correct'), 'success');
4203
                                    } else {
4204
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4205
                                        if ($this->showExpectedChoice()) {
4206
                                            $data = $options[$real_list[$s_user_answer] - 1];
4207
                                            $user_answer = $data['answer'];
4208
                                        }
4209
                                    }
4210
                                } else {
4211
                                    if ($s_user_answer == $i_answer_correct_answer) {
4212
                                        $questionScore += $i_answerWeighting;
4213
                                        $totalScore += $i_answerWeighting;
4214
                                        $status = Display::label(get_lang('Correct'), 'success');
4215
4216
                                        // Try with id
4217
                                        if (isset($real_list[$i_answer_id])) {
4218
                                            $user_answer = Display::span(
4219
                                                $real_list[$i_answer_id],
4220
                                                ['style' => 'color: #008000; font-weight: bold;']
4221
                                            );
4222
                                        }
4223
4224
                                        // Try with $i_answer_id_auto
4225
                                        if (empty($user_answer)) {
4226
                                            if (isset($real_list[$i_answer_id_auto])) {
4227
                                                $user_answer = Display::span(
4228
                                                    $real_list[$i_answer_id_auto],
4229
                                                    ['style' => 'color: #008000; font-weight: bold;']
4230
                                                );
4231
                                            }
4232
                                        }
4233
4234
                                        if (isset($real_list[$i_answer_correct_answer])) {
4235
                                            $user_answer = Display::span(
4236
                                                $real_list[$i_answer_correct_answer],
4237
                                                ['style' => 'color: #008000; font-weight: bold;']
4238
                                            );
4239
                                        }
4240
                                    } else {
4241
                                        $user_answer = Display::span(
4242
                                            $real_list[$s_user_answer],
4243
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4244
                                        );
4245
                                        if ($this->showExpectedChoice()) {
4246
                                            if (isset($real_list[$s_user_answer])) {
4247
                                                $user_answer = Display::span($real_list[$s_user_answer]);
4248
                                            }
4249
                                        }
4250
                                    }
4251
                                }
4252
                            } elseif ($answerType == DRAGGABLE) {
4253
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4254
                                if ($this->showExpectedChoice()) {
4255
                                    $user_answer = '';
4256
                                }
4257
                            } else {
4258
                                $user_answer = Display::span(
4259
                                    get_lang('Incorrect').' &nbsp;',
4260
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4261
                                );
4262
                                if ($this->showExpectedChoice()) {
4263
                                    $user_answer = '';
4264
                                }
4265
                            }
4266
4267
                            if ($results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
4268
                                if ($s_user_answer != $i_answer_correct_answer) {
4269
                                    continue;
4270
                                }
4271
                            }
4272
4273
                            if ($show_result) {
4274
                                if ($this->showExpectedChoice() == false &&
4275
                                    $showTotalScoreAndUserChoicesInLastAttempt === false
4276
                                ) {
4277
                                    $user_answer = '';
4278
                                }
4279
                                switch ($answerType) {
4280
                                    case MATCHING:
4281
                                    case MATCHING_DRAGGABLE:
4282
                                        echo '<tr>';
4283
                                        if ($this->showExpectedChoice()) {
4284
                                            echo '<td>'.$s_answer_label.'</td>';
4285
                                            echo '<td>'.$user_answer.'</td>';
4286
                                            echo '<td>';
4287
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4288
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4289
                                                    $showTotalScoreAndUserChoicesInLastAttempt == true
4290
                                                ) {
4291
                                                    echo Display::span(
4292
                                                        $real_list[$i_answer_correct_answer]
4293
                                                    );
4294
                                                }
4295
                                            }
4296
                                            echo '</td>';
4297
                                            echo '<td>'.$status.'</td>';
4298
                                        } else {
4299
                                            echo '<td>'.$s_answer_label.'</td>';
4300
                                            echo '<td>'.$user_answer.'</td>';
4301
                                            echo '<td>';
4302
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4303
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4304
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4305
                                                ) {
4306
                                                    echo Display::span(
4307
                                                        $real_list[$i_answer_correct_answer],
4308
                                                        ['style' => 'color: #008000; font-weight: bold;']
4309
                                                    );
4310
                                                }
4311
                                            }
4312
                                            echo '</td>';
4313
                                        }
4314
                                        echo '</tr>';
4315
                                        break;
4316
                                    case DRAGGABLE:
4317
                                        if ($showTotalScoreAndUserChoicesInLastAttempt == false) {
4318
                                            $s_answer_label = '';
4319
                                        }
4320
                                        echo '<tr>';
4321
                                        if ($this->showExpectedChoice()) {
4322
                                            echo '<td>'.$user_answer.'</td>';
4323
                                            echo '<td>'.$s_answer_label.'</td>';
4324
                                            echo '<td>'.$status.'</td>';
4325
                                        } else {
4326
                                            echo '<td>'.$s_answer_label.'</td>';
4327
                                            echo '<td>'.$user_answer.'</td>';
4328
                                            echo '<td>';
4329
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4330
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4331
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4332
                                                ) {
4333
                                                    echo Display::span(
4334
                                                        $real_list[$i_answer_correct_answer],
4335
                                                        ['style' => 'color: #008000; font-weight: bold;']
4336
                                                    );
4337
                                                }
4338
                                            }
4339
                                            echo '</td>';
4340
                                        }
4341
                                        echo '</tr>';
4342
                                        break;
4343
                                }
4344
                            }
4345
                            $counterAnswer++;
4346
                        }
4347
                        break 2; // break the switch and the "for" condition
4348
                    } else {
4349
                        if ($answerCorrect) {
4350
                            if (isset($choice[$answerAutoId]) &&
4351
                                $answerCorrect == $choice[$answerAutoId]
4352
                            ) {
4353
                                $questionScore += $answerWeighting;
4354
                                $totalScore += $answerWeighting;
4355
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4356
                            } else {
4357
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4358
                                    $user_answer = Display::span(
4359
                                        $answerMatching[$choice[$answerAutoId]],
4360
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4361
                                    );
4362
                                }
4363
                            }
4364
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4365
                        }
4366
                    }
4367
                    break;
4368
                case HOT_SPOT:
4369
                    if ($from_database) {
4370
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4371
                        // Check auto id
4372
                        $sql = "SELECT hotspot_correct
4373
                                FROM $TBL_TRACK_HOTSPOT
4374
                                WHERE
4375
                                    hotspot_exe_id = $exeId AND
4376
                                    hotspot_question_id= $questionId AND
4377
                                    hotspot_answer_id = ".intval($answerAutoId)."
4378
                                ORDER BY hotspot_id ASC";
4379
                        $result = Database::query($sql);
4380
                        if (Database::num_rows($result)) {
4381
                            $studentChoice = Database::result(
4382
                                $result,
4383
                                0,
4384
                                'hotspot_correct'
4385
                            );
4386
4387
                            if ($studentChoice) {
4388
                                $questionScore += $answerWeighting;
4389
                                $totalScore += $answerWeighting;
4390
                            }
4391
                        } else {
4392
                            // If answer.id is different:
4393
                            $sql = "SELECT hotspot_correct
4394
                                FROM $TBL_TRACK_HOTSPOT
4395
                                WHERE
4396
                                    hotspot_exe_id = $exeId AND
4397
                                    hotspot_question_id= $questionId AND
4398
                                    hotspot_answer_id = ".intval($answerId)."
4399
                                ORDER BY hotspot_id ASC";
4400
                            $result = Database::query($sql);
4401
4402
                            if (Database::num_rows($result)) {
4403
                                $studentChoice = Database::result(
4404
                                    $result,
4405
                                    0,
4406
                                    'hotspot_correct'
4407
                                );
4408
4409
                                if ($studentChoice) {
4410
                                    $questionScore += $answerWeighting;
4411
                                    $totalScore += $answerWeighting;
4412
                                }
4413
                            } else {
4414
                                // check answer.iid
4415
                                if (!empty($answerIid)) {
4416
                                    $sql = "SELECT hotspot_correct
4417
                                            FROM $TBL_TRACK_HOTSPOT
4418
                                            WHERE
4419
                                                hotspot_exe_id = $exeId AND
4420
                                                hotspot_question_id= $questionId AND
4421
                                                hotspot_answer_id = ".intval($answerIid)."
4422
                                            ORDER BY hotspot_id ASC";
4423
                                    $result = Database::query($sql);
4424
4425
                                    $studentChoice = Database::result(
4426
                                        $result,
4427
                                        0,
4428
                                        'hotspot_correct'
4429
                                    );
4430
4431
                                    if ($studentChoice) {
4432
                                        $questionScore += $answerWeighting;
4433
                                        $totalScore += $answerWeighting;
4434
                                    }
4435
                                }
4436
                            }
4437
                        }
4438
                    } else {
4439
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4440
                            $choice[$answerAutoId] = 0;
4441
                            $choice[$answerIid] = 0;
4442
                        } else {
4443
                            $studentChoice = $choice[$answerAutoId];
4444
                            if (empty($studentChoice)) {
4445
                                $studentChoice = $choice[$answerIid];
4446
                            }
4447
                            $choiceIsValid = false;
4448
                            if (!empty($studentChoice)) {
4449
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4450
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4451
                                $choicePoint = Geometry::decodePoint($studentChoice);
4452
4453
                                switch ($hotspotType) {
4454
                                    case 'square':
4455
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4456
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4457
                                        break;
4458
                                    case 'circle':
4459
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4460
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4461
                                        break;
4462
                                    case 'poly':
4463
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4464
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4465
                                        break;
4466
                                }
4467
                            }
4468
4469
                            $choice[$answerAutoId] = 0;
4470
                            if ($choiceIsValid) {
4471
                                $questionScore += $answerWeighting;
4472
                                $totalScore += $answerWeighting;
4473
                                $choice[$answerAutoId] = 1;
4474
                                $choice[$answerIid] = 1;
4475
                            }
4476
                        }
4477
                    }
4478
                    break;
4479
                case HOT_SPOT_ORDER:
4480
                    // @todo never added to chamilo
4481
                    // for hotspot with fixed order
4482
                    $studentChoice = $choice['order'][$answerId];
4483
                    if ($studentChoice == $answerId) {
4484
                        $questionScore += $answerWeighting;
4485
                        $totalScore += $answerWeighting;
4486
                        $studentChoice = true;
4487
                    } else {
4488
                        $studentChoice = false;
4489
                    }
4490
                    break;
4491
                case HOT_SPOT_DELINEATION:
4492
                    // for hotspot with delineation
4493
                    if ($from_database) {
4494
                        // getting the user answer
4495
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4496
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4497
                                    FROM $TBL_TRACK_HOTSPOT
4498
                                    WHERE
4499
                                        hotspot_exe_id = '".$exeId."' AND
4500
                                        hotspot_question_id= '".$questionId."' AND
4501
                                        hotspot_answer_id='1'";
4502
                        //by default we take 1 because it's a delineation
4503
                        $resq = Database::query($query);
4504
                        $row = Database::fetch_array($resq, 'ASSOC');
4505
4506
                        $choice = $row['hotspot_correct'];
4507
                        $user_answer = $row['hotspot_coordinate'];
4508
4509
                        // THIS is very important otherwise the poly_compile will throw an error!!
4510
                        // round-up the coordinates
4511
                        $coords = explode('/', $user_answer);
4512
                        $user_array = '';
4513
                        foreach ($coords as $coord) {
4514
                            list($x, $y) = explode(';', $coord);
4515
                            $user_array .= round($x).';'.round($y).'/';
4516
                        }
4517
                        $user_array = substr($user_array, 0, -1);
4518
                    } else {
4519
                        if (!empty($studentChoice)) {
4520
                            $newquestionList[] = $questionId;
4521
                        }
4522
4523
                        if ($answerId === 1) {
4524
                            $studentChoice = $choice[$answerId];
4525
                            $questionScore += $answerWeighting;
4526
4527
                            if ($hotspot_delineation_result[1] == 1) {
4528
                                $totalScore += $answerWeighting; //adding the total
4529
                            }
4530
                        }
4531
                    }
4532
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4533
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4534
                    break;
4535
                case ANNOTATION:
4536
                    if ($from_database) {
4537
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4538
                                WHERE 
4539
                                  exe_id = $exeId AND 
4540
                                  question_id= ".$questionId;
4541
                        $resq = Database::query($sql);
4542
                        $data = Database::fetch_array($resq);
4543
4544
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4545
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4546
4547
                        $arrques = $questionName;
4548
                        break;
4549
                    }
4550
                    $studentChoice = $choice;
4551
                    if ($studentChoice) {
4552
                        $questionScore = 0;
4553
                        $totalScore += 0;
4554
                    }
4555
                    break;
4556
            } // end switch Answertype
4557
4558
            if ($show_result) {
4559
                if ($debug) {
4560
                    error_log('Showing questions $from '.$from);
4561
                }
4562
                if ($from === 'exercise_result') {
4563
                    //display answers (if not matching type, or if the answer is correct)
4564
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4565
                        $answerCorrect
4566
                    ) {
4567
                        if (in_array(
4568
                            $answerType,
4569
                            [
4570
                                UNIQUE_ANSWER,
4571
                                UNIQUE_ANSWER_IMAGE,
4572
                                UNIQUE_ANSWER_NO_OPTION,
4573
                                MULTIPLE_ANSWER,
4574
                                MULTIPLE_ANSWER_COMBINATION,
4575
                                GLOBAL_MULTIPLE_ANSWER,
4576
                                READING_COMPREHENSION,
4577
                            ]
4578
                        )) {
4579
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4580
                                $this,
4581
                                $feedback_type,
4582
                                $answerType,
4583
                                $studentChoice,
4584
                                $answer,
4585
                                $answerComment,
4586
                                $answerCorrect,
4587
                                0,
4588
                                0,
4589
                                0,
4590
                                $results_disabled,
4591
                                $showTotalScoreAndUserChoicesInLastAttempt,
4592
                                $this->export
4593
                            );
4594
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4595
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4596
                                $this,
4597
                                $feedback_type,
4598
                                $answerType,
4599
                                $studentChoice,
4600
                                $answer,
4601
                                $answerComment,
4602
                                $answerCorrect,
4603
                                0,
4604
                                $questionId,
4605
                                0,
4606
                                $results_disabled,
4607
                                $showTotalScoreAndUserChoicesInLastAttempt
4608
                            );
4609
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
4610
                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
4611
                                $feedback_type,
4612
                                $studentChoice,
4613
                                $studentChoiceDegree,
4614
                                $answer,
4615
                                $answerComment,
4616
                                $answerCorrect,
4617
                                $questionId,
4618
                                $results_disabled
4619
                            );
4620
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4621
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4622
                                $this,
4623
                                $feedback_type,
4624
                                $answerType,
4625
                                $studentChoice,
4626
                                $answer,
4627
                                $answerComment,
4628
                                $answerCorrect,
4629
                                0,
4630
                                0,
4631
                                0,
4632
                                $results_disabled,
4633
                                $showTotalScoreAndUserChoicesInLastAttempt
4634
                            );
4635
                        } elseif ($answerType == FILL_IN_BLANKS) {
4636
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4637
                                $feedback_type,
4638
                                $answer,
4639
                                0,
4640
                                0,
4641
                                $results_disabled,
4642
                                '',
4643
                                $showTotalScoreAndUserChoicesInLastAttempt
4644
                            );
4645
                        } elseif ($answerType == CALCULATED_ANSWER) {
4646
                            ExerciseShowFunctions::display_calculated_answer(
4647
                                $this,
4648
                                $feedback_type,
4649
                                $answer,
4650
                                0,
4651
                                0,
4652
                                $results_disabled,
4653
                                $showTotalScoreAndUserChoicesInLastAttempt,
4654
                                $expectedAnswer,
4655
                                $calculatedChoice,
4656
                                $calculatedStatus
4657
                            );
4658
                        } elseif ($answerType == FREE_ANSWER) {
4659
                            ExerciseShowFunctions::display_free_answer(
4660
                                $feedback_type,
4661
                                $choice,
4662
                                $exeId,
4663
                                $questionId,
4664
                                $questionScore,
4665
                                $results_disabled
4666
                            );
4667
                        } elseif ($answerType == ORAL_EXPRESSION) {
4668
                            // to store the details of open questions in an array to be used in mail
4669
                            /** @var OralExpression $objQuestionTmp */
4670
                            ExerciseShowFunctions::display_oral_expression_answer(
4671
                                $feedback_type,
4672
                                $choice,
4673
                                0,
4674
                                0,
4675
                                $objQuestionTmp->getFileUrl(true),
4676
                                $results_disabled,
4677
                                $questionScore
4678
                            );
4679
                        } elseif ($answerType == HOT_SPOT) {
4680
                            $correctAnswerId = 0;
4681
                            /**
4682
                             * @var int
4683
                             * @var TrackEHotspot $hotspot
4684
                             */
4685
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4686
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4687
                                    break;
4688
                                }
4689
                            }
4690
4691
                            // force to show whether the choice is correct or not
4692
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4693
                            ExerciseShowFunctions::display_hotspot_answer(
4694
                                $feedback_type,
4695
                                ++$correctAnswerId,
4696
                                $answer,
4697
                                $studentChoice,
4698
                                $answerComment,
4699
                                $results_disabled,
4700
                                $correctAnswerId,
4701
                                $showTotalScoreAndUserChoicesInLastAttempt
4702
                            );
4703
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4704
                            ExerciseShowFunctions::display_hotspot_order_answer(
4705
                                $feedback_type,
4706
                                $answerId,
4707
                                $answer,
4708
                                $studentChoice,
4709
                                $answerComment
4710
                            );
4711
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4712
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4713
4714
                            //round-up the coordinates
4715
                            $coords = explode('/', $user_answer);
4716
                            $user_array = '';
4717
                            foreach ($coords as $coord) {
4718
                                list($x, $y) = explode(';', $coord);
4719
                                $user_array .= round($x).';'.round($y).'/';
4720
                            }
4721
                            $user_array = substr($user_array, 0, -1);
4722
4723
                            if ($next) {
4724
                                $user_answer = $user_array;
4725
                                // we compare only the delineation not the other points
4726
                                $answer_question = $_SESSION['hotspot_coord'][1];
4727
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4728
4729
                                //calculating the area
4730
                                $poly_user = convert_coordinates($user_answer, '/');
4731
                                $poly_answer = convert_coordinates($answer_question, '|');
4732
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4733
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4734
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4735
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4736
4737
                                $overlap = $poly_results['both'];
4738
                                $poly_answer_area = $poly_results['s1'];
4739
                                $poly_user_area = $poly_results['s2'];
4740
                                $missing = $poly_results['s1Only'];
4741
                                $excess = $poly_results['s2Only'];
4742
4743
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4744
                                // //this is an area in pixels
4745
                                if ($debug > 0) {
4746
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4747
                                }
4748
4749
                                if ($overlap < 1) {
4750
                                    //shortcut to avoid complicated calculations
4751
                                    $final_overlap = 0;
4752
                                    $final_missing = 100;
4753
                                    $final_excess = 100;
4754
                                } else {
4755
                                    // the final overlap is the percentage of the initial polygon
4756
                                    // that is overlapped by the user's polygon
4757
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4758
                                    if ($debug > 1) {
4759
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4760
                                    }
4761
                                    // the final missing area is the percentage of the initial polygon
4762
                                    // that is not overlapped by the user's polygon
4763
                                    $final_missing = 100 - $final_overlap;
4764
                                    if ($debug > 1) {
4765
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4766
                                    }
4767
                                    // the final excess area is the percentage of the initial polygon's size
4768
                                    // that is covered by the user's polygon outside of the initial polygon
4769
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4770
                                    if ($debug > 1) {
4771
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4772
                                    }
4773
                                }
4774
4775
                                //checking the destination parameters parsing the "@@"
4776
                                $destination_items = explode(
4777
                                    '@@',
4778
                                    $answerDestination
4779
                                );
4780
                                $threadhold_total = $destination_items[0];
4781
                                $threadhold_items = explode(
4782
                                    ';',
4783
                                    $threadhold_total
4784
                                );
4785
                                $threadhold1 = $threadhold_items[0]; // overlap
4786
                                $threadhold2 = $threadhold_items[1]; // excess
4787
                                $threadhold3 = $threadhold_items[2]; //missing
4788
4789
                                // if is delineation
4790
                                if ($answerId === 1) {
4791
                                    //setting colors
4792
                                    if ($final_overlap >= $threadhold1) {
4793
                                        $overlap_color = true; //echo 'a';
4794
                                    }
4795
                                    //echo $excess.'-'.$threadhold2;
4796
                                    if ($final_excess <= $threadhold2) {
4797
                                        $excess_color = true; //echo 'b';
4798
                                    }
4799
                                    //echo '--------'.$missing.'-'.$threadhold3;
4800
                                    if ($final_missing <= $threadhold3) {
4801
                                        $missing_color = true; //echo 'c';
4802
                                    }
4803
4804
                                    // if pass
4805
                                    if ($final_overlap >= $threadhold1 &&
4806
                                        $final_missing <= $threadhold3 &&
4807
                                        $final_excess <= $threadhold2
4808
                                    ) {
4809
                                        $next = 1; //go to the oars
4810
                                        $result_comment = get_lang('Acceptable');
4811
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4812
                                    } else {
4813
                                        $next = 0;
4814
                                        $result_comment = get_lang('Unacceptable');
4815
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4816
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4817
                                        // checking the destination parameters parsing the "@@"
4818
                                        $destination_items = explode('@@', $answerDestination);
4819
                                    }
4820
                                } elseif ($answerId > 1) {
4821
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4822
                                        if ($debug > 0) {
4823
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4824
                                        }
4825
                                        //type no error shouldn't be treated
4826
                                        $next = 1;
4827
                                        continue;
4828
                                    }
4829
                                    if ($debug > 0) {
4830
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4831
                                    }
4832
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4833
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4834
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4835
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4836
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4837
4838
                                    if ($overlap == false) {
4839
                                        //all good, no overlap
4840
                                        $next = 1;
4841
                                        continue;
4842
                                    } else {
4843
                                        if ($debug > 0) {
4844
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4845
                                        }
4846
                                        $organs_at_risk_hit++;
4847
                                        //show the feedback
4848
                                        $next = 0;
4849
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4850
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4851
4852
                                        $destination_items = explode('@@', $answerDestination);
4853
                                        $try_hotspot = $destination_items[1];
4854
                                        $lp_hotspot = $destination_items[2];
4855
                                        $select_question_hotspot = $destination_items[3];
4856
                                        $url_hotspot = $destination_items[4];
4857
                                    }
4858
                                }
4859
                            } else {
4860
                                // the first delineation feedback
4861
                                if ($debug > 0) {
4862
                                    error_log(__LINE__.' first', 0);
4863
                                }
4864
                            }
4865
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4866
                            echo '<tr>';
4867
                            echo Display::tag('td', $answerMatching[$answerId]);
4868
                            echo Display::tag(
4869
                                'td',
4870
                                "$user_answer / ".Display::tag(
4871
                                    'strong',
4872
                                    $answerMatching[$answerCorrect],
4873
                                    ['style' => 'color: #008000; font-weight: bold;']
4874
                                )
4875
                            );
4876
                            echo '</tr>';
4877
                        } elseif ($answerType == ANNOTATION) {
4878
                            ExerciseShowFunctions::displayAnnotationAnswer(
4879
                                $feedback_type,
4880
                                $exeId,
4881
                                $questionId,
4882
                                $questionScore,
4883
                                $results_disabled
4884
                            );
4885
                        }
4886
                    }
4887
                } else {
4888
                    if ($debug) {
4889
                        error_log('Showing questions $from '.$from);
4890
                    }
4891
4892
                    switch ($answerType) {
4893
                        case UNIQUE_ANSWER:
4894
                        case UNIQUE_ANSWER_IMAGE:
4895
                        case UNIQUE_ANSWER_NO_OPTION:
4896
                        case MULTIPLE_ANSWER:
4897
                        case GLOBAL_MULTIPLE_ANSWER:
4898
                        case MULTIPLE_ANSWER_COMBINATION:
4899
                        case READING_COMPREHENSION:
4900
                            if ($answerId == 1) {
4901
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4902
                                    $this,
4903
                                    $feedback_type,
4904
                                    $answerType,
4905
                                    $studentChoice,
4906
                                    $answer,
4907
                                    $answerComment,
4908
                                    $answerCorrect,
4909
                                    $exeId,
4910
                                    $questionId,
4911
                                    $answerId,
4912
                                    $results_disabled,
4913
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4914
                                    $this->export
4915
                                );
4916
                            } else {
4917
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4918
                                    $this,
4919
                                    $feedback_type,
4920
                                    $answerType,
4921
                                    $studentChoice,
4922
                                    $answer,
4923
                                    $answerComment,
4924
                                    $answerCorrect,
4925
                                    $exeId,
4926
                                    $questionId,
4927
                                    '',
4928
                                    $results_disabled,
4929
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4930
                                    $this->export
4931
                                );
4932
                            }
4933
                            break;
4934
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4935
                            if ($answerId == 1) {
4936
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4937
                                    $this,
4938
                                    $feedback_type,
4939
                                    $answerType,
4940
                                    $studentChoice,
4941
                                    $answer,
4942
                                    $answerComment,
4943
                                    $answerCorrect,
4944
                                    $exeId,
4945
                                    $questionId,
4946
                                    $answerId,
4947
                                    $results_disabled,
4948
                                    $showTotalScoreAndUserChoicesInLastAttempt
4949
                                );
4950
                            } else {
4951
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4952
                                    $this,
4953
                                    $feedback_type,
4954
                                    $answerType,
4955
                                    $studentChoice,
4956
                                    $answer,
4957
                                    $answerComment,
4958
                                    $answerCorrect,
4959
                                    $exeId,
4960
                                    $questionId,
4961
                                    '',
4962
                                    $results_disabled,
4963
                                    $showTotalScoreAndUserChoicesInLastAttempt
4964
                                );
4965
                            }
4966
                            break;
4967
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4968
                            if ($answerId == 1) {
4969
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4970
                                    $this,
4971
                                    $feedback_type,
4972
                                    $answerType,
4973
                                    $studentChoice,
4974
                                    $answer,
4975
                                    $answerComment,
4976
                                    $answerCorrect,
4977
                                    $exeId,
4978
                                    $questionId,
4979
                                    $answerId,
4980
                                    $results_disabled,
4981
                                    $showTotalScoreAndUserChoicesInLastAttempt
4982
                                );
4983
                            } else {
4984
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4985
                                    $this,
4986
                                    $feedback_type,
4987
                                    $answerType,
4988
                                    $studentChoice,
4989
                                    $answer,
4990
                                    $answerComment,
4991
                                    $answerCorrect,
4992
                                    $exeId,
4993
                                    $questionId,
4994
                                    '',
4995
                                    $results_disabled,
4996
                                    $showTotalScoreAndUserChoicesInLastAttempt
4997
                                );
4998
                            }
4999
                            break;
5000
                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
5001
                            if ($answerId == 1) {
5002
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5003
                                    $feedback_type,
5004
                                    $studentChoice,
5005
                                    $studentChoiceDegree,
5006
                                    $answer,
5007
                                    $answerComment,
5008
                                    $answerCorrect,
5009
                                    $questionId,
5010
                                    $results_disabled
5011
                                );
5012
                            } else {
5013
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5014
                                    $feedback_type,
5015
                                    $studentChoice,
5016
                                    $studentChoiceDegree,
5017
                                    $answer,
5018
                                    $answerComment,
5019
                                    $answerCorrect,
5020
                                    $questionId,
5021
                                    $results_disabled
5022
                                );
5023
                            }
5024
                            break;
5025
                        case FILL_IN_BLANKS:
5026
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5027
                                $feedback_type,
5028
                                $answer,
5029
                                $exeId,
5030
                                $questionId,
5031
                                $results_disabled,
5032
                                $str,
5033
                                $showTotalScoreAndUserChoicesInLastAttempt
5034
                            );
5035
                            break;
5036
                        case CALCULATED_ANSWER:
5037
                            ExerciseShowFunctions::display_calculated_answer(
5038
                                $this,
5039
                                $feedback_type,
5040
                                $answer,
5041
                                $exeId,
5042
                                $questionId,
5043
                                $results_disabled,
5044
                                '',
5045
                                $showTotalScoreAndUserChoicesInLastAttempt
5046
                            );
5047
                            break;
5048
                        case FREE_ANSWER:
5049
                            echo ExerciseShowFunctions::display_free_answer(
5050
                                $feedback_type,
5051
                                $choice,
5052
                                $exeId,
5053
                                $questionId,
5054
                                $questionScore,
5055
                                $results_disabled
5056
                            );
5057
                            break;
5058
                        case ORAL_EXPRESSION:
5059
                            echo '<tr>
5060
                                <td valign="top">'.
5061
                                ExerciseShowFunctions::display_oral_expression_answer(
5062
                                    $feedback_type,
5063
                                    $choice,
5064
                                    $exeId,
5065
                                    $questionId,
5066
                                    $objQuestionTmp->getFileUrl(),
5067
                                    $results_disabled,
5068
                                    $questionScore
5069
                                ).'</td>
5070
                                </tr>
5071
                                </table>';
5072
                            break;
5073
                        case HOT_SPOT:
5074
                            ExerciseShowFunctions::display_hotspot_answer(
5075
                                $feedback_type,
5076
                                $answerId,
5077
                                $answer,
5078
                                $studentChoice,
5079
                                $answerComment,
5080
                                $results_disabled,
5081
                                $answerId,
5082
                                $showTotalScoreAndUserChoicesInLastAttempt
5083
                            );
5084
                            break;
5085
                        case HOT_SPOT_DELINEATION:
5086
                            $user_answer = $user_array;
5087
                            if ($next) {
5088
                                $user_answer = $user_array;
5089
                                // we compare only the delineation not the other points
5090
                                $answer_question = $_SESSION['hotspot_coord'][1];
5091
                                $answerDestination = $_SESSION['hotspot_dest'][1];
5092
5093
                                // calculating the area
5094
                                $poly_user = convert_coordinates($user_answer, '/');
5095
                                $poly_answer = convert_coordinates($answer_question, '|');
5096
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5097
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5098
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5099
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5100
5101
                                $overlap = $poly_results['both'];
5102
                                $poly_answer_area = $poly_results['s1'];
5103
                                $poly_user_area = $poly_results['s2'];
5104
                                $missing = $poly_results['s1Only'];
5105
                                $excess = $poly_results['s2Only'];
5106
                                if ($debug > 0) {
5107
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5108
                                }
5109
                                if ($overlap < 1) {
5110
                                    //shortcut to avoid complicated calculations
5111
                                    $final_overlap = 0;
5112
                                    $final_missing = 100;
5113
                                    $final_excess = 100;
5114
                                } else {
5115
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
5116
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5117
                                    if ($debug > 1) {
5118
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5119
                                    }
5120
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
5121
                                    $final_missing = 100 - $final_overlap;
5122
                                    if ($debug > 1) {
5123
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5124
                                    }
5125
                                    // 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
5126
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5127
                                    if ($debug > 1) {
5128
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5129
                                    }
5130
                                }
5131
5132
                                // Checking the destination parameters parsing the "@@"
5133
                                $destination_items = explode('@@', $answerDestination);
5134
                                $threadhold_total = $destination_items[0];
5135
                                $threadhold_items = explode(';', $threadhold_total);
5136
                                $threadhold1 = $threadhold_items[0]; // overlap
5137
                                $threadhold2 = $threadhold_items[1]; // excess
5138
                                $threadhold3 = $threadhold_items[2]; //missing
5139
                                // if is delineation
5140
                                if ($answerId === 1) {
5141
                                    //setting colors
5142
                                    if ($final_overlap >= $threadhold1) {
5143
                                        $overlap_color = true; //echo 'a';
5144
                                    }
5145
                                    if ($final_excess <= $threadhold2) {
5146
                                        $excess_color = true; //echo 'b';
5147
                                    }
5148
                                    if ($final_missing <= $threadhold3) {
5149
                                        $missing_color = true; //echo 'c';
5150
                                    }
5151
5152
                                    // if pass
5153
                                    if ($final_overlap >= $threadhold1 &&
5154
                                        $final_missing <= $threadhold3 &&
5155
                                        $final_excess <= $threadhold2
5156
                                    ) {
5157
                                        $next = 1; //go to the oars
5158
                                        $result_comment = get_lang('Acceptable');
5159
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5160
                                    } else {
5161
                                        $next = 0;
5162
                                        $result_comment = get_lang('Unacceptable');
5163
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5164
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5165
                                        //checking the destination parameters parsing the "@@"
5166
                                        $destination_items = explode('@@', $answerDestination);
5167
                                    }
5168
                                } elseif ($answerId > 1) {
5169
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5170
                                        if ($debug > 0) {
5171
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5172
                                        }
5173
                                        //type no error shouldn't be treated
5174
                                        $next = 1;
5175
                                        continue;
5176
                                    }
5177
                                    if ($debug > 0) {
5178
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5179
                                    }
5180
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5181
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5182
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5183
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5184
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5185
5186
                                    if ($overlap == false) {
5187
                                        //all good, no overlap
5188
                                        $next = 1;
5189
                                        continue;
5190
                                    } else {
5191
                                        if ($debug > 0) {
5192
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5193
                                        }
5194
                                        $organs_at_risk_hit++;
5195
                                        //show the feedback
5196
                                        $next = 0;
5197
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5198
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5199
5200
                                        $destination_items = explode('@@', $answerDestination);
5201
                                        $try_hotspot = $destination_items[1];
5202
                                        $lp_hotspot = $destination_items[2];
5203
                                        $select_question_hotspot = $destination_items[3];
5204
                                        $url_hotspot = $destination_items[4];
5205
                                    }
5206
                                }
5207
                            } else {
5208
                                // the first delineation feedback
5209
                                if ($debug > 0) {
5210
                                    error_log(__LINE__.' first', 0);
5211
                                }
5212
                            }
5213
                            break;
5214
                        case HOT_SPOT_ORDER:
5215
                            ExerciseShowFunctions::display_hotspot_order_answer(
5216
                                $feedback_type,
5217
                                $answerId,
5218
                                $answer,
5219
                                $studentChoice,
5220
                                $answerComment
5221
                            );
5222
                            break;
5223
                        case DRAGGABLE:
5224
                        case MATCHING_DRAGGABLE:
5225
                        case MATCHING:
5226
                            echo '<tr>';
5227
                            echo Display::tag('td', $answerMatching[$answerId]);
5228
                            echo Display::tag(
5229
                                'td',
5230
                                "$user_answer / ".Display::tag(
5231
                                    'strong',
5232
                                    $answerMatching[$answerCorrect],
5233
                                    ['style' => 'color: #008000; font-weight: bold;']
5234
                                )
5235
                            );
5236
                            echo '</tr>';
5237
                            break;
5238
                        case ANNOTATION:
5239
                            ExerciseShowFunctions::displayAnnotationAnswer(
5240
                                $feedback_type,
5241
                                $exeId,
5242
                                $questionId,
5243
                                $questionScore,
5244
                                $results_disabled
5245
                            );
5246
                            break;
5247
                    }
5248
                }
5249
            }
5250
            if ($debug) {
5251
                error_log(' ------ ');
5252
            }
5253
        } // end for that loops over all answers of the current question
5254
5255
        if ($debug) {
5256
            error_log('-- end answer loop --');
5257
        }
5258
5259
        $final_answer = true;
5260
5261
        foreach ($real_answers as $my_answer) {
5262
            if (!$my_answer) {
5263
                $final_answer = false;
5264
            }
5265
        }
5266
5267
        //we add the total score after dealing with the answers
5268
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5269
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5270
        ) {
5271
            if ($final_answer) {
5272
                //getting only the first score where we save the weight of all the question
5273
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5274
                $questionScore += $answerWeighting;
5275
                $totalScore += $answerWeighting;
5276
            }
5277
        }
5278
5279
        //Fixes multiple answer question in order to be exact
5280
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5281
        /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
5282
             $diff = @array_diff($answer_correct_array, $real_answers);
5283
5284
             // All good answers or nothing works like exact
5285
5286
             $counter = 1;
5287
             $correct_answer = true;
5288
             foreach ($real_answers as $my_answer) {
5289
                 if ($debug)
5290
                     error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
5291
                 if ($my_answer != $answer_correct_array[$counter]) {
5292
                     $correct_answer = false;
5293
                     break;
5294
                 }
5295
                 $counter++;
5296
             }
5297
5298
             if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
5299
             if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
5300
             if ($debug) error_log(" correct_answer: ".$correct_answer);
5301
5302
             if ($correct_answer == false) {
5303
                 $questionScore = 0;
5304
             }
5305
5306
             // This makes the result non exact
5307
             if (!empty($diff)) {
5308
                 $questionScore = 0;
5309
             }
5310
         }*/
5311
5312
        $extra_data = [
5313
            'final_overlap' => $final_overlap,
5314
            'final_missing' => $final_missing,
5315
            'final_excess' => $final_excess,
5316
            'overlap_color' => $overlap_color,
5317
            'missing_color' => $missing_color,
5318
            'excess_color' => $excess_color,
5319
            'threadhold1' => $threadhold1,
5320
            'threadhold2' => $threadhold2,
5321
            'threadhold3' => $threadhold3,
5322
        ];
5323
        if ($from == 'exercise_result') {
5324
            // if answer is hotspot. To the difference of exercise_show.php,
5325
            //  we use the results from the session (from_db=0)
5326
            // TODO Change this, because it is wrong to show the user
5327
            //  some results that haven't been stored in the database yet
5328
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5329
                if ($debug) {
5330
                    error_log('$from AND this is a hotspot kind of question ');
5331
                }
5332
                $my_exe_id = 0;
5333
                $from_database = 0;
5334
                if ($answerType == HOT_SPOT_DELINEATION) {
5335
                    if (0) {
5336
                        if ($overlap_color) {
5337
                            $overlap_color = 'green';
5338
                        } else {
5339
                            $overlap_color = 'red';
5340
                        }
5341
                        if ($missing_color) {
5342
                            $missing_color = 'green';
5343
                        } else {
5344
                            $missing_color = 'red';
5345
                        }
5346
                        if ($excess_color) {
5347
                            $excess_color = 'green';
5348
                        } else {
5349
                            $excess_color = 'red';
5350
                        }
5351
                        if (!is_numeric($final_overlap)) {
5352
                            $final_overlap = 0;
5353
                        }
5354
                        if (!is_numeric($final_missing)) {
5355
                            $final_missing = 0;
5356
                        }
5357
                        if (!is_numeric($final_excess)) {
5358
                            $final_excess = 0;
5359
                        }
5360
5361
                        if ($final_overlap > 100) {
5362
                            $final_overlap = 100;
5363
                        }
5364
5365
                        $table_resume = '<table class="data_table">
5366
                                <tr class="row_odd" >
5367
                                    <td></td>
5368
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5369
                                    <td><b>'.get_lang('YourAnswer').'</b></td>
5370
                                </tr>
5371
                                <tr class="row_even">
5372
                                    <td><b>'.get_lang('Overlap').'</b></td>
5373
                                    <td>'.get_lang('Min').' '.$threadhold1.'</td>
5374
                                    <td><div style="color:'.$overlap_color.'">'
5375
                            .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5376
                                </tr>
5377
                                <tr>
5378
                                    <td><b>'.get_lang('Excess').'</b></td>
5379
                                    <td>'.get_lang('Max').' '.$threadhold2.'</td>
5380
                                    <td><div style="color:'.$excess_color.'">'
5381
                            .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5382
                                </tr>
5383
                                <tr class="row_even">
5384
                                    <td><b>'.get_lang('Missing').'</b></td>
5385
                                    <td>'.get_lang('Max').' '.$threadhold3.'</td>
5386
                                    <td><div style="color:'.$missing_color.'">'
5387
                            .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5388
                                </tr>
5389
                            </table>';
5390
                        if ($next == 0) {
5391
                            $try = $try_hotspot;
5392
                            $lp = $lp_hotspot;
5393
                            $destinationid = $select_question_hotspot;
5394
                            $url = $url_hotspot;
5395
                        } else {
5396
                            //show if no error
5397
                            //echo 'no error';
5398
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5399
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5400
                        }
5401
5402
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5403
                            <p style="text-align:center">';
5404
5405
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5406
                        $message .= $table_resume;
5407
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5408
                        if ($organs_at_risk_hit > 0) {
5409
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5410
                        }
5411
                        $message .= '<p>'.$comment.'</p>';
5412
                        echo $message;
5413
                    } else {
5414
                        echo $hotspot_delineation_result[0]; //prints message
5415
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5416
                    }
5417
5418
                    //save the score attempts
5419
                    if (1) {
5420
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5421
                        $final_answer = $hotspot_delineation_result[1];
5422
                        if ($final_answer == 0) {
5423
                            $questionScore = 0;
5424
                        }
5425
                        // we always insert the answer_id 1 = delineation
5426
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5427
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5428
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5429
                        Event::saveExerciseAttemptHotspot(
5430
                            $exeId,
5431
                            $quesId,
5432
                            1,
5433
                            $hotspotValue,
5434
                            $exerciseResultCoordinates[$quesId]
5435
                        );
5436
                    } else {
5437
                        if ($final_answer == 0) {
5438
                            $questionScore = 0;
5439
                            $answer = 0;
5440
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5441
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5442
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5443
                                    Event::saveExerciseAttemptHotspot(
5444
                                        $exeId,
5445
                                        $quesId,
5446
                                        $idx,
5447
                                        0,
5448
                                        $val
5449
                                    );
5450
                                }
5451
                            }
5452
                        } else {
5453
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5454
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5455
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5456
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5457
                                    Event::saveExerciseAttemptHotspot(
5458
                                        $exeId,
5459
                                        $quesId,
5460
                                        $idx,
5461
                                        $hotspotValue,
5462
                                        $val
5463
                                    );
5464
                                }
5465
                            }
5466
                        }
5467
                    }
5468
                    $my_exe_id = $exeId;
5469
                }
5470
            }
5471
5472
            $relPath = api_get_path(WEB_CODE_PATH);
5473
5474
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5475
                // We made an extra table for the answers
5476
                if ($show_result) {
5477
                    echo '</table></td></tr>';
5478
                    echo "
5479
                        <tr>
5480
                            <td colspan=\"2\">
5481
                                <p><em>".get_lang('HotSpot')."</em></p>
5482
                                <div id=\"hotspot-solution-$questionId\"></div>
5483
                                <script>
5484
                                    $(function() {
5485
                                        new HotspotQuestion({
5486
                                            questionId: $questionId,
5487
                                            exerciseId: {$this->id},
5488
                                            exeId: $exeId,
5489
                                            selector: '#hotspot-solution-$questionId',
5490
                                            for: 'solution',
5491
                                            relPath: '$relPath'
5492
                                        });
5493
                                    });
5494
                                </script>
5495
                            </td>
5496
                        </tr>
5497
                    ";
5498
                }
5499
            } elseif ($answerType == ANNOTATION) {
5500
                if ($show_result) {
5501
                    echo '
5502
                        <p><em>'.get_lang('Annotation').'</em></p>
5503
                        <div id="annotation-canvas-'.$questionId.'"></div>
5504
                        <script>
5505
                            AnnotationQuestion({
5506
                                questionId: parseInt('.$questionId.'),
5507
                                exerciseId: parseInt('.$exeId.'),
5508
                                relPath: \''.$relPath.'\',
5509
                                courseId: parseInt('.$course_id.')
5510
                            });
5511
                        </script>
5512
                    ';
5513
                }
5514
            }
5515
5516
            //if ($origin != 'learnpath') {
5517
            if ($show_result && $answerType != ANNOTATION) {
5518
                echo '</table>';
5519
            }
5520
            //	}
5521
        }
5522
        unset($objAnswerTmp);
5523
5524
        $totalWeighting += $questionWeighting;
5525
        // Store results directly in the database
5526
        // For all in one page exercises, the results will be
5527
        // stored by exercise_results.php (using the session)
5528
        if ($saved_results) {
5529
            if ($debug) {
5530
                error_log("Save question results $saved_results");
5531
                error_log('choice: ');
5532
                error_log(print_r($choice, 1));
5533
            }
5534
5535
            if (empty($choice)) {
5536
                $choice = 0;
5537
            }
5538
            // with certainty degree
5539
            if (empty($choiceDegreeCertainty)) {
5540
                $choiceDegreeCertainty = 0;
5541
            }
5542
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
5543
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ||
5544
                $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
5545
            ) {
5546
                if ($choice != 0) {
5547
                    $reply = array_keys($choice);
5548
                    $countReply = count($reply);
5549
                    for ($i = 0; $i < $countReply; $i++) {
5550
                        $chosenAnswer = $reply[$i];
5551
                        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5552
                            if ($choiceDegreeCertainty != 0) {
5553
                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
5554
                                $answerDegreeCertainty = isset($replyDegreeCertainty[$i]) ? $replyDegreeCertainty[$i] : '';
5555
                                $answerValue = isset($choiceDegreeCertainty[$answerDegreeCertainty]) ? $choiceDegreeCertainty[$answerDegreeCertainty] : '';
5556
                                Event::saveQuestionAttempt(
5557
                                    $questionScore,
5558
                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.$answerValue,
5559
                                    $quesId,
5560
                                    $exeId,
5561
                                    $i,
5562
                                    $this->id,
5563
                                    $updateResults
5564
                                );
5565
                            }
5566
                        } else {
5567
                            Event::saveQuestionAttempt(
5568
                                $questionScore,
5569
                                $chosenAnswer.':'.$choice[$chosenAnswer],
5570
                                $quesId,
5571
                                $exeId,
5572
                                $i,
5573
                                $this->id,
5574
                                $updateResults
5575
                            );
5576
                        }
5577
                        if ($debug) {
5578
                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
5579
                        }
5580
                    }
5581
                } else {
5582
                    Event::saveQuestionAttempt(
5583
                        $questionScore,
5584
                        0,
5585
                        $quesId,
5586
                        $exeId,
5587
                        0,
5588
                        $this->id
5589
                    );
5590
                }
5591
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5592
                if ($choice != 0) {
5593
                    $reply = array_keys($choice);
5594
5595
                    if ($debug) {
5596
                        error_log("reply ".print_r($reply, 1)."");
5597
                    }
5598
                    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...
5599
                        $ans = $reply[$i];
5600
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5601
                    }
5602
                } else {
5603
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5604
                }
5605
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5606
                if ($choice != 0) {
5607
                    $reply = array_keys($choice);
5608
                    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...
5609
                        $ans = $reply[$i];
5610
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5611
                    }
5612
                } else {
5613
                    Event::saveQuestionAttempt(
5614
                        $questionScore,
5615
                        0,
5616
                        $quesId,
5617
                        $exeId,
5618
                        0,
5619
                        $this->id
5620
                    );
5621
                }
5622
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5623
                if (isset($matching)) {
5624
                    foreach ($matching as $j => $val) {
5625
                        Event::saveQuestionAttempt(
5626
                            $questionScore,
5627
                            $val,
5628
                            $quesId,
5629
                            $exeId,
5630
                            $j,
5631
                            $this->id
5632
                        );
5633
                    }
5634
                }
5635
            } elseif ($answerType == FREE_ANSWER) {
5636
                $answer = $choice;
5637
                Event::saveQuestionAttempt(
5638
                    $questionScore,
5639
                    $answer,
5640
                    $quesId,
5641
                    $exeId,
5642
                    0,
5643
                    $this->id
5644
                );
5645
            } elseif ($answerType == ORAL_EXPRESSION) {
5646
                $answer = $choice;
5647
                Event::saveQuestionAttempt(
5648
                    $questionScore,
5649
                    $answer,
5650
                    $quesId,
5651
                    $exeId,
5652
                    0,
5653
                    $this->id,
5654
                    false,
5655
                    $objQuestionTmp->getAbsoluteFilePath()
5656
                );
5657
            } elseif (
5658
                in_array(
5659
                    $answerType,
5660
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
5661
                )
5662
            ) {
5663
                $answer = $choice;
5664
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5665
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5666
                $answer = [];
5667
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5668
                    if ($debug) {
5669
                        error_log('Checking result coordinates');
5670
                    }
5671
                    Database::delete(
5672
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5673
                        [
5674
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5675
                                $exeId,
5676
                                $questionId,
5677
                                api_get_course_int_id(),
5678
                            ],
5679
                        ]
5680
                    );
5681
5682
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5683
                        $answer[] = $val;
5684
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5685
                        if ($debug) {
5686
                            error_log('Hotspot value: '.$hotspotValue);
5687
                        }
5688
                        Event::saveExerciseAttemptHotspot(
5689
                            $exeId,
5690
                            $quesId,
5691
                            $idx,
5692
                            $hotspotValue,
5693
                            $val,
5694
                            false,
5695
                            $this->id
5696
                        );
5697
                    }
5698
                } else {
5699
                    if ($debug) {
5700
                        error_log('Empty: exerciseResultCoordinates');
5701
                    }
5702
                }
5703
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5704
            } else {
5705
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5706
            }
5707
        }
5708
5709
        if ($propagate_neg == 0 && $questionScore < 0) {
5710
            $questionScore = 0;
5711
        }
5712
5713
        if ($saved_results) {
5714
            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5715
            $sql = "UPDATE $statsTable SET
5716
                        score = score + ".floatval($questionScore)."
5717
                    WHERE exe_id = $exeId";
5718
            Database::query($sql);
5719
            if ($debug) {
5720
                error_log($sql);
5721
            }
5722
        }
5723
5724
        $return_array = [
5725
            'score' => $questionScore,
5726
            'weight' => $questionWeighting,
5727
            'extra' => $extra_data,
5728
            'open_question' => $arrques,
5729
            'open_answer' => $arrans,
5730
            'answer_type' => $answerType,
5731
            'generated_oral_file' => $generatedFile,
5732
        ];
5733
5734
        return $return_array;
5735
    }
5736
5737
    /**
5738
     * Sends a notification when a user ends an examn.
5739
     *
5740
     * @param string $type                  'start' or 'end' of an exercise
5741
     * @param array  $question_list_answers
5742
     * @param string $origin
5743
     * @param int    $exe_id
5744
     * @param float  $score
5745
     * @param float  $weight
5746
     *
5747
     * @return bool
5748
     */
5749
    public function send_mail_notification_for_exam(
5750
        $type = 'end',
5751
        $question_list_answers,
5752
        $origin,
5753
        $exe_id,
5754
        $score = null,
5755
        $weight = null
5756
    ) {
5757
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5758
5759
        if (empty($setting) && empty($this->getNotifications())) {
5760
            return false;
5761
        }
5762
5763
        $settingFromExercise = $this->getNotifications();
5764
        if (!empty($settingFromExercise)) {
5765
            $setting = $settingFromExercise;
5766
        }
5767
5768
        // Email configuration settings
5769
        $courseCode = api_get_course_id();
5770
        $courseInfo = api_get_course_info($courseCode);
5771
5772
        if (empty($courseInfo)) {
5773
            return false;
5774
        }
5775
5776
        $sessionId = api_get_session_id();
5777
        $sendStart = false;
5778
        $sendEnd = false;
5779
        $sendEndOpenQuestion = false;
5780
        $sendEndOralQuestion = false;
5781
5782
        foreach ($setting as $option) {
5783
            switch ($option) {
5784
                case 0:
5785
                    return false;
5786
                    break;
5787
                case 1: // End
5788
                    if ($type == 'end') {
5789
                        $sendEnd = true;
5790
                    }
5791
                    break;
5792
                case 2: // start
5793
                    if ($type == 'start') {
5794
                        $sendStart = true;
5795
                    }
5796
                    break;
5797
                case 3: // end + open
5798
                    if ($type == 'end') {
5799
                        $sendEndOpenQuestion = true;
5800
                    }
5801
                    break;
5802
                case 4: // end + oral
5803
                    if ($type == 'end') {
5804
                        $sendEndOralQuestion = true;
5805
                    }
5806
                    break;
5807
            }
5808
        }
5809
5810
        $user_info = api_get_user_info(api_get_user_id());
5811
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
5812
            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
5813
5814
        if (!empty($sessionId)) {
5815
            $addGeneralCoach = true;
5816
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5817
            if ($setting === true) {
5818
                $addGeneralCoach = false;
5819
            }
5820
            $teachers = CourseManager::get_coach_list_from_course_code(
5821
                $courseCode,
5822
                $sessionId,
5823
                $addGeneralCoach
5824
            );
5825
        } else {
5826
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5827
        }
5828
5829
        if ($sendEndOpenQuestion) {
5830
            $this->sendNotificationForOpenQuestions(
5831
                $question_list_answers,
5832
                $origin,
5833
                $exe_id,
5834
                $user_info,
5835
                $url,
5836
                $teachers
5837
            );
5838
        }
5839
5840
        if ($sendEndOralQuestion) {
5841
            $this->sendNotificationForOralQuestions(
5842
                $question_list_answers,
5843
                $origin,
5844
                $exe_id,
5845
                $user_info,
5846
                $url,
5847
                $teachers
5848
            );
5849
        }
5850
5851
        if (!$sendEnd && !$sendStart) {
5852
            return false;
5853
        }
5854
5855
        $scoreLabel = '';
5856
        if ($sendEnd &&
5857
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
5858
        ) {
5859
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
5860
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
5861
            $scoreLabel = "<tr>
5862
                            <td>".get_lang('Score')."</td>
5863
                            <td>&nbsp;$scoreLabel</td>
5864
                        </tr>";
5865
        }
5866
5867
        if ($sendEnd) {
5868
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5869
        } else {
5870
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5871
        }
5872
5873
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5874
                    <table>
5875
                        <tr>
5876
                            <td>'.get_lang('CourseName').'</td>
5877
                            <td>#course#</td>
5878
                        </tr>
5879
                        <tr>
5880
                            <td>'.get_lang('Exercise').'</td>
5881
                            <td>&nbsp;#exercise#</td>
5882
                        </tr>
5883
                        <tr>
5884
                            <td>'.get_lang('StudentName').'</td>
5885
                            <td>&nbsp;#student_complete_name#</td>
5886
                        </tr>
5887
                        <tr>
5888
                            <td>'.get_lang('StudentEmail').'</td>
5889
                            <td>&nbsp;#email#</td>
5890
                        </tr>
5891
                        '.$scoreLabel.'
5892
                    </table>';
5893
5894
        $variables = [
5895
            '#email#' => $user_info['email'],
5896
            '#exercise#' => $this->exercise,
5897
            '#student_complete_name#' => $user_info['complete_name'],
5898
            '#course#' => $courseInfo['title'],
5899
        ];
5900
5901
        if ($sendEnd) {
5902
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5903
            $variables['#url#'] = $url;
5904
        }
5905
5906
        $content = str_replace(array_keys($variables), array_values($variables), $msg);
5907
5908
        if ($sendEnd) {
5909
            $subject = get_lang('ExerciseAttempted');
5910
        } else {
5911
            $subject = get_lang('StudentStartExercise');
5912
        }
5913
5914
        if (!empty($teachers)) {
5915
            foreach ($teachers as $user_id => $teacher_data) {
5916
                MessageManager::send_message_simple(
5917
                    $user_id,
5918
                    $subject,
5919
                    $content
5920
                );
5921
            }
5922
        }
5923
    }
5924
5925
    /**
5926
     * @param array $user_data         result of api_get_user_info()
5927
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
5928
     *
5929
     * @return string
5930
     */
5931
    public function showExerciseResultHeader(
5932
        $user_data,
5933
        $trackExerciseInfo
5934
    ) {
5935
        $start_date = null;
5936
        if (isset($trackExerciseInfo['start_date'])) {
5937
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
5938
        }
5939
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
5940
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
5941
5942
        $array = [];
5943
        if (!empty($user_data)) {
5944
            $userFullName = $user_data['complete_name'];
5945
            if (api_is_teacher() || api_is_platform_admin(true, true)) {
5946
                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.$user_data['complete_name'].'</a>';
5947
            }
5948
            $array[] = [
5949
                'title' => get_lang('Name'),
5950
                'content' => $userFullName,
5951
            ];
5952
            $array[] = [
5953
                'title' => get_lang('Username'),
5954
                'content' => $user_data['username'],
5955
            ];
5956
            if (!empty($user_data['official_code'])) {
5957
                $array[] = [
5958
                    'title' => get_lang('OfficialCode'),
5959
                    'content' => $user_data['official_code'],
5960
                ];
5961
            }
5962
        }
5963
        // Description can be very long and is generally meant to explain
5964
        //   rules *before* the exam. Leaving here to make display easier if
5965
        //   necessary
5966
        /*
5967
        if (!empty($this->description)) {
5968
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5969
        }
5970
        */
5971
        if (!empty($start_date)) {
5972
            $array[] = ['title' => get_lang('StartDate'), 'content' => $start_date];
5973
        }
5974
5975
        if (!empty($duration)) {
5976
            $array[] = ['title' => get_lang('Duration'), 'content' => $duration];
5977
        }
5978
5979
        if (!empty($ip)) {
5980
            $array[] = ['title' => get_lang('IP'), 'content' => $ip];
5981
        }
5982
5983
        $icon = Display::return_icon(
5984
            'test-quiz.png',
5985
            get_lang('Result'),
5986
            null,
5987
            ICON_SIZE_MEDIUM
5988
        );
5989
5990
        $html = '<div class="question-result">';
5991
        if (api_get_configuration_value('save_titles_as_html')) {
5992
            $html .= $this->get_formated_title();
5993
            $html .= Display::page_header(get_lang('Result'));
5994
        } else {
5995
            $html .= Display::page_header(
5996
                $icon.PHP_EOL.$this->exercise.' : '.get_lang('Result')
5997
            );
5998
        }
5999
6000
        $hide = api_get_configuration_value('hide_user_info_in_quiz_result');
6001
6002
        if ($hide === false) {
6003
            $html .= Display::description($array);
6004
        }
6005
6006
        $html .= "</div>";
6007
6008
        return $html;
6009
    }
6010
6011
    /**
6012
     * Returns the exercise result.
6013
     *
6014
     * @param 	int		attempt id
6015
     *
6016
     * @return float exercise result
6017
     */
6018
    public function get_exercise_result($exe_id)
6019
    {
6020
        $result = [];
6021
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
6022
6023
        if (!empty($track_exercise_info)) {
6024
            $totalScore = 0;
6025
            $objExercise = new Exercise();
6026
            $objExercise->read($track_exercise_info['exe_exo_id']);
6027
            if (!empty($track_exercise_info['data_tracking'])) {
6028
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6029
            }
6030
            foreach ($question_list as $questionId) {
6031
                $question_result = $objExercise->manage_answer(
6032
                    $exe_id,
6033
                    $questionId,
6034
                    '',
6035
                    'exercise_show',
6036
                    [],
6037
                    false,
6038
                    true,
6039
                    false,
6040
                    $objExercise->selectPropagateNeg()
6041
                );
6042
                $totalScore += $question_result['score'];
6043
            }
6044
6045
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
6046
                $totalScore = 0;
6047
            }
6048
            $result = [
6049
                'score' => $totalScore,
6050
                'weight' => $track_exercise_info['max_score'],
6051
            ];
6052
        }
6053
6054
        return $result;
6055
    }
6056
6057
    /**
6058
     * Checks if the exercise is visible due a lot of conditions
6059
     * visibility, time limits, student attempts
6060
     * Return associative array
6061
     * value : true if exercise visible
6062
     * message : HTML formatted message
6063
     * rawMessage : text message.
6064
     *
6065
     * @param int  $lpId
6066
     * @param int  $lpItemId
6067
     * @param int  $lpItemViewId
6068
     * @param bool $filterByAdmin
6069
     *
6070
     * @return array
6071
     */
6072
    public function is_visible(
6073
        $lpId = 0,
6074
        $lpItemId = 0,
6075
        $lpItemViewId = 0,
6076
        $filterByAdmin = true
6077
    ) {
6078
        // 1. By default the exercise is visible
6079
        $isVisible = true;
6080
        $message = null;
6081
6082
        // 1.1 Admins and teachers can access to the exercise
6083
        if ($filterByAdmin) {
6084
            if (api_is_platform_admin() || api_is_course_admin()) {
6085
                return ['value' => true, 'message' => ''];
6086
            }
6087
        }
6088
6089
        // Deleted exercise.
6090
        if ($this->active == -1) {
6091
            return [
6092
                'value' => false,
6093
                'message' => Display::return_message(
6094
                    get_lang('ExerciseNotFound'),
6095
                    'warning',
6096
                    false
6097
                ),
6098
                'rawMessage' => get_lang('ExerciseNotFound'),
6099
            ];
6100
        }
6101
6102
        // Checking visibility in the item_property table.
6103
        $visibility = api_get_item_visibility(
6104
            api_get_course_info(),
6105
            TOOL_QUIZ,
6106
            $this->id,
6107
            api_get_session_id()
6108
        );
6109
6110
        if ($visibility == 0 || $visibility == 2) {
6111
            $this->active = 0;
6112
        }
6113
6114
        // 2. If the exercise is not active.
6115
        if (empty($lpId)) {
6116
            // 2.1 LP is OFF
6117
            if ($this->active == 0) {
6118
                return [
6119
                    'value' => false,
6120
                    'message' => Display::return_message(
6121
                        get_lang('ExerciseNotFound'),
6122
                        'warning',
6123
                        false
6124
                    ),
6125
                    'rawMessage' => get_lang('ExerciseNotFound'),
6126
                ];
6127
            }
6128
        } else {
6129
            // 2.1 LP is loaded
6130
            if ($this->active == 0 &&
6131
                !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())
6132
            ) {
6133
                return [
6134
                    'value' => false,
6135
                    'message' => Display::return_message(
6136
                        get_lang('ExerciseNotFound'),
6137
                        'warning',
6138
                        false
6139
                    ),
6140
                    'rawMessage' => get_lang('ExerciseNotFound'),
6141
                ];
6142
            }
6143
        }
6144
6145
        // 3. We check if the time limits are on
6146
        $limitTimeExists = false;
6147
        if (!empty($this->start_time) || !empty($this->end_time)) {
6148
            $limitTimeExists = true;
6149
        }
6150
6151
        if ($limitTimeExists) {
6152
            $timeNow = time();
6153
            $existsStartDate = false;
6154
            $nowIsAfterStartDate = true;
6155
            $existsEndDate = false;
6156
            $nowIsBeforeEndDate = true;
6157
6158
            if (!empty($this->start_time)) {
6159
                $existsStartDate = true;
6160
            }
6161
6162
            if (!empty($this->end_time)) {
6163
                $existsEndDate = true;
6164
            }
6165
6166
            // check if we are before-or-after end-or-start date
6167
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6168
                $nowIsAfterStartDate = false;
6169
            }
6170
6171
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6172
                $nowIsBeforeEndDate = false;
6173
            }
6174
6175
            // lets check all cases
6176
            if ($existsStartDate && !$existsEndDate) {
6177
                // exists start date and dont exists end date
6178
                if ($nowIsAfterStartDate) {
6179
                    // after start date, no end date
6180
                    $isVisible = true;
6181
                    $message = sprintf(
6182
                        get_lang('ExerciseAvailableSinceX'),
6183
                        api_convert_and_format_date($this->start_time)
6184
                    );
6185
                } else {
6186
                    // before start date, no end date
6187
                    $isVisible = false;
6188
                    $message = sprintf(
6189
                        get_lang('ExerciseAvailableFromX'),
6190
                        api_convert_and_format_date($this->start_time)
6191
                    );
6192
                }
6193
            } elseif (!$existsStartDate && $existsEndDate) {
6194
                // doesnt exist start date, exists end date
6195
                if ($nowIsBeforeEndDate) {
6196
                    // before end date, no start date
6197
                    $isVisible = true;
6198
                    $message = sprintf(
6199
                        get_lang('ExerciseAvailableUntilX'),
6200
                        api_convert_and_format_date($this->end_time)
6201
                    );
6202
                } else {
6203
                    // after end date, no start date
6204
                    $isVisible = false;
6205
                    $message = sprintf(
6206
                        get_lang('ExerciseAvailableUntilX'),
6207
                        api_convert_and_format_date($this->end_time)
6208
                    );
6209
                }
6210
            } elseif ($existsStartDate && $existsEndDate) {
6211
                // exists start date and end date
6212
                if ($nowIsAfterStartDate) {
6213
                    if ($nowIsBeforeEndDate) {
6214
                        // after start date and before end date
6215
                        $isVisible = true;
6216
                        $message = sprintf(
6217
                            get_lang('ExerciseIsActivatedFromXToY'),
6218
                            api_convert_and_format_date($this->start_time),
6219
                            api_convert_and_format_date($this->end_time)
6220
                        );
6221
                    } else {
6222
                        // after start date and after end date
6223
                        $isVisible = false;
6224
                        $message = sprintf(
6225
                            get_lang('ExerciseWasActivatedFromXToY'),
6226
                            api_convert_and_format_date($this->start_time),
6227
                            api_convert_and_format_date($this->end_time)
6228
                        );
6229
                    }
6230
                } else {
6231
                    if ($nowIsBeforeEndDate) {
6232
                        // before start date and before end date
6233
                        $isVisible = false;
6234
                        $message = sprintf(
6235
                            get_lang('ExerciseWillBeActivatedFromXToY'),
6236
                            api_convert_and_format_date($this->start_time),
6237
                            api_convert_and_format_date($this->end_time)
6238
                        );
6239
                    }
6240
                    // case before start date and after end date is impossible
6241
                }
6242
            } elseif (!$existsStartDate && !$existsEndDate) {
6243
                // doesnt exist start date nor end date
6244
                $isVisible = true;
6245
                $message = '';
6246
            }
6247
        }
6248
6249
        // 4. We check if the student have attempts
6250
        if ($isVisible) {
6251
            $exerciseAttempts = $this->selectAttempts();
6252
6253
            if ($exerciseAttempts > 0) {
6254
                $attemptCount = Event::get_attempt_count_not_finished(
6255
                    api_get_user_id(),
6256
                    $this->id,
6257
                    $lpId,
6258
                    $lpItemId,
6259
                    $lpItemViewId
6260
                );
6261
6262
                if ($attemptCount >= $exerciseAttempts) {
6263
                    $message = sprintf(
6264
                        get_lang('ReachedMaxAttempts'),
6265
                        $this->name,
6266
                        $exerciseAttempts
6267
                    );
6268
                    $isVisible = false;
6269
                }
6270
            } else {
6271
                $isLimitReached = ExerciseLib::isQuestionsLimitPerDayReached(
6272
                    api_get_user_id(),
6273
                    $this->selectNbrQuestions(),
6274
                    api_get_course_int_id(),
6275
                    api_get_session_id()
6276
                );
6277
6278
                if ($isLimitReached) {
6279
                    $maxQuestionsAnswered = (int) api_get_course_setting('quiz_question_limit_per_day');
6280
                    $message = sprintf(get_lang('QuizQuestionsLimitPerDayXReached'), $maxQuestionsAnswered);
6281
                    $isVisible = false;
6282
                }
6283
            }
6284
        }
6285
6286
        $rawMessage = '';
6287
        if (!empty($message)) {
6288
            $rawMessage = $message;
6289
            $message = Display::return_message($message, 'warning', false);
6290
        }
6291
6292
        return [
6293
            'value' => $isVisible,
6294
            'message' => $message,
6295
            'rawMessage' => $rawMessage,
6296
        ];
6297
    }
6298
6299
    public function added_in_lp()
6300
    {
6301
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6302
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6303
                WHERE 
6304
                    c_id = {$this->course_id} AND 
6305
                    item_type = '".TOOL_QUIZ."' AND 
6306
                    path = '{$this->id}'";
6307
        $result = Database::query($sql);
6308
        if (Database::num_rows($result) > 0) {
6309
            return true;
6310
        }
6311
6312
        return false;
6313
    }
6314
6315
    /**
6316
     * Returns an array with this form.
6317
     *
6318
     * @example
6319
     * <code>
6320
     * array (size=3)
6321
     * 999 =>
6322
     * array (size=3)
6323
     * 0 => int 3422
6324
     * 1 => int 3423
6325
     * 2 => int 3424
6326
     * 100 =>
6327
     * array (size=2)
6328
     * 0 => int 3469
6329
     * 1 => int 3470
6330
     * 101 =>
6331
     * array (size=1)
6332
     * 0 => int 3482
6333
     * </code>
6334
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6335
     * this case is special because 999 means "no media".
6336
     *
6337
     * @return array
6338
     */
6339
    public function getMediaList()
6340
    {
6341
        return $this->mediaList;
6342
    }
6343
6344
    /**
6345
     * Is media question activated?
6346
     *
6347
     * @return bool
6348
     */
6349
    public function mediaIsActivated()
6350
    {
6351
        $mediaQuestions = $this->getMediaList();
6352
        $active = false;
6353
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6354
            $media_count = count($mediaQuestions);
6355
            if ($media_count > 1) {
6356
                return true;
6357
            } elseif ($media_count == 1) {
6358
                if (isset($mediaQuestions[999])) {
6359
                    return false;
6360
                } else {
6361
                    return true;
6362
                }
6363
            }
6364
        }
6365
6366
        return $active;
6367
    }
6368
6369
    /**
6370
     * Gets question list from the exercise.
6371
     *
6372
     * @return array
6373
     */
6374
    public function getQuestionList()
6375
    {
6376
        return $this->questionList;
6377
    }
6378
6379
    /**
6380
     * Question list with medias compressed like this.
6381
     *
6382
     * @example
6383
     * <code>
6384
     * array(
6385
     *      question_id_1,
6386
     *      question_id_2,
6387
     *      media_id, <- this media id contains question ids
6388
     *      question_id_3,
6389
     * )
6390
     * </code>
6391
     *
6392
     * @return array
6393
     */
6394
    public function getQuestionListWithMediasCompressed()
6395
    {
6396
        return $this->questionList;
6397
    }
6398
6399
    /**
6400
     * Question list with medias uncompressed like this.
6401
     *
6402
     * @example
6403
     * <code>
6404
     * array(
6405
     *      question_id,
6406
     *      question_id,
6407
     *      question_id, <- belongs to a media id
6408
     *      question_id, <- belongs to a media id
6409
     *      question_id,
6410
     * )
6411
     * </code>
6412
     *
6413
     * @return array
6414
     */
6415
    public function getQuestionListWithMediasUncompressed()
6416
    {
6417
        return $this->questionListUncompressed;
6418
    }
6419
6420
    /**
6421
     * Sets the question list when the exercise->read() is executed.
6422
     *
6423
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
6424
     */
6425
    public function setQuestionList($adminView = false)
6426
    {
6427
        // Getting question list.
6428
        $questionList = $this->selectQuestionList(true, $adminView);
6429
        $this->setMediaList($questionList);
6430
        $this->questionList = $this->transformQuestionListWithMedias(
6431
            $questionList,
6432
            false
6433
        );
6434
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6435
            $questionList,
6436
            true
6437
        );
6438
    }
6439
6440
    /**
6441
     * @params array question list
6442
     * @params bool expand or not question list (true show all questions,
6443
     * false show media question id instead of the question ids)
6444
     */
6445
    public function transformQuestionListWithMedias(
6446
        $question_list,
6447
        $expand_media_questions = false
6448
    ) {
6449
        $new_question_list = [];
6450
        if (!empty($question_list)) {
6451
            $media_questions = $this->getMediaList();
6452
6453
            $media_active = $this->mediaIsActivated($media_questions);
6454
6455
            if ($media_active) {
6456
                $counter = 1;
6457
                foreach ($question_list as $question_id) {
6458
                    $add_question = true;
6459
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6460
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6461
                            $add_question = false;
6462
                            if (!in_array($media_id, $new_question_list)) {
6463
                                $new_question_list[$counter] = $media_id;
6464
                                $counter++;
6465
                            }
6466
                            break;
6467
                        }
6468
                    }
6469
                    if ($add_question) {
6470
                        $new_question_list[$counter] = $question_id;
6471
                        $counter++;
6472
                    }
6473
                }
6474
                if ($expand_media_questions) {
6475
                    $media_key_list = array_keys($media_questions);
6476
                    foreach ($new_question_list as &$question_id) {
6477
                        if (in_array($question_id, $media_key_list)) {
6478
                            $question_id = $media_questions[$question_id];
6479
                        }
6480
                    }
6481
                    $new_question_list = array_flatten($new_question_list);
6482
                }
6483
            } else {
6484
                $new_question_list = $question_list;
6485
            }
6486
        }
6487
6488
        return $new_question_list;
6489
    }
6490
6491
    /**
6492
     * Get question list depend on the random settings.
6493
     *
6494
     * @return array
6495
     */
6496
    public function get_validated_question_list()
6497
    {
6498
        $result = [];
6499
        $isRandomByCategory = $this->isRandomByCat();
6500
        if ($isRandomByCategory == 0) {
6501
            if ($this->isRandom()) {
6502
                $result = $this->getRandomList();
6503
            } else {
6504
                $result = $this->selectQuestionList();
6505
            }
6506
        } else {
6507
            if ($this->isRandom()) {
6508
                // USE question categories
6509
                // get questions by category for this exercise
6510
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6511
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6512
                // value is the array of question id of this category
6513
                $questionList = [];
6514
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6515
                $isRandomByCategory = $this->getRandomByCategory();
6516
                // We sort categories based on the term between [] in the head
6517
                // of the category's description
6518
                /* examples of categories :
6519
                 * [biologie] Maitriser les mecanismes de base de la genetique
6520
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6521
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6522
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6523
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6524
                 * [chimie] Connaître les charges des particules
6525
                 * We want that in the order of the groups defined by the term
6526
                 * between brackets at the beginning of the category title
6527
                */
6528
                // If test option is Grouped By Categories
6529
                if ($isRandomByCategory == 2) {
6530
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6531
                }
6532
                foreach ($tabCategoryQuestions as $tabquestion) {
6533
                    $number_of_random_question = $this->random;
6534
                    if ($this->random == -1) {
6535
                        $number_of_random_question = count($this->questionList);
6536
                    }
6537
                    $questionList = array_merge(
6538
                        $questionList,
6539
                        TestCategory::getNElementsFromArray(
6540
                            $tabquestion,
6541
                            $number_of_random_question
6542
                        )
6543
                    );
6544
                }
6545
                // shuffle the question list if test is not grouped by categories
6546
                if ($isRandomByCategory == 1) {
6547
                    shuffle($questionList); // or not
6548
                }
6549
                $result = $questionList;
6550
            } else {
6551
                // Problem, random by category has been selected and
6552
                // we have no $this->isRandom number of question selected
6553
                // Should not happened
6554
            }
6555
        }
6556
6557
        return $result;
6558
    }
6559
6560
    public function get_question_list($expand_media_questions = false)
6561
    {
6562
        $question_list = $this->get_validated_question_list();
6563
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6564
6565
        return $question_list;
6566
    }
6567
6568
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6569
    {
6570
        $new_question_list = [];
6571
        if (!empty($question_list)) {
6572
            $media_questions = $this->getMediaList();
6573
            $media_active = $this->mediaIsActivated($media_questions);
6574
6575
            if ($media_active) {
6576
                $counter = 1;
6577
                foreach ($question_list as $question_id) {
6578
                    $add_question = true;
6579
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6580
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6581
                            $add_question = false;
6582
                            if (!in_array($media_id, $new_question_list)) {
6583
                                $new_question_list[$counter] = $media_id;
6584
                                $counter++;
6585
                            }
6586
                            break;
6587
                        }
6588
                    }
6589
                    if ($add_question) {
6590
                        $new_question_list[$counter] = $question_id;
6591
                        $counter++;
6592
                    }
6593
                }
6594
                if ($expand_media_questions) {
6595
                    $media_key_list = array_keys($media_questions);
6596
                    foreach ($new_question_list as &$question_id) {
6597
                        if (in_array($question_id, $media_key_list)) {
6598
                            $question_id = $media_questions[$question_id];
6599
                        }
6600
                    }
6601
                    $new_question_list = array_flatten($new_question_list);
6602
                }
6603
            } else {
6604
                $new_question_list = $question_list;
6605
            }
6606
        }
6607
6608
        return $new_question_list;
6609
    }
6610
6611
    /**
6612
     * @param int $exe_id
6613
     *
6614
     * @return array
6615
     */
6616
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6617
    {
6618
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6619
        $exe_id = (int) $exe_id;
6620
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
6621
        $result = Database::query($sql_track);
6622
        $new_array = [];
6623
        if (Database::num_rows($result) > 0) {
6624
            $new_array = Database::fetch_array($result, 'ASSOC');
6625
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6626
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6627
            $new_array['duration_formatted'] = '';
6628
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
6629
                $time = api_format_time($new_array['exe_duration'], 'js');
6630
                $new_array['duration_formatted'] = $time;
6631
            }
6632
        }
6633
6634
        return $new_array;
6635
    }
6636
6637
    /**
6638
     * @param int $exeId
6639
     *
6640
     * @return bool
6641
     */
6642
    public function removeAllQuestionToRemind($exeId)
6643
    {
6644
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6645
        $exeId = (int) $exeId;
6646
        if (empty($exeId)) {
6647
            return false;
6648
        }
6649
        $sql = "UPDATE $table 
6650
                SET questions_to_check = '' 
6651
                WHERE exe_id = $exeId ";
6652
        Database::query($sql);
6653
6654
        return true;
6655
    }
6656
6657
    /**
6658
     * @param int   $exeId
6659
     * @param array $questionList
6660
     *
6661
     * @return bool
6662
     */
6663
    public function addAllQuestionToRemind($exeId, $questionList = [])
6664
    {
6665
        $exeId = (int) $exeId;
6666
        if (empty($questionList)) {
6667
            return false;
6668
        }
6669
6670
        $questionListToString = implode(',', $questionList);
6671
        $questionListToString = Database::escape_string($questionListToString);
6672
6673
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6674
        $sql = "UPDATE $table 
6675
                SET questions_to_check = '$questionListToString' 
6676
                WHERE exe_id = $exeId";
6677
        Database::query($sql);
6678
6679
        return true;
6680
    }
6681
6682
    /**
6683
     * @param int    $exe_id
6684
     * @param int    $question_id
6685
     * @param string $action
6686
     */
6687
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6688
    {
6689
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6690
        $question_id = (int) $question_id;
6691
        $exe_id = (int) $exe_id;
6692
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6693
        if ($exercise_info) {
6694
            if (empty($exercise_info['questions_to_check'])) {
6695
                if ($action == 'add') {
6696
                    $sql = "UPDATE $track_exercises 
6697
                            SET questions_to_check = '$question_id' 
6698
                            WHERE exe_id = $exe_id ";
6699
                    Database::query($sql);
6700
                }
6701
            } else {
6702
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6703
                $remind_list_string = '';
6704
                if ($action == 'add') {
6705
                    if (!in_array($question_id, $remind_list)) {
6706
                        $newRemindList = [];
6707
                        $remind_list[] = $question_id;
6708
                        $questionListInSession = Session::read('questionList');
6709
                        if (!empty($questionListInSession)) {
6710
                            foreach ($questionListInSession as $originalQuestionId) {
6711
                                if (in_array($originalQuestionId, $remind_list)) {
6712
                                    $newRemindList[] = $originalQuestionId;
6713
                                }
6714
                            }
6715
                        }
6716
                        $remind_list_string = implode(',', $newRemindList);
6717
                    }
6718
                } elseif ($action == 'delete') {
6719
                    if (!empty($remind_list)) {
6720
                        if (in_array($question_id, $remind_list)) {
6721
                            $remind_list = array_flip($remind_list);
6722
                            unset($remind_list[$question_id]);
6723
                            $remind_list = array_flip($remind_list);
6724
6725
                            if (!empty($remind_list)) {
6726
                                sort($remind_list);
6727
                                array_filter($remind_list);
6728
                                $remind_list_string = implode(',', $remind_list);
6729
                            }
6730
                        }
6731
                    }
6732
                }
6733
                $value = Database::escape_string($remind_list_string);
6734
                $sql = "UPDATE $track_exercises 
6735
                        SET questions_to_check = '$value' 
6736
                        WHERE exe_id = $exe_id ";
6737
                Database::query($sql);
6738
            }
6739
        }
6740
    }
6741
6742
    /**
6743
     * @param string $answer
6744
     *
6745
     * @return mixed
6746
     */
6747
    public function fill_in_blank_answer_to_array($answer)
6748
    {
6749
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6750
        $teacher_answer_list = $teacher_answer_list[0];
6751
6752
        return $teacher_answer_list;
6753
    }
6754
6755
    /**
6756
     * @param string $answer
6757
     *
6758
     * @return string
6759
     */
6760
    public function fill_in_blank_answer_to_string($answer)
6761
    {
6762
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6763
        $result = '';
6764
        if (!empty($teacher_answer_list)) {
6765
            $i = 0;
6766
            foreach ($teacher_answer_list as $teacher_item) {
6767
                $value = null;
6768
                //Cleaning student answer list
6769
                $value = strip_tags($teacher_item);
6770
                $value = api_substr($value, 1, api_strlen($value) - 2);
6771
                $value = explode('/', $value);
6772
                if (!empty($value[0])) {
6773
                    $value = trim($value[0]);
6774
                    $value = str_replace('&nbsp;', '', $value);
6775
                    $result .= $value;
6776
                }
6777
            }
6778
        }
6779
6780
        return $result;
6781
    }
6782
6783
    /**
6784
     * @return string
6785
     */
6786
    public function return_time_left_div()
6787
    {
6788
        $html = '<div id="clock_warning" style="display:none">';
6789
        $html .= Display::return_message(
6790
            get_lang('ReachedTimeLimit'),
6791
            'warning'
6792
        );
6793
        $html .= ' ';
6794
        $html .= sprintf(
6795
            get_lang('YouWillBeRedirectedInXSeconds'),
6796
            '<span id="counter_to_redirect" class="red_alert"></span>'
6797
        );
6798
        $html .= '</div>';
6799
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6800
6801
        return $html;
6802
    }
6803
6804
    /**
6805
     * Get categories added in the exercise--category matrix.
6806
     *
6807
     * @return array
6808
     */
6809
    public function getCategoriesInExercise()
6810
    {
6811
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6812
        if (!empty($this->id)) {
6813
            $sql = "SELECT * FROM $table
6814
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6815
            $result = Database::query($sql);
6816
            $list = [];
6817
            if (Database::num_rows($result)) {
6818
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6819
                    $list[$row['category_id']] = $row;
6820
                }
6821
6822
                return $list;
6823
            }
6824
        }
6825
6826
        return [];
6827
    }
6828
6829
    /**
6830
     * Get total number of question that will be parsed when using the category/exercise.
6831
     *
6832
     * @return int
6833
     */
6834
    public function getNumberQuestionExerciseCategory()
6835
    {
6836
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6837
        if (!empty($this->id)) {
6838
            $sql = "SELECT SUM(count_questions) count_questions
6839
                    FROM $table
6840
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6841
            $result = Database::query($sql);
6842
            if (Database::num_rows($result)) {
6843
                $row = Database::fetch_array($result);
6844
6845
                return $row['count_questions'];
6846
            }
6847
        }
6848
6849
        return 0;
6850
    }
6851
6852
    /**
6853
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
6854
     *
6855
     * @param array $categories
6856
     */
6857
    public function save_categories_in_exercise($categories)
6858
    {
6859
        if (!empty($categories) && !empty($this->id)) {
6860
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6861
            $sql = "DELETE FROM $table
6862
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6863
            Database::query($sql);
6864
            if (!empty($categories)) {
6865
                foreach ($categories as $categoryId => $countQuestions) {
6866
                    $params = [
6867
                        'c_id' => $this->course_id,
6868
                        'exercise_id' => $this->id,
6869
                        'category_id' => $categoryId,
6870
                        'count_questions' => $countQuestions,
6871
                    ];
6872
                    Database::insert($table, $params);
6873
                }
6874
            }
6875
        }
6876
    }
6877
6878
    /**
6879
     * @param array  $questionList
6880
     * @param int    $currentQuestion
6881
     * @param array  $conditions
6882
     * @param string $link
6883
     *
6884
     * @return string
6885
     */
6886
    public function progressExercisePaginationBar(
6887
        $questionList,
6888
        $currentQuestion,
6889
        $conditions,
6890
        $link
6891
    ) {
6892
        $mediaQuestions = $this->getMediaList();
6893
6894
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6895
        $counter = 0;
6896
        $nextValue = 0;
6897
        $wasMedia = false;
6898
        $before = 0;
6899
        $counterNoMedias = 0;
6900
        foreach ($questionList as $questionId) {
6901
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6902
6903
            if (!empty($nextValue)) {
6904
                if ($wasMedia) {
6905
                    $nextValue = $nextValue - $before + 1;
6906
                }
6907
            }
6908
6909
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6910
                $fixedValue = $counterNoMedias;
6911
6912
                $html .= Display::progressPaginationBar(
6913
                    $nextValue,
6914
                    $mediaQuestions[$questionId],
6915
                    $currentQuestion,
6916
                    $fixedValue,
6917
                    $conditions,
6918
                    $link,
6919
                    true,
6920
                    true
6921
                );
6922
6923
                $counter += count($mediaQuestions[$questionId]) - 1;
6924
                $before = count($questionList);
6925
                $wasMedia = true;
6926
                $nextValue += count($questionList);
6927
            } else {
6928
                $html .= Display::parsePaginationItem(
6929
                    $questionId,
6930
                    $isCurrent,
6931
                    $conditions,
6932
                    $link,
6933
                    $counter
6934
                );
6935
                $counter++;
6936
                $nextValue++;
6937
                $wasMedia = false;
6938
            }
6939
            $counterNoMedias++;
6940
        }
6941
        $html .= '</ul></div>';
6942
6943
        return $html;
6944
    }
6945
6946
    /**
6947
     *  Shows a list of numbers that represents the question to answer in a exercise.
6948
     *
6949
     * @param array  $categories
6950
     * @param int    $current
6951
     * @param array  $conditions
6952
     * @param string $link
6953
     *
6954
     * @return string
6955
     */
6956
    public function progressExercisePaginationBarWithCategories(
6957
        $categories,
6958
        $current,
6959
        $conditions = [],
6960
        $link = null
6961
    ) {
6962
        $html = null;
6963
        $counterNoMedias = 0;
6964
        $nextValue = 0;
6965
        $wasMedia = false;
6966
        $before = 0;
6967
6968
        if (!empty($categories)) {
6969
            $selectionType = $this->getQuestionSelectionType();
6970
            $useRootAsCategoryTitle = false;
6971
6972
            // Grouping questions per parent category see BT#6540
6973
            if (in_array(
6974
                $selectionType,
6975
                [
6976
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6977
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
6978
                ]
6979
            )) {
6980
                $useRootAsCategoryTitle = true;
6981
            }
6982
6983
            // If the exercise is set to only show the titles of the categories
6984
            // at the root of the tree, then pre-order the categories tree by
6985
            // removing children and summing their questions into the parent
6986
            // categories
6987
            if ($useRootAsCategoryTitle) {
6988
                // The new categories list starts empty
6989
                $newCategoryList = [];
6990
                foreach ($categories as $category) {
6991
                    $rootElement = $category['root'];
6992
6993
                    if (isset($category['parent_info'])) {
6994
                        $rootElement = $category['parent_info']['id'];
6995
                    }
6996
6997
                    //$rootElement = $category['id'];
6998
                    // If the current category's ancestor was never seen
6999
                    // before, then declare it and assign the current
7000
                    // category to it.
7001
                    if (!isset($newCategoryList[$rootElement])) {
7002
                        $newCategoryList[$rootElement] = $category;
7003
                    } else {
7004
                        // If it was already seen, then merge the previous with
7005
                        // the current category
7006
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7007
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7008
                        $newCategoryList[$rootElement] = $category;
7009
                    }
7010
                }
7011
                // Now use the newly built categories list, with only parents
7012
                $categories = $newCategoryList;
7013
            }
7014
7015
            foreach ($categories as $category) {
7016
                $questionList = $category['question_list'];
7017
                // Check if in this category there questions added in a media
7018
                $mediaQuestionId = $category['media_question'];
7019
                $isMedia = false;
7020
                $fixedValue = null;
7021
7022
                // Media exists!
7023
                if ($mediaQuestionId != 999) {
7024
                    $isMedia = true;
7025
                    $fixedValue = $counterNoMedias;
7026
                }
7027
7028
                //$categoryName = $category['path']; << show the path
7029
                $categoryName = $category['name'];
7030
7031
                if ($useRootAsCategoryTitle) {
7032
                    if (isset($category['parent_info'])) {
7033
                        $categoryName = $category['parent_info']['title'];
7034
                    }
7035
                }
7036
                $html .= '<div class="row">';
7037
                $html .= '<div class="span2">'.$categoryName.'</div>';
7038
                $html .= '<div class="span8">';
7039
7040
                if (!empty($nextValue)) {
7041
                    if ($wasMedia) {
7042
                        $nextValue = $nextValue - $before + 1;
7043
                    }
7044
                }
7045
                $html .= Display::progressPaginationBar(
7046
                    $nextValue,
7047
                    $questionList,
7048
                    $current,
7049
                    $fixedValue,
7050
                    $conditions,
7051
                    $link,
7052
                    $isMedia,
7053
                    true
7054
                );
7055
                $html .= '</div>';
7056
                $html .= '</div>';
7057
7058
                if ($mediaQuestionId == 999) {
7059
                    $counterNoMedias += count($questionList);
7060
                } else {
7061
                    $counterNoMedias++;
7062
                }
7063
7064
                $nextValue += count($questionList);
7065
                $before = count($questionList);
7066
7067
                if ($mediaQuestionId != 999) {
7068
                    $wasMedia = true;
7069
                } else {
7070
                    $wasMedia = false;
7071
                }
7072
            }
7073
        }
7074
7075
        return $html;
7076
    }
7077
7078
    /**
7079
     * Renders a question list.
7080
     *
7081
     * @param array $questionList    (with media questions compressed)
7082
     * @param int   $currentQuestion
7083
     * @param array $exerciseResult
7084
     * @param array $attemptList
7085
     * @param array $remindList
7086
     */
7087
    public function renderQuestionList(
7088
        $questionList,
7089
        $currentQuestion,
7090
        $exerciseResult,
7091
        $attemptList,
7092
        $remindList
7093
    ) {
7094
        $mediaQuestions = $this->getMediaList();
7095
        $i = 0;
7096
7097
        // Normal question list render (medias compressed)
7098
        foreach ($questionList as $questionId) {
7099
            $i++;
7100
            // For sequential exercises
7101
7102
            if ($this->type == ONE_PER_PAGE) {
7103
                // If it is not the right question, goes to the next loop iteration
7104
                if ($currentQuestion != $i) {
7105
                    continue;
7106
                } else {
7107
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
7108
                        // if the user has already answered this question
7109
                        if (isset($exerciseResult[$questionId])) {
7110
                            echo Display::return_message(
7111
                                get_lang('AlreadyAnswered'),
7112
                                'normal'
7113
                            );
7114
                            break;
7115
                        }
7116
                    }
7117
                }
7118
            }
7119
7120
            // The $questionList contains the media id we check
7121
            // if this questionId is a media question type
7122
            if (isset($mediaQuestions[$questionId]) &&
7123
                $mediaQuestions[$questionId] != 999
7124
            ) {
7125
                // The question belongs to a media
7126
                $mediaQuestionList = $mediaQuestions[$questionId];
7127
                $objQuestionTmp = Question::read($questionId);
7128
7129
                $counter = 1;
7130
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7131
                    echo $objQuestionTmp->show_media_content();
7132
7133
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7134
7135
                    // Show questions that belongs to a media
7136
                    if (!empty($mediaQuestionList)) {
7137
                        // In order to parse media questions we use letters a, b, c, etc.
7138
                        $letterCounter = 97;
7139
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7140
                            $isLastQuestionInMedia = false;
7141
                            if ($counter == $countQuestionsInsideMedia) {
7142
                                $isLastQuestionInMedia = true;
7143
                            }
7144
                            $this->renderQuestion(
7145
                                $questionIdInsideMedia,
7146
                                $attemptList,
7147
                                $remindList,
7148
                                chr($letterCounter),
7149
                                $currentQuestion,
7150
                                $mediaQuestionList,
7151
                                $isLastQuestionInMedia,
7152
                                $questionList
7153
                            );
7154
                            $letterCounter++;
7155
                            $counter++;
7156
                        }
7157
                    }
7158
                } else {
7159
                    $this->renderQuestion(
7160
                        $questionId,
7161
                        $attemptList,
7162
                        $remindList,
7163
                        $i,
7164
                        $currentQuestion,
7165
                        null,
7166
                        null,
7167
                        $questionList
7168
                    );
7169
                    $i++;
7170
                }
7171
            } else {
7172
                // Normal question render.
7173
                $this->renderQuestion(
7174
                    $questionId,
7175
                    $attemptList,
7176
                    $remindList,
7177
                    $i,
7178
                    $currentQuestion,
7179
                    null,
7180
                    null,
7181
                    $questionList
7182
                );
7183
            }
7184
7185
            // For sequential exercises.
7186
            if ($this->type == ONE_PER_PAGE) {
7187
                // quits the loop
7188
                break;
7189
            }
7190
        }
7191
        // end foreach()
7192
7193
        if ($this->type == ALL_ON_ONE_PAGE) {
7194
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7195
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7196
        }
7197
    }
7198
7199
    /**
7200
     * @param int   $questionId
7201
     * @param array $attemptList
7202
     * @param array $remindList
7203
     * @param int   $i
7204
     * @param int   $current_question
7205
     * @param array $questions_in_media
7206
     * @param bool  $last_question_in_media
7207
     * @param array $realQuestionList
7208
     * @param bool  $generateJS
7209
     */
7210
    public function renderQuestion(
7211
        $questionId,
7212
        $attemptList,
7213
        $remindList,
7214
        $i,
7215
        $current_question,
7216
        $questions_in_media = [],
7217
        $last_question_in_media = false,
7218
        $realQuestionList,
7219
        $generateJS = true
7220
    ) {
7221
        // With this option on the question is loaded via AJAX
7222
        //$generateJS = true;
7223
        //$this->loadQuestionAJAX = true;
7224
7225
        if ($generateJS && $this->loadQuestionAJAX) {
7226
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7227
            $params = [
7228
                'questionId' => $questionId,
7229
                'attemptList' => $attemptList,
7230
                'remindList' => $remindList,
7231
                'i' => $i,
7232
                'current_question' => $current_question,
7233
                'questions_in_media' => $questions_in_media,
7234
                'last_question_in_media' => $last_question_in_media,
7235
            ];
7236
            $params = json_encode($params);
7237
7238
            $script = '<script>
7239
            $(function(){
7240
                var params = '.$params.';
7241
                $.ajax({
7242
                    type: "GET",
7243
                    async: false,
7244
                    data: params,
7245
                    url: "'.$url.'",
7246
                    success: function(return_value) {
7247
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7248
                    }
7249
                });
7250
            });
7251
            </script>
7252
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7253
            echo $script;
7254
        } else {
7255
            global $origin;
7256
            $question_obj = Question::read($questionId);
7257
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7258
            $remind_highlight = null;
7259
7260
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7261
            // see #4542 no_remind_highlight class hide with jquery
7262
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7263
                $remind_highlight = 'no_remind_highlight';
7264
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7265
                    return null;
7266
                }
7267
            }
7268
7269
            $attributes = ['id' => 'remind_list['.$questionId.']'];
7270
7271
            // Showing the question
7272
            $exercise_actions = null;
7273
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7274
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7275
7276
            // Shows the question + possible answers
7277
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7278
            echo $this->showQuestion(
7279
                $question_obj,
7280
                false,
7281
                $origin,
7282
                $i,
7283
                $showTitle,
7284
                false,
7285
                $user_choice,
7286
                false,
7287
                null,
7288
                false,
7289
                $this->getModelType(),
7290
                $this->categoryMinusOne
7291
            );
7292
7293
            // Button save and continue
7294
            switch ($this->type) {
7295
                case ONE_PER_PAGE:
7296
                    $exercise_actions .= $this->show_button(
7297
                        $questionId,
7298
                        $current_question,
7299
                        null,
7300
                        $remindList
7301
                    );
7302
                    break;
7303
                case ALL_ON_ONE_PAGE:
7304
                    if (api_is_allowed_to_session_edit()) {
7305
                        $button = [
7306
                            Display::button(
7307
                                'save_now',
7308
                                get_lang('SaveForNow'),
7309
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7310
                            ),
7311
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7312
                        ];
7313
                        $exercise_actions .= Display::div(
7314
                            implode(PHP_EOL, $button),
7315
                            ['class' => 'exercise_save_now_button']
7316
                        );
7317
                    }
7318
                    break;
7319
            }
7320
7321
            if (!empty($questions_in_media)) {
7322
                $count_of_questions_inside_media = count($questions_in_media);
7323
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7324
                    $button = [
7325
                        Display::button(
7326
                            'save_now',
7327
                            get_lang('SaveForNow'),
7328
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7329
                        ),
7330
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7331
                    ];
7332
                    $exercise_actions = Display::div(
7333
                        implode(PHP_EOL, $button),
7334
                        ['class' => 'exercise_save_now_button']
7335
                    );
7336
                }
7337
7338
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7339
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7340
                }
7341
            }
7342
7343
            // Checkbox review answers
7344
            if ($this->review_answers &&
7345
                !in_array($question_obj->type, Question::question_type_no_review())
7346
            ) {
7347
                $remind_question_div = Display::tag(
7348
                    'label',
7349
                    Display::input(
7350
                        'checkbox',
7351
                        'remind_list['.$questionId.']',
7352
                        '',
7353
                        $attributes
7354
                    ).get_lang('ReviewQuestionLater'),
7355
                    [
7356
                        'class' => 'checkbox',
7357
                        'for' => 'remind_list['.$questionId.']',
7358
                    ]
7359
                );
7360
                $exercise_actions .= Display::div(
7361
                    $remind_question_div,
7362
                    ['class' => 'exercise_save_now_button']
7363
                );
7364
            }
7365
7366
            echo Display::div(' ', ['class' => 'clear']);
7367
7368
            $paginationCounter = null;
7369
            if ($this->type == ONE_PER_PAGE) {
7370
                if (empty($questions_in_media)) {
7371
                    $paginationCounter = Display::paginationIndicator(
7372
                        $current_question,
7373
                        count($realQuestionList)
7374
                    );
7375
                } else {
7376
                    if ($last_question_in_media) {
7377
                        $paginationCounter = Display::paginationIndicator(
7378
                            $current_question,
7379
                            count($realQuestionList)
7380
                        );
7381
                    }
7382
                }
7383
            }
7384
7385
            echo '<div class="row"><div class="float-right">'.$paginationCounter.'</div></div>';
7386
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7387
            echo '</div>';
7388
        }
7389
    }
7390
7391
    /**
7392
     * Returns an array of categories details for the questions of the current
7393
     * exercise.
7394
     *
7395
     * @return array
7396
     */
7397
    public function getQuestionWithCategories()
7398
    {
7399
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7400
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7401
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7402
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7403
        $sql = "SELECT DISTINCT cat.*
7404
                FROM $TBL_EXERCICE_QUESTION e
7405
                INNER JOIN $TBL_QUESTIONS q
7406
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7407
                INNER JOIN $categoryRelTable catRel
7408
                ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id)
7409
                INNER JOIN $categoryTable cat
7410
                ON (cat.id = catRel.category_id AND cat.c_id = e.c_id)
7411
                WHERE
7412
                  e.c_id = {$this->course_id} AND
7413
                  e.exercice_id	= ".intval($this->id);
7414
7415
        $result = Database::query($sql);
7416
        $categoriesInExercise = [];
7417
        if (Database::num_rows($result)) {
7418
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7419
        }
7420
7421
        return $categoriesInExercise;
7422
    }
7423
7424
    /**
7425
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
7426
     */
7427
    public function get_max_score()
7428
    {
7429
        $out_max_score = 0;
7430
        // list of question's id !!! the array key start at 1 !!!
7431
        $questionList = $this->selectQuestionList(true);
7432
7433
        // test is randomQuestions - see field random of test
7434
        if ($this->random > 0 && $this->randomByCat == 0) {
7435
            $numberRandomQuestions = $this->random;
7436
            $questionScoreList = [];
7437
            foreach ($questionList as $questionId) {
7438
                $tmpobj_question = Question::read($questionId);
7439
                if (is_object($tmpobj_question)) {
7440
                    $questionScoreList[] = $tmpobj_question->weighting;
7441
                }
7442
            }
7443
7444
            rsort($questionScoreList);
7445
            // add the first $numberRandomQuestions value of score array to get max_score
7446
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7447
                $out_max_score += $questionScoreList[$i];
7448
            }
7449
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7450
            // test is random by category
7451
            // get the $numberRandomQuestions best score question of each category
7452
            $numberRandomQuestions = $this->random;
7453
            $tab_categories_scores = [];
7454
            foreach ($questionList as $questionId) {
7455
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7456
                if (!is_array($tab_categories_scores[$question_category_id])) {
7457
                    $tab_categories_scores[$question_category_id] = [];
7458
                }
7459
                $tmpobj_question = Question::read($questionId);
7460
                if (is_object($tmpobj_question)) {
7461
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7462
                }
7463
            }
7464
7465
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7466
            foreach ($tab_categories_scores as $tab_scores) {
7467
                rsort($tab_scores);
7468
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7469
                    $out_max_score += $tab_scores[$i];
7470
                }
7471
            }
7472
        } else {
7473
            // standard test, just add each question score
7474
            foreach ($questionList as $questionId) {
7475
                $question = Question::read($questionId, $this->course_id);
7476
                $out_max_score += $question->weighting;
7477
            }
7478
        }
7479
7480
        return $out_max_score;
7481
    }
7482
7483
    /**
7484
     * @return string
7485
     */
7486
    public function get_formated_title()
7487
    {
7488
        if (api_get_configuration_value('save_titles_as_html')) {
7489
        }
7490
7491
        return api_html_entity_decode($this->selectTitle());
7492
    }
7493
7494
    /**
7495
     * @param string $title
7496
     *
7497
     * @return string
7498
     */
7499
    public static function get_formated_title_variable($title)
7500
    {
7501
        return api_html_entity_decode($title);
7502
    }
7503
7504
    /**
7505
     * @return string
7506
     */
7507
    public function format_title()
7508
    {
7509
        return api_htmlentities($this->title);
7510
    }
7511
7512
    /**
7513
     * @param string $title
7514
     *
7515
     * @return string
7516
     */
7517
    public static function format_title_variable($title)
7518
    {
7519
        return api_htmlentities($title);
7520
    }
7521
7522
    /**
7523
     * @param int $courseId
7524
     * @param int $sessionId
7525
     *
7526
     * @return array exercises
7527
     */
7528
    public function getExercisesByCourseSession($courseId, $sessionId)
7529
    {
7530
        $courseId = (int) $courseId;
7531
        $sessionId = (int) $sessionId;
7532
7533
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7534
        $sql = "SELECT * FROM $tbl_quiz cq
7535
                WHERE
7536
                    cq.c_id = %s AND
7537
                    (cq.session_id = %s OR cq.session_id = 0) AND
7538
                    cq.active = 0
7539
                ORDER BY cq.id";
7540
        $sql = sprintf($sql, $courseId, $sessionId);
7541
7542
        $result = Database::query($sql);
7543
7544
        $rows = [];
7545
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7546
            $rows[] = $row;
7547
        }
7548
7549
        return $rows;
7550
    }
7551
7552
    /**
7553
     * @param int   $courseId
7554
     * @param int   $sessionId
7555
     * @param array $quizId
7556
     *
7557
     * @return array exercises
7558
     */
7559
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7560
    {
7561
        if (empty($quizId)) {
7562
            return [];
7563
        }
7564
7565
        $sessionId = (int) $sessionId;
7566
        $courseId = (int) $courseId;
7567
7568
        $ids = is_array($quizId) ? $quizId : [$quizId];
7569
        $ids = array_map('intval', $ids);
7570
        $ids = implode(',', $ids);
7571
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7572
        if ($sessionId != 0) {
7573
            $sql = "SELECT * FROM $track_exercises te
7574
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7575
              WHERE
7576
              te.id = %s AND
7577
              te.session_id = %s AND
7578
              cq.id IN (%s)
7579
              ORDER BY cq.id";
7580
7581
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7582
        } else {
7583
            $sql = "SELECT * FROM $track_exercises te
7584
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7585
              WHERE
7586
              te.id = %s AND
7587
              cq.id IN (%s)
7588
              ORDER BY cq.id";
7589
            $sql = sprintf($sql, $courseId, $ids);
7590
        }
7591
        $result = Database::query($sql);
7592
        $rows = [];
7593
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7594
            $rows[] = $row;
7595
        }
7596
7597
        return $rows;
7598
    }
7599
7600
    /**
7601
     * @param $exeId
7602
     * @param $exercise_stat_info
7603
     * @param $remindList
7604
     * @param $currentQuestion
7605
     *
7606
     * @return int|null
7607
     */
7608
    public static function getNextQuestionId(
7609
        $exeId,
7610
        $exercise_stat_info,
7611
        $remindList,
7612
        $currentQuestion
7613
    ) {
7614
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7615
7616
        if (isset($result[$exeId])) {
7617
            $result = $result[$exeId];
7618
        } else {
7619
            return null;
7620
        }
7621
7622
        $data_tracking = $exercise_stat_info['data_tracking'];
7623
        $data_tracking = explode(',', $data_tracking);
7624
7625
        // if this is the final question do nothing.
7626
        if ($currentQuestion == count($data_tracking)) {
7627
            return null;
7628
        }
7629
7630
        $currentQuestion = $currentQuestion - 1;
7631
7632
        if (!empty($result['question_list'])) {
7633
            $answeredQuestions = [];
7634
            foreach ($result['question_list'] as $question) {
7635
                if (!empty($question['answer'])) {
7636
                    $answeredQuestions[] = $question['question_id'];
7637
                }
7638
            }
7639
7640
            // Checking answered questions
7641
            $counterAnsweredQuestions = 0;
7642
            foreach ($data_tracking as $questionId) {
7643
                if (!in_array($questionId, $answeredQuestions)) {
7644
                    if ($currentQuestion != $counterAnsweredQuestions) {
7645
                        break;
7646
                    }
7647
                }
7648
                $counterAnsweredQuestions++;
7649
            }
7650
7651
            $counterRemindListQuestions = 0;
7652
            // Checking questions saved in the reminder list
7653
            if (!empty($remindList)) {
7654
                foreach ($data_tracking as $questionId) {
7655
                    if (in_array($questionId, $remindList)) {
7656
                        // Skip the current question
7657
                        if ($currentQuestion != $counterRemindListQuestions) {
7658
                            break;
7659
                        }
7660
                    }
7661
                    $counterRemindListQuestions++;
7662
                }
7663
7664
                if ($counterRemindListQuestions < $currentQuestion) {
7665
                    return null;
7666
                }
7667
7668
                if (!empty($counterRemindListQuestions)) {
7669
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7670
                        return $counterAnsweredQuestions;
7671
                    } else {
7672
                        return $counterRemindListQuestions;
7673
                    }
7674
                }
7675
            }
7676
7677
            return $counterAnsweredQuestions;
7678
        }
7679
    }
7680
7681
    /**
7682
     * Gets the position of a questionId in the question list.
7683
     *
7684
     * @param $questionId
7685
     *
7686
     * @return int
7687
     */
7688
    public function getPositionInCompressedQuestionList($questionId)
7689
    {
7690
        $questionList = $this->getQuestionListWithMediasCompressed();
7691
        $mediaQuestions = $this->getMediaList();
7692
        $position = 1;
7693
        foreach ($questionList as $id) {
7694
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7695
                $mediaQuestionList = $mediaQuestions[$id];
7696
                if (in_array($questionId, $mediaQuestionList)) {
7697
                    return $position;
7698
                } else {
7699
                    $position++;
7700
                }
7701
            } else {
7702
                if ($id == $questionId) {
7703
                    return $position;
7704
                } else {
7705
                    $position++;
7706
                }
7707
            }
7708
        }
7709
7710
        return 1;
7711
    }
7712
7713
    /**
7714
     * Get the correct answers in all attempts.
7715
     *
7716
     * @param int $learnPathId
7717
     * @param int $learnPathItemId
7718
     *
7719
     * @return array
7720
     */
7721
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7722
    {
7723
        $attempts = Event::getExerciseResultsByUser(
7724
            api_get_user_id(),
7725
            $this->id,
7726
            api_get_course_int_id(),
7727
            api_get_session_id(),
7728
            $learnPathId,
7729
            $learnPathItemId,
7730
            'asc'
7731
        );
7732
7733
        $corrects = [];
7734
        foreach ($attempts as $attempt) {
7735
            foreach ($attempt['question_list'] as $answers) {
7736
                foreach ($answers as $answer) {
7737
                    $objAnswer = new Answer($answer['question_id']);
7738
7739
                    switch ($objAnswer->getQuestionType()) {
7740
                        case FILL_IN_BLANKS:
7741
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7742
                            break;
7743
                        case MATCHING:
7744
                        case DRAGGABLE:
7745
                        case MATCHING_DRAGGABLE:
7746
                            $isCorrect = Matching::isCorrect(
7747
                                $answer['position'],
7748
                                $answer['answer'],
7749
                                $answer['question_id']
7750
                            );
7751
                            break;
7752
                        case ORAL_EXPRESSION:
7753
                            $isCorrect = false;
7754
                            break;
7755
                        default:
7756
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7757
                    }
7758
7759
                    if ($isCorrect) {
7760
                        $corrects[$answer['question_id']][] = $answer;
7761
                    }
7762
                }
7763
            }
7764
        }
7765
7766
        return $corrects;
7767
    }
7768
7769
    /**
7770
     * @return bool
7771
     */
7772
    public function showPreviousButton()
7773
    {
7774
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
7775
        if ($allow === false) {
7776
            return true;
7777
        }
7778
7779
        return $this->showPreviousButton;
7780
    }
7781
7782
    /**
7783
     * @param bool $showPreviousButton
7784
     *
7785
     * @return Exercise
7786
     */
7787
    public function setShowPreviousButton($showPreviousButton)
7788
    {
7789
        $this->showPreviousButton = $showPreviousButton;
7790
7791
        return $this;
7792
    }
7793
7794
    /**
7795
     * @param array $notifications
7796
     */
7797
    public function setNotifications($notifications)
7798
    {
7799
        $this->notifications = $notifications;
7800
    }
7801
7802
    /**
7803
     * @return array
7804
     */
7805
    public function getNotifications()
7806
    {
7807
        return $this->notifications;
7808
    }
7809
7810
    /**
7811
     * @return bool
7812
     */
7813
    public function showExpectedChoice()
7814
    {
7815
        return api_get_configuration_value('show_exercise_expected_choice');
7816
    }
7817
7818
    /**
7819
     * @param string $class
7820
     * @param string $scoreLabel
7821
     * @param string $result
7822
     * @param array
7823
     *
7824
     * @return string
7825
     */
7826
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
7827
    {
7828
        if ($this->showExpectedChoice()) {
7829
            $html = null;
7830
            $hideLabel = api_get_configuration_value('exercise_hide_label');
7831
            $label = '<div class="rib rib-'.$class.'">
7832
                        <h3>'.$scoreLabel.'</h3>
7833
                      </div>';
7834
            if (!empty($result)) {
7835
                $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
7836
            }
7837
            if ($hideLabel === true) {
7838
                $answerUsed = (int) $array['used'];
7839
                $answerMissing = (int) $array['missing'] - $answerUsed;
7840
                for ($i = 1; $i <= $answerUsed; $i++) {
7841
                    $html .= '<span class="score-img">'.
7842
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
7843
                        '</span>';
7844
                }
7845
                for ($i = 1; $i <= $answerMissing; $i++) {
7846
                    $html .= '<span class="score-img">'.
7847
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
7848
                        '</span>';
7849
                }
7850
                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
7851
                $label .= '<div class="score-limits">';
7852
                $label .= $html;
7853
                $label .= '</div>';
7854
            }
7855
7856
            return '<div class="ribbon">
7857
                '.$label.'
7858
                </div>'
7859
                ;
7860
        } else {
7861
            $html = '<div class="ribbon">
7862
                        <div class="rib rib-'.$class.'">
7863
                            <h3>'.$scoreLabel.'</h3>
7864
                        </div>';
7865
            if (!empty($result)) {
7866
                $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
7867
            }
7868
            $html .= '</div>';
7869
7870
            return $html;
7871
        }
7872
    }
7873
7874
    /**
7875
     * @return int
7876
     */
7877
    public function getAutoLaunch()
7878
    {
7879
        return $this->autolaunch;
7880
    }
7881
7882
    /**
7883
     * Clean auto launch settings for all exercise in course/course-session.
7884
     */
7885
    public function enableAutoLaunch()
7886
    {
7887
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7888
        $sql = "UPDATE $table SET autolaunch = 1
7889
                WHERE iid = ".$this->iId;
7890
        Database::query($sql);
7891
    }
7892
7893
    /**
7894
     * Clean auto launch settings for all exercise in course/course-session.
7895
     */
7896
    public function cleanCourseLaunchSettings()
7897
    {
7898
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7899
        $sql = "UPDATE $table SET autolaunch = 0  
7900
                WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId;
7901
        Database::query($sql);
7902
    }
7903
7904
    /**
7905
     * Get the title without HTML tags.
7906
     *
7907
     * @return string
7908
     */
7909
    public function getUnformattedTitle()
7910
    {
7911
        return strip_tags(api_html_entity_decode($this->title));
7912
    }
7913
7914
    /**
7915
     * @param int $start
7916
     * @param int $lenght
7917
     *
7918
     * @return array
7919
     */
7920
    public function getQuestionForTeacher($start = 0, $lenght = 10)
7921
    {
7922
        $start = (int) $start;
7923
        if ($start < 0) {
7924
            $start = 0;
7925
        }
7926
7927
        $lenght = (int) $lenght;
7928
7929
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7930
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7931
        $sql = "SELECT DISTINCT e.question_id, e.question_order
7932
                FROM $TBL_EXERCICE_QUESTION e
7933
                INNER JOIN $TBL_QUESTIONS q
7934
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7935
                WHERE
7936
                    e.c_id = {$this->course_id} AND
7937
                    e.exercice_id = '".$this->id."'
7938
                ORDER BY question_order
7939
                LIMIT $start, $lenght
7940
            ";
7941
        $result = Database::query($sql);
7942
        $questionList = [];
7943
        while ($object = Database::fetch_object($result)) {
7944
            $questionList[$object->question_order] = $object->question_id;
7945
        }
7946
7947
        return $questionList;
7948
    }
7949
7950
    /**
7951
     * Gets the question list ordered by the question_order setting (drag and drop).
7952
     *
7953
     * @return array
7954
     */
7955
    private function getQuestionOrderedList()
7956
    {
7957
        $questionList = [];
7958
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7959
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7960
7961
        // Getting question_order to verify that the question
7962
        // list is correct and all question_order's were set
7963
        $sql = "SELECT DISTINCT e.question_order
7964
                FROM $TBL_EXERCICE_QUESTION e
7965
                INNER JOIN $TBL_QUESTIONS q
7966
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7967
                WHERE
7968
                  e.c_id = {$this->course_id} AND
7969
                  e.exercice_id	= ".$this->id;
7970
7971
        $result = Database::query($sql);
7972
        $count_question_orders = Database::num_rows($result);
7973
7974
        // Getting question list from the order (question list drag n drop interface).
7975
        $sql = "SELECT DISTINCT e.question_id, e.question_order
7976
                FROM $TBL_EXERCICE_QUESTION e
7977
                INNER JOIN $TBL_QUESTIONS q
7978
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7979
                WHERE
7980
                    e.c_id = {$this->course_id} AND
7981
                    e.exercice_id = '".$this->id."'
7982
                ORDER BY question_order";
7983
        $result = Database::query($sql);
7984
7985
        // Fills the array with the question ID for this exercise
7986
        // the key of the array is the question position
7987
        $temp_question_list = [];
7988
        $counter = 1;
7989
        while ($new_object = Database::fetch_object($result)) {
7990
            // Correct order.
7991
            $questionList[$new_object->question_order] = $new_object->question_id;
7992
            // Just in case we save the order in other array
7993
            $temp_question_list[$counter] = $new_object->question_id;
7994
            $counter++;
7995
        }
7996
7997
        if (!empty($temp_question_list)) {
7998
            /* If both array don't match it means that question_order was not correctly set
7999
               for all questions using the default mysql order */
8000
            if (count($temp_question_list) != $count_question_orders) {
8001
                $questionList = $temp_question_list;
8002
            }
8003
        }
8004
8005
        return $questionList;
8006
    }
8007
8008
    /**
8009
     * Select N values from the questions per category array.
8010
     *
8011
     * @param array $categoriesAddedInExercise
8012
     * @param array $question_list
8013
     * @param array $questions_by_category     per category
8014
     * @param bool  $flatResult
8015
     * @param bool  $randomizeQuestions
8016
     *
8017
     * @return array
8018
     */
8019
    private function pickQuestionsPerCategory(
8020
        $categoriesAddedInExercise,
8021
        $question_list,
8022
        &$questions_by_category,
8023
        $flatResult = true,
8024
        $randomizeQuestions = false
8025
    ) {
8026
        $addAll = true;
8027
        $categoryCountArray = [];
8028
8029
        // Getting how many questions will be selected per category.
8030
        if (!empty($categoriesAddedInExercise)) {
8031
            $addAll = false;
8032
            // Parsing question according the category rel exercise settings
8033
            foreach ($categoriesAddedInExercise as $category_info) {
8034
                $category_id = $category_info['category_id'];
8035
                if (isset($questions_by_category[$category_id])) {
8036
                    // How many question will be picked from this category.
8037
                    $count = $category_info['count_questions'];
8038
                    // -1 means all questions
8039
                    $categoryCountArray[$category_id] = $count;
8040
                    if ($count == -1) {
8041
                        $categoryCountArray[$category_id] = 999;
8042
                    }
8043
                }
8044
            }
8045
        }
8046
8047
        if (!empty($questions_by_category)) {
8048
            $temp_question_list = [];
8049
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
8050
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
8051
                    $numberOfQuestions = 0;
8052
                    if (isset($categoryCountArray[$category_id])) {
8053
                        $numberOfQuestions = $categoryCountArray[$category_id];
8054
                    }
8055
                }
8056
8057
                if ($addAll) {
8058
                    $numberOfQuestions = 999;
8059
                }
8060
8061
                if (!empty($numberOfQuestions)) {
8062
                    $elements = TestCategory::getNElementsFromArray(
8063
                        $categoryQuestionList,
8064
                        $numberOfQuestions,
8065
                        $randomizeQuestions
8066
                    );
8067
8068
                    if (!empty($elements)) {
8069
                        $temp_question_list[$category_id] = $elements;
8070
                        $categoryQuestionList = $elements;
8071
                    }
8072
                }
8073
            }
8074
8075
            if (!empty($temp_question_list)) {
8076
                if ($flatResult) {
8077
                    $temp_question_list = array_flatten($temp_question_list);
8078
                }
8079
                $question_list = $temp_question_list;
8080
            }
8081
        }
8082
8083
        return $question_list;
8084
    }
8085
8086
    /**
8087
     * Changes the exercise id.
8088
     *
8089
     * @param int $id - exercise id
8090
     */
8091
    private function updateId($id)
8092
    {
8093
        $this->id = $id;
8094
    }
8095
8096
    /**
8097
     * Sends a notification when a user ends an examn.
8098
     *
8099
     * @param array  $question_list_answers
8100
     * @param string $origin
8101
     * @param int    $exe_id
8102
     */
8103
    private function sendNotificationForOpenQuestions(
8104
        $question_list_answers,
8105
        $origin,
8106
        $exe_id,
8107
        $user_info,
8108
        $url_email,
8109
        $teachers
8110
    ) {
8111
        // Email configuration settings
8112
        $courseCode = api_get_course_id();
8113
        $courseInfo = api_get_course_info($courseCode);
8114
8115
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
8116
            .get_lang('AttemptDetails').' : <br /><br />'
8117
            .'<table>'
8118
            .'<tr>'
8119
            .'<td><em>'.get_lang('CourseName').'</em></td>'
8120
            .'<td>&nbsp;<b>#course#</b></td>'
8121
            .'</tr>'
8122
            .'<tr>'
8123
            .'<td>'.get_lang('TestAttempted').'</td>'
8124
            .'<td>&nbsp;#exercise#</td>'
8125
            .'</tr>'
8126
            .'<tr>'
8127
            .'<td>'.get_lang('StudentName').'</td>'
8128
            .'<td>&nbsp;#firstName# #lastName#</td>'
8129
            .'</tr>'
8130
            .'<tr>'
8131
            .'<td>'.get_lang('StudentEmail').'</td>'
8132
            .'<td>&nbsp;#mail#</td>'
8133
            .'</tr>'
8134
            .'</table>';
8135
        $open_question_list = null;
8136
        foreach ($question_list_answers as $item) {
8137
            $question = $item['question'];
8138
            $answer = $item['answer'];
8139
            $answer_type = $item['answer_type'];
8140
8141
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
8142
                $open_question_list .=
8143
                    '<tr>'
8144
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
8145
                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
8146
                    .'</tr>'
8147
                    .'<tr>'
8148
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
8149
                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
8150
                    .'</tr>';
8151
            }
8152
        }
8153
8154
        if (!empty($open_question_list)) {
8155
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
8156
                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
8157
            $msg .= $open_question_list;
8158
            $msg .= '</table><br />';
8159
8160
            $msg = str_replace("#exercise#", $this->exercise, $msg);
8161
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg);
8162
            $msg = str_replace("#lastName#", $user_info['lastname'], $msg);
8163
            $msg = str_replace("#mail#", $user_info['email'], $msg);
8164
            $msg = str_replace("#course#", $courseInfo['name'], $msg);
8165
8166
            if ($origin != 'learnpath') {
8167
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8168
            }
8169
            $msg = str_replace("#url#", $url_email, $msg);
8170
            $subject = get_lang('OpenQuestionsAttempted');
8171
8172
            if (!empty($teachers)) {
8173
                foreach ($teachers as $user_id => $teacher_data) {
8174
                    MessageManager::send_message_simple(
8175
                        $user_id,
8176
                        $subject,
8177
                        $msg
8178
                    );
8179
                }
8180
            }
8181
        }
8182
    }
8183
8184
    /**
8185
     * Send notification for oral questions.
8186
     *
8187
     * @param array  $question_list_answers
8188
     * @param string $origin
8189
     * @param int    $exe_id
8190
     * @param array  $user_info
8191
     * @param string $url_email
8192
     * @param array  $teachers
8193
     */
8194
    private function sendNotificationForOralQuestions(
8195
        $question_list_answers,
8196
        $origin,
8197
        $exe_id,
8198
        $user_info,
8199
        $url_email,
8200
        $teachers
8201
    ) {
8202
        // Email configuration settings
8203
        $courseCode = api_get_course_id();
8204
        $courseInfo = api_get_course_info($courseCode);
8205
        $oral_question_list = null;
8206
        foreach ($question_list_answers as $item) {
8207
            $question = $item['question'];
8208
            $file = $item['generated_oral_file'];
8209
            $answer = $item['answer'];
8210
            if ($answer == 0) {
8211
                $answer = '';
8212
            }
8213
            $answer_type = $item['answer_type'];
8214
            if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) {
8215
                if (!empty($file)) {
8216
                    $file = Display::url($file, $file);
8217
                }
8218
                $oral_question_list .= '<br />
8219
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
8220
                    <tr>
8221
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
8222
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
8223
                    </tr>
8224
                    <tr>
8225
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
8226
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
8227
                    </tr></table>';
8228
            }
8229
        }
8230
8231
        if (!empty($oral_question_list)) {
8232
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
8233
                    '.get_lang('AttemptDetails').' : <br /><br />
8234
                    <table>
8235
                        <tr>
8236
                            <td><em>'.get_lang('CourseName').'</em></td>
8237
                            <td>&nbsp;<b>#course#</b></td>
8238
                        </tr>
8239
                        <tr>
8240
                            <td>'.get_lang('TestAttempted').'</td>
8241
                            <td>&nbsp;#exercise#</td>
8242
                        </tr>
8243
                        <tr>
8244
                            <td>'.get_lang('StudentName').'</td>
8245
                            <td>&nbsp;#firstName# #lastName#</td>
8246
                        </tr>
8247
                        <tr>
8248
                            <td>'.get_lang('StudentEmail').'</td>
8249
                            <td>&nbsp;#mail#</td>
8250
                        </tr>
8251
                    </table>';
8252
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
8253
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
8254
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
8255
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
8256
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
8257
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
8258
8259
            if ($origin != 'learnpath') {
8260
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8261
            }
8262
            $msg1 = str_replace("#url#", $url_email, $msg);
8263
            $mail_content = $msg1;
8264
            $subject = get_lang('OralQuestionsAttempted');
8265
8266
            if (!empty($teachers)) {
8267
                foreach ($teachers as $user_id => $teacher_data) {
8268
                    MessageManager::send_message_simple(
8269
                        $user_id,
8270
                        $subject,
8271
                        $mail_content
8272
                    );
8273
                }
8274
            }
8275
        }
8276
    }
8277
8278
    /**
8279
     * Returns an array with the media list.
8280
     *
8281
     * @param array question list
8282
     *
8283
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
8284
     * <code>
8285
     * array (size=2)
8286
     *  999 =>
8287
     *    array (size=3)
8288
     *      0 => int 7
8289
     *      1 => int 6
8290
     *      2 => int 3254
8291
     *  100 =>
8292
     *   array (size=1)
8293
     *      0 => int 5
8294
     *  </code>
8295
     */
8296
    private function setMediaList($questionList)
8297
    {
8298
        $mediaList = [];
8299
        if (!empty($questionList)) {
8300
            foreach ($questionList as $questionId) {
8301
                $objQuestionTmp = Question::read($questionId, $this->course_id);
8302
                // If a media question exists
8303
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
8304
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
8305
                } else {
8306
                    if ($objQuestionTmp) {
8307
                        // Always the last item
8308
                        $mediaList[999][] = $objQuestionTmp->id;
8309
                    }
8310
                }
8311
            }
8312
        }
8313
        $this->mediaList = $mediaList;
8314
    }
8315
8316
    /**
8317
     * @param FormValidator $form
8318
     *
8319
     * @return HTML_QuickForm_group
8320
     */
8321
    private function setResultDisabledGroup(FormValidator $form)
8322
    {
8323
        $resultDisabledGroup = [];
8324
8325
        $resultDisabledGroup[] = $form->createElement(
8326
            'radio',
8327
            'results_disabled',
8328
            null,
8329
            get_lang('ShowScoreAndRightAnswer'),
8330
            '0',
8331
            ['id' => 'result_disabled_0']
8332
        );
8333
8334
        $resultDisabledGroup[] = $form->createElement(
8335
            'radio',
8336
            'results_disabled',
8337
            null,
8338
            get_lang('DoNotShowScoreNorRightAnswer'),
8339
            '1',
8340
            ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
8341
        );
8342
8343
        $resultDisabledGroup[] = $form->createElement(
8344
            'radio',
8345
            'results_disabled',
8346
            null,
8347
            get_lang('OnlyShowScore'),
8348
            '2',
8349
            ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
8350
        );
8351
8352
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT) {
8353
            $group = $form->addGroup(
8354
                $resultDisabledGroup,
8355
                null,
8356
                get_lang('ShowResultsToStudents')
8357
            );
8358
8359
            return $group;
8360
        }
8361
8362
        $resultDisabledGroup[] = $form->createElement(
8363
            'radio',
8364
            'results_disabled',
8365
            null,
8366
            get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
8367
            '4',
8368
            ['id' => 'result_disabled_4']
8369
        );
8370
8371
        $resultDisabledGroup[] = $form->createElement(
8372
            'radio',
8373
            'results_disabled',
8374
            null,
8375
            get_lang('DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt'),
8376
            '5',
8377
            ['id' => 'result_disabled_5', 'onclick' => 'check_results_disabled()']
8378
        );
8379
8380
        $resultDisabledGroup[] = $form->createElement(
8381
            'radio',
8382
            'results_disabled',
8383
            null,
8384
            get_lang('ExerciseRankingMode'),
8385
            '6',
8386
            ['id' => 'result_disabled_6']
8387
        );
8388
8389
        $resultDisabledGroup[] = $form->createElement(
8390
            'radio',
8391
            'results_disabled',
8392
            null,
8393
            get_lang('ExerciseShowOnlyGlobalScoreAndCorrectAnswers'),
8394
            '7',
8395
            ['id' => 'result_disabled_7']
8396
        );
8397
8398
        $group = $form->addGroup(
8399
            $resultDisabledGroup,
8400
            null,
8401
            get_lang('ShowResultsToStudents')
8402
        );
8403
8404
        return $group;
8405
    }
8406
}
8407