Passed
Push — master ( e739b7...1cdc43 )
by Julito
10:11
created

Exercise::sendNotificationForOpenQuestions()   C

Complexity

Conditions 11

Size

Total Lines 91
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 70
nop 5
dl 0
loc 91
rs 6.5077
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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