Completed
Push — master ( b77cfe...000f93 )
by Julito
22:58 queued 11:19
created

Exercise::removeFromList()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 4
nop 1
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\TrackEHotspot;
5
use Chamilo\CourseBundle\Entity\CQuizCategory;
6
use ChamiloSession as Session;
7
8
/**
9
 * Class Exercise.
10
 *
11
 * Allows to instantiate an object of type Exercise
12
 *
13
 * @package chamilo.exercise
14
 *
15
 * @todo use getters and setters correctly
16
 *
17
 * @author Olivier Brouckaert
18
 * @author Julio Montoya Cleaning exercises
19
 * Modified by Hubert Borderiou #294
20
 */
21
class Exercise
22
{
23
    public $iId;
24
    public $id;
25
    public $name;
26
    public $title;
27
    public $exercise;
28
    public $description;
29
    public $sound;
30
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
31
    public $random;
32
    public $random_answers;
33
    public $active;
34
    public $timeLimit;
35
    public $attempts;
36
    public $feedback_type;
37
    public $end_time;
38
    public $start_time;
39
    public $questionList; // array with the list of this exercise's questions
40
    /* including question list of the media */
41
    public $questionListUncompressed;
42
    public $results_disabled;
43
    public $expired_time;
44
    public $course;
45
    public $course_id;
46
    public $propagate_neg;
47
    public $saveCorrectAnswers;
48
    public $review_answers;
49
    public $randomByCat;
50
    public $text_when_finished;
51
    public $display_category_name;
52
    public $pass_percentage;
53
    public $edit_exercise_in_lp = false;
54
    public $is_gradebook_locked = false;
55
    public $exercise_was_added_in_lp = false;
56
    public $lpList = [];
57
    public $force_edit_exercise_in_lp = false;
58
    public $categories;
59
    public $categories_grouping = true;
60
    public $endButton = 0;
61
    public $categoryWithQuestionList;
62
    public $mediaList;
63
    public $loadQuestionAJAX = false;
64
    // Notification send to the teacher.
65
    public $emailNotificationTemplate = null;
66
    // Notification send to the student.
67
    public $emailNotificationTemplateToUser = null;
68
    public $countQuestions = 0;
69
    public $fastEdition = false;
70
    public $modelType = 1;
71
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
72
    public $hideQuestionTitle = 0;
73
    public $scoreTypeModel = 0;
74
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
75
    public $globalCategoryId = null;
76
    public $onSuccessMessage = null;
77
    public $onFailedMessage = null;
78
    public $emailAlert;
79
    public $notifyUserByEmail = '';
80
    public $sessionId = 0;
81
    public $questionFeedbackEnabled = false;
82
    public $questionTypeWithFeedback;
83
    public $showPreviousButton;
84
    public $notifications;
85
    public $export = false;
86
    public $autolaunch;
87
88
    /**
89
     * Constructor of the class.
90
     *
91
     * @param int $courseId
92
     *
93
     * @author Olivier Brouckaert
94
     */
95
    public function __construct($courseId = 0)
96
    {
97
        $this->iId = 0;
98
        $this->id = 0;
99
        $this->exercise = '';
100
        $this->description = '';
101
        $this->sound = '';
102
        $this->type = ALL_ON_ONE_PAGE;
103
        $this->random = 0;
104
        $this->random_answers = 0;
105
        $this->active = 1;
106
        $this->questionList = [];
107
        $this->timeLimit = 0;
108
        $this->end_time = '';
109
        $this->start_time = '';
110
        $this->results_disabled = 1;
111
        $this->expired_time = 0;
112
        $this->propagate_neg = 0;
113
        $this->saveCorrectAnswers = 0;
114
        $this->review_answers = false;
115
        $this->randomByCat = 0;
116
        $this->text_when_finished = '';
117
        $this->display_category_name = 0;
118
        $this->pass_percentage = 0;
119
        $this->modelType = 1;
120
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
121
        $this->endButton = 0;
122
        $this->scoreTypeModel = 0;
123
        $this->globalCategoryId = null;
124
        $this->notifications = [];
125
126
        if (!empty($courseId)) {
127
            $courseInfo = api_get_course_info_by_id($courseId);
128
        } else {
129
            $courseInfo = api_get_course_info();
130
        }
131
        $this->course_id = $courseInfo['real_id'];
132
        $this->course = $courseInfo;
133
        $this->sessionId = api_get_session_id();
134
135
        // ALTER TABLE c_quiz_question ADD COLUMN feedback text;
136
        $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback');
137
        $this->showPreviousButton = true;
138
    }
139
140
    /**
141
     * Reads exercise information from the data base.
142
     *
143
     * @author Olivier Brouckaert
144
     *
145
     * @param int  $id                - exercise Id
146
     * @param bool $parseQuestionList
147
     *
148
     * @return bool - true if exercise exists, otherwise false
149
     */
150
    public function read($id, $parseQuestionList = true)
151
    {
152
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
153
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
154
155
        $id = (int) $id;
156
        if (empty($this->course_id)) {
157
            return false;
158
        }
159
160
        $sql = "SELECT * FROM $table 
161
                WHERE c_id = ".$this->course_id." AND id = ".$id;
162
        $result = Database::query($sql);
163
164
        // if the exercise has been found
165
        if ($object = Database::fetch_object($result)) {
166
            $this->iId = $object->iid;
167
            $this->id = $id;
168
            $this->exercise = $object->title;
169
            $this->name = $object->title;
170
            $this->title = $object->title;
171
            $this->description = $object->description;
172
            $this->sound = $object->sound;
173
            $this->type = $object->type;
174
            if (empty($this->type)) {
175
                $this->type = ONE_PER_PAGE;
176
            }
177
            $this->random = $object->random;
178
            $this->random_answers = $object->random_answers;
179
            $this->active = $object->active;
180
            $this->results_disabled = $object->results_disabled;
181
            $this->attempts = $object->max_attempt;
182
            $this->feedback_type = $object->feedback_type;
183
            $this->sessionId = $object->session_id;
184
            $this->propagate_neg = $object->propagate_neg;
185
            $this->saveCorrectAnswers = $object->save_correct_answers;
186
            $this->randomByCat = $object->random_by_category;
187
            $this->text_when_finished = $object->text_when_finished;
188
            $this->display_category_name = $object->display_category_name;
189
            $this->pass_percentage = $object->pass_percentage;
190
            $this->sessionId = $object->session_id;
191
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
192
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
193
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
194
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
195
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0;
196
            $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0;
197
198
            $this->notifications = [];
199
            if (!empty($object->notifications)) {
200
                $this->notifications = explode(',', $object->notifications);
201
            }
202
203
            if (isset($object->show_previous_button)) {
204
                $this->showPreviousButton = $object->show_previous_button == 1 ? true : false;
205
            }
206
207
            $sql = "SELECT lp_id, max_score
208
                    FROM $tableLpItem
209
                    WHERE   
210
                        c_id = {$this->course_id} AND
211
                        item_type = '".TOOL_QUIZ."' AND
212
                        path = '".$id."'";
213
            $result = Database::query($sql);
214
215
            if (Database::num_rows($result) > 0) {
216
                $this->exercise_was_added_in_lp = true;
217
                $this->lpList = Database::store_result($result, 'ASSOC');
218
            }
219
220
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
221
222
            if ($this->exercise_was_added_in_lp) {
223
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
224
            } else {
225
                $this->edit_exercise_in_lp = true;
226
            }
227
228
            if (!empty($object->end_time)) {
229
                $this->end_time = $object->end_time;
230
            }
231
            if (!empty($object->start_time)) {
232
                $this->start_time = $object->start_time;
233
            }
234
235
            // Control time
236
            $this->expired_time = $object->expired_time;
237
238
            // Checking if question_order is correctly set
239
            if ($parseQuestionList) {
240
                $this->setQuestionList(true);
241
            }
242
243
            //overload questions list with recorded questions list
244
            //load questions only for exercises of type 'one question per page'
245
            //this is needed only is there is no questions
246
247
            // @todo not sure were in the code this is used somebody mess with the exercise tool
248
            // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
249
            /*global $_configuration, $questionList;
250
            if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST'
251
                && defined('QUESTION_LIST_ALREADY_LOGGED') &&
252
                isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']
253
            ) {
254
                $this->questionList = $questionList;
255
            }*/
256
            return true;
257
        }
258
259
        return false;
260
    }
261
262
    /**
263
     * @return string
264
     */
265
    public function getCutTitle()
266
    {
267
        $title = $this->getUnformattedTitle();
268
269
        return cut($title, EXERCISE_MAX_NAME_SIZE);
270
    }
271
272
    /**
273
     * returns the exercise ID.
274
     *
275
     * @author Olivier Brouckaert
276
     *
277
     * @return int - exercise ID
278
     */
279
    public function selectId()
280
    {
281
        return $this->id;
282
    }
283
284
    /**
285
     * returns the exercise title.
286
     *
287
     * @author Olivier Brouckaert
288
     *
289
     * @param bool $unformattedText Optional. Get the title without HTML tags
290
     *
291
     * @return string - exercise title
292
     */
293
    public function selectTitle($unformattedText = false)
294
    {
295
        if ($unformattedText) {
296
            return $this->getUnformattedTitle();
297
        }
298
299
        return $this->exercise;
300
    }
301
302
    /**
303
     * returns the number of attempts setted.
304
     *
305
     * @return int - exercise attempts
306
     */
307
    public function selectAttempts()
308
    {
309
        return $this->attempts;
310
    }
311
312
    /** returns the number of FeedbackType  *
313
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback.
314
     *
315
     * @return int - exercise attempts
316
     */
317
    public function selectFeedbackType()
318
    {
319
        return $this->feedback_type;
320
    }
321
322
    /**
323
     * returns the time limit.
324
     *
325
     * @return int
326
     */
327
    public function selectTimeLimit()
328
    {
329
        return $this->timeLimit;
330
    }
331
332
    /**
333
     * returns the exercise description.
334
     *
335
     * @author Olivier Brouckaert
336
     *
337
     * @return string - exercise description
338
     */
339
    public function selectDescription()
340
    {
341
        return $this->description;
342
    }
343
344
    /**
345
     * returns the exercise sound file.
346
     *
347
     * @author Olivier Brouckaert
348
     *
349
     * @return string - exercise description
350
     */
351
    public function selectSound()
352
    {
353
        return $this->sound;
354
    }
355
356
    /**
357
     * returns the exercise type.
358
     *
359
     * @author Olivier Brouckaert
360
     *
361
     * @return int - exercise type
362
     */
363
    public function selectType()
364
    {
365
        return $this->type;
366
    }
367
368
    /**
369
     * @return int
370
     */
371
    public function getModelType()
372
    {
373
        return $this->modelType;
374
    }
375
376
    /**
377
     * @return int
378
     */
379
    public function selectEndButton()
380
    {
381
        return $this->endButton;
382
    }
383
384
    /**
385
     * @return string
386
     */
387
    public function getOnSuccessMessage()
388
    {
389
        return $this->onSuccessMessage;
390
    }
391
392
    /**
393
     * @return string
394
     */
395
    public function getOnFailedMessage()
396
    {
397
        return $this->onFailedMessage;
398
    }
399
400
    /**
401
     * @author hubert borderiou 30-11-11
402
     *
403
     * @return int : do we display the question category name for students
404
     */
405
    public function selectDisplayCategoryName()
406
    {
407
        return $this->display_category_name;
408
    }
409
410
    /**
411
     * @return int
412
     */
413
    public function selectPassPercentage()
414
    {
415
        return $this->pass_percentage;
416
    }
417
418
    /**
419
     * Modify object to update the switch display_category_name.
420
     *
421
     * @author hubert borderiou 30-11-11
422
     *
423
     * @param int $value is an integer 0 or 1
424
     */
425
    public function updateDisplayCategoryName($value)
426
    {
427
        $this->display_category_name = $value;
428
    }
429
430
    /**
431
     * @author hubert borderiou 28-11-11
432
     *
433
     * @return string html text : the text to display ay the end of the test
434
     */
435
    public function selectTextWhenFinished()
436
    {
437
        return $this->text_when_finished;
438
    }
439
440
    /**
441
     * @param string $text
442
     *
443
     * @author hubert borderiou 28-11-11
444
     */
445
    public function updateTextWhenFinished($text)
446
    {
447
        $this->text_when_finished = $text;
448
    }
449
450
    /**
451
     * return 1 or 2 if randomByCat.
452
     *
453
     * @author hubert borderiou
454
     *
455
     * @return int - quiz random by category
456
     */
457
    public function getRandomByCategory()
458
    {
459
        return $this->randomByCat;
460
    }
461
462
    /**
463
     * return 0 if no random by cat
464
     * return 1 if random by cat, categories shuffled
465
     * return 2 if random by cat, categories sorted by alphabetic order.
466
     *
467
     * @author hubert borderiou
468
     *
469
     * @return int - quiz random by category
470
     */
471
    public function isRandomByCat()
472
    {
473
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
474
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
475
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
476
        } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
477
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
478
        }
479
480
        return $res;
481
    }
482
483
    /**
484
     * return nothing
485
     * update randomByCat value for object.
486
     *
487
     * @param int $random
488
     *
489
     * @author hubert borderiou
490
     */
491
    public function updateRandomByCat($random)
492
    {
493
        $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
494
        if (in_array(
495
            $random,
496
            [
497
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
498
                EXERCISE_CATEGORY_RANDOM_ORDERED,
499
                EXERCISE_CATEGORY_RANDOM_DISABLED,
500
            ]
501
        )) {
502
            $this->randomByCat = $random;
503
        }
504
    }
505
506
    /**
507
     * Tells if questions are selected randomly, and if so returns the draws.
508
     *
509
     * @author Carlos Vargas
510
     *
511
     * @return int - results disabled exercise
512
     */
513
    public function selectResultsDisabled()
514
    {
515
        return $this->results_disabled;
516
    }
517
518
    /**
519
     * tells if questions are selected randomly, and if so returns the draws.
520
     *
521
     * @author Olivier Brouckaert
522
     *
523
     * @return bool
524
     */
525
    public function isRandom()
526
    {
527
        $isRandom = false;
528
        // "-1" means all questions will be random
529
        if ($this->random > 0 || $this->random == -1) {
530
            $isRandom = true;
531
        }
532
533
        return $isRandom;
534
    }
535
536
    /**
537
     * returns random answers status.
538
     *
539
     * @author Juan Carlos Rana
540
     */
541
    public function getRandomAnswers()
542
    {
543
        return $this->random_answers;
544
    }
545
546
    /**
547
     * Same as isRandom() but has a name applied to values different than 0 or 1.
548
     *
549
     * @return int
550
     */
551
    public function getShuffle()
552
    {
553
        return $this->random;
554
    }
555
556
    /**
557
     * returns the exercise status (1 = enabled ; 0 = disabled).
558
     *
559
     * @author Olivier Brouckaert
560
     *
561
     * @return int - 1 if enabled, otherwise 0
562
     */
563
    public function selectStatus()
564
    {
565
        return $this->active;
566
    }
567
568
    /**
569
     * If false the question list will be managed as always if true
570
     * the question will be filtered
571
     * depending of the exercise settings (table c_quiz_rel_category).
572
     *
573
     * @param bool $status active or inactive grouping
574
     */
575
    public function setCategoriesGrouping($status)
576
    {
577
        $this->categories_grouping = (bool) $status;
578
    }
579
580
    /**
581
     * @return int
582
     */
583
    public function getHideQuestionTitle()
584
    {
585
        return $this->hideQuestionTitle;
586
    }
587
588
    /**
589
     * @param $value
590
     */
591
    public function setHideQuestionTitle($value)
592
    {
593
        $this->hideQuestionTitle = (int) $value;
594
    }
595
596
    /**
597
     * @return int
598
     */
599
    public function getScoreTypeModel()
600
    {
601
        return $this->scoreTypeModel;
602
    }
603
604
    /**
605
     * @param int $value
606
     */
607
    public function setScoreTypeModel($value)
608
    {
609
        $this->scoreTypeModel = (int) $value;
610
    }
611
612
    /**
613
     * @return int
614
     */
615
    public function getGlobalCategoryId()
616
    {
617
        return $this->globalCategoryId;
618
    }
619
620
    /**
621
     * @param int $value
622
     */
623
    public function setGlobalCategoryId($value)
624
    {
625
        if (is_array($value) && isset($value[0])) {
626
            $value = $value[0];
627
        }
628
        $this->globalCategoryId = (int) $value;
629
    }
630
631
    /**
632
     * @param int    $start
633
     * @param int    $limit
634
     * @param int    $sidx
635
     * @param string $sord
636
     * @param array  $whereCondition
637
     * @param array  $extraFields
638
     *
639
     * @return array
640
     */
641
    public function getQuestionListPagination(
642
        $start,
643
        $limit,
644
        $sidx,
645
        $sord,
646
        $whereCondition = [],
647
        $extraFields = []
648
    ) {
649
        if (!empty($this->id)) {
650
            $category_list = TestCategory::getListOfCategoriesNameForTest(
651
                $this->id,
652
                false
653
            );
654
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
655
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
656
657
            $sql = "SELECT q.iid
658
                    FROM $TBL_EXERCICE_QUESTION e 
659
                    INNER JOIN $TBL_QUESTIONS  q
660
                    ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
661
					WHERE e.exercice_id	= '".$this->id."' ";
662
663
            $orderCondition = "ORDER BY question_order";
664
665
            if (!empty($sidx) && !empty($sord)) {
666
                if ($sidx == 'question') {
667
                    if (in_array(strtolower($sord), ['desc', 'asc'])) {
668
                        $orderCondition = " ORDER BY q.$sidx $sord";
669
                    }
670
                }
671
            }
672
673
            $sql .= $orderCondition;
674
            $limitCondition = null;
675
            if (isset($start) && isset($limit)) {
676
                $start = intval($start);
677
                $limit = intval($limit);
678
                $limitCondition = " LIMIT $start, $limit";
679
            }
680
            $sql .= $limitCondition;
681
            $result = Database::query($sql);
682
            $questions = [];
683
            if (Database::num_rows($result)) {
684
                if (!empty($extraFields)) {
685
                    $extraFieldValue = new ExtraFieldValue('question');
686
                }
687
                while ($question = Database::fetch_array($result, 'ASSOC')) {
688
                    /** @var Question $objQuestionTmp */
689
                    $objQuestionTmp = Question::read($question['iid']);
690
                    $category_labels = TestCategory::return_category_labels(
691
                        $objQuestionTmp->category_list,
692
                        $category_list
693
                    );
694
695
                    if (empty($category_labels)) {
696
                        $category_labels = "-";
697
                    }
698
699
                    // Question type
700
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
701
702
                    $question_media = null;
703
                    if (!empty($objQuestionTmp->parent_id)) {
704
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
705
                        $question_media = Question::getMediaLabel($objQuestionMedia->question);
706
                    }
707
708
                    $questionType = Display::tag(
709
                        'div',
710
                        Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media
711
                    );
712
713
                    $question = [
714
                        'id' => $question['iid'],
715
                        'question' => $objQuestionTmp->selectTitle(),
716
                        'type' => $questionType,
717
                        'category' => Display::tag(
718
                            'div',
719
                            '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'
720
                        ),
721
                        'score' => $objQuestionTmp->selectWeighting(),
722
                        'level' => $objQuestionTmp->level,
723
                    ];
724
725
                    if (!empty($extraFields)) {
726
                        foreach ($extraFields as $extraField) {
727
                            $value = $extraFieldValue->get_values_by_handler_and_field_id(
728
                                $question['id'],
729
                                $extraField['id']
730
                            );
731
                            $stringValue = null;
732
                            if ($value) {
733
                                $stringValue = $value['field_value'];
734
                            }
735
                            $question[$extraField['field_variable']] = $stringValue;
736
                        }
737
                    }
738
                    $questions[] = $question;
739
                }
740
            }
741
742
            return $questions;
743
        }
744
    }
745
746
    /**
747
     * Get question count per exercise from DB (any special treatment).
748
     *
749
     * @return int
750
     */
751
    public function getQuestionCount()
752
    {
753
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
754
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
755
        $sql = "SELECT count(q.id) as count
756
                FROM $TBL_EXERCICE_QUESTION e 
757
                INNER JOIN $TBL_QUESTIONS q
758
                ON (e.question_id = q.id AND e.c_id = q.c_id)
759
                WHERE 
760
                    e.c_id = {$this->course_id} AND 
761
                    e.exercice_id = ".$this->id;
762
        $result = Database::query($sql);
763
764
        $count = 0;
765
        if (Database::num_rows($result)) {
766
            $row = Database::fetch_array($result);
767
            $count = $row['count'];
768
        }
769
770
        return $count;
771
    }
772
773
    /**
774
     * @return array
775
     */
776
    public function getQuestionOrderedListByName()
777
    {
778
        $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
779
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
780
781
        // Getting question list from the order (question list drag n drop interface ).
782
        $sql = "SELECT e.question_id
783
                FROM $exerciseQuestionTable e 
784
                INNER JOIN $questionTable q
785
                ON (e.question_id= q.id AND e.c_id = q.c_id)
786
                WHERE 
787
                    e.c_id = {$this->course_id} AND 
788
                    e.exercice_id = '".$this->id."'
789
                ORDER BY q.question";
790
        $result = Database::query($sql);
791
        $list = [];
792
        if (Database::num_rows($result)) {
793
            $list = Database::store_result($result, 'ASSOC');
794
        }
795
796
        return $list;
797
    }
798
799
    /**
800
     * Selecting question list depending in the exercise-category
801
     * relationship (category table in exercise settings).
802
     *
803
     * @param array $question_list
804
     * @param int   $questionSelectionType
805
     *
806
     * @return array
807
     */
808
    public function getQuestionListWithCategoryListFilteredByCategorySettings(
809
        $question_list,
810
        $questionSelectionType
811
    ) {
812
        $result = [
813
            'question_list' => [],
814
            'category_with_questions_list' => [],
815
        ];
816
817
        // Order/random categories
818
        $cat = new TestCategory();
819
820
        // Setting category order.
821
        switch ($questionSelectionType) {
822
            case EX_Q_SELECTION_ORDERED: // 1
823
            case EX_Q_SELECTION_RANDOM:  // 2
824
                // This options are not allowed here.
825
                break;
826
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
827
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
828
                    $this,
829
                    $this->course['real_id'],
830
                    'title ASC',
831
                    false,
832
                    true
833
                );
834
835
                $questions_by_category = TestCategory::getQuestionsByCat(
836
                    $this->id,
837
                    $question_list,
838
                    $categoriesAddedInExercise
839
                );
840
841
                $question_list = $this->pickQuestionsPerCategory(
842
                    $categoriesAddedInExercise,
843
                    $question_list,
844
                    $questions_by_category,
845
                    true,
846
                    false
847
                );
848
                break;
849
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
850
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
851
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
852
                    $this,
853
                    $this->course['real_id'],
854
                    null,
855
                    true,
856
                    true
857
                );
858
                $questions_by_category = TestCategory::getQuestionsByCat(
859
                    $this->id,
860
                    $question_list,
861
                    $categoriesAddedInExercise
862
                );
863
                $question_list = $this->pickQuestionsPerCategory(
864
                    $categoriesAddedInExercise,
865
                    $question_list,
866
                    $questions_by_category,
867
                    true,
868
                    false
869
                );
870
                break;
871
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
872
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
873
                    $this,
874
                    $this->course['real_id'],
875
                    'title ASC',
876
                    false,
877
                    true
878
                );
879
                $questions_by_category = TestCategory::getQuestionsByCat(
880
                    $this->id,
881
                    $question_list,
882
                    $categoriesAddedInExercise
883
                );
884
                $question_list = $this->pickQuestionsPerCategory(
885
                    $categoriesAddedInExercise,
886
                    $question_list,
887
                    $questions_by_category,
888
                    true,
889
                    true
890
                );
891
                break;
892
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
893
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
894
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
895
                    $this,
896
                    $this->course['real_id'],
897
                    null,
898
                    true,
899
                    true
900
                );
901
902
                $questions_by_category = TestCategory::getQuestionsByCat(
903
                    $this->id,
904
                    $question_list,
905
                    $categoriesAddedInExercise
906
                );
907
908
                $question_list = $this->pickQuestionsPerCategory(
909
                    $categoriesAddedInExercise,
910
                    $question_list,
911
                    $questions_by_category,
912
                    true,
913
                    true
914
                );
915
                break;
916
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
917
                break;
918
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
919
                break;
920
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
921
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
922
                    $this,
923
                    $this->course['real_id'],
924
                    'root ASC, lft ASC',
925
                    false,
926
                    true
927
                );
928
                $questions_by_category = TestCategory::getQuestionsByCat(
929
                    $this->id,
930
                    $question_list,
931
                    $categoriesAddedInExercise
932
                );
933
                $question_list = $this->pickQuestionsPerCategory(
934
                    $categoriesAddedInExercise,
935
                    $question_list,
936
                    $questions_by_category,
937
                    true,
938
                    false
939
                );
940
                break;
941
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
942
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
943
                    $this,
944
                    $this->course['real_id'],
945
                    'root, lft ASC',
946
                    false,
947
                    true
948
                );
949
                $questions_by_category = TestCategory::getQuestionsByCat(
950
                    $this->id,
951
                    $question_list,
952
                    $categoriesAddedInExercise
953
                );
954
                $question_list = $this->pickQuestionsPerCategory(
955
                    $categoriesAddedInExercise,
956
                    $question_list,
957
                    $questions_by_category,
958
                    true,
959
                    true
960
                );
961
                break;
962
        }
963
964
        $result['question_list'] = isset($question_list) ? $question_list : [];
965
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : [];
966
        // Adding category info in the category list with question list:
967
        if (!empty($questions_by_category)) {
968
            $newCategoryList = [];
969
            $em = Database::getManager();
970
            foreach ($questions_by_category as $categoryId => $questionList) {
971
                $cat = new TestCategory();
972
                $cat = $cat->getCategory($categoryId);
973
                $cat = (array) $cat;
974
                $cat['iid'] = $cat['id'];
975
                $categoryParentInfo = null;
976
                // Parent is not set no loop here
977
                if (!empty($cat['parent_id'])) {
978
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
979
                        $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
980
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
981
                    } else {
982
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
983
                    }
984
                    $path = $repo->getPath($categoryEntity);
985
                    $index = 0;
986
                    if ($this->categoryMinusOne) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
987
                        //$index = 1;
988
                    }
989
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent */
990
                    foreach ($path as $categoryParent) {
991
                        $visibility = $categoryParent->getVisibility();
992
                        if ($visibility == 0) {
993
                            $categoryParentId = $categoryId;
994
                            $categoryTitle = $cat['title'];
995
                            if (count($path) > 1) {
996
                                continue;
997
                            }
998
                        } else {
999
                            $categoryParentId = $categoryParent->getIid();
1000
                            $categoryTitle = $categoryParent->getTitle();
1001
                        }
1002
1003
                        $categoryParentInfo['id'] = $categoryParentId;
1004
                        $categoryParentInfo['iid'] = $categoryParentId;
1005
                        $categoryParentInfo['parent_path'] = null;
1006
                        $categoryParentInfo['title'] = $categoryTitle;
1007
                        $categoryParentInfo['name'] = $categoryTitle;
1008
                        $categoryParentInfo['parent_id'] = null;
1009
                        break;
1010
                    }
1011
                }
1012
                $cat['parent_info'] = $categoryParentInfo;
1013
                $newCategoryList[$categoryId] = [
1014
                    'category' => $cat,
1015
                    'question_list' => $questionList,
1016
                ];
1017
            }
1018
1019
            $result['category_with_questions_list'] = $newCategoryList;
1020
        }
1021
1022
        return $result;
1023
    }
1024
1025
    /**
1026
     * returns the array with the question ID list.
1027
     *
1028
     * @param bool $from_db   Whether the results should be fetched in the database or just from memory
1029
     * @param bool $adminView Whether we should return all questions (admin view) or
1030
     *                        just a list limited by the max number of random questions
1031
     *
1032
     * @author Olivier Brouckaert
1033
     *
1034
     * @return array - question ID list
1035
     */
1036
    public function selectQuestionList($from_db = false, $adminView = false)
1037
    {
1038
        if ($from_db && !empty($this->id)) {
1039
            $nbQuestions = $this->getQuestionCount();
1040
            $questionSelectionType = $this->getQuestionSelectionType();
1041
1042
            switch ($questionSelectionType) {
1043
                case EX_Q_SELECTION_ORDERED:
1044
                    $questionList = $this->getQuestionOrderedList();
1045
                    break;
1046
                case EX_Q_SELECTION_RANDOM:
1047
                    // Not a random exercise, or if there are not at least 2 questions
1048
                    if ($this->random == 0 || $nbQuestions < 2) {
1049
                        $questionList = $this->getQuestionOrderedList();
1050
                    } else {
1051
                        $questionList = $this->getRandomList($adminView);
1052
                    }
1053
                    break;
1054
                default:
1055
                    $questionList = $this->getQuestionOrderedList();
1056
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1057
                        $questionList,
1058
                        $questionSelectionType
1059
                    );
1060
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1061
                    $questionList = $result['question_list'];
1062
                    break;
1063
            }
1064
1065
            return $questionList;
1066
        }
1067
1068
        return $this->questionList;
1069
    }
1070
1071
    /**
1072
     * returns the number of questions in this exercise.
1073
     *
1074
     * @author Olivier Brouckaert
1075
     *
1076
     * @return int - number of questions
1077
     */
1078
    public function selectNbrQuestions()
1079
    {
1080
        return sizeof($this->questionList);
1081
    }
1082
1083
    /**
1084
     * @return int
1085
     */
1086
    public function selectPropagateNeg()
1087
    {
1088
        return $this->propagate_neg;
1089
    }
1090
1091
    /**
1092
     * @return int
1093
     */
1094
    public function selectSaveCorrectAnswers()
1095
    {
1096
        return $this->saveCorrectAnswers;
1097
    }
1098
1099
    /**
1100
     * Selects questions randomly in the question list.
1101
     *
1102
     * @author Olivier Brouckaert
1103
     * @author Hubert Borderiou 15 nov 2011
1104
     *
1105
     * @param bool $adminView Whether we should return all
1106
     *                        questions (admin view) or just a list limited by the max number of random questions
1107
     *
1108
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1109
     *               without randomizing, otherwise, returns the list with questions selected randomly
1110
     */
1111
    public function getRandomList($adminView = false)
1112
    {
1113
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1114
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1115
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1116
1117
        // Random with limit
1118
        $randomLimit = "ORDER BY RAND() LIMIT $random";
1119
1120
        // Random with no limit
1121
        if ($random == -1) {
1122
            $randomLimit = "ORDER BY RAND() ";
1123
        }
1124
1125
        // Admin see the list in default order
1126
        if ($adminView === true) {
1127
            // If viewing it as admin for edition, don't show it randomly, use title + id
1128
            $randomLimit = 'ORDER BY e.question_order';
1129
        }
1130
1131
        $sql = "SELECT e.question_id
1132
                FROM $quizRelQuestion e 
1133
                INNER JOIN $question q
1134
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1135
                WHERE 
1136
                    e.c_id = {$this->course_id} AND 
1137
                    e.exercice_id = '".Database::escape_string($this->id)."'
1138
                    $randomLimit ";
1139
        $result = Database::query($sql);
1140
        $questionList = [];
1141
        while ($row = Database::fetch_object($result)) {
1142
            $questionList[] = $row->question_id;
1143
        }
1144
1145
        return $questionList;
1146
    }
1147
1148
    /**
1149
     * returns 'true' if the question ID is in the question list.
1150
     *
1151
     * @author Olivier Brouckaert
1152
     *
1153
     * @param int $questionId - question ID
1154
     *
1155
     * @return bool - true if in the list, otherwise false
1156
     */
1157
    public function isInList($questionId)
1158
    {
1159
        $inList = false;
1160
        if (is_array($this->questionList)) {
1161
            $inList = in_array($questionId, $this->questionList);
1162
        }
1163
1164
        return $inList;
1165
    }
1166
1167
    /**
1168
     * changes the exercise title.
1169
     *
1170
     * @author Olivier Brouckaert
1171
     *
1172
     * @param string $title - exercise title
1173
     */
1174
    public function updateTitle($title)
1175
    {
1176
        $this->title = $this->exercise = $title;
1177
    }
1178
1179
    /**
1180
     * changes the exercise max attempts.
1181
     *
1182
     * @param int $attempts - exercise max attempts
1183
     */
1184
    public function updateAttempts($attempts)
1185
    {
1186
        $this->attempts = $attempts;
1187
    }
1188
1189
    /**
1190
     * changes the exercise feedback type.
1191
     *
1192
     * @param int $feedback_type
1193
     */
1194
    public function updateFeedbackType($feedback_type)
1195
    {
1196
        $this->feedback_type = $feedback_type;
1197
    }
1198
1199
    /**
1200
     * changes the exercise description.
1201
     *
1202
     * @author Olivier Brouckaert
1203
     *
1204
     * @param string $description - exercise description
1205
     */
1206
    public function updateDescription($description)
1207
    {
1208
        $this->description = $description;
1209
    }
1210
1211
    /**
1212
     * changes the exercise expired_time.
1213
     *
1214
     * @author Isaac flores
1215
     *
1216
     * @param int $expired_time The expired time of the quiz
1217
     */
1218
    public function updateExpiredTime($expired_time)
1219
    {
1220
        $this->expired_time = $expired_time;
1221
    }
1222
1223
    /**
1224
     * @param $value
1225
     */
1226
    public function updatePropagateNegative($value)
1227
    {
1228
        $this->propagate_neg = $value;
1229
    }
1230
1231
    /**
1232
     * @param $value int
1233
     */
1234
    public function updateSaveCorrectAnswers($value)
1235
    {
1236
        $this->saveCorrectAnswers = $value;
1237
    }
1238
1239
    /**
1240
     * @param $value
1241
     */
1242
    public function updateReviewAnswers($value)
1243
    {
1244
        $this->review_answers = isset($value) && $value ? true : false;
1245
    }
1246
1247
    /**
1248
     * @param $value
1249
     */
1250
    public function updatePassPercentage($value)
1251
    {
1252
        $this->pass_percentage = $value;
1253
    }
1254
1255
    /**
1256
     * @param string $text
1257
     */
1258
    public function updateEmailNotificationTemplate($text)
1259
    {
1260
        $this->emailNotificationTemplate = $text;
1261
    }
1262
1263
    /**
1264
     * @param string $text
1265
     */
1266
    public function setEmailNotificationTemplateToUser($text)
1267
    {
1268
        $this->emailNotificationTemplateToUser = $text;
1269
    }
1270
1271
    /**
1272
     * @param string $value
1273
     */
1274
    public function setNotifyUserByEmail($value)
1275
    {
1276
        $this->notifyUserByEmail = $value;
1277
    }
1278
1279
    /**
1280
     * @param int $value
1281
     */
1282
    public function updateEndButton($value)
1283
    {
1284
        $this->endButton = (int) $value;
1285
    }
1286
1287
    /**
1288
     * @param string $value
1289
     */
1290
    public function setOnSuccessMessage($value)
1291
    {
1292
        $this->onSuccessMessage = $value;
1293
    }
1294
1295
    /**
1296
     * @param string $value
1297
     */
1298
    public function setOnFailedMessage($value)
1299
    {
1300
        $this->onFailedMessage = $value;
1301
    }
1302
1303
    /**
1304
     * @param $value
1305
     */
1306
    public function setModelType($value)
1307
    {
1308
        $this->modelType = (int) $value;
1309
    }
1310
1311
    /**
1312
     * @param int $value
1313
     */
1314
    public function setQuestionSelectionType($value)
1315
    {
1316
        $this->questionSelectionType = (int) $value;
1317
    }
1318
1319
    /**
1320
     * @return int
1321
     */
1322
    public function getQuestionSelectionType()
1323
    {
1324
        return $this->questionSelectionType;
1325
    }
1326
1327
    /**
1328
     * @param array $categories
1329
     */
1330
    public function updateCategories($categories)
1331
    {
1332
        if (!empty($categories)) {
1333
            $categories = array_map('intval', $categories);
1334
            $this->categories = $categories;
1335
        }
1336
    }
1337
1338
    /**
1339
     * changes the exercise sound file.
1340
     *
1341
     * @author Olivier Brouckaert
1342
     *
1343
     * @param string $sound  - exercise sound file
1344
     * @param string $delete - ask to delete the file
1345
     */
1346
    public function updateSound($sound, $delete)
1347
    {
1348
        global $audioPath, $documentPath;
1349
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1350
1351
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1352
            $this->sound = $sound['name'];
1353
1354
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1355
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1356
                        WHERE 
1357
                            c_id = ".$this->course_id." AND 
1358
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1359
                $result = Database::query($sql);
1360
1361
                if (!Database::num_rows($result)) {
1362
                    $id = add_document(
1363
                        $this->course,
1364
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1365
                        'file',
1366
                        $sound['size'],
1367
                        $sound['name']
1368
                    );
1369
                    api_item_property_update(
1370
                        $this->course,
1371
                        TOOL_DOCUMENT,
1372
                        $id,
1373
                        'DocumentAdded',
1374
                        api_get_user_id()
1375
                    );
1376
                    item_property_update_on_folder(
1377
                        $this->course,
1378
                        str_replace($documentPath, '', $audioPath),
1379
                        api_get_user_id()
1380
                    );
1381
                }
1382
            }
1383
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1384
            $this->sound = '';
1385
        }
1386
    }
1387
1388
    /**
1389
     * changes the exercise type.
1390
     *
1391
     * @author Olivier Brouckaert
1392
     *
1393
     * @param int $type - exercise type
1394
     */
1395
    public function updateType($type)
1396
    {
1397
        $this->type = $type;
1398
    }
1399
1400
    /**
1401
     * sets to 0 if questions are not selected randomly
1402
     * if questions are selected randomly, sets the draws.
1403
     *
1404
     * @author Olivier Brouckaert
1405
     *
1406
     * @param int $random - 0 if not random, otherwise the draws
1407
     */
1408
    public function setRandom($random)
1409
    {
1410
        $this->random = $random;
1411
    }
1412
1413
    /**
1414
     * sets to 0 if answers are not selected randomly
1415
     * if answers are selected randomly.
1416
     *
1417
     * @author Juan Carlos Rana
1418
     *
1419
     * @param int $random_answers - random answers
1420
     */
1421
    public function updateRandomAnswers($random_answers)
1422
    {
1423
        $this->random_answers = $random_answers;
1424
    }
1425
1426
    /**
1427
     * enables the exercise.
1428
     *
1429
     * @author Olivier Brouckaert
1430
     */
1431
    public function enable()
1432
    {
1433
        $this->active = 1;
1434
    }
1435
1436
    /**
1437
     * disables the exercise.
1438
     *
1439
     * @author Olivier Brouckaert
1440
     */
1441
    public function disable()
1442
    {
1443
        $this->active = 0;
1444
    }
1445
1446
    /**
1447
     * Set disable results.
1448
     */
1449
    public function disable_results()
1450
    {
1451
        $this->results_disabled = true;
1452
    }
1453
1454
    /**
1455
     * Enable results.
1456
     */
1457
    public function enable_results()
1458
    {
1459
        $this->results_disabled = false;
1460
    }
1461
1462
    /**
1463
     * @param int $results_disabled
1464
     */
1465
    public function updateResultsDisabled($results_disabled)
1466
    {
1467
        $this->results_disabled = (int) $results_disabled;
1468
    }
1469
1470
    /**
1471
     * updates the exercise in the data base.
1472
     *
1473
     * @param string $type_e
1474
     *
1475
     * @author Olivier Brouckaert
1476
     */
1477
    public function save($type_e = '')
1478
    {
1479
        $_course = $this->course;
1480
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1481
1482
        $id = $this->id;
1483
        $exercise = $this->exercise;
1484
        $description = $this->description;
1485
        $sound = $this->sound;
1486
        $type = $this->type;
1487
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1488
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1489
        $random = $this->random;
1490
        $random_answers = $this->random_answers;
1491
        $active = $this->active;
1492
        $propagate_neg = (int) $this->propagate_neg;
1493
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1494
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1495
        $randomByCat = intval($this->randomByCat);
1496
        $text_when_finished = $this->text_when_finished;
1497
        $display_category_name = intval($this->display_category_name);
1498
        $pass_percentage = intval($this->pass_percentage);
1499
        $session_id = $this->sessionId;
1500
1501
        // If direct we do not show results
1502
        $results_disabled = intval($this->results_disabled);
1503
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1504
            $results_disabled = 0;
1505
        }
1506
        $expired_time = intval($this->expired_time);
1507
1508
        // Exercise already exists
1509
        if ($id) {
1510
            // we prepare date in the database using the api_get_utc_datetime() function
1511
            $start_time = null;
1512
            if (!empty($this->start_time)) {
1513
                $start_time = $this->start_time;
1514
            }
1515
1516
            $end_time = null;
1517
            if (!empty($this->end_time)) {
1518
                $end_time = $this->end_time;
1519
            }
1520
1521
            $params = [
1522
                'title' => $exercise,
1523
                'description' => $description,
1524
            ];
1525
1526
            $paramsExtra = [];
1527
            if ($type_e != 'simple') {
1528
                $paramsExtra = [
1529
                    'sound' => $sound,
1530
                    'type' => $type,
1531
                    'random' => $random,
1532
                    'random_answers' => $random_answers,
1533
                    'active' => $active,
1534
                    'feedback_type' => $feedback_type,
1535
                    'start_time' => $start_time,
1536
                    'end_time' => $end_time,
1537
                    'max_attempt' => $attempts,
1538
                    'expired_time' => $expired_time,
1539
                    'propagate_neg' => $propagate_neg,
1540
                    'save_correct_answers' => $saveCorrectAnswers,
1541
                    'review_answers' => $review_answers,
1542
                    'random_by_category' => $randomByCat,
1543
                    'text_when_finished' => $text_when_finished,
1544
                    'display_category_name' => $display_category_name,
1545
                    'pass_percentage' => $pass_percentage,
1546
                    'results_disabled' => $results_disabled,
1547
                    'question_selection_type' => $this->getQuestionSelectionType(),
1548
                    'hide_question_title' => $this->getHideQuestionTitle(),
1549
                ];
1550
1551
                $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1552
                if ($allow === true) {
1553
                    $paramsExtra['show_previous_button'] = $this->showPreviousButton();
1554
                }
1555
1556
                $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1557
                if ($allow === true) {
1558
                    $notifications = $this->getNotifications();
1559
                    $notifications = implode(',', $notifications);
1560
                    $paramsExtra['notifications'] = $notifications;
1561
                }
1562
            }
1563
1564
            $params = array_merge($params, $paramsExtra);
1565
1566
            Database::update(
1567
                $TBL_EXERCISES,
1568
                $params,
1569
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1570
            );
1571
1572
            // update into the item_property table
1573
            api_item_property_update(
1574
                $_course,
1575
                TOOL_QUIZ,
1576
                $id,
1577
                'QuizUpdated',
1578
                api_get_user_id()
1579
            );
1580
1581
            if (api_get_setting('search_enabled') == 'true') {
1582
                $this->search_engine_edit();
1583
            }
1584
        } else {
1585
            // Creates a new exercise
1586
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1587
            // for date because, bellow, we call function api_set_default_visibility()
1588
            // In this function, api_set_default_visibility,
1589
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1590
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1591
            $start_time = null;
1592
            if (!empty($this->start_time)) {
1593
                $start_time = $this->start_time;
1594
            }
1595
1596
            $end_time = null;
1597
            if (!empty($this->end_time)) {
1598
                $end_time = $this->end_time;
1599
            }
1600
1601
            $params = [
1602
                'c_id' => $this->course_id,
1603
                'start_time' => $start_time,
1604
                'end_time' => $end_time,
1605
                'title' => $exercise,
1606
                'description' => $description,
1607
                'sound' => $sound,
1608
                'type' => $type,
1609
                'random' => $random,
1610
                'random_answers' => $random_answers,
1611
                'active' => $active,
1612
                'results_disabled' => $results_disabled,
1613
                'max_attempt' => $attempts,
1614
                'feedback_type' => $feedback_type,
1615
                'expired_time' => $expired_time,
1616
                'session_id' => $session_id,
1617
                'review_answers' => $review_answers,
1618
                'random_by_category' => $randomByCat,
1619
                'text_when_finished' => $text_when_finished,
1620
                'display_category_name' => $display_category_name,
1621
                'pass_percentage' => $pass_percentage,
1622
                'save_correct_answers' => (int) $saveCorrectAnswers,
1623
                'propagate_neg' => $propagate_neg,
1624
                'hide_question_title' => $this->getHideQuestionTitle(),
1625
            ];
1626
1627
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1628
            if ($allow === true) {
1629
                $params['show_previous_button'] = $this->showPreviousButton();
1630
            }
1631
1632
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1633
            if ($allow === true) {
1634
                $notifications = $this->getNotifications();
1635
                $params['notifications'] = '';
1636
                if (!empty($notifications)) {
1637
                    $notifications = implode(',', $notifications);
1638
                    $params['notifications'] = $notifications;
1639
                }
1640
            }
1641
1642
            $this->id = $this->iId = Database::insert($TBL_EXERCISES, $params);
1643
1644
            if ($this->id) {
1645
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1646
                Database::query($sql);
1647
1648
                $sql = "UPDATE $TBL_EXERCISES
1649
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1650
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1651
                Database::query($sql);
1652
1653
                // insert into the item_property table
1654
                api_item_property_update(
1655
                    $this->course,
1656
                    TOOL_QUIZ,
1657
                    $this->id,
1658
                    'QuizAdded',
1659
                    api_get_user_id()
1660
                );
1661
1662
                // This function save the quiz again, carefull about start_time
1663
                // and end_time if you remove this line (see above)
1664
                api_set_default_visibility(
1665
                    $this->id,
1666
                    TOOL_QUIZ,
1667
                    null,
1668
                    $this->course
1669
                );
1670
1671
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1672
                    $this->search_engine_save();
1673
                }
1674
            }
1675
        }
1676
1677
        $this->save_categories_in_exercise($this->categories);
1678
1679
        // Updates the question position
1680
        $this->update_question_positions();
1681
1682
        return $this->iId;
1683
    }
1684
1685
    /**
1686
     * Updates question position.
1687
     */
1688
    public function update_question_positions()
1689
    {
1690
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1691
        //Fixes #3483 when updating order
1692
        $question_list = $this->selectQuestionList(true);
1693
        if (!empty($question_list)) {
1694
            foreach ($question_list as $position => $questionId) {
1695
                $sql = "UPDATE $table SET
1696
                            question_order ='".intval($position)."'
1697
                        WHERE
1698
                            c_id = ".$this->course_id." AND
1699
                            question_id = ".intval($questionId)." AND
1700
                            exercice_id=".intval($this->id);
1701
                Database::query($sql);
1702
            }
1703
        }
1704
    }
1705
1706
    /**
1707
     * Adds a question into the question list.
1708
     *
1709
     * @author Olivier Brouckaert
1710
     *
1711
     * @param int $questionId - question ID
1712
     *
1713
     * @return bool - true if the question has been added, otherwise false
1714
     */
1715
    public function addToList($questionId)
1716
    {
1717
        // checks if the question ID is not in the list
1718
        if (!$this->isInList($questionId)) {
1719
            // selects the max position
1720
            if (!$this->selectNbrQuestions()) {
1721
                $pos = 1;
1722
            } else {
1723
                if (is_array($this->questionList)) {
1724
                    $pos = max(array_keys($this->questionList)) + 1;
1725
                }
1726
            }
1727
            $this->questionList[$pos] = $questionId;
1728
1729
            return true;
1730
        }
1731
1732
        return false;
1733
    }
1734
1735
    /**
1736
     * removes a question from the question list.
1737
     *
1738
     * @author Olivier Brouckaert
1739
     *
1740
     * @param int $questionId - question ID
1741
     *
1742
     * @return bool - true if the question has been removed, otherwise false
1743
     */
1744
    public function removeFromList($questionId)
1745
    {
1746
        // searches the position of the question ID in the list
1747
        $pos = array_search($questionId, $this->questionList);
1748
        // question not found
1749
        if ($pos === false) {
1750
            return false;
1751
        } else {
1752
            // dont reduce the number of random question if we use random by category option, or if
1753
            // random all questions
1754
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1755
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1756
                    $this->random -= 1;
1757
                    $this->save();
1758
                }
1759
            }
1760
            // deletes the position from the array containing the wanted question ID
1761
            unset($this->questionList[$pos]);
1762
1763
            return true;
1764
        }
1765
    }
1766
1767
    /**
1768
     * deletes the exercise from the database
1769
     * Notice : leaves the question in the data base.
1770
     *
1771
     * @author Olivier Brouckaert
1772
     */
1773
    public function delete()
1774
    {
1775
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1776
        $sql = "UPDATE $table SET active='-1'
1777
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1778
        Database::query($sql);
1779
1780
        api_item_property_update(
1781
            $this->course,
1782
            TOOL_QUIZ,
1783
            $this->id,
1784
            'QuizDeleted',
1785
            api_get_user_id()
1786
        );
1787
        api_item_property_update(
1788
            $this->course,
1789
            TOOL_QUIZ,
1790
            $this->id,
1791
            'delete',
1792
            api_get_user_id()
1793
        );
1794
1795
        Skill::deleteSkillsFromItem($this->iId, ITEM_TYPE_EXERCISE);
1796
1797
        if (api_get_setting('search_enabled') == 'true' &&
1798
            extension_loaded('xapian')
1799
        ) {
1800
            $this->search_engine_delete();
1801
        }
1802
    }
1803
1804
    /**
1805
     * Creates the form to create / edit an exercise.
1806
     *
1807
     * @param FormValidator $form
1808
     * @param string        $type
1809
     */
1810
    public function createForm($form, $type = 'full')
1811
    {
1812
        if (empty($type)) {
1813
            $type = 'full';
1814
        }
1815
1816
        // form title
1817
        if (!empty($_GET['exerciseId'])) {
1818
            $form_title = get_lang('ModifyExercise');
1819
        } else {
1820
            $form_title = get_lang('NewEx');
1821
        }
1822
1823
        $form->addElement('header', $form_title);
1824
1825
        // Title.
1826
        if (api_get_configuration_value('save_titles_as_html')) {
1827
            $form->addHtmlEditor(
1828
                'exerciseTitle',
1829
                get_lang('ExerciseName'),
1830
                false,
1831
                false,
1832
                ['ToolbarSet' => 'Minimal']
1833
            );
1834
        } else {
1835
            $form->addElement(
1836
                'text',
1837
                'exerciseTitle',
1838
                get_lang('ExerciseName'),
1839
                ['id' => 'exercise_title']
1840
            );
1841
        }
1842
1843
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1844
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1845
1846
        $editor_config = [
1847
            'ToolbarSet' => 'TestQuestionDescription',
1848
            'Width' => '100%',
1849
            'Height' => '150',
1850
        ];
1851
        if (is_array($type)) {
1852
            $editor_config = array_merge($editor_config, $type);
1853
        }
1854
1855
        $form->addHtmlEditor(
1856
            'exerciseDescription',
1857
            get_lang('ExerciseDescription'),
1858
            false,
1859
            false,
1860
            $editor_config
1861
        );
1862
1863
        $skillList = [];
1864
        if ($type == 'full') {
1865
            //Can't modify a DirectFeedback question
1866
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1867
                // feedback type
1868
                $radios_feedback = [];
1869
                $radios_feedback[] = $form->createElement(
1870
                    'radio',
1871
                    'exerciseFeedbackType',
1872
                    null,
1873
                    get_lang('ExerciseAtTheEndOfTheTest'),
1874
                    '0',
1875
                    [
1876
                        'id' => 'exerciseType_0',
1877
                        'onclick' => 'check_feedback()',
1878
                    ]
1879
                );
1880
1881
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1882
                    // Can't convert a question from one feedback to another
1883
                    // if there is more than 1 question already added
1884
                    if ($this->selectNbrQuestions() == 0) {
1885
                        $radios_feedback[] = $form->createElement(
1886
                            'radio',
1887
                            'exerciseFeedbackType',
1888
                            null,
1889
                            get_lang('DirectFeedback'),
1890
                            '1',
1891
                            [
1892
                                'id' => 'exerciseType_1',
1893
                                'onclick' => 'check_direct_feedback()',
1894
                            ]
1895
                        );
1896
                    }
1897
                }
1898
1899
                $radios_feedback[] = $form->createElement(
1900
                    'radio',
1901
                    'exerciseFeedbackType',
1902
                    null,
1903
                    get_lang('NoFeedback'),
1904
                    '2',
1905
                    ['id' => 'exerciseType_2']
1906
                );
1907
                $form->addGroup(
1908
                    $radios_feedback,
1909
                    null,
1910
                    [
1911
                        get_lang('FeedbackType'),
1912
                        get_lang('FeedbackDisplayOptions'),
1913
                    ]
1914
                );
1915
1916
                // Type of results display on the final page
1917
                $radios_results_disabled = [];
1918
                $radios_results_disabled[] = $form->createElement(
1919
                    'radio',
1920
                    'results_disabled',
1921
                    null,
1922
                    get_lang('ShowScoreAndRightAnswer'),
1923
                    '0',
1924
                    ['id' => 'result_disabled_0']
1925
                );
1926
                $radios_results_disabled[] = $form->createElement(
1927
                    'radio',
1928
                    'results_disabled',
1929
                    null,
1930
                    get_lang('DoNotShowScoreNorRightAnswer'),
1931
                    '1',
1932
                    ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
1933
                );
1934
                $radios_results_disabled[] = $form->createElement(
1935
                    'radio',
1936
                    'results_disabled',
1937
                    null,
1938
                    get_lang('OnlyShowScore'),
1939
                    '2',
1940
                    ['id' => 'result_disabled_2']
1941
                );
1942
1943
                $radios_results_disabled[] = $form->createElement(
1944
                    'radio',
1945
                    'results_disabled',
1946
                    null,
1947
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1948
                    '4',
1949
                    ['id' => 'result_disabled_4']
1950
                );
1951
1952
                $form->addGroup(
1953
                    $radios_results_disabled,
1954
                    null,
1955
                    get_lang('ShowResultsToStudents')
1956
                );
1957
1958
                // Type of questions disposition on page
1959
                $radios = [];
1960
                $radios[] = $form->createElement(
1961
                    'radio',
1962
                    'exerciseType',
1963
                    null,
1964
                    get_lang('SimpleExercise'),
1965
                    '1',
1966
                    [
1967
                        'onclick' => 'check_per_page_all()',
1968
                        'id' => 'option_page_all',
1969
                    ]
1970
                );
1971
                $radios[] = $form->createElement(
1972
                    'radio',
1973
                    'exerciseType',
1974
                    null,
1975
                    get_lang('SequentialExercise'),
1976
                    '2',
1977
                    [
1978
                        'onclick' => 'check_per_page_one()',
1979
                        'id' => 'option_page_one',
1980
                    ]
1981
                );
1982
1983
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1984
            } else {
1985
                // if is Direct feedback but has not questions we can allow to modify the question type
1986
                if ($this->selectNbrQuestions() == 0) {
1987
                    // feedback type
1988
                    $radios_feedback = [];
1989
                    $radios_feedback[] = $form->createElement(
1990
                        'radio',
1991
                        'exerciseFeedbackType',
1992
                        null,
1993
                        get_lang('ExerciseAtTheEndOfTheTest'),
1994
                        '0',
1995
                        ['id' => 'exerciseType_0', 'onclick' => 'check_feedback()']
1996
                    );
1997
1998
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
1999
                        $radios_feedback[] = $form->createElement(
2000
                            'radio',
2001
                            'exerciseFeedbackType',
2002
                            null,
2003
                            get_lang('DirectFeedback'),
2004
                            '1',
2005
                            ['id' => 'exerciseType_1', 'onclick' => 'check_direct_feedback()']
2006
                        );
2007
                    }
2008
                    $radios_feedback[] = $form->createElement(
2009
                        'radio',
2010
                        'exerciseFeedbackType',
2011
                        null,
2012
                        get_lang('NoFeedback'),
2013
                        '2',
2014
                        ['id' => 'exerciseType_2']
2015
                    );
2016
                    $form->addGroup(
2017
                        $radios_feedback,
2018
                        null,
2019
                        [get_lang('FeedbackType'), get_lang('FeedbackDisplayOptions')]
2020
                    );
2021
                    $radios_results_disabled = [];
2022
                    $radios_results_disabled[] = $form->createElement(
2023
                        'radio',
2024
                        'results_disabled',
2025
                        null,
2026
                        get_lang('ShowScoreAndRightAnswer'),
2027
                        '0',
2028
                        ['id' => 'result_disabled_0']
2029
                    );
2030
                    $radios_results_disabled[] = $form->createElement(
2031
                        'radio',
2032
                        'results_disabled',
2033
                        null,
2034
                        get_lang('DoNotShowScoreNorRightAnswer'),
2035
                        '1',
2036
                        ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
2037
                    );
2038
                    $radios_results_disabled[] = $form->createElement(
2039
                        'radio',
2040
                        'results_disabled',
2041
                        null,
2042
                        get_lang('OnlyShowScore'),
2043
                        '2',
2044
                        ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
2045
                    );
2046
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
2047
2048
                    // Type of questions disposition on page
2049
                    $radios = [];
2050
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
2051
                    $radios[] = $form->createElement(
2052
                        'radio',
2053
                        'exerciseType',
2054
                        null,
2055
                        get_lang('SequentialExercise'),
2056
                        '2'
2057
                    );
2058
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2059
                } else {
2060
                    // Show options freeze
2061
                    $radios_results_disabled[] = $form->createElement(
2062
                        'radio',
2063
                        'results_disabled',
2064
                        null,
2065
                        get_lang('ShowScoreAndRightAnswer'),
2066
                        '0',
2067
                        ['id' => 'result_disabled_0']
2068
                    );
2069
                    $radios_results_disabled[] = $form->createElement(
2070
                        'radio',
2071
                        'results_disabled',
2072
                        null,
2073
                        get_lang('DoNotShowScoreNorRightAnswer'),
2074
                        '1',
2075
                        ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
2076
                    );
2077
                    $radios_results_disabled[] = $form->createElement(
2078
                        'radio',
2079
                        'results_disabled',
2080
                        null,
2081
                        get_lang('OnlyShowScore'),
2082
                        '2',
2083
                        ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
2084
                    );
2085
                    $result_disable_group = $form->addGroup(
2086
                        $radios_results_disabled,
2087
                        null,
2088
                        get_lang('ShowResultsToStudents')
2089
                    );
2090
                    $result_disable_group->freeze();
2091
2092
                    // we force the options to the DirectFeedback exercisetype
2093
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2094
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2095
2096
                    // Type of questions disposition on page
2097
                    $radios[] = $form->createElement(
2098
                        'radio',
2099
                        'exerciseType',
2100
                        null,
2101
                        get_lang('SimpleExercise'),
2102
                        '1',
2103
                        [
2104
                            'onclick' => 'check_per_page_all()',
2105
                            'id' => 'option_page_all',
2106
                        ]
2107
                    );
2108
                    $radios[] = $form->createElement(
2109
                        'radio',
2110
                        'exerciseType',
2111
                        null,
2112
                        get_lang('SequentialExercise'),
2113
                        '2',
2114
                        [
2115
                            'onclick' => 'check_per_page_one()',
2116
                            'id' => 'option_page_one',
2117
                        ]
2118
                    );
2119
2120
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2121
                    $type_group->freeze();
2122
                }
2123
            }
2124
2125
            $option = [
2126
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2127
                //  Defined by user
2128
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2129
                // 1-10, All
2130
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2131
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2132
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2133
                // A 123 B 456 C 78 (0, 1, all)
2134
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2135
                // C 78 B 456 A 123
2136
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2137
                // A 321 B 654 C 87
2138
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2139
                // C 87 B 654 A 321
2140
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2141
                /*    B 456 C 78 A 123
2142
                        456 78 123
2143
                        123 456 78
2144
                */
2145
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2146
                /*
2147
                    A 123 B 456 C 78
2148
                    B 456 C 78 A 123
2149
                    B 654 C 87 A 321
2150
                    654 87 321
2151
                    165 842 73
2152
                */
2153
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2154
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2155
            ];
2156
2157
            $form->addElement(
2158
                'select',
2159
                'question_selection_type',
2160
                [get_lang('QuestionSelection')],
2161
                $option,
2162
                [
2163
                    'id' => 'questionSelection',
2164
                    'onchange' => 'checkQuestionSelection()',
2165
                ]
2166
            );
2167
2168
            $displayMatrix = 'none';
2169
            $displayRandom = 'none';
2170
            $selectionType = $this->getQuestionSelectionType();
2171
            switch ($selectionType) {
2172
                case EX_Q_SELECTION_RANDOM:
2173
                    $displayRandom = 'block';
2174
                    break;
2175
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2176
                    $displayMatrix = 'block';
2177
                    break;
2178
            }
2179
2180
            $form->addElement(
2181
                'html',
2182
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2183
            );
2184
            // Number of random question.
2185
            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2186
            $option = range(0, $max);
2187
            $option[0] = get_lang('No');
2188
            $option[-1] = get_lang('AllQuestionsShort');
2189
            $form->addElement(
2190
                'select',
2191
                'randomQuestions',
2192
                [
2193
                    get_lang('RandomQuestions'),
2194
                    get_lang('RandomQuestionsHelp'),
2195
                ],
2196
                $option,
2197
                ['id' => 'randomQuestions']
2198
            );
2199
            $form->addElement('html', '</div>');
2200
2201
            $form->addElement(
2202
                'html',
2203
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2204
            );
2205
2206
            // Category selection.
2207
            $cat = new TestCategory();
2208
            $cat_form = $cat->returnCategoryForm($this);
2209
            if (empty($cat_form)) {
2210
                $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>';
2211
            }
2212
            $form->addElement('label', null, $cat_form);
2213
            $form->addElement('html', '</div>');
2214
2215
            // Category name.
2216
            $radio_display_cat_name = [
2217
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2218
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'),
2219
            ];
2220
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2221
2222
            // Random answers.
2223
            $radios_random_answers = [
2224
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2225
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'),
2226
            ];
2227
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2228
2229
            // Hide question title.
2230
            $group = [
2231
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2232
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'),
2233
            ];
2234
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2235
2236
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
2237
2238
            if ($allow === true) {
2239
                // Hide question title.
2240
                $group = [
2241
                    $form->createElement(
2242
                        'radio',
2243
                        'show_previous_button',
2244
                        null,
2245
                        get_lang('Yes'),
2246
                        '1'
2247
                    ),
2248
                    $form->createElement(
2249
                        'radio',
2250
                        'show_previous_button',
2251
                        null,
2252
                        get_lang('No'),
2253
                        '0'
2254
                    ),
2255
                ];
2256
                $form->addGroup($group, null, get_lang('ShowPreviousButton'));
2257
            }
2258
2259
            // Attempts
2260
            $attempt_option = range(0, 10);
2261
            $attempt_option[0] = get_lang('Infinite');
2262
2263
            $form->addElement(
2264
                'select',
2265
                'exerciseAttempts',
2266
                get_lang('ExerciseAttempts'),
2267
                $attempt_option,
2268
                ['id' => 'exerciseAttempts']
2269
            );
2270
2271
            // Exercise time limit
2272
            $form->addElement(
2273
                'checkbox',
2274
                'activate_start_date_check',
2275
                null,
2276
                get_lang('EnableStartTime'),
2277
                ['onclick' => 'activate_start_date()']
2278
            );
2279
2280
            $var = self::selectTimeLimit();
2281
2282
            if (!empty($this->start_time)) {
2283
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2284
            } else {
2285
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2286
            }
2287
2288
            $form->addElement('date_time_picker', 'start_time');
2289
            $form->addElement('html', '</div>');
2290
            $form->addElement(
2291
                'checkbox',
2292
                'activate_end_date_check',
2293
                null,
2294
                get_lang('EnableEndTime'),
2295
                ['onclick' => 'activate_end_date()']
2296
            );
2297
2298
            if (!empty($this->end_time)) {
2299
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2300
            } else {
2301
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2302
            }
2303
2304
            $form->addElement('date_time_picker', 'end_time');
2305
            $form->addElement('html', '</div>');
2306
2307
            $display = 'block';
2308
            $form->addElement(
2309
                'checkbox',
2310
                'propagate_neg',
2311
                null,
2312
                get_lang('PropagateNegativeResults')
2313
            );
2314
            $form->addCheckBox(
2315
                'save_correct_answers',
2316
                null,
2317
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2318
            );
2319
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2320
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2321
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2322
2323
            // Timer control
2324
            $form->addElement(
2325
                'checkbox',
2326
                'enabletimercontrol',
2327
                null,
2328
                get_lang('EnableTimerControl'),
2329
                [
2330
                    'onclick' => 'option_time_expired()',
2331
                    'id' => 'enabletimercontrol',
2332
                    'onload' => 'check_load_time()',
2333
                ]
2334
            );
2335
2336
            $expired_date = (int) $this->selectExpiredTime();
2337
2338
            if (($expired_date != '0')) {
2339
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2340
            } else {
2341
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2342
            }
2343
            $form->addText(
2344
                'enabletimercontroltotalminutes',
2345
                get_lang('ExerciseTotalDurationInMinutes'),
2346
                false,
2347
                [
2348
                    'id' => 'enabletimercontroltotalminutes',
2349
                    'cols-size' => [2, 2, 8],
2350
                ]
2351
            );
2352
            $form->addElement('html', '</div>');
2353
            $form->addElement(
2354
                'text',
2355
                'pass_percentage',
2356
                [get_lang('PassPercentage'), null, '%'],
2357
                ['id' => 'pass_percentage']
2358
            );
2359
2360
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2361
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2362
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2363
2364
            // add the text_when_finished textbox
2365
            $form->addHtmlEditor(
2366
                'text_when_finished',
2367
                get_lang('TextWhenFinished'),
2368
                false,
2369
                false,
2370
                $editor_config
2371
            );
2372
2373
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
2374
            if ($allow === true) {
2375
                $settings = ExerciseLib::getNotificationSettings();
2376
                $group = [];
2377
                foreach ($settings as $itemId => $label) {
2378
                    $group[] = $form->createElement(
2379
                        'checkbox',
2380
                        'notifications[]',
2381
                        null,
2382
                        $label,
2383
                        ['value' => $itemId]
2384
                    );
2385
                }
2386
                $form->addGroup($group, '', [get_lang('EmailNotifications')]);
2387
            }
2388
2389
            $form->addCheckBox(
2390
                'update_title_in_lps',
2391
                null,
2392
                get_lang('UpdateTitleInLps')
2393
            );
2394
2395
            $defaults = [];
2396
            if (api_get_setting('search_enabled') === 'true') {
2397
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2398
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2399
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2400
                $specific_fields = get_specific_field_list();
2401
2402
                foreach ($specific_fields as $specific_field) {
2403
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2404
                    $filter = [
2405
                        'c_id' => api_get_course_int_id(),
2406
                        'field_id' => $specific_field['id'],
2407
                        'ref_id' => $this->id,
2408
                        'tool_id' => "'".TOOL_QUIZ."'",
2409
                    ];
2410
                    $values = get_specific_field_values_list($filter, ['value']);
2411
                    if (!empty($values)) {
2412
                        $arr_str_values = [];
2413
                        foreach ($values as $value) {
2414
                            $arr_str_values[] = $value['value'];
2415
                        }
2416
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2417
                    }
2418
                }
2419
            }
2420
2421
            $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_EXERCISE, $this->iId);
2422
2423
            $form->addElement('html', '</div>'); //End advanced setting
2424
            $form->addElement('html', '</div>');
2425
        }
2426
2427
        // submit
2428
        if (isset($_GET['exerciseId'])) {
2429
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2430
        } else {
2431
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2432
        }
2433
2434
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2435
2436
        if ($type == 'full') {
2437
            // rules
2438
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2439
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2440
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2441
        }
2442
2443
        // defaults
2444
        if ($type == 'full') {
2445
            if ($this->id > 0) {
2446
                if ($this->random > $this->selectNbrQuestions()) {
2447
                    $defaults['randomQuestions'] = $this->selectNbrQuestions();
2448
                } else {
2449
                    $defaults['randomQuestions'] = $this->random;
2450
                }
2451
2452
                $defaults['randomAnswers'] = $this->getRandomAnswers();
2453
                $defaults['exerciseType'] = $this->selectType();
2454
                $defaults['exerciseTitle'] = $this->get_formated_title();
2455
                $defaults['exerciseDescription'] = $this->selectDescription();
2456
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2457
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2458
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2459
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2460
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2461
                $defaults['review_answers'] = $this->review_answers;
2462
                $defaults['randomByCat'] = $this->getRandomByCategory();
2463
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2464
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2465
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2466
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2467
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2468
                $defaults['show_previous_button'] = $this->showPreviousButton();
2469
2470
                if (!empty($this->start_time)) {
2471
                    $defaults['activate_start_date_check'] = 1;
2472
                }
2473
                if (!empty($this->end_time)) {
2474
                    $defaults['activate_end_date_check'] = 1;
2475
                }
2476
2477
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2478
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2479
2480
                // Get expired time
2481
                if ($this->expired_time != '0') {
2482
                    $defaults['enabletimercontrol'] = 1;
2483
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2484
                } else {
2485
                    $defaults['enabletimercontroltotalminutes'] = 0;
2486
                }
2487
                $defaults['skills'] = array_keys($skillList);
2488
                $defaults['notifications'] = $this->getNotifications();
2489
            } else {
2490
                $defaults['exerciseType'] = 2;
2491
                $defaults['exerciseAttempts'] = 0;
2492
                $defaults['randomQuestions'] = 0;
2493
                $defaults['randomAnswers'] = 0;
2494
                $defaults['exerciseDescription'] = '';
2495
                $defaults['exerciseFeedbackType'] = 0;
2496
                $defaults['results_disabled'] = 0;
2497
                $defaults['randomByCat'] = 0;
2498
                $defaults['text_when_finished'] = '';
2499
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2500
                $defaults['display_category_name'] = 1;
2501
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2502
                $defaults['pass_percentage'] = '';
2503
                $defaults['end_button'] = $this->selectEndButton();
2504
                $defaults['question_selection_type'] = 1;
2505
                $defaults['hide_question_title'] = 0;
2506
                $defaults['show_previous_button'] = 1;
2507
                $defaults['on_success_message'] = null;
2508
                $defaults['on_failed_message'] = null;
2509
            }
2510
        } else {
2511
            $defaults['exerciseTitle'] = $this->selectTitle();
2512
            $defaults['exerciseDescription'] = $this->selectDescription();
2513
        }
2514
2515
        if (api_get_setting('search_enabled') === 'true') {
2516
            $defaults['index_document'] = 'checked="checked"';
2517
        }
2518
        $form->setDefaults($defaults);
2519
2520
        // Freeze some elements.
2521
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2522
            $elementsToFreeze = [
2523
                'randomQuestions',
2524
                //'randomByCat',
2525
                'exerciseAttempts',
2526
                'propagate_neg',
2527
                'enabletimercontrol',
2528
                'review_answers',
2529
            ];
2530
2531
            foreach ($elementsToFreeze as $elementName) {
2532
                /** @var HTML_QuickForm_element $element */
2533
                $element = $form->getElement($elementName);
2534
                $element->freeze();
2535
            }
2536
        }
2537
    }
2538
2539
    /**
2540
     * function which process the creation of exercises.
2541
     *
2542
     * @param FormValidator $form
2543
     * @param string
2544
     *
2545
     * @return int c_quiz.iid
2546
     */
2547
    public function processCreation($form, $type = '')
2548
    {
2549
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2550
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2551
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2552
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2553
        $this->updateType($form->getSubmitValue('exerciseType'));
2554
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2555
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2556
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2557
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2558
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2559
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2560
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2561
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2562
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2563
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2564
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2565
        $this->updateCategories($form->getSubmitValue('category'));
2566
        $this->updateEndButton($form->getSubmitValue('end_button'));
2567
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2568
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2569
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2570
        $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2571
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2572
        $this->setModelType($form->getSubmitValue('model_type'));
2573
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2574
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2575
        $this->sessionId = api_get_session_id();
2576
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2577
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2578
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2579
        $this->setShowPreviousButton($form->getSubmitValue('show_previous_button'));
2580
        $this->setNotifications($form->getSubmitValue('notifications'));
2581
2582
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2583
            $start_time = $form->getSubmitValue('start_time');
2584
            $this->start_time = api_get_utc_datetime($start_time);
2585
        } else {
2586
            $this->start_time = null;
2587
        }
2588
2589
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2590
            $end_time = $form->getSubmitValue('end_time');
2591
            $this->end_time = api_get_utc_datetime($end_time);
2592
        } else {
2593
            $this->end_time = null;
2594
        }
2595
2596
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2597
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2598
            if ($this->expired_time == 0) {
2599
                $this->expired_time = $expired_total_time;
2600
            }
2601
        } else {
2602
            $this->expired_time = 0;
2603
        }
2604
2605
        if ($form->getSubmitValue('randomAnswers') == 1) {
2606
            $this->random_answers = 1;
2607
        } else {
2608
            $this->random_answers = 0;
2609
        }
2610
2611
        // Update title in all LPs that have this quiz added
2612
        if ($form->getSubmitValue('update_title_in_lps') == 1) {
2613
            $courseId = api_get_course_int_id();
2614
            $table = Database::get_course_table(TABLE_LP_ITEM);
2615
            $sql = "SELECT * FROM $table 
2616
                    WHERE 
2617
                        c_id = $courseId AND 
2618
                        item_type = 'quiz' AND 
2619
                        path = '".$this->id."'
2620
                    ";
2621
            $result = Database::query($sql);
2622
            $items = Database::store_result($result);
2623
            if (!empty($items)) {
2624
                foreach ($items as $item) {
2625
                    $itemId = $item['iid'];
2626
                    $sql = "UPDATE $table SET title = '".$this->title."'                             
2627
                            WHERE iid = $itemId AND c_id = $courseId ";
2628
                    Database::query($sql);
2629
                }
2630
            }
2631
        }
2632
2633
        $iId = $this->save($type);
2634
        if (!empty($iId)) {
2635
            Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iId);
2636
        }
2637
    }
2638
2639
    public function search_engine_save()
2640
    {
2641
        if ($_POST['index_document'] != 1) {
2642
            return;
2643
        }
2644
        $course_id = api_get_course_id();
2645
2646
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2647
2648
        $specific_fields = get_specific_field_list();
2649
        $ic_slide = new IndexableChunk();
2650
2651
        $all_specific_terms = '';
2652
        foreach ($specific_fields as $specific_field) {
2653
            if (isset($_REQUEST[$specific_field['code']])) {
2654
                $sterms = trim($_REQUEST[$specific_field['code']]);
2655
                if (!empty($sterms)) {
2656
                    $all_specific_terms .= ' '.$sterms;
2657
                    $sterms = explode(',', $sterms);
2658
                    foreach ($sterms as $sterm) {
2659
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2660
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2661
                    }
2662
                }
2663
            }
2664
        }
2665
2666
        // build the chunk to index
2667
        $ic_slide->addValue("title", $this->exercise);
2668
        $ic_slide->addCourseId($course_id);
2669
        $ic_slide->addToolId(TOOL_QUIZ);
2670
        $xapian_data = [
2671
            SE_COURSE_ID => $course_id,
2672
            SE_TOOL_ID => TOOL_QUIZ,
2673
            SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2674
            SE_USER => (int) api_get_user_id(),
2675
        ];
2676
        $ic_slide->xapian_data = serialize($xapian_data);
2677
        $exercise_description = $all_specific_terms.' '.$this->description;
2678
        $ic_slide->addValue("content", $exercise_description);
2679
2680
        $di = new ChamiloIndexer();
2681
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2682
        $di->connectDb(null, null, $lang);
2683
        $di->addChunk($ic_slide);
2684
2685
        //index and return search engine document id
2686
        $did = $di->index();
2687
        if ($did) {
2688
            // save it to db
2689
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2690
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2691
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2692
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2693
            Database::query($sql);
2694
        }
2695
    }
2696
2697
    public function search_engine_edit()
2698
    {
2699
        // update search enchine and its values table if enabled
2700
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2701
            $course_id = api_get_course_id();
2702
2703
            // actually, it consists on delete terms from db,
2704
            // insert new ones, create a new search engine document, and remove the old one
2705
            // get search_did
2706
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2707
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2708
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2709
            $res = Database::query($sql);
2710
2711
            if (Database::num_rows($res) > 0) {
2712
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2713
2714
                $se_ref = Database::fetch_array($res);
2715
                $specific_fields = get_specific_field_list();
2716
                $ic_slide = new IndexableChunk();
2717
2718
                $all_specific_terms = '';
2719
                foreach ($specific_fields as $specific_field) {
2720
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2721
                    if (isset($_REQUEST[$specific_field['code']])) {
2722
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2723
                        $all_specific_terms .= ' '.$sterms;
2724
                        $sterms = explode(',', $sterms);
2725
                        foreach ($sterms as $sterm) {
2726
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2727
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2728
                        }
2729
                    }
2730
                }
2731
2732
                // build the chunk to index
2733
                $ic_slide->addValue('title', $this->exercise);
2734
                $ic_slide->addCourseId($course_id);
2735
                $ic_slide->addToolId(TOOL_QUIZ);
2736
                $xapian_data = [
2737
                    SE_COURSE_ID => $course_id,
2738
                    SE_TOOL_ID => TOOL_QUIZ,
2739
                    SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2740
                    SE_USER => (int) api_get_user_id(),
2741
                ];
2742
                $ic_slide->xapian_data = serialize($xapian_data);
2743
                $exercise_description = $all_specific_terms.' '.$this->description;
2744
                $ic_slide->addValue("content", $exercise_description);
2745
2746
                $di = new ChamiloIndexer();
2747
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2748
                $di->connectDb(null, null, $lang);
2749
                $di->remove_document($se_ref['search_did']);
2750
                $di->addChunk($ic_slide);
2751
2752
                //index and return search engine document id
2753
                $did = $di->index();
2754
                if ($did) {
2755
                    // save it to db
2756
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2757
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2758
                    Database::query($sql);
2759
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2760
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2761
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2762
                    Database::query($sql);
2763
                }
2764
            } else {
2765
                $this->search_engine_save();
2766
            }
2767
        }
2768
    }
2769
2770
    public function search_engine_delete()
2771
    {
2772
        // remove from search engine if enabled
2773
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2774
            $course_id = api_get_course_id();
2775
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2776
            $sql = 'SELECT * FROM %s
2777
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2778
                    LIMIT 1';
2779
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2780
            $res = Database::query($sql);
2781
            if (Database::num_rows($res) > 0) {
2782
                $row = Database::fetch_array($res);
2783
                $di = new ChamiloIndexer();
2784
                $di->remove_document($row['search_did']);
2785
                unset($di);
2786
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2787
                foreach ($this->questionList as $question_i) {
2788
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2789
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2790
                    $qres = Database::query($sql);
2791
                    if (Database::num_rows($qres) > 0) {
2792
                        $qrow = Database::fetch_array($qres);
2793
                        $objQuestion = Question::getInstance($qrow['type']);
2794
                        $objQuestion = Question::read((int) $question_i);
2795
                        $objQuestion->search_engine_edit($this->id, false, true);
2796
                        unset($objQuestion);
2797
                    }
2798
                }
2799
            }
2800
            $sql = 'DELETE FROM %s 
2801
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL 
2802
                    LIMIT 1';
2803
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2804
            Database::query($sql);
2805
2806
            // remove terms from db
2807
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2808
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2809
        }
2810
    }
2811
2812
    public function selectExpiredTime()
2813
    {
2814
        return $this->expired_time;
2815
    }
2816
2817
    /**
2818
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2819
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2820
     * Works with exercises in sessions.
2821
     *
2822
     * @param bool   $cleanLpTests
2823
     * @param string $cleanResultBeforeDate
2824
     *
2825
     * @return int quantity of user's exercises deleted
2826
     */
2827
    public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
2828
    {
2829
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2830
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2831
2832
        $sql_where = '  AND
2833
                        orig_lp_id = 0 AND
2834
                        orig_lp_item_id = 0';
2835
2836
        // if we want to delete results from LP too
2837
        if ($cleanLpTests) {
2838
            $sql_where = "";
2839
        }
2840
2841
        // if we want to delete attempts before date $cleanResultBeforeDate
2842
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2843
2844
        if (!empty($cleanResultBeforeDate)) {
2845
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2846
            if (api_is_valid_date($cleanResultBeforeDate)) {
2847
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2848
            } else {
2849
                return 0;
2850
            }
2851
        }
2852
2853
        $sql = "SELECT exe_id
2854
                FROM $table_track_e_exercises
2855
                WHERE
2856
                    c_id = ".api_get_course_int_id()." AND
2857
                    exe_exo_id = ".$this->id." AND
2858
                    session_id = ".api_get_session_id()." ".
2859
                    $sql_where;
2860
2861
        $result = Database::query($sql);
2862
        $exe_list = Database::store_result($result);
2863
2864
        // deleting TRACK_E_ATTEMPT table
2865
        // check if exe in learning path or not
2866
        $i = 0;
2867
        if (is_array($exe_list) && count($exe_list) > 0) {
2868
            foreach ($exe_list as $item) {
2869
                $sql = "DELETE FROM $table_track_e_attempt
2870
                        WHERE exe_id = '".$item['exe_id']."'";
2871
                Database::query($sql);
2872
                $i++;
2873
            }
2874
        }
2875
2876
        $session_id = api_get_session_id();
2877
        // delete TRACK_E_EXERCISES table
2878
        $sql = "DELETE FROM $table_track_e_exercises
2879
                WHERE c_id = ".api_get_course_int_id()."
2880
                AND exe_exo_id = ".$this->id."
2881
                $sql_where
2882
                AND session_id = ".$session_id;
2883
        Database::query($sql);
2884
2885
        Event::addEvent(
2886
            LOG_EXERCISE_RESULT_DELETE,
2887
            LOG_EXERCISE_ID,
2888
            $this->id,
2889
            null,
2890
            null,
2891
            api_get_course_int_id(),
2892
            $session_id
2893
        );
2894
2895
        return $i;
2896
    }
2897
2898
    /**
2899
     * Copies an exercise (duplicate all questions and answers).
2900
     */
2901
    public function copyExercise()
2902
    {
2903
        $exerciseObject = $this;
2904
        $categories = $exerciseObject->getCategoriesInExercise();
2905
        // Get all questions no matter the order/category settings
2906
        $questionList = $exerciseObject->getQuestionOrderedList();
2907
        // Force the creation of a new exercise
2908
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
2909
        // Hides the new exercise
2910
        $exerciseObject->updateStatus(false);
2911
        $exerciseObject->updateId(0);
2912
        $exerciseObject->save();
2913
        $newId = $exerciseObject->selectId();
2914
        if ($newId && !empty($questionList)) {
2915
            // Question creation
2916
            foreach ($questionList as $oldQuestionId) {
2917
                $oldQuestionObj = Question::read($oldQuestionId);
2918
                $newQuestionId = $oldQuestionObj->duplicate();
2919
                if ($newQuestionId) {
2920
                    $newQuestionObj = Question::read($newQuestionId);
2921
                    if (isset($newQuestionObj) && $newQuestionObj) {
2922
                        $newQuestionObj->addToList($newId);
2923
                        if (!empty($oldQuestionObj->category)) {
2924
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
2925
                        }
2926
2927
                        // This should be moved to the duplicate function
2928
                        $newAnswerObj = new Answer($oldQuestionId);
2929
                        $newAnswerObj->read();
2930
                        $newAnswerObj->duplicate($newQuestionObj);
2931
                    }
2932
                }
2933
            }
2934
            if (!empty($categories)) {
2935
                $newCategoryList = [];
2936
                foreach ($categories as $category) {
2937
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
2938
                }
2939
                $exerciseObject->save_categories_in_exercise($newCategoryList);
2940
            }
2941
        }
2942
    }
2943
2944
    /**
2945
     * Changes the exercise status.
2946
     *
2947
     * @param string $status - exercise status
2948
     */
2949
    public function updateStatus($status)
2950
    {
2951
        $this->active = $status;
2952
    }
2953
2954
    /**
2955
     * @param int    $lp_id
2956
     * @param int    $lp_item_id
2957
     * @param int    $lp_item_view_id
2958
     * @param string $status
2959
     *
2960
     * @return array
2961
     */
2962
    public function get_stat_track_exercise_info(
2963
        $lp_id = 0,
2964
        $lp_item_id = 0,
2965
        $lp_item_view_id = 0,
2966
        $status = 'incomplete'
2967
    ) {
2968
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2969
        if (empty($lp_id)) {
2970
            $lp_id = 0;
2971
        }
2972
        if (empty($lp_item_id)) {
2973
            $lp_item_id = 0;
2974
        }
2975
        if (empty($lp_item_view_id)) {
2976
            $lp_item_view_id = 0;
2977
        }
2978
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
2979
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
2980
					   c_id                 = '.api_get_course_int_id().' AND
2981
					   status 				= '."'".Database::escape_string($status)."'".' AND
2982
					   orig_lp_id 			= '."'".$lp_id."'".' AND
2983
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
2984
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
2985
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
2986
2987
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2988
2989
        $result = Database::query($sql_track);
2990
        $new_array = [];
2991
        if (Database::num_rows($result) > 0) {
2992
            $new_array = Database::fetch_array($result, 'ASSOC');
2993
            $new_array['num_exe'] = Database::num_rows($result);
2994
        }
2995
2996
        return $new_array;
2997
    }
2998
2999
    /**
3000
     * Saves a test attempt.
3001
     *
3002
     * @param int $clock_expired_time clock_expired_time
3003
     * @param int  int lp id
3004
     * @param int  int lp item id
3005
     * @param int  int lp item_view id
3006
     * @param array $questionList
3007
     * @param float $weight
3008
     *
3009
     * @return int
3010
     */
3011
    public function save_stat_track_exercise_info(
3012
        $clock_expired_time = 0,
3013
        $safe_lp_id = 0,
3014
        $safe_lp_item_id = 0,
3015
        $safe_lp_item_view_id = 0,
3016
        $questionList = [],
3017
        $weight = 0
3018
    ) {
3019
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3020
        $safe_lp_id = intval($safe_lp_id);
3021
        $safe_lp_item_id = intval($safe_lp_item_id);
3022
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
3023
3024
        if (empty($safe_lp_id)) {
3025
            $safe_lp_id = 0;
3026
        }
3027
        if (empty($safe_lp_item_id)) {
3028
            $safe_lp_item_id = 0;
3029
        }
3030
        if (empty($clock_expired_time)) {
3031
            $clock_expired_time = null;
3032
        }
3033
3034
        $questionList = array_map('intval', $questionList);
3035
3036
        $params = [
3037
            'exe_exo_id' => $this->id,
3038
            'exe_user_id' => api_get_user_id(),
3039
            'c_id' => api_get_course_int_id(),
3040
            'status' => 'incomplete',
3041
            'session_id' => api_get_session_id(),
3042
            'data_tracking' => implode(',', $questionList),
3043
            'start_date' => api_get_utc_datetime(),
3044
            'orig_lp_id' => $safe_lp_id,
3045
            'orig_lp_item_id' => $safe_lp_item_id,
3046
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
3047
            'exe_weighting' => $weight,
3048
            'user_ip' => Database::escape_string(api_get_real_ip()),
3049
            'exe_date' => api_get_utc_datetime(),
3050
            'exe_result' => 0,
3051
            'steps_counter' => 0,
3052
            'exe_duration' => 0,
3053
            'expired_time_control' => $clock_expired_time,
3054
            'questions_to_check' => '',
3055
        ];
3056
3057
        $id = Database::insert($track_exercises, $params);
3058
3059
        return $id;
3060
    }
3061
3062
    /**
3063
     * @param int    $question_id
3064
     * @param int    $questionNum
3065
     * @param array  $questions_in_media
3066
     * @param string $currentAnswer
3067
     * @param array  $myRemindList
3068
     *
3069
     * @return string
3070
     */
3071
    public function show_button(
3072
        $question_id,
3073
        $questionNum,
3074
        $questions_in_media = [],
3075
        $currentAnswer = '',
3076
        $myRemindList = []
3077
    ) {
3078
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3079
        $nbrQuestions = $this->get_count_question_list();
3080
        $buttonList = [];
3081
        $html = $label = '';
3082
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3083
3084
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
3085
            $urlTitle = get_lang('ContinueTest');
3086
            if ($questionNum == count($this->questionList)) {
3087
                $urlTitle = get_lang('EndTest');
3088
            }
3089
3090
            $html .= Display::url(
3091
                $urlTitle,
3092
                'exercise_submit_modal.php?'.http_build_query([
3093
                    'learnpath_id' => $safe_lp_id,
3094
                    'learnpath_item_id' => $safe_lp_item_id,
3095
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
3096
                    'origin' => $origin,
3097
                    'hotspot' => $hotspot_get,
3098
                    'nbrQuestions' => $nbrQuestions,
3099
                    'num' => $questionNum,
3100
                    'exerciseType' => $this->type,
3101
                    'exerciseId' => $this->id,
3102
                    'reminder' => empty($myRemindList) ? null : 2,
3103
                ]),
3104
                [
3105
                    'class' => 'ajax btn btn-default',
3106
                    'data-title' => $urlTitle,
3107
                    'data-size' => 'md',
3108
                ]
3109
            );
3110
            $html .= '<br />';
3111
        } else {
3112
            // User
3113
            if (api_is_allowed_to_session_edit()) {
3114
                $endReminderValue = false;
3115
                if (!empty($myRemindList)) {
3116
                    $endValue = end($myRemindList);
3117
                    if ($endValue == $question_id) {
3118
                        $endReminderValue = true;
3119
                    }
3120
                }
3121
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3122
                    if ($this->review_answers) {
3123
                        $label = get_lang('ReviewQuestions');
3124
                        $class = 'btn btn-success';
3125
                    } else {
3126
                        $label = get_lang('EndTest');
3127
                        $class = 'btn btn-warning';
3128
                    }
3129
                } else {
3130
                    $label = get_lang('NextQuestion');
3131
                    $class = 'btn btn-primary';
3132
                }
3133
                // used to select it with jquery
3134
                $class .= ' question-validate-btn';
3135
                if ($this->type == ONE_PER_PAGE) {
3136
                    if ($questionNum != 1) {
3137
                        if ($this->showPreviousButton()) {
3138
                            $prev_question = $questionNum - 2;
3139
                            $showPreview = true;
3140
                            if (!empty($myRemindList)) {
3141
                                $beforeId = null;
3142
                                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...
3143
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3144
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3145
                                        break;
3146
                                    }
3147
                                }
3148
3149
                                if (empty($beforeId)) {
3150
                                    $showPreview = false;
3151
                                } else {
3152
                                    $num = 0;
3153
                                    foreach ($this->questionList as $originalQuestionId) {
3154
                                        if ($originalQuestionId == $beforeId) {
3155
                                            break;
3156
                                        }
3157
                                        $num++;
3158
                                    }
3159
                                    $prev_question = $num;
3160
                                    //$question_id = $beforeId;
3161
                                }
3162
                            }
3163
3164
                            if ($showPreview) {
3165
                                $buttonList[] = Display::button(
3166
                                    'previous_question_and_save',
3167
                                    get_lang('PreviousQuestion'),
3168
                                    [
3169
                                        'type' => 'button',
3170
                                        'class' => 'btn btn-default',
3171
                                        'data-prev' => $prev_question,
3172
                                        'data-question' => $question_id,
3173
                                    ]
3174
                                );
3175
                            }
3176
                        }
3177
                    }
3178
3179
                    // Next question
3180
                    if (!empty($questions_in_media)) {
3181
                        $buttonList[] = Display::button(
3182
                            'save_question_list',
3183
                            $label,
3184
                            [
3185
                                'type' => 'button',
3186
                                'class' => $class,
3187
                                'data-list' => implode(",", $questions_in_media),
3188
                            ]
3189
                        );
3190
                    } else {
3191
                        $buttonList[] = Display::button(
3192
                            'save_now',
3193
                            $label,
3194
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3195
                        );
3196
                    }
3197
                    $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3198
3199
                    $html .= implode(PHP_EOL, $buttonList);
3200
                } else {
3201
                    if ($this->review_answers) {
3202
                        $all_label = get_lang('ReviewQuestions');
3203
                        $class = 'btn btn-success';
3204
                    } else {
3205
                        $all_label = get_lang('EndTest');
3206
                        $class = 'btn btn-warning';
3207
                    }
3208
                    // used to select it with jquery
3209
                    $class .= ' question-validate-btn';
3210
                    $buttonList[] = Display::button(
3211
                        'validate_all',
3212
                        $all_label,
3213
                        ['type' => 'button', 'class' => $class]
3214
                    );
3215
                    $buttonList[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_response']);
3216
                    $html .= implode(PHP_EOL, $buttonList);
3217
                }
3218
            }
3219
        }
3220
3221
        return $html;
3222
    }
3223
3224
    /**
3225
     * So the time control will work.
3226
     *
3227
     * @param string $time_left
3228
     *
3229
     * @return string
3230
     */
3231
    public function showTimeControlJS($time_left)
3232
    {
3233
        $time_left = intval($time_left);
3234
3235
        return "<script>
3236
3237
            function get_expired_date_string(expired_time) {
3238
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3239
                var day, month, year, hours, minutes, seconds, date_string;
3240
                var obj_date = new Date(expired_time);
3241
                day     = obj_date.getDate();
3242
                if (day < 10) day = '0' + day;
3243
                    month   = obj_date.getMonth();
3244
                    year    = obj_date.getFullYear();
3245
                    hours   = obj_date.getHours();
3246
                if (hours < 10) hours = '0' + hours;
3247
                minutes = obj_date.getMinutes();
3248
                if (minutes < 10) minutes = '0' + minutes;
3249
                seconds = obj_date.getSeconds();
3250
                if (seconds < 10) seconds = '0' + seconds;
3251
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3252
                return date_string;
3253
            }
3254
3255
            function open_clock_warning() {
3256
                $('#clock_warning').dialog({
3257
                    modal:true,
3258
                    height:250,
3259
                    closeOnEscape: false,
3260
                    resizable: false,
3261
                    buttons: {
3262
                        '".addslashes(get_lang("EndTest"))."': function() {
3263
                            $('#clock_warning').dialog('close');
3264
                        }
3265
                    },
3266
                    close: function() {
3267
                        send_form();
3268
                    }
3269
                });
3270
                $('#clock_warning').dialog('open');
3271
3272
                $('#counter_to_redirect').epiclock({
3273
                    mode: $.epiclock.modes.countdown,
3274
                    offset: {seconds: 5},
3275
                    format: 's'
3276
                }).bind('timer', function () {
3277
                    send_form();
3278
                });
3279
            }
3280
3281
            function send_form() {
3282
                if ($('#exercise_form').length) {
3283
                    $('#exercise_form').submit();
3284
                } else {
3285
                    // In exercise_reminder.php
3286
                    final_submit();
3287
                }
3288
            }
3289
3290
            function onExpiredTimeExercise() {
3291
                $('#wrapper-clock').hide();
3292
                //$('#exercise_form').hide();
3293
                $('#expired-message-id').show();
3294
3295
                //Fixes bug #5263
3296
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3297
                open_clock_warning();
3298
            }
3299
3300
			$(document).ready(function() {
3301
				var current_time = new Date().getTime();
3302
				// time in seconds when using minutes there are some seconds lost
3303
                var time_left    = parseInt(".$time_left."); 
3304
				var expired_time = current_time + (time_left*1000);
3305
				var expired_date = get_expired_date_string(expired_time);
3306
3307
                $('#exercise_clock_warning').epiclock({
3308
                    mode: $.epiclock.modes.countdown,
3309
                    offset: {seconds: time_left},
3310
                    format: 'x:i:s',
3311
                    renderer: 'minute'
3312
                }).bind('timer', function () {
3313
                    onExpiredTimeExercise();
3314
                });
3315
	       		$('#submit_save').click(function () {});
3316
	    });
3317
	    </script>";
3318
    }
3319
3320
    /**
3321
     * This function was originally found in the exercise_show.php.
3322
     *
3323
     * @param int    $exeId
3324
     * @param int    $questionId
3325
     * @param int    $choice                                    the user selected
3326
     * @param string $from                                      function is called from 'exercise_show' or 'exercise_result'
3327
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] = coordinates
3328
     * @param bool   $saved_results                             save results in the DB or just show the reponse
3329
     * @param bool   $from_database                             gets information from DB or from the current selection
3330
     * @param bool   $show_result                               show results or not
3331
     * @param int    $propagate_neg
3332
     * @param array  $hotspot_delineation_result
3333
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3334
     *
3335
     * @todo    reduce parameters of this function
3336
     *
3337
     * @return string html code
3338
     */
3339
    public function manage_answer(
3340
        $exeId,
3341
        $questionId,
3342
        $choice,
3343
        $from = 'exercise_show',
3344
        $exerciseResultCoordinates = [],
3345
        $saved_results = true,
3346
        $from_database = false,
3347
        $show_result = true,
3348
        $propagate_neg = 0,
3349
        $hotspot_delineation_result = [],
3350
        $showTotalScoreAndUserChoicesInLastAttempt = true
3351
    ) {
3352
        $debug = false;
3353
        //needed in order to use in the exercise_attempt() for the time
3354
        global $learnpath_id, $learnpath_item_id;
3355
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3356
        $em = Database::getManager();
3357
        $feedback_type = $this->selectFeedbackType();
3358
        $results_disabled = $this->selectResultsDisabled();
3359
3360
        if ($debug) {
3361
            error_log("<------ manage_answer ------> ");
3362
            error_log('exe_id: '.$exeId);
3363
            error_log('$from:  '.$from);
3364
            error_log('$saved_results: '.intval($saved_results));
3365
            error_log('$from_database: '.intval($from_database));
3366
            error_log('$show_result: '.intval($show_result));
3367
            error_log('$propagate_neg: '.$propagate_neg);
3368
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3369
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3370
            error_log('$learnpath_id: '.$learnpath_id);
3371
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3372
            error_log('$choice: '.print_r($choice, 1));
3373
        }
3374
3375
        $final_overlap = 0;
3376
        $final_missing = 0;
3377
        $final_excess = 0;
3378
        $overlap_color = 0;
3379
        $missing_color = 0;
3380
        $excess_color = 0;
3381
        $threadhold1 = 0;
3382
        $threadhold2 = 0;
3383
        $threadhold3 = 0;
3384
        $arrques = null;
3385
        $arrans = null;
3386
        $questionId = intval($questionId);
3387
        $exeId = intval($exeId);
3388
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3389
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3390
3391
        // Creates a temporary Question object
3392
        $course_id = $this->course_id;
3393
        $objQuestionTmp = Question::read($questionId, $course_id);
3394
3395
        if ($objQuestionTmp === false) {
3396
            return false;
3397
        }
3398
3399
        $questionName = $objQuestionTmp->selectTitle();
3400
        $questionWeighting = $objQuestionTmp->selectWeighting();
3401
        $answerType = $objQuestionTmp->selectType();
3402
        $quesId = $objQuestionTmp->selectId();
3403
        $extra = $objQuestionTmp->extra;
3404
        $next = 1; //not for now
3405
        $totalWeighting = 0;
3406
        $totalScore = 0;
3407
3408
        // Extra information of the question
3409
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE && !empty($extra)) {
3410
            $extra = explode(':', $extra);
3411
            if ($debug) {
3412
                error_log(print_r($extra, 1));
3413
            }
3414
            // Fixes problems with negatives values using intval
3415
            $true_score = floatval(trim($extra[0]));
3416
            $false_score = floatval(trim($extra[1]));
3417
            $doubt_score = floatval(trim($extra[2]));
3418
        }
3419
3420
        // Construction of the Answer object
3421
        $objAnswerTmp = new Answer($questionId, $course_id);
3422
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3423
3424
        if ($debug) {
3425
            error_log('Count of answers: '.$nbrAnswers);
3426
            error_log('$answerType: '.$answerType);
3427
        }
3428
3429
        if ($answerType == FREE_ANSWER ||
3430
            $answerType == ORAL_EXPRESSION ||
3431
            $answerType == CALCULATED_ANSWER ||
3432
            $answerType == ANNOTATION
3433
        ) {
3434
            $nbrAnswers = 1;
3435
        }
3436
3437
        $generatedFile = '';
3438
        if ($answerType == ORAL_EXPRESSION) {
3439
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3440
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3441
3442
            $objQuestionTmp->initFile(
3443
                api_get_session_id(),
3444
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3445
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3446
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3447
            );
3448
3449
            //probably this attempt came in an exercise all question by page
3450
            if ($feedback_type == 0) {
3451
                $objQuestionTmp->replaceWithRealExe($exeId);
3452
            }
3453
            $generatedFile = $objQuestionTmp->getFileUrl();
3454
        }
3455
3456
        $user_answer = '';
3457
        // Get answer list for matching
3458
        $sql = "SELECT id_auto, id, answer
3459
                FROM $table_ans
3460
                WHERE c_id = $course_id AND question_id = $questionId";
3461
        $res_answer = Database::query($sql);
3462
3463
        $answerMatching = [];
3464
        while ($real_answer = Database::fetch_array($res_answer)) {
3465
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3466
        }
3467
3468
        $real_answers = [];
3469
        $quiz_question_options = Question::readQuestionOption(
3470
            $questionId,
3471
            $course_id
3472
        );
3473
3474
        $organs_at_risk_hit = 0;
3475
        $questionScore = 0;
3476
        $answer_correct_array = [];
3477
        $orderedHotspots = [];
3478
3479
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3480
            $orderedHotspots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3481
                [
3482
                    'hotspotQuestionId' => $questionId,
3483
                    'cId' => $course_id,
3484
                    'hotspotExeId' => $exeId,
3485
                ],
3486
                ['hotspotAnswerId' => 'ASC']
3487
            );
3488
        }
3489
        if ($debug) {
3490
            error_log('Start answer loop ');
3491
        }
3492
3493
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3494
            $answer = $objAnswerTmp->selectAnswer($answerId);
3495
            $answerComment = $objAnswerTmp->selectComment($answerId);
3496
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3497
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3498
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3499
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3500
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3501
3502
            if ($debug) {
3503
                error_log("answer auto id: $answerAutoId ");
3504
                error_log("answer correct: $answerCorrect ");
3505
            }
3506
3507
            // Delineation
3508
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3509
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3510
3511
            switch ($answerType) {
3512
                // for unique answer
3513
                case UNIQUE_ANSWER:
3514
                case UNIQUE_ANSWER_IMAGE:
3515
                case UNIQUE_ANSWER_NO_OPTION:
3516
                case READING_COMPREHENSION:
3517
                    if ($from_database) {
3518
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3519
                                WHERE
3520
                                    exe_id = '".$exeId."' AND
3521
                                    question_id= '".$questionId."'";
3522
                        $result = Database::query($sql);
3523
                        $choice = Database::result($result, 0, 'answer');
3524
3525
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3526
                        if ($studentChoice) {
3527
                            $questionScore += $answerWeighting;
3528
                            $totalScore += $answerWeighting;
3529
                        }
3530
                    } else {
3531
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3532
                        if ($studentChoice) {
3533
                            $questionScore += $answerWeighting;
3534
                            $totalScore += $answerWeighting;
3535
                        }
3536
                    }
3537
                    break;
3538
                // for multiple answers
3539
                case MULTIPLE_ANSWER_TRUE_FALSE:
3540
                    if ($from_database) {
3541
                        $choice = [];
3542
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3543
                                WHERE
3544
                                    exe_id = $exeId AND
3545
                                    question_id = ".$questionId;
3546
3547
                        $result = Database::query($sql);
3548
                        while ($row = Database::fetch_array($result)) {
3549
                            $values = explode(':', $row['answer']);
3550
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3551
                            $option = isset($values[1]) ? $values[1] : '';
3552
                            $choice[$my_answer_id] = $option;
3553
                        }
3554
                    }
3555
3556
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3557
3558
                    if (!empty($studentChoice)) {
3559
                        if ($studentChoice == $answerCorrect) {
3560
                            $questionScore += $true_score;
3561
                        } else {
3562
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3563
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3564
                            ) {
3565
                                $questionScore += $doubt_score;
3566
                            } else {
3567
                                $questionScore += $false_score;
3568
                            }
3569
                        }
3570
                    } else {
3571
                        // If no result then the user just hit don't know
3572
                        $studentChoice = 3;
3573
                        $questionScore += $doubt_score;
3574
                    }
3575
                    $totalScore = $questionScore;
3576
                    break;
3577
                case MULTIPLE_ANSWER: //2
3578
                    if ($from_database) {
3579
                        $choice = [];
3580
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3581
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3582
                        $resultans = Database::query($sql);
3583
                        while ($row = Database::fetch_array($resultans)) {
3584
                            $choice[$row['answer']] = 1;
3585
                        }
3586
3587
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3588
                        $real_answers[$answerId] = (bool) $studentChoice;
3589
3590
                        if ($studentChoice) {
3591
                            $questionScore += $answerWeighting;
3592
                        }
3593
                    } else {
3594
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3595
                        $real_answers[$answerId] = (bool) $studentChoice;
3596
3597
                        if (isset($studentChoice)) {
3598
                            $questionScore += $answerWeighting;
3599
                        }
3600
                    }
3601
                    $totalScore += $answerWeighting;
3602
3603
                    if ($debug) {
3604
                        error_log("studentChoice: $studentChoice");
3605
                    }
3606
                    break;
3607
                case GLOBAL_MULTIPLE_ANSWER:
3608
                    if ($from_database) {
3609
                        $choice = [];
3610
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3611
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3612
                        $resultans = Database::query($sql);
3613
                        while ($row = Database::fetch_array($resultans)) {
3614
                            $choice[$row['answer']] = 1;
3615
                        }
3616
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3617
                        $real_answers[$answerId] = (bool) $studentChoice;
3618
                        if ($studentChoice) {
3619
                            $questionScore += $answerWeighting;
3620
                        }
3621
                    } else {
3622
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3623
                        if (isset($studentChoice)) {
3624
                            $questionScore += $answerWeighting;
3625
                        }
3626
                        $real_answers[$answerId] = (bool) $studentChoice;
3627
                    }
3628
                    $totalScore += $answerWeighting;
3629
                    if ($debug) {
3630
                        error_log("studentChoice: $studentChoice");
3631
                    }
3632
                    break;
3633
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3634
                    if ($from_database) {
3635
                        $choice = [];
3636
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3637
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3638
                        $resultans = Database::query($sql);
3639
                        while ($row = Database::fetch_array($resultans)) {
3640
                            $result = explode(':', $row['answer']);
3641
                            if (isset($result[0])) {
3642
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3643
                                $option = isset($result[1]) ? $result[1] : '';
3644
                                $choice[$my_answer_id] = $option;
3645
                            }
3646
                        }
3647
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3648
3649
                        if ($answerCorrect == $studentChoice) {
3650
                            //$answerCorrect = 1;
3651
                            $real_answers[$answerId] = true;
3652
                        } else {
3653
                            //$answerCorrect = 0;
3654
                            $real_answers[$answerId] = false;
3655
                        }
3656
                    } else {
3657
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3658
                        if ($answerCorrect == $studentChoice) {
3659
                            //$answerCorrect = 1;
3660
                            $real_answers[$answerId] = true;
3661
                        } else {
3662
                            //$answerCorrect = 0;
3663
                            $real_answers[$answerId] = false;
3664
                        }
3665
                    }
3666
                    break;
3667
                case MULTIPLE_ANSWER_COMBINATION:
3668
                    if ($from_database) {
3669
                        $choice = [];
3670
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3671
                                WHERE exe_id = $exeId AND question_id= $questionId";
3672
                        $resultans = Database::query($sql);
3673
                        while ($row = Database::fetch_array($resultans)) {
3674
                            $choice[$row['answer']] = 1;
3675
                        }
3676
3677
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3678
                        if ($answerCorrect == 1) {
3679
                            if ($studentChoice) {
3680
                                $real_answers[$answerId] = true;
3681
                            } else {
3682
                                $real_answers[$answerId] = false;
3683
                            }
3684
                        } else {
3685
                            if ($studentChoice) {
3686
                                $real_answers[$answerId] = false;
3687
                            } else {
3688
                                $real_answers[$answerId] = true;
3689
                            }
3690
                        }
3691
                    } else {
3692
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3693
                        if ($answerCorrect == 1) {
3694
                            if ($studentChoice) {
3695
                                $real_answers[$answerId] = true;
3696
                            } else {
3697
                                $real_answers[$answerId] = false;
3698
                            }
3699
                        } else {
3700
                            if ($studentChoice) {
3701
                                $real_answers[$answerId] = false;
3702
                            } else {
3703
                                $real_answers[$answerId] = true;
3704
                            }
3705
                        }
3706
                    }
3707
                    break;
3708
                case FILL_IN_BLANKS:
3709
                    $str = '';
3710
                    $answerFromDatabase = '';
3711
                    if ($from_database) {
3712
                        $sql = "SELECT answer
3713
                                FROM $TBL_TRACK_ATTEMPT
3714
                                WHERE
3715
                                    exe_id = $exeId AND
3716
                                    question_id= ".intval($questionId);
3717
                        $result = Database::query($sql);
3718
                        if ($debug) {
3719
                            error_log($sql);
3720
                        }
3721
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3722
                    }
3723
3724
                    if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3725
                        // the question is encoded like this
3726
                        // [A] B [C] D [E] F::10,10,10@1
3727
                        // number 1 before the "@" means that is a switchable fill in blank question
3728
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3729
                        // means that is a normal fill blank question
3730
                        // first we explode the "::"
3731
                        $pre_array = explode('::', $answer);
3732
3733
                        // is switchable fill blank or not
3734
                        $last = count($pre_array) - 1;
3735
                        $is_set_switchable = explode('@', $pre_array[$last]);
3736
                        $switchable_answer_set = false;
3737
                        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3738
                            $switchable_answer_set = true;
3739
                        }
3740
                        $answer = '';
3741
                        for ($k = 0; $k < $last; $k++) {
3742
                            $answer .= $pre_array[$k];
3743
                        }
3744
                        // splits weightings that are joined with a comma
3745
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3746
                        // we save the answer because it will be modified
3747
                        $temp = $answer;
3748
                        $answer = '';
3749
                        $j = 0;
3750
                        //initialise answer tags
3751
                        $user_tags = $correct_tags = $real_text = [];
3752
                        // the loop will stop at the end of the text
3753
                        while (1) {
3754
                            // quits the loop if there are no more blanks (detect '[')
3755
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3756
                                // adds the end of the text
3757
                                $answer = $temp;
3758
                                $real_text[] = $answer;
3759
                                break; //no more "blanks", quit the loop
3760
                            }
3761
                            // adds the piece of text that is before the blank
3762
                            //and ends with '[' into a general storage array
3763
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3764
                            $answer .= api_substr($temp, 0, $pos + 1);
3765
                            //take the string remaining (after the last "[" we found)
3766
                            $temp = api_substr($temp, $pos + 1);
3767
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3768
                            if (($pos = api_strpos($temp, ']')) === false) {
3769
                                // adds the end of the text
3770
                                $answer .= $temp;
3771
                                break;
3772
                            }
3773
                            if ($from_database) {
3774
                                $str = $answerFromDatabase;
3775
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3776
                                $str = str_replace('\r\n', '', $str);
3777
3778
                                $choice = $arr[1];
3779
                                if (isset($choice[$j])) {
3780
                                    $tmp = api_strrpos($choice[$j], ' / ');
3781
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3782
                                    $choice[$j] = trim($choice[$j]);
3783
                                    // Needed to let characters ' and " to work as part of an answer
3784
                                    $choice[$j] = stripslashes($choice[$j]);
3785
                                } else {
3786
                                    $choice[$j] = null;
3787
                                }
3788
                            } else {
3789
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3790
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3791
                            }
3792
3793
                            $user_tags[] = $choice[$j];
3794
                            // Put the contents of the [] answer tag into correct_tags[]
3795
                            $correct_tags[] = api_substr($temp, 0, $pos);
3796
                            $j++;
3797
                            $temp = api_substr($temp, $pos + 1);
3798
                        }
3799
                        $answer = '';
3800
                        $real_correct_tags = $correct_tags;
3801
                        $chosen_list = [];
3802
                        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...
3803
                            if ($i == 0) {
3804
                                $answer .= $real_text[0];
3805
                            }
3806
                            if (!$switchable_answer_set) {
3807
                                // Needed to parse ' and " characters
3808
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3809
                                if ($correct_tags[$i] == $user_tags[$i]) {
3810
                                    // gives the related weighting to the student
3811
                                    $questionScore += $answerWeighting[$i];
3812
                                    // increments total score
3813
                                    $totalScore += $answerWeighting[$i];
3814
                                    // adds the word in green at the end of the string
3815
                                    $answer .= $correct_tags[$i];
3816
                                } elseif (!empty($user_tags[$i])) {
3817
                                    // else if the word entered by the student IS NOT the same as
3818
                                    // the one defined by the professor
3819
                                    // adds the word in red at the end of the string, and strikes it
3820
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3821
                                } else {
3822
                                    // adds a tabulation if no word has been typed by the student
3823
                                    $answer .= ''; // remove &nbsp; that causes issue
3824
                                }
3825
                            } else {
3826
                                // switchable fill in the blanks
3827
                                if (in_array($user_tags[$i], $correct_tags)) {
3828
                                    $chosen_list[] = $user_tags[$i];
3829
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3830
                                    // gives the related weighting to the student
3831
                                    $questionScore += $answerWeighting[$i];
3832
                                    // increments total score
3833
                                    $totalScore += $answerWeighting[$i];
3834
                                    // adds the word in green at the end of the string
3835
                                    $answer .= $user_tags[$i];
3836
                                } elseif (!empty($user_tags[$i])) {
3837
                                    // else if the word entered by the student IS NOT the same
3838
                                    // as the one defined by the professor
3839
                                    // adds the word in red at the end of the string, and strikes it
3840
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3841
                                } else {
3842
                                    // adds a tabulation if no word has been typed by the student
3843
                                    $answer .= ''; // remove &nbsp; that causes issue
3844
                                }
3845
                            }
3846
3847
                            // adds the correct word, followed by ] to close the blank
3848
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3849
                            if (isset($real_text[$i + 1])) {
3850
                                $answer .= $real_text[$i + 1];
3851
                            }
3852
                        }
3853
                    } else {
3854
                        // insert the student result in the track_e_attempt table, field answer
3855
                        // $answer is the answer like in the c_quiz_answer table for the question
3856
                        // student data are choice[]
3857
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3858
                            $answer
3859
                        );
3860
3861
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3862
                        $answerWeighting = $listCorrectAnswers['weighting'];
3863
                        // user choices is an array $choice
3864
3865
                        // get existing user data in n the BDD
3866
                        if ($from_database) {
3867
                            $listStudentResults = FillBlanks::getAnswerInfo(
3868
                                $answerFromDatabase,
3869
                                true
3870
                            );
3871
                            $choice = $listStudentResults['student_answer'];
3872
                        }
3873
3874
                        // loop other all blanks words
3875
                        if (!$switchableAnswerSet) {
3876
                            // not switchable answer, must be in the same place than teacher order
3877
                            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...
3878
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3879
                                $correctAnswer = $listCorrectAnswers['words'][$i];
3880
3881
                                if ($debug) {
3882
                                    error_log("Student answer: $i");
3883
                                    error_log($studentAnswer);
3884
                                }
3885
3886
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3887
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3888
                                // ENT_QUOTES is used in order to transform ' to &#039;
3889
                                if (!$from_database) {
3890
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3891
                                    if ($debug) {
3892
                                        error_log("Student answer cleaned:");
3893
                                        error_log($studentAnswer);
3894
                                    }
3895
                                }
3896
3897
                                $isAnswerCorrect = 0;
3898
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
3899
                                    // gives the related weighting to the student
3900
                                    $questionScore += $answerWeighting[$i];
3901
                                    // increments total score
3902
                                    $totalScore += $answerWeighting[$i];
3903
                                    $isAnswerCorrect = 1;
3904
                                }
3905
                                if ($debug) {
3906
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
3907
                                }
3908
3909
                                $studentAnswerToShow = $studentAnswer;
3910
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3911
                                if ($debug) {
3912
                                    error_log("Fill in blank type: $type");
3913
                                }
3914
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3915
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3916
                                    if ($studentAnswer != '') {
3917
                                        foreach ($listMenu as $item) {
3918
                                            if (sha1($item) == $studentAnswer) {
3919
                                                $studentAnswerToShow = $item;
3920
                                            }
3921
                                        }
3922
                                    }
3923
                                }
3924
3925
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3926
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
3927
                            }
3928
                        } else {
3929
                            // switchable answer
3930
                            $listStudentAnswerTemp = $choice;
3931
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
3932
3933
                            // for every teacher answer, check if there is a student answer
3934
                            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...
3935
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3936
                                $studentAnswerToShow = $studentAnswer;
3937
3938
                                if ($debug) {
3939
                                    error_log("Student answer: $i");
3940
                                    error_log($studentAnswer);
3941
                                }
3942
3943
                                $found = false;
3944
                                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...
3945
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3946
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3947
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3948
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3949
                                        if (!empty($studentAnswer)) {
3950
                                            foreach ($listMenu as $key => $item) {
3951
                                                if ($key == $correctAnswer) {
3952
                                                    $studentAnswerToShow = $item;
3953
                                                    break;
3954
                                                }
3955
                                            }
3956
                                        }
3957
                                    }
3958
3959
                                    if (!$found) {
3960
                                        if (FillBlanks::isStudentAnswerGood(
3961
                                            $studentAnswer,
3962
                                            $correctAnswer,
3963
                                            $from_database
3964
                                        )
3965
                                        ) {
3966
                                            $questionScore += $answerWeighting[$i];
3967
                                            $totalScore += $answerWeighting[$i];
3968
                                            $listTeacherAnswerTemp[$j] = '';
3969
                                            $found = true;
3970
                                        }
3971
                                    }
3972
                                }
3973
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3974
                                if (!$found) {
3975
                                    $listCorrectAnswers['student_score'][$i] = 0;
3976
                                } else {
3977
                                    $listCorrectAnswers['student_score'][$i] = 1;
3978
                                }
3979
                            }
3980
                        }
3981
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3982
                            $listCorrectAnswers
3983
                        );
3984
                    }
3985
                    break;
3986
                case CALCULATED_ANSWER:
3987
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
3988
                    if (!empty($calculatedAnswerList)) {
3989
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
3990
                        $preArray = explode('@@', $answer);
3991
                        $last = count($preArray) - 1;
3992
                        $answer = '';
3993
                        for ($k = 0; $k < $last; $k++) {
3994
                            $answer .= $preArray[$k];
3995
                        }
3996
                        $answerWeighting = [$answerWeighting];
3997
                        // we save the answer because it will be modified
3998
                        $temp = $answer;
3999
                        $answer = '';
4000
                        $j = 0;
4001
                        //initialise answer tags
4002
                        $userTags = $correctTags = $realText = [];
4003
                        // the loop will stop at the end of the text
4004
                        while (1) {
4005
                            // quits the loop if there are no more blanks (detect '[')
4006
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4007
                                // adds the end of the text
4008
                                $answer = $temp;
4009
                                $realText[] = $answer;
4010
                                break; //no more "blanks", quit the loop
4011
                            }
4012
                            // adds the piece of text that is before the blank
4013
                            //and ends with '[' into a general storage array
4014
                            $realText[] = api_substr($temp, 0, $pos + 1);
4015
                            $answer .= api_substr($temp, 0, $pos + 1);
4016
                            //take the string remaining (after the last "[" we found)
4017
                            $temp = api_substr($temp, $pos + 1);
4018
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4019
                            if (($pos = api_strpos($temp, ']')) === false) {
4020
                                // adds the end of the text
4021
                                $answer .= $temp;
4022
                                break;
4023
                            }
4024
4025
                            if ($from_database) {
4026
                                $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
4027
                                    WHERE
4028
                                        exe_id = '".$exeId."' AND
4029
                                        question_id = ".intval($questionId);
4030
                                $result = Database::query($sql);
4031
                                $str = Database::result($result, 0, 'answer');
4032
4033
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4034
                                $str = str_replace('\r\n', '', $str);
4035
                                $choice = $arr[1];
4036
                                if (isset($choice[$j])) {
4037
                                    $tmp = api_strrpos($choice[$j], ' / ');
4038
4039
                                    if ($tmp) {
4040
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4041
                                    } else {
4042
                                        $tmp = ltrim($tmp, '[');
4043
                                        $tmp = rtrim($tmp, ']');
4044
                                    }
4045
4046
                                    $choice[$j] = trim($choice[$j]);
4047
                                    // Needed to let characters ' and " to work as part of an answer
4048
                                    $choice[$j] = stripslashes($choice[$j]);
4049
                                } else {
4050
                                    $choice[$j] = null;
4051
                                }
4052
                            } else {
4053
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
4054
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4055
                            }
4056
                            $userTags[] = $choice[$j];
4057
                            //put the contents of the [] answer tag into correct_tags[]
4058
                            $correctTags[] = api_substr($temp, 0, $pos);
4059
                            $j++;
4060
                            $temp = api_substr($temp, $pos + 1);
4061
                        }
4062
                        $answer = '';
4063
                        $realCorrectTags = $correctTags;
4064
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4065
                        $expectedAnswer = '';
4066
                        $calculatedChoice = '';
4067
4068
                        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...
4069
                            if ($i == 0) {
4070
                                $answer .= $realText[0];
4071
                            }
4072
                            // Needed to parse ' and " characters
4073
                            $userTags[$i] = stripslashes($userTags[$i]);
4074
                            if ($correctTags[$i] == $userTags[$i]) {
4075
                                // gives the related weighting to the student
4076
                                $questionScore += $answerWeighting[$i];
4077
                                // increments total score
4078
                                $totalScore += $answerWeighting[$i];
4079
                                // adds the word in green at the end of the string
4080
                                $answer .= $correctTags[$i];
4081
                                $calculatedChoice = $correctTags[$i];
4082
                            } elseif (!empty($userTags[$i])) {
4083
                                // else if the word entered by the student IS NOT the same as
4084
                                // the one defined by the professor
4085
                                // adds the word in red at the end of the string, and strikes it
4086
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4087
                                $calculatedChoice = $userTags[$i];
4088
                            } else {
4089
                                // adds a tabulation if no word has been typed by the student
4090
                                $answer .= ''; // remove &nbsp; that causes issue
4091
                            }
4092
                            // adds the correct word, followed by ] to close the blank
4093
4094
                            if ($this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM) {
4095
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4096
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4097
                                $expectedAnswer = $realCorrectTags[$i];
4098
                            }
4099
                            $answer .= ']';
4100
4101
                            if (isset($realText[$i + 1])) {
4102
                                $answer .= $realText[$i + 1];
4103
                            }
4104
                        }
4105
                    } else {
4106
                        if ($from_database) {
4107
                            $sql = "SELECT *
4108
                                FROM $TBL_TRACK_ATTEMPT
4109
                                WHERE
4110
                                    exe_id = $exeId AND
4111
                                    question_id= ".intval($questionId);
4112
                            $result = Database::query($sql);
4113
                            $resultData = Database::fetch_array($result, 'ASSOC');
4114
                            $answer = $resultData['answer'];
4115
                            $questionScore = $resultData['marks'];
4116
                        }
4117
                    }
4118
                    break;
4119
                case FREE_ANSWER:
4120
                    if ($from_database) {
4121
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4122
                                 WHERE 
4123
                                    exe_id = $exeId AND 
4124
                                    question_id= ".$questionId;
4125
                        $result = Database::query($sql);
4126
                        $data = Database::fetch_array($result);
4127
4128
                        $choice = $data['answer'];
4129
                        $choice = str_replace('\r\n', '', $choice);
4130
                        $choice = stripslashes($choice);
4131
                        $questionScore = $data['marks'];
4132
4133
                        if ($questionScore == -1) {
4134
                            $totalScore += 0;
4135
                        } else {
4136
                            $totalScore += $questionScore;
4137
                        }
4138
                        if ($questionScore == '') {
4139
                            $questionScore = 0;
4140
                        }
4141
                        $arrques = $questionName;
4142
                        $arrans = $choice;
4143
                    } else {
4144
                        $studentChoice = $choice;
4145
                        if ($studentChoice) {
4146
                            //Fixing negative puntation see #2193
4147
                            $questionScore = 0;
4148
                            $totalScore += 0;
4149
                        }
4150
                    }
4151
                    break;
4152
                case ORAL_EXPRESSION:
4153
                    if ($from_database) {
4154
                        $query = "SELECT answer, marks 
4155
                                  FROM $TBL_TRACK_ATTEMPT
4156
                                  WHERE 
4157
                                        exe_id = $exeId AND 
4158
                                        question_id = $questionId
4159
                                 ";
4160
                        $resq = Database::query($query);
4161
                        $row = Database::fetch_assoc($resq);
4162
                        $choice = $row['answer'];
4163
                        $choice = str_replace('\r\n', '', $choice);
4164
                        $choice = stripslashes($choice);
4165
                        $questionScore = $row['marks'];
4166
                        if ($questionScore == -1) {
4167
                            $totalScore += 0;
4168
                        } else {
4169
                            $totalScore += $questionScore;
4170
                        }
4171
                        $arrques = $questionName;
4172
                        $arrans = $choice;
4173
                    } else {
4174
                        $studentChoice = $choice;
4175
                        if ($studentChoice) {
4176
                            //Fixing negative puntation see #2193
4177
                            $questionScore = 0;
4178
                            $totalScore += 0;
4179
                        }
4180
                    }
4181
                    break;
4182
                case DRAGGABLE:
4183
                case MATCHING_DRAGGABLE:
4184
                case MATCHING:
4185
                    if ($from_database) {
4186
                        $sql = "SELECT id, answer, id_auto
4187
                                FROM $table_ans
4188
                                WHERE
4189
                                    c_id = $course_id AND
4190
                                    question_id = $questionId AND
4191
                                    correct = 0
4192
                                ";
4193
                        $result = Database::query($sql);
4194
                        // Getting the real answer
4195
                        $real_list = [];
4196
                        while ($realAnswer = Database::fetch_array($result)) {
4197
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4198
                        }
4199
4200
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4201
                                FROM $table_ans
4202
                                WHERE
4203
                                    c_id = $course_id AND
4204
                                    question_id = $questionId AND
4205
                                    correct <> 0
4206
                                ORDER BY id_auto";
4207
                        $result = Database::query($sql);
4208
                        $options = [];
4209
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4210
                            $options[] = $row;
4211
                        }
4212
4213
                        $questionScore = 0;
4214
                        $counterAnswer = 1;
4215
                        foreach ($options as $a_answers) {
4216
                            $i_answer_id = $a_answers['id']; //3
4217
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4218
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4219
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4220
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4221
                                    WHERE
4222
                                        exe_id = '$exeId' AND
4223
                                        question_id = '$questionId' AND
4224
                                        position = '$i_answer_id_auto'";
4225
                            $result = Database::query($sql);
4226
                            $s_user_answer = 0;
4227
                            if (Database::num_rows($result) > 0) {
4228
                                //  rich - good looking
4229
                                $s_user_answer = Database::result($result, 0, 0);
4230
                            }
4231
                            $i_answerWeighting = $a_answers['ponderation'];
4232
                            $user_answer = '';
4233
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4234
                            if (!empty($s_user_answer)) {
4235
                                if ($answerType == DRAGGABLE) {
4236
                                    if ($s_user_answer == $i_answer_correct_answer) {
4237
                                        $questionScore += $i_answerWeighting;
4238
                                        $totalScore += $i_answerWeighting;
4239
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4240
                                        if ($this->showExpectedChoice()) {
4241
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4242
                                        }
4243
                                        $status = Display::label(get_lang('Correct'), 'success');
4244
                                    } else {
4245
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4246
                                        if ($this->showExpectedChoice()) {
4247
                                            $data = $options[$real_list[$s_user_answer] - 1];
4248
                                            $user_answer = $data['answer'];
4249
                                        }
4250
                                    }
4251
                                } else {
4252
                                    if ($s_user_answer == $i_answer_correct_answer) {
4253
                                        $questionScore += $i_answerWeighting;
4254
                                        $totalScore += $i_answerWeighting;
4255
                                        $status = Display::label(get_lang('Correct'), 'success');
4256
4257
                                        // Try with id
4258
                                        if (isset($real_list[$i_answer_id])) {
4259
                                            $user_answer = Display::span($real_list[$i_answer_id]);
4260
                                        }
4261
4262
                                        // Try with $i_answer_id_auto
4263
                                        if (empty($user_answer)) {
4264
                                            if (isset($real_list[$i_answer_id_auto])) {
4265
                                                $user_answer = Display::span($real_list[$i_answer_id_auto]);
4266
                                            }
4267
                                        }
4268
                                        if ($this->showExpectedChoice()) {
4269
                                            if (isset($real_list[$i_answer_correct_answer])) {
4270
                                                $user_answer = Display::span($real_list[$i_answer_correct_answer]);
4271
                                            }
4272
                                        }
4273
                                    } else {
4274
                                        $user_answer = Display::span(
4275
                                            $real_list[$s_user_answer],
4276
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4277
                                        );
4278
                                        if ($this->showExpectedChoice()) {
4279
                                            $user_answer = '';
4280
                                        }
4281
                                    }
4282
                                }
4283
                            } elseif ($answerType == DRAGGABLE) {
4284
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4285
                                if ($this->showExpectedChoice()) {
4286
                                    $user_answer = '';
4287
                                }
4288
                            } else {
4289
                                $user_answer = Display::span(
4290
                                    get_lang('Incorrect').' &nbsp;',
4291
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4292
                                );
4293
                                if ($this->showExpectedChoice()) {
4294
                                    $user_answer = '';
4295
                                }
4296
                            }
4297
4298
                            if ($show_result) {
4299
                                if ($this->showExpectedChoice() == false &&
4300
                                    $showTotalScoreAndUserChoicesInLastAttempt === false
4301
                                ) {
4302
                                    $user_answer = '';
4303
                                }
4304
                                switch ($answerType) {
4305
                                    case MATCHING:
4306
                                    case MATCHING_DRAGGABLE:
4307
                                        echo '<tr>';
4308
                                        if ($this->showExpectedChoice()) {
4309
                                            echo '<td>'.$s_answer_label.'</td>';
4310
                                            echo '<td>'.$user_answer.'</td>';
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>'.$s_answer_label.'</td>';
4325
                                            echo '<td>'.$user_answer.'</td>';
4326
                                            echo '<td>';
4327
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4328
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4329
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4330
                                                ) {
4331
                                                    echo Display::span(
4332
                                                        $real_list[$i_answer_correct_answer],
4333
                                                        ['style' => 'color: #008000; font-weight: bold;']
4334
                                                    );
4335
                                                }
4336
                                            }
4337
                                            echo '</td>';
4338
                                        }
4339
                                        echo '</tr>';
4340
                                        break;
4341
                                    case DRAGGABLE:
4342
                                        if ($showTotalScoreAndUserChoicesInLastAttempt == false) {
4343
                                            $s_answer_label = '';
4344
                                        }
4345
                                        echo '<tr>';
4346
                                        if ($this->showExpectedChoice()) {
4347
                                            echo '<td>'.$user_answer.'</td>';
4348
                                            echo '<td>'.$s_answer_label.'</td>';
4349
                                            echo '<td>'.$status.'</td>';
4350
                                        } else {
4351
                                            echo '<td>'.$s_answer_label.'</td>';
4352
                                            echo '<td>'.$user_answer.'</td>';
4353
                                            echo '<td>';
4354
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4355
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4356
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4357
                                                ) {
4358
                                                    echo Display::span(
4359
                                                        $real_list[$i_answer_correct_answer],
4360
                                                        ['style' => 'color: #008000; font-weight: bold;']
4361
                                                    );
4362
                                                }
4363
                                            }
4364
                                            echo '</td>';
4365
                                        }
4366
                                        echo '</tr>';
4367
                                        break;
4368
                                }
4369
                            }
4370
                            $counterAnswer++;
4371
                        }
4372
                        break 2; // break the switch and the "for" condition
4373
                    } else {
4374
                        if ($answerCorrect) {
4375
                            if (isset($choice[$answerAutoId]) &&
4376
                                $answerCorrect == $choice[$answerAutoId]
4377
                            ) {
4378
                                $questionScore += $answerWeighting;
4379
                                $totalScore += $answerWeighting;
4380
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4381
                            } else {
4382
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4383
                                    $user_answer = Display::span(
4384
                                        $answerMatching[$choice[$answerAutoId]],
4385
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4386
                                    );
4387
                                }
4388
                            }
4389
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4390
                        }
4391
                    }
4392
                    break;
4393
                case HOT_SPOT:
4394
                    if ($from_database) {
4395
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4396
                        // Check auto id
4397
                        $sql = "SELECT hotspot_correct
4398
                                FROM $TBL_TRACK_HOTSPOT
4399
                                WHERE
4400
                                    hotspot_exe_id = $exeId AND
4401
                                    hotspot_question_id= $questionId AND
4402
                                    hotspot_answer_id = ".intval($answerAutoId)."
4403
                                ORDER BY hotspot_id ASC";
4404
                        $result = Database::query($sql);
4405
                        if (Database::num_rows($result)) {
4406
                            $studentChoice = Database::result(
4407
                                $result,
4408
                                0,
4409
                                'hotspot_correct'
4410
                            );
4411
4412
                            if ($studentChoice) {
4413
                                $questionScore += $answerWeighting;
4414
                                $totalScore += $answerWeighting;
4415
                            }
4416
                        } else {
4417
                            // If answer.id is different:
4418
                            $sql = "SELECT hotspot_correct
4419
                                FROM $TBL_TRACK_HOTSPOT
4420
                                WHERE
4421
                                    hotspot_exe_id = $exeId AND
4422
                                    hotspot_question_id= $questionId AND
4423
                                    hotspot_answer_id = ".intval($answerId)."
4424
                                ORDER BY hotspot_id ASC";
4425
                            $result = Database::query($sql);
4426
4427
                            if (Database::num_rows($result)) {
4428
                                $studentChoice = Database::result(
4429
                                    $result,
4430
                                    0,
4431
                                    'hotspot_correct'
4432
                                );
4433
4434
                                if ($studentChoice) {
4435
                                    $questionScore += $answerWeighting;
4436
                                    $totalScore += $answerWeighting;
4437
                                }
4438
                            } else {
4439
                                // check answer.iid
4440
                                if (!empty($answerIid)) {
4441
                                    $sql = "SELECT hotspot_correct
4442
                                            FROM $TBL_TRACK_HOTSPOT
4443
                                            WHERE
4444
                                                hotspot_exe_id = $exeId AND
4445
                                                hotspot_question_id= $questionId AND
4446
                                                hotspot_answer_id = ".intval($answerIid)."
4447
                                            ORDER BY hotspot_id ASC";
4448
                                    $result = Database::query($sql);
4449
4450
                                    $studentChoice = Database::result(
4451
                                        $result,
4452
                                        0,
4453
                                        'hotspot_correct'
4454
                                    );
4455
4456
                                    if ($studentChoice) {
4457
                                        $questionScore += $answerWeighting;
4458
                                        $totalScore += $answerWeighting;
4459
                                    }
4460
                                }
4461
                            }
4462
                        }
4463
                    } else {
4464
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4465
                            $choice[$answerAutoId] = 0;
4466
                            $choice[$answerIid] = 0;
4467
                        } else {
4468
                            $studentChoice = $choice[$answerAutoId];
4469
                            if (empty($studentChoice)) {
4470
                                $studentChoice = $choice[$answerIid];
4471
                            }
4472
                            $choiceIsValid = false;
4473
                            if (!empty($studentChoice)) {
4474
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4475
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4476
                                $choicePoint = Geometry::decodePoint($studentChoice);
4477
4478
                                switch ($hotspotType) {
4479
                                    case 'square':
4480
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4481
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4482
                                        break;
4483
                                    case 'circle':
4484
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4485
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4486
                                        break;
4487
                                    case 'poly':
4488
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4489
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4490
                                        break;
4491
                                }
4492
                            }
4493
4494
                            $choice[$answerAutoId] = 0;
4495
                            if ($choiceIsValid) {
4496
                                $questionScore += $answerWeighting;
4497
                                $totalScore += $answerWeighting;
4498
                                $choice[$answerAutoId] = 1;
4499
                                $choice[$answerIid] = 1;
4500
                            }
4501
                        }
4502
                    }
4503
                    break;
4504
                // @todo never added to chamilo
4505
                //for hotspot with fixed order
4506
                case HOT_SPOT_ORDER:
4507
                    $studentChoice = $choice['order'][$answerId];
4508
                    if ($studentChoice == $answerId) {
4509
                        $questionScore += $answerWeighting;
4510
                        $totalScore += $answerWeighting;
4511
                        $studentChoice = true;
4512
                    } else {
4513
                        $studentChoice = false;
4514
                    }
4515
                    break;
4516
                // for hotspot with delineation
4517
                case HOT_SPOT_DELINEATION:
4518
                    if ($from_database) {
4519
                        // getting the user answer
4520
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4521
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4522
                                    FROM $TBL_TRACK_HOTSPOT
4523
                                    WHERE
4524
                                        hotspot_exe_id = '".$exeId."' AND
4525
                                        hotspot_question_id= '".$questionId."' AND
4526
                                        hotspot_answer_id='1'";
4527
                        //by default we take 1 because it's a delineation
4528
                        $resq = Database::query($query);
4529
                        $row = Database::fetch_array($resq, 'ASSOC');
4530
4531
                        $choice = $row['hotspot_correct'];
4532
                        $user_answer = $row['hotspot_coordinate'];
4533
4534
                        // THIS is very important otherwise the poly_compile will throw an error!!
4535
                        // round-up the coordinates
4536
                        $coords = explode('/', $user_answer);
4537
                        $user_array = '';
4538
                        foreach ($coords as $coord) {
4539
                            list($x, $y) = explode(';', $coord);
4540
                            $user_array .= round($x).';'.round($y).'/';
4541
                        }
4542
                        $user_array = substr($user_array, 0, -1);
4543
                    } else {
4544
                        if (!empty($studentChoice)) {
4545
                            $newquestionList[] = $questionId;
4546
                        }
4547
4548
                        if ($answerId === 1) {
4549
                            $studentChoice = $choice[$answerId];
4550
                            $questionScore += $answerWeighting;
4551
4552
                            if ($hotspot_delineation_result[1] == 1) {
4553
                                $totalScore += $answerWeighting; //adding the total
4554
                            }
4555
                        }
4556
                    }
4557
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4558
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4559
                    break;
4560
                case ANNOTATION:
4561
                    if ($from_database) {
4562
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4563
                                 WHERE 
4564
                                    exe_id = $exeId AND 
4565
                                    question_id= ".$questionId;
4566
                        $resq = Database::query($sql);
4567
                        $data = Database::fetch_array($resq);
4568
4569
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4570
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4571
4572
                        $arrques = $questionName;
4573
                        break;
4574
                    }
4575
4576
                    $studentChoice = $choice;
4577
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_COMBINATION_TRUE_FALSE) {
4637
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4638
                                $this,
4639
                                $feedback_type,
4640
                                $answerType,
4641
                                $studentChoice,
4642
                                $answer,
4643
                                $answerComment,
4644
                                $answerCorrect,
4645
                                0,
4646
                                0,
4647
                                0,
4648
                                $results_disabled,
4649
                                $showTotalScoreAndUserChoicesInLastAttempt
4650
                            );
4651
                        } elseif ($answerType == FILL_IN_BLANKS) {
4652
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4653
                                $feedback_type,
4654
                                $answer,
4655
                                0,
4656
                                0,
4657
                                $results_disabled,
4658
                                '',
4659
                                $showTotalScoreAndUserChoicesInLastAttempt
4660
                            );
4661
                        } elseif ($answerType == CALCULATED_ANSWER) {
4662
                            ExerciseShowFunctions::display_calculated_answer(
4663
                                $this,
4664
                                $feedback_type,
4665
                                $answer,
4666
                                0,
4667
                                0,
4668
                                $results_disabled,
4669
                                $showTotalScoreAndUserChoicesInLastAttempt,
4670
                                $expectedAnswer,
4671
                                $calculatedChoice,
4672
                                $calculatedStatus
4673
                            );
4674
                        } elseif ($answerType == FREE_ANSWER) {
4675
                            ExerciseShowFunctions::display_free_answer(
4676
                                $feedback_type,
4677
                                $choice,
4678
                                $exeId,
4679
                                $questionId,
4680
                                $questionScore,
4681
                                $results_disabled
4682
                            );
4683
                        } elseif ($answerType == ORAL_EXPRESSION) {
4684
                            // to store the details of open questions in an array to be used in mail
4685
                            /** @var OralExpression $objQuestionTmp */
4686
                            ExerciseShowFunctions::display_oral_expression_answer(
4687
                                $feedback_type,
4688
                                $choice,
4689
                                0,
4690
                                0,
4691
                                $objQuestionTmp->getFileUrl(true),
4692
                                $results_disabled,
4693
                                $questionScore
4694
                            );
4695
                        } elseif ($answerType == HOT_SPOT) {
4696
                            /**
4697
                             * @var int
4698
                             * @var TrackEHotspot $hotspot
4699
                             */
4700
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4701
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4702
                                    break;
4703
                                }
4704
                            }
4705
4706
                            // force to show whether the choice is correct or not
4707
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4708
4709
                            ExerciseShowFunctions::display_hotspot_answer(
4710
                                $feedback_type,
4711
                                ++$correctAnswerId,
4712
                                $answer,
4713
                                $studentChoice,
4714
                                $answerComment,
4715
                                $results_disabled,
4716
                                $correctAnswerId,
4717
                                $showTotalScoreAndUserChoicesInLastAttempt
4718
                            );
4719
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4720
                            ExerciseShowFunctions::display_hotspot_order_answer(
4721
                                $feedback_type,
4722
                                $answerId,
4723
                                $answer,
4724
                                $studentChoice,
4725
                                $answerComment
4726
                            );
4727
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4728
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4729
4730
                            //round-up the coordinates
4731
                            $coords = explode('/', $user_answer);
4732
                            $user_array = '';
4733
                            foreach ($coords as $coord) {
4734
                                list($x, $y) = explode(';', $coord);
4735
                                $user_array .= round($x).';'.round($y).'/';
4736
                            }
4737
                            $user_array = substr($user_array, 0, -1);
4738
4739
                            if ($next) {
4740
                                $user_answer = $user_array;
4741
                                // we compare only the delineation not the other points
4742
                                $answer_question = $_SESSION['hotspot_coord'][1];
4743
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4744
4745
                                //calculating the area
4746
                                $poly_user = convert_coordinates($user_answer, '/');
4747
                                $poly_answer = convert_coordinates($answer_question, '|');
4748
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4749
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4750
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4751
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4752
4753
                                $overlap = $poly_results['both'];
4754
                                $poly_answer_area = $poly_results['s1'];
4755
                                $poly_user_area = $poly_results['s2'];
4756
                                $missing = $poly_results['s1Only'];
4757
                                $excess = $poly_results['s2Only'];
4758
4759
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4760
                                // //this is an area in pixels
4761
                                if ($debug > 0) {
4762
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4763
                                }
4764
4765
                                if ($overlap < 1) {
4766
                                    //shortcut to avoid complicated calculations
4767
                                    $final_overlap = 0;
4768
                                    $final_missing = 100;
4769
                                    $final_excess = 100;
4770
                                } else {
4771
                                    // the final overlap is the percentage of the initial polygon
4772
                                    // that is overlapped by the user's polygon
4773
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4774
                                    if ($debug > 1) {
4775
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4776
                                    }
4777
                                    // the final missing area is the percentage of the initial polygon
4778
                                    // that is not overlapped by the user's polygon
4779
                                    $final_missing = 100 - $final_overlap;
4780
                                    if ($debug > 1) {
4781
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4782
                                    }
4783
                                    // the final excess area is the percentage of the initial polygon's size
4784
                                    // that is covered by the user's polygon outside of the initial polygon
4785
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4786
                                    if ($debug > 1) {
4787
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4788
                                    }
4789
                                }
4790
4791
                                //checking the destination parameters parsing the "@@"
4792
                                $destination_items = explode(
4793
                                    '@@',
4794
                                    $answerDestination
4795
                                );
4796
                                $threadhold_total = $destination_items[0];
4797
                                $threadhold_items = explode(
4798
                                    ';',
4799
                                    $threadhold_total
4800
                                );
4801
                                $threadhold1 = $threadhold_items[0]; // overlap
4802
                                $threadhold2 = $threadhold_items[1]; // excess
4803
                                $threadhold3 = $threadhold_items[2]; //missing
4804
4805
                                // if is delineation
4806
                                if ($answerId === 1) {
4807
                                    //setting colors
4808
                                    if ($final_overlap >= $threadhold1) {
4809
                                        $overlap_color = true; //echo 'a';
4810
                                    }
4811
                                    //echo $excess.'-'.$threadhold2;
4812
                                    if ($final_excess <= $threadhold2) {
4813
                                        $excess_color = true; //echo 'b';
4814
                                    }
4815
                                    //echo '--------'.$missing.'-'.$threadhold3;
4816
                                    if ($final_missing <= $threadhold3) {
4817
                                        $missing_color = true; //echo 'c';
4818
                                    }
4819
4820
                                    // if pass
4821
                                    if ($final_overlap >= $threadhold1 &&
4822
                                        $final_missing <= $threadhold3 &&
4823
                                        $final_excess <= $threadhold2
4824
                                    ) {
4825
                                        $next = 1; //go to the oars
4826
                                        $result_comment = get_lang('Acceptable');
4827
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4828
                                    } else {
4829
                                        $next = 0;
4830
                                        $result_comment = get_lang('Unacceptable');
4831
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4832
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4833
                                        // checking the destination parameters parsing the "@@"
4834
                                        $destination_items = explode('@@', $answerDestination);
4835
                                    }
4836
                                } elseif ($answerId > 1) {
4837
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4838
                                        if ($debug > 0) {
4839
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4840
                                        }
4841
                                        //type no error shouldn't be treated
4842
                                        $next = 1;
4843
                                        continue;
4844
                                    }
4845
                                    if ($debug > 0) {
4846
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4847
                                    }
4848
                                    $inter = $result['success'];
4849
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4850
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4851
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4852
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4853
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4854
4855
                                    if ($overlap == false) {
4856
                                        //all good, no overlap
4857
                                        $next = 1;
4858
                                        continue;
4859
                                    } else {
4860
                                        if ($debug > 0) {
4861
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4862
                                        }
4863
                                        $organs_at_risk_hit++;
4864
                                        //show the feedback
4865
                                        $next = 0;
4866
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4867
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4868
4869
                                        $destination_items = explode('@@', $answerDestination);
4870
                                        $try_hotspot = $destination_items[1];
4871
                                        $lp_hotspot = $destination_items[2];
4872
                                        $select_question_hotspot = $destination_items[3];
4873
                                        $url_hotspot = $destination_items[4];
4874
                                    }
4875
                                }
4876
                            } else {
4877
                                // the first delineation feedback
4878
                                if ($debug > 0) {
4879
                                    error_log(__LINE__.' first', 0);
4880
                                }
4881
                            }
4882
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4883
                            echo '<tr>';
4884
                            echo Display::tag('td', $answerMatching[$answerId]);
4885
                            echo Display::tag(
4886
                                'td',
4887
                                "$user_answer / ".Display::tag(
4888
                                    'strong',
4889
                                    $answerMatching[$answerCorrect],
4890
                                    ['style' => 'color: #008000; font-weight: bold;']
4891
                                )
4892
                            );
4893
                            echo '</tr>';
4894
                        } elseif ($answerType == ANNOTATION) {
4895
                            ExerciseShowFunctions::displayAnnotationAnswer(
4896
                                $feedback_type,
4897
                                $exeId,
4898
                                $questionId,
4899
                                $questionScore,
4900
                                $results_disabled
4901
                            );
4902
                        }
4903
                    }
4904
                } else {
4905
                    if ($debug) {
4906
                        error_log('Showing questions $from '.$from);
4907
                    }
4908
4909
                    switch ($answerType) {
4910
                        case UNIQUE_ANSWER:
4911
                        case UNIQUE_ANSWER_IMAGE:
4912
                        case UNIQUE_ANSWER_NO_OPTION:
4913
                        case MULTIPLE_ANSWER:
4914
                        case GLOBAL_MULTIPLE_ANSWER:
4915
                        case MULTIPLE_ANSWER_COMBINATION:
4916
                        case READING_COMPREHENSION:
4917
                            if ($answerId == 1) {
4918
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4919
                                    $this,
4920
                                    $feedback_type,
4921
                                    $answerType,
4922
                                    $studentChoice,
4923
                                    $answer,
4924
                                    $answerComment,
4925
                                    $answerCorrect,
4926
                                    $exeId,
4927
                                    $questionId,
4928
                                    $answerId,
4929
                                    $results_disabled,
4930
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4931
                                    $this->export
4932
                                );
4933
                            } else {
4934
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4935
                                    $this,
4936
                                    $feedback_type,
4937
                                    $answerType,
4938
                                    $studentChoice,
4939
                                    $answer,
4940
                                    $answerComment,
4941
                                    $answerCorrect,
4942
                                    $exeId,
4943
                                    $questionId,
4944
                                    '',
4945
                                    $results_disabled,
4946
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4947
                                    $this->export
4948
                                );
4949
                            }
4950
                            break;
4951
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4952
                            if ($answerId == 1) {
4953
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4954
                                    $this,
4955
                                    $feedback_type,
4956
                                    $answerType,
4957
                                    $studentChoice,
4958
                                    $answer,
4959
                                    $answerComment,
4960
                                    $answerCorrect,
4961
                                    $exeId,
4962
                                    $questionId,
4963
                                    $answerId,
4964
                                    $results_disabled,
4965
                                    $showTotalScoreAndUserChoicesInLastAttempt
4966
                                );
4967
                            } else {
4968
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4969
                                    $this,
4970
                                    $feedback_type,
4971
                                    $answerType,
4972
                                    $studentChoice,
4973
                                    $answer,
4974
                                    $answerComment,
4975
                                    $answerCorrect,
4976
                                    $exeId,
4977
                                    $questionId,
4978
                                    '',
4979
                                    $results_disabled,
4980
                                    $showTotalScoreAndUserChoicesInLastAttempt
4981
                                );
4982
                            }
4983
                            break;
4984
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4985
                            if ($answerId == 1) {
4986
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4987
                                    $this,
4988
                                    $feedback_type,
4989
                                    $answerType,
4990
                                    $studentChoice,
4991
                                    $answer,
4992
                                    $answerComment,
4993
                                    $answerCorrect,
4994
                                    $exeId,
4995
                                    $questionId,
4996
                                    $answerId,
4997
                                    $results_disabled,
4998
                                    $showTotalScoreAndUserChoicesInLastAttempt
4999
                                );
5000
                            } else {
5001
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5002
                                    $this,
5003
                                    $feedback_type,
5004
                                    $answerType,
5005
                                    $studentChoice,
5006
                                    $answer,
5007
                                    $answerComment,
5008
                                    $answerCorrect,
5009
                                    $exeId,
5010
                                    $questionId,
5011
                                    '',
5012
                                    $results_disabled,
5013
                                    $showTotalScoreAndUserChoicesInLastAttempt
5014
                                );
5015
                            }
5016
                            break;
5017
                        case FILL_IN_BLANKS:
5018
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5019
                                $feedback_type,
5020
                                $answer,
5021
                                $exeId,
5022
                                $questionId,
5023
                                $results_disabled,
5024
                                $str,
5025
                                $showTotalScoreAndUserChoicesInLastAttempt
5026
                            );
5027
                            break;
5028
                        case CALCULATED_ANSWER:
5029
                            ExerciseShowFunctions::display_calculated_answer(
5030
                                $this,
5031
                                $feedback_type,
5032
                                $answer,
5033
                                $exeId,
5034
                                $questionId,
5035
                                $results_disabled,
5036
                                '',
5037
                                $showTotalScoreAndUserChoicesInLastAttempt
5038
                            );
5039
                            break;
5040
                        case FREE_ANSWER:
5041
                            echo ExerciseShowFunctions::display_free_answer(
5042
                                $feedback_type,
5043
                                $choice,
5044
                                $exeId,
5045
                                $questionId,
5046
                                $questionScore,
5047
                                $results_disabled
5048
                            );
5049
                            break;
5050
                        case ORAL_EXPRESSION:
5051
                            echo '<tr>
5052
                                <td valign="top">'.
5053
                                ExerciseShowFunctions::display_oral_expression_answer(
5054
                                    $feedback_type,
5055
                                    $choice,
5056
                                    $exeId,
5057
                                    $questionId,
5058
                                    $objQuestionTmp->getFileUrl(),
5059
                                    $results_disabled,
5060
                                    $questionScore
5061
                                ).'</td>
5062
                                </tr>
5063
                                </table>';
5064
                            break;
5065
                        case HOT_SPOT:
5066
                            ExerciseShowFunctions::display_hotspot_answer(
5067
                                $feedback_type,
5068
                                $answerId,
5069
                                $answer,
5070
                                $studentChoice,
5071
                                $answerComment,
5072
                                $results_disabled,
5073
                                $answerId,
5074
                                $showTotalScoreAndUserChoicesInLastAttempt
5075
                            );
5076
                            break;
5077
                        case HOT_SPOT_DELINEATION:
5078
                            $user_answer = $user_array;
5079
                            if ($next) {
5080
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
5081
                                // Save into db
5082
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
5083
                                 * hotspot_user_id,
5084
                                 *  hotspot_course_code,
5085
                                 *  hotspot_exe_id,
5086
                                 *  hotspot_question_id,
5087
                                 *  hotspot_answer_id,
5088
                                 *  hotspot_correct,
5089
                                 *  hotspot_coordinate
5090
                                 *  )
5091
                                VALUES (
5092
                                 * '".Database::escape_string($_user['user_id'])."',
5093
                                 *  '".Database::escape_string($_course['id'])."',
5094
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
5095
                                 *  '".Database::escape_string($answerId)."',
5096
                                 *  '".Database::escape_string($studentChoice)."',
5097
                                 *  '".Database::escape_string($user_array)."')";
5098
                                $result = Database::query($sql,__FILE__,__LINE__);
5099
                                 */
5100
                                $user_answer = $user_array;
5101
                                // we compare only the delineation not the other points
5102
                                $answer_question = $_SESSION['hotspot_coord'][1];
5103
                                $answerDestination = $_SESSION['hotspot_dest'][1];
5104
5105
                                // calculating the area
5106
                                $poly_user = convert_coordinates($user_answer, '/');
5107
                                $poly_answer = convert_coordinates($answer_question, '|');
5108
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5109
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5110
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5111
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5112
5113
                                $overlap = $poly_results['both'];
5114
                                $poly_answer_area = $poly_results['s1'];
5115
                                $poly_user_area = $poly_results['s2'];
5116
                                $missing = $poly_results['s1Only'];
5117
                                $excess = $poly_results['s2Only'];
5118
                                if ($debug > 0) {
5119
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5120
                                }
5121
                                if ($overlap < 1) {
5122
                                    //shortcut to avoid complicated calculations
5123
                                    $final_overlap = 0;
5124
                                    $final_missing = 100;
5125
                                    $final_excess = 100;
5126
                                } else {
5127
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
5128
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5129
                                    if ($debug > 1) {
5130
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5131
                                    }
5132
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
5133
                                    $final_missing = 100 - $final_overlap;
5134
                                    if ($debug > 1) {
5135
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5136
                                    }
5137
                                    // 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
5138
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5139
                                    if ($debug > 1) {
5140
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5141
                                    }
5142
                                }
5143
5144
                                // Checking the destination parameters parsing the "@@"
5145
                                $destination_items = explode('@@', $answerDestination);
5146
                                $threadhold_total = $destination_items[0];
5147
                                $threadhold_items = explode(';', $threadhold_total);
5148
                                $threadhold1 = $threadhold_items[0]; // overlap
5149
                                $threadhold2 = $threadhold_items[1]; // excess
5150
                                $threadhold3 = $threadhold_items[2]; //missing
5151
                                // if is delineation
5152
                                if ($answerId === 1) {
5153
                                    //setting colors
5154
                                    if ($final_overlap >= $threadhold1) {
5155
                                        $overlap_color = true; //echo 'a';
5156
                                    }
5157
                                    if ($final_excess <= $threadhold2) {
5158
                                        $excess_color = true; //echo 'b';
5159
                                    }
5160
                                    if ($final_missing <= $threadhold3) {
5161
                                        $missing_color = true; //echo 'c';
5162
                                    }
5163
5164
                                    // if pass
5165
                                    if ($final_overlap >= $threadhold1 &&
5166
                                        $final_missing <= $threadhold3 &&
5167
                                        $final_excess <= $threadhold2
5168
                                    ) {
5169
                                        $next = 1; //go to the oars
5170
                                        $result_comment = get_lang('Acceptable');
5171
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5172
                                    } else {
5173
                                        $next = 0;
5174
                                        $result_comment = get_lang('Unacceptable');
5175
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5176
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5177
                                        //checking the destination parameters parsing the "@@"
5178
                                        $destination_items = explode('@@', $answerDestination);
5179
                                    }
5180
                                } elseif ($answerId > 1) {
5181
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5182
                                        if ($debug > 0) {
5183
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5184
                                        }
5185
                                        //type no error shouldn't be treated
5186
                                        $next = 1;
5187
                                        continue;
5188
                                    }
5189
                                    if ($debug > 0) {
5190
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5191
                                    }
5192
                                    //check the intersection between the oar and the user
5193
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
5194
                                    //echo 'official';print_r($x_list);print_r($y_list);
5195
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
5196
                                    $inter = $result['success'];
5197
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5198
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5199
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5200
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5201
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5202
5203
                                    if ($overlap == false) {
5204
                                        //all good, no overlap
5205
                                        $next = 1;
5206
                                        continue;
5207
                                    } else {
5208
                                        if ($debug > 0) {
5209
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5210
                                        }
5211
                                        $organs_at_risk_hit++;
5212
                                        //show the feedback
5213
                                        $next = 0;
5214
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5215
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5216
5217
                                        $destination_items = explode('@@', $answerDestination);
5218
                                        $try_hotspot = $destination_items[1];
5219
                                        $lp_hotspot = $destination_items[2];
5220
                                        $select_question_hotspot = $destination_items[3];
5221
                                        $url_hotspot = $destination_items[4];
5222
                                    }
5223
                                }
5224
                            } else {
5225
                                // the first delineation feedback
5226
                                if ($debug > 0) {
5227
                                    error_log(__LINE__.' first', 0);
5228
                                }
5229
                            }
5230
                            break;
5231
                        case HOT_SPOT_ORDER:
5232
                            ExerciseShowFunctions::display_hotspot_order_answer(
5233
                                $feedback_type,
5234
                                $answerId,
5235
                                $answer,
5236
                                $studentChoice,
5237
                                $answerComment
5238
                            );
5239
                            break;
5240
                        case DRAGGABLE:
5241
                        case MATCHING_DRAGGABLE:
5242
                        case MATCHING:
5243
                            echo '<tr>';
5244
                            echo Display::tag('td', $answerMatching[$answerId]);
5245
                            echo Display::tag(
5246
                                'td',
5247
                                "$user_answer / ".Display::tag(
5248
                                    'strong',
5249
                                    $answerMatching[$answerCorrect],
5250
                                    ['style' => 'color: #008000; font-weight: bold;']
5251
                                )
5252
                            );
5253
                            echo '</tr>';
5254
5255
                            break;
5256
                        case ANNOTATION:
5257
                            ExerciseShowFunctions::displayAnnotationAnswer(
5258
                                $feedback_type,
5259
                                $exeId,
5260
                                $questionId,
5261
                                $questionScore,
5262
                                $results_disabled
5263
                            );
5264
                            break;
5265
                    }
5266
                }
5267
            }
5268
            if ($debug) {
5269
                error_log(' ------ ');
5270
            }
5271
        } // end for that loops over all answers of the current question
5272
5273
        if ($debug) {
5274
            error_log('-- end answer loop --');
5275
        }
5276
5277
        $final_answer = true;
5278
5279
        foreach ($real_answers as $my_answer) {
5280
            if (!$my_answer) {
5281
                $final_answer = false;
5282
            }
5283
        }
5284
5285
        //we add the total score after dealing with the answers
5286
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5287
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5288
        ) {
5289
            if ($final_answer) {
5290
                //getting only the first score where we save the weight of all the question
5291
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5292
                $questionScore += $answerWeighting;
5293
                $totalScore += $answerWeighting;
5294
            }
5295
        }
5296
5297
        //Fixes multiple answer question in order to be exact
5298
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5299
        /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
5300
             $diff = @array_diff($answer_correct_array, $real_answers);
5301
5302
             // All good answers or nothing works like exact
5303
5304
             $counter = 1;
5305
             $correct_answer = true;
5306
             foreach ($real_answers as $my_answer) {
5307
                 if ($debug)
5308
                     error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
5309
                 if ($my_answer != $answer_correct_array[$counter]) {
5310
                     $correct_answer = false;
5311
                     break;
5312
                 }
5313
                 $counter++;
5314
             }
5315
5316
             if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
5317
             if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
5318
             if ($debug) error_log(" correct_answer: ".$correct_answer);
5319
5320
             if ($correct_answer == false) {
5321
                 $questionScore = 0;
5322
             }
5323
5324
             // This makes the result non exact
5325
             if (!empty($diff)) {
5326
                 $questionScore = 0;
5327
             }
5328
         }*/
5329
5330
        $extra_data = [
5331
            'final_overlap' => $final_overlap,
5332
            'final_missing' => $final_missing,
5333
            'final_excess' => $final_excess,
5334
            'overlap_color' => $overlap_color,
5335
            'missing_color' => $missing_color,
5336
            'excess_color' => $excess_color,
5337
            'threadhold1' => $threadhold1,
5338
            'threadhold2' => $threadhold2,
5339
            'threadhold3' => $threadhold3,
5340
        ];
5341
        if ($from == 'exercise_result') {
5342
            // if answer is hotspot. To the difference of exercise_show.php,
5343
            //  we use the results from the session (from_db=0)
5344
            // TODO Change this, because it is wrong to show the user
5345
            //  some results that haven't been stored in the database yet
5346
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5347
                if ($debug) {
5348
                    error_log('$from AND this is a hotspot kind of question ');
5349
                }
5350
                $my_exe_id = 0;
5351
                $from_database = 0;
5352
                if ($answerType == HOT_SPOT_DELINEATION) {
5353
                    if (0) {
5354
                        if ($overlap_color) {
5355
                            $overlap_color = 'green';
5356
                        } else {
5357
                            $overlap_color = 'red';
5358
                        }
5359
                        if ($missing_color) {
5360
                            $missing_color = 'green';
5361
                        } else {
5362
                            $missing_color = 'red';
5363
                        }
5364
                        if ($excess_color) {
5365
                            $excess_color = 'green';
5366
                        } else {
5367
                            $excess_color = 'red';
5368
                        }
5369
                        if (!is_numeric($final_overlap)) {
5370
                            $final_overlap = 0;
5371
                        }
5372
                        if (!is_numeric($final_missing)) {
5373
                            $final_missing = 0;
5374
                        }
5375
                        if (!is_numeric($final_excess)) {
5376
                            $final_excess = 0;
5377
                        }
5378
5379
                        if ($final_overlap > 100) {
5380
                            $final_overlap = 100;
5381
                        }
5382
5383
                        $table_resume = '<table class="data_table">
5384
                                <tr class="row_odd" >
5385
                                    <td></td>
5386
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5387
                                    <td><b>'.get_lang('YourAnswer').'</b></td>
5388
                                </tr>
5389
                                <tr class="row_even">
5390
                                    <td><b>'.get_lang('Overlap').'</b></td>
5391
                                    <td>'.get_lang('Min').' '.$threadhold1.'</td>
5392
                                    <td><div style="color:'.$overlap_color.'">'
5393
                                        .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5394
                                </tr>
5395
                                <tr>
5396
                                    <td><b>'.get_lang('Excess').'</b></td>
5397
                                    <td>'.get_lang('Max').' '.$threadhold2.'</td>
5398
                                    <td><div style="color:'.$excess_color.'">'
5399
                                        .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5400
                                </tr>
5401
                                <tr class="row_even">
5402
                                    <td><b>'.get_lang('Missing').'</b></td>
5403
                                    <td>'.get_lang('Max').' '.$threadhold3.'</td>
5404
                                    <td><div style="color:'.$missing_color.'">'
5405
                                        .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5406
                                </tr>
5407
                            </table>';
5408
                        if ($next == 0) {
5409
                            $try = $try_hotspot;
5410
                            $lp = $lp_hotspot;
5411
                            $destinationid = $select_question_hotspot;
5412
                            $url = $url_hotspot;
5413
                        } else {
5414
                            //show if no error
5415
                            //echo 'no error';
5416
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5417
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5418
                        }
5419
5420
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5421
                            <p style="text-align:center">';
5422
5423
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5424
                        $message .= $table_resume;
5425
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5426
                        if ($organs_at_risk_hit > 0) {
5427
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5428
                        }
5429
                        $message .= '<p>'.$comment.'</p>';
5430
                        echo $message;
5431
                    } else {
5432
                        echo $hotspot_delineation_result[0]; //prints message
5433
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5434
                    }
5435
5436
                    //save the score attempts
5437
                    if (1) {
5438
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5439
                        $final_answer = $hotspot_delineation_result[1];
5440
                        if ($final_answer == 0) {
5441
                            $questionScore = 0;
5442
                        }
5443
                        // we always insert the answer_id 1 = delineation
5444
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5445
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5446
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5447
                        Event::saveExerciseAttemptHotspot(
5448
                            $exeId,
5449
                            $quesId,
5450
                            1,
5451
                            $hotspotValue,
5452
                            $exerciseResultCoordinates[$quesId]
5453
                        );
5454
                    } else {
5455
                        if ($final_answer == 0) {
5456
                            $questionScore = 0;
5457
                            $answer = 0;
5458
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5459
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5460
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5461
                                    Event::saveExerciseAttemptHotspot(
5462
                                        $exeId,
5463
                                        $quesId,
5464
                                        $idx,
5465
                                        0,
5466
                                        $val
5467
                                    );
5468
                                }
5469
                            }
5470
                        } else {
5471
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5472
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5473
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5474
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5475
                                    Event::saveExerciseAttemptHotspot(
5476
                                        $exeId,
5477
                                        $quesId,
5478
                                        $idx,
5479
                                        $hotspotValue,
5480
                                        $val
5481
                                    );
5482
                                }
5483
                            }
5484
                        }
5485
                    }
5486
                    $my_exe_id = $exeId;
5487
                }
5488
            }
5489
5490
            $relPath = api_get_path(WEB_CODE_PATH);
5491
5492
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5493
                // We made an extra table for the answers
5494
                if ($show_result) {
5495
                    echo '</table></td></tr>';
5496
                    echo "
5497
                        <tr>
5498
                            <td colspan=\"2\">
5499
                                <p><em>".get_lang('HotSpot')."</em></p>
5500
                                <div id=\"hotspot-solution-$questionId\"></div>
5501
                                <script>
5502
                                    $(document).on('ready', function () {
5503
                                        new HotspotQuestion({
5504
                                            questionId: $questionId,
5505
                                            exerciseId: {$this->id},
5506
                                            exeId: $exeId,
5507
                                            selector: '#hotspot-solution-$questionId',
5508
                                            for: 'solution',
5509
                                            relPath: '$relPath'
5510
                                        });
5511
                                    });
5512
                                </script>
5513
                            </td>
5514
                        </tr>
5515
                    ";
5516
                }
5517
            } elseif ($answerType == ANNOTATION) {
5518
                if ($show_result) {
5519
                    echo '
5520
                        <p><em>'.get_lang('Annotation').'</em></p>
5521
                        <div id="annotation-canvas-'.$questionId.'"></div>
5522
                        <script>
5523
                            AnnotationQuestion({
5524
                                questionId: parseInt('.$questionId.'),
5525
                                exerciseId: parseInt('.$exeId.'),
5526
                                relPath: \''.$relPath.'\',
5527
                                courseId: parseInt('.$course_id.')
5528
                            });
5529
                        </script>
5530
                    ';
5531
                }
5532
            }
5533
5534
            if ($show_result && $answerType != ANNOTATION) {
5535
                echo '</table>';
5536
            }
5537
        }
5538
        unset($objAnswerTmp);
5539
5540
        $totalWeighting += $questionWeighting;
5541
        // Store results directly in the database
5542
        // For all in one page exercises, the results will be
5543
        // stored by exercise_results.php (using the session)
5544
        if ($saved_results) {
5545
            if ($debug) {
5546
                error_log("Save question results $saved_results");
5547
            }
5548
            if ($debug) {
5549
                error_log(print_r($choice, 1));
5550
            }
5551
5552
            if (empty($choice)) {
5553
                $choice = 0;
5554
            }
5555
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5556
                if ($choice != 0) {
5557
                    $reply = array_keys($choice);
5558
                    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...
5559
                        $ans = $reply[$i];
5560
                        Event::saveQuestionAttempt(
5561
                            $questionScore,
5562
                            $ans.':'.$choice[$ans],
5563
                            $quesId,
5564
                            $exeId,
5565
                            $i,
5566
                            $this->id
5567
                        );
5568
                        if ($debug) {
5569
                            error_log('result =>'.$questionScore.' '.$ans.':'.$choice[$ans]);
5570
                        }
5571
                    }
5572
                } else {
5573
                    Event::saveQuestionAttempt(
5574
                        $questionScore,
5575
                        0,
5576
                        $quesId,
5577
                        $exeId,
5578
                        0,
5579
                        $this->id
5580
                    );
5581
                }
5582
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5583
                if ($choice != 0) {
5584
                    $reply = array_keys($choice);
5585
5586
                    if ($debug) {
5587
                        error_log("reply ".print_r($reply, 1)."");
5588
                    }
5589
                    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...
5590
                        $ans = $reply[$i];
5591
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5592
                    }
5593
                } else {
5594
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5595
                }
5596
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5597
                if ($choice != 0) {
5598
                    $reply = array_keys($choice);
5599
                    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...
5600
                        $ans = $reply[$i];
5601
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5602
                    }
5603
                } else {
5604
                    Event::saveQuestionAttempt(
5605
                        $questionScore,
5606
                        0,
5607
                        $quesId,
5608
                        $exeId,
5609
                        0,
5610
                        $this->id
5611
                    );
5612
                }
5613
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5614
                if (isset($matching)) {
5615
                    foreach ($matching as $j => $val) {
5616
                        Event::saveQuestionAttempt(
5617
                            $questionScore,
5618
                            $val,
5619
                            $quesId,
5620
                            $exeId,
5621
                            $j,
5622
                            $this->id
5623
                        );
5624
                    }
5625
                }
5626
            } elseif ($answerType == FREE_ANSWER) {
5627
                $answer = $choice;
5628
                Event::saveQuestionAttempt(
5629
                    $questionScore,
5630
                    $answer,
5631
                    $quesId,
5632
                    $exeId,
5633
                    0,
5634
                    $this->id
5635
                );
5636
            } elseif ($answerType == ORAL_EXPRESSION) {
5637
                $answer = $choice;
5638
                Event::saveQuestionAttempt(
5639
                    $questionScore,
5640
                    $answer,
5641
                    $quesId,
5642
                    $exeId,
5643
                    0,
5644
                    $this->id,
5645
                    false,
5646
                    $objQuestionTmp->getAbsoluteFilePath()
5647
                );
5648
            } elseif (
5649
                in_array(
5650
                    $answerType,
5651
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
5652
                )
5653
            ) {
5654
                $answer = $choice;
5655
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5656
            //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5657
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5658
                $answer = [];
5659
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5660
                    Database::delete(
5661
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5662
                        [
5663
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5664
                                $exeId,
5665
                                $questionId,
5666
                                api_get_course_int_id(),
5667
                            ],
5668
                        ]
5669
                    );
5670
5671
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5672
                        $answer[] = $val;
5673
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5674
                        Event::saveExerciseAttemptHotspot(
5675
                            $exeId,
5676
                            $quesId,
5677
                            $idx,
5678
                            $hotspotValue,
5679
                            $val,
5680
                            false,
5681
                            $this->id
5682
                        );
5683
                    }
5684
                }
5685
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5686
            } else {
5687
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5688
            }
5689
        }
5690
5691
        if ($propagate_neg == 0 && $questionScore < 0) {
5692
            $questionScore = 0;
5693
        }
5694
5695
        if ($saved_results) {
5696
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5697
            $sql = 'UPDATE '.$table.' SET
5698
                        exe_result = exe_result + '.floatval($questionScore).'
5699
                    WHERE exe_id = '.$exeId;
5700
            Database::query($sql);
5701
        }
5702
5703
        $return = [
5704
            'score' => $questionScore,
5705
            'weight' => $questionWeighting,
5706
            'extra' => $extra_data,
5707
            'open_question' => $arrques,
5708
            'open_answer' => $arrans,
5709
            'answer_type' => $answerType,
5710
            'generated_oral_file' => $generatedFile,
5711
        ];
5712
5713
        return $return;
5714
    }
5715
5716
    /**
5717
     * Sends a notification when a user ends an examn.
5718
     *
5719
     * @param string $type                  'start' or 'end' of an exercise
5720
     * @param array  $question_list_answers
5721
     * @param string $origin
5722
     * @param int    $exe_id
5723
     * @param float  $score
5724
     * @param float  $weight
5725
     *
5726
     * @return bool
5727
     */
5728
    public function send_mail_notification_for_exam(
5729
        $type = 'end',
5730
        $question_list_answers,
5731
        $origin,
5732
        $exe_id,
5733
        $score = null,
5734
        $weight = null
5735
    ) {
5736
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5737
5738
        if (empty($setting) && empty($this->getNotifications())) {
5739
            return false;
5740
        }
5741
5742
        $settingFromExercise = $this->getNotifications();
5743
        if (!empty($settingFromExercise)) {
5744
            $setting = $settingFromExercise;
5745
        }
5746
5747
        // Email configuration settings
5748
        $courseCode = api_get_course_id();
5749
        $courseInfo = api_get_course_info($courseCode);
5750
5751
        if (empty($courseInfo)) {
5752
            return false;
5753
        }
5754
5755
        $sessionId = api_get_session_id();
5756
        $sendStart = false;
5757
        $sendEnd = false;
5758
        $sendEndOpenQuestion = false;
5759
        $sendEndOralQuestion = false;
5760
5761
        foreach ($setting as $option) {
5762
            switch ($option) {
5763
                case 0:
5764
                    return false;
5765
                    break;
5766
                case 1: // End
5767
                    if ($type == 'end') {
5768
                        $sendEnd = true;
5769
                    }
5770
                    break;
5771
                case 2: // start
5772
                    if ($type == 'start') {
5773
                        $sendStart = true;
5774
                    }
5775
                    break;
5776
                case 3: // end + open
5777
                    if ($type == 'end') {
5778
                        $sendEndOpenQuestion = true;
5779
                    }
5780
                    break;
5781
                case 4: // end + oral
5782
                    if ($type == 'end') {
5783
                        $sendEndOralQuestion = true;
5784
                    }
5785
                    break;
5786
            }
5787
        }
5788
5789
        $user_info = api_get_user_info(api_get_user_id());
5790
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.api_get_cidreq().'&id='.$exe_id.'&action=qualify';
5791
5792
        if (!empty($sessionId)) {
5793
            $addGeneralCoach = true;
5794
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5795
            if ($setting === true) {
5796
                $addGeneralCoach = false;
5797
            }
5798
            $teachers = CourseManager::get_coach_list_from_course_code(
5799
                $courseCode,
5800
                $sessionId,
5801
                $addGeneralCoach
5802
            );
5803
        } else {
5804
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5805
        }
5806
5807
        if ($sendEndOpenQuestion) {
5808
            $this->sendNotificationForOpenQuestions(
5809
                $question_list_answers,
5810
                $origin,
5811
                $exe_id,
5812
                $user_info,
5813
                $url,
5814
                $teachers
5815
            );
5816
        }
5817
5818
        if ($sendEndOralQuestion) {
5819
            $this->sendNotificationForOralQuestions(
5820
                $question_list_answers,
5821
                $origin,
5822
                $exe_id,
5823
                $user_info,
5824
                $url,
5825
                $teachers
5826
            );
5827
        }
5828
5829
        if (!$sendEnd && !$sendStart) {
5830
            return false;
5831
        }
5832
5833
        $scoreLabel = '';
5834
        if ($sendEnd &&
5835
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
5836
        ) {
5837
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
5838
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
5839
            $scoreLabel = "<tr>
5840
                            <td>".get_lang('Score')."</td>
5841
                            <td>&nbsp;$scoreLabel</td>
5842
                        </tr>";
5843
        }
5844
5845
        if ($sendEnd) {
5846
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5847
        } else {
5848
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5849
        }
5850
5851
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5852
                    <table>
5853
                        <tr>
5854
                            <td>'.get_lang('CourseName').'</td>
5855
                            <td>#course#</td>
5856
                        </tr>
5857
                        <tr>
5858
                            <td>'.get_lang('Exercise').'</td>
5859
                            <td>&nbsp;#exercise#</td>
5860
                        </tr>
5861
                        <tr>
5862
                            <td>'.get_lang('StudentName').'</td>
5863
                            <td>&nbsp;#student_complete_name#</td>
5864
                        </tr>
5865
                        <tr>
5866
                            <td>'.get_lang('StudentEmail').'</td>
5867
                            <td>&nbsp;#email#</td>
5868
                        </tr>
5869
                        '.$scoreLabel.'
5870
                    </table>';
5871
5872
        $variables = [
5873
            '#email#' => $user_info['email'],
5874
            '#exercise#' => $this->exercise,
5875
            '#student_complete_name#' => $user_info['complete_name'],
5876
            '#course#' => $courseInfo['title'],
5877
        ];
5878
        if ($origin != 'learnpath' && $sendEnd) {
5879
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5880
            $variables['#url#'] = $url;
5881
        }
5882
5883
        $mail_content = str_replace(array_keys($variables), array_values($variables), $msg);
5884
5885
        if ($sendEnd) {
5886
            $subject = get_lang('ExerciseAttempted');
5887
        } else {
5888
            $subject = get_lang('StudentStartExercise');
5889
        }
5890
5891
        if (!empty($teachers)) {
5892
            foreach ($teachers as $user_id => $teacher_data) {
5893
                MessageManager::send_message_simple(
5894
                    $user_id,
5895
                    $subject,
5896
                    $mail_content
5897
                );
5898
            }
5899
        }
5900
    }
5901
5902
    /**
5903
     * @param array $user_data         result of api_get_user_info()
5904
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
5905
     *
5906
     * @return string
5907
     */
5908
    public function showExerciseResultHeader(
5909
        $user_data,
5910
        $trackExerciseInfo
5911
    ) {
5912
        $start_date = null;
5913
        if (isset($trackExerciseInfo['start_date'])) {
5914
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
5915
        }
5916
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
5917
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
5918
5919
        $array = [];
5920
        if (!empty($user_data)) {
5921
            $array[] = [
5922
                'title' => get_lang('Name'),
5923
                'content' => $user_data['complete_name'],
5924
            ];
5925
            $array[] = [
5926
                'title' => get_lang('Username'),
5927
                'content' => $user_data['username'],
5928
            ];
5929
            if (!empty($user_data['official_code'])) {
5930
                $array[] = [
5931
                    'title' => get_lang('OfficialCode'),
5932
                    'content' => $user_data['official_code'],
5933
                ];
5934
            }
5935
        }
5936
        // Description can be very long and is generally meant to explain
5937
        //   rules *before* the exam. Leaving here to make display easier if
5938
        //   necessary
5939
        /*
5940
        if (!empty($this->description)) {
5941
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5942
        }
5943
        */
5944
        if (!empty($start_date)) {
5945
            $array[] = ['title' => get_lang('StartDate'), 'content' => $start_date];
5946
        }
5947
5948
        if (!empty($duration)) {
5949
            $array[] = ['title' => get_lang('Duration'), 'content' => $duration];
5950
        }
5951
5952
        if (!empty($ip)) {
5953
            $array[] = ['title' => get_lang('IP'), 'content' => $ip];
5954
        }
5955
5956
        $icon = Display::return_icon(
5957
            'test-quiz.png',
5958
            get_lang('Result'),
5959
            null,
5960
            ICON_SIZE_MEDIUM
5961
        );
5962
5963
        $html = '<div class="question-result">';
5964
        if (api_get_configuration_value('save_titles_as_html')) {
5965
            $html .= $this->get_formated_title();
5966
            $html .= Display::page_header(get_lang('Result'));
5967
        } else {
5968
            $html .= Display::page_header(
5969
                $icon.PHP_EOL.$this->exercise.' : '.get_lang('Result')
5970
            );
5971
        }
5972
5973
        $hide = api_get_configuration_value('hide_user_info_in_quiz_result');
5974
5975
        if ($hide === false) {
5976
            $html .= Display::description($array);
5977
        }
5978
5979
        $html .= "</div>";
5980
5981
        return $html;
5982
    }
5983
5984
    /**
5985
     * Returns the exercise result.
5986
     *
5987
     * @param 	int		attempt id
5988
     *
5989
     * @return float exercise result
5990
     */
5991
    public function get_exercise_result($exe_id)
5992
    {
5993
        $result = [];
5994
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5995
5996
        if (!empty($track_exercise_info)) {
5997
            $totalScore = 0;
5998
            $objExercise = new Exercise();
5999
            $objExercise->read($track_exercise_info['exe_exo_id']);
6000
            if (!empty($track_exercise_info['data_tracking'])) {
6001
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6002
            }
6003
            foreach ($question_list as $questionId) {
6004
                $question_result = $objExercise->manage_answer(
6005
                    $exe_id,
6006
                    $questionId,
6007
                    '',
6008
                    'exercise_show',
6009
                    [],
6010
                    false,
6011
                    true,
6012
                    false,
6013
                    $objExercise->selectPropagateNeg()
6014
                );
6015
                $totalScore += $question_result['score'];
6016
            }
6017
6018
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
6019
                $totalScore = 0;
6020
            }
6021
            $result = [
6022
                'score' => $totalScore,
6023
                'weight' => $track_exercise_info['exe_weighting'],
6024
            ];
6025
        }
6026
6027
        return $result;
6028
    }
6029
6030
    /**
6031
     * Checks if the exercise is visible due a lot of conditions
6032
     * visibility, time limits, student attempts
6033
     * Return associative array
6034
     * value : true if exercise visible
6035
     * message : HTML formatted message
6036
     * rawMessage : text message.
6037
     *
6038
     * @param int  $lpId
6039
     * @param int  $lpItemId
6040
     * @param int  $lpItemViewId
6041
     * @param bool $filterByAdmin
6042
     *
6043
     * @return array
6044
     */
6045
    public function is_visible(
6046
        $lpId = 0,
6047
        $lpItemId = 0,
6048
        $lpItemViewId = 0,
6049
        $filterByAdmin = true
6050
    ) {
6051
        // 1. By default the exercise is visible
6052
        $isVisible = true;
6053
        $message = null;
6054
6055
        // 1.1 Admins and teachers can access to the exercise
6056
        if ($filterByAdmin) {
6057
            if (api_is_platform_admin() || api_is_course_admin()) {
6058
                return ['value' => true, 'message' => ''];
6059
            }
6060
        }
6061
6062
        // Deleted exercise.
6063
        if ($this->active == -1) {
6064
            return [
6065
                'value' => false,
6066
                'message' => Display::return_message(
6067
                    get_lang('ExerciseNotFound'),
6068
                    'warning',
6069
                    false
6070
                ),
6071
                'rawMessage' => get_lang('ExerciseNotFound'),
6072
            ];
6073
        }
6074
6075
        // Checking visibility in the item_property table.
6076
        $visibility = api_get_item_visibility(
6077
            api_get_course_info(),
6078
            TOOL_QUIZ,
6079
            $this->id,
6080
            api_get_session_id()
6081
        );
6082
6083
        if ($visibility == 0 || $visibility == 2) {
6084
            $this->active = 0;
6085
        }
6086
6087
        // 2. If the exercise is not active.
6088
        if (empty($lpId)) {
6089
            // 2.1 LP is OFF
6090
            if ($this->active == 0) {
6091
                return [
6092
                    'value' => false,
6093
                    'message' => Display::return_message(
6094
                        get_lang('ExerciseNotFound'),
6095
                        'warning',
6096
                        false
6097
                    ),
6098
                    'rawMessage' => get_lang('ExerciseNotFound'),
6099
                ];
6100
            }
6101
        } else {
6102
            // 2.1 LP is loaded
6103
            if ($this->active == 0 &&
6104
                !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())
6105
            ) {
6106
                return [
6107
                    'value' => false,
6108
                    'message' => Display::return_message(
6109
                        get_lang('ExerciseNotFound'),
6110
                        'warning',
6111
                        false
6112
                    ),
6113
                    'rawMessage' => get_lang('ExerciseNotFound'),
6114
                ];
6115
            }
6116
        }
6117
6118
        // 3. We check if the time limits are on
6119
        $limitTimeExists = false;
6120
        if (!empty($this->start_time) || !empty($this->end_time)) {
6121
            $limitTimeExists = true;
6122
        }
6123
6124
        if ($limitTimeExists) {
6125
            $timeNow = time();
6126
            $existsStartDate = false;
6127
            $nowIsAfterStartDate = true;
6128
            $existsEndDate = false;
6129
            $nowIsBeforeEndDate = true;
6130
6131
            if (!empty($this->start_time)) {
6132
                $existsStartDate = true;
6133
            }
6134
6135
            if (!empty($this->end_time)) {
6136
                $existsEndDate = true;
6137
            }
6138
6139
            // check if we are before-or-after end-or-start date
6140
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6141
                $nowIsAfterStartDate = false;
6142
            }
6143
6144
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6145
                $nowIsBeforeEndDate = false;
6146
            }
6147
6148
            // lets check all cases
6149
            if ($existsStartDate && !$existsEndDate) {
6150
                // exists start date and dont exists end date
6151
                if ($nowIsAfterStartDate) {
6152
                    // after start date, no end date
6153
                    $isVisible = true;
6154
                    $message = sprintf(
6155
                        get_lang('ExerciseAvailableSinceX'),
6156
                        api_convert_and_format_date($this->start_time)
6157
                    );
6158
                } else {
6159
                    // before start date, no end date
6160
                    $isVisible = false;
6161
                    $message = sprintf(
6162
                        get_lang('ExerciseAvailableFromX'),
6163
                        api_convert_and_format_date($this->start_time)
6164
                    );
6165
                }
6166
            } elseif (!$existsStartDate && $existsEndDate) {
6167
                // doesnt exist start date, exists end date
6168
                if ($nowIsBeforeEndDate) {
6169
                    // before end date, no start date
6170
                    $isVisible = true;
6171
                    $message = sprintf(
6172
                        get_lang('ExerciseAvailableUntilX'),
6173
                        api_convert_and_format_date($this->end_time)
6174
                    );
6175
                } else {
6176
                    // after end date, no start date
6177
                    $isVisible = false;
6178
                    $message = sprintf(
6179
                        get_lang('ExerciseAvailableUntilX'),
6180
                        api_convert_and_format_date($this->end_time)
6181
                    );
6182
                }
6183
            } elseif ($existsStartDate && $existsEndDate) {
6184
                // exists start date and end date
6185
                if ($nowIsAfterStartDate) {
6186
                    if ($nowIsBeforeEndDate) {
6187
                        // after start date and before end date
6188
                        $isVisible = true;
6189
                        $message = sprintf(
6190
                            get_lang('ExerciseIsActivatedFromXToY'),
6191
                            api_convert_and_format_date($this->start_time),
6192
                            api_convert_and_format_date($this->end_time)
6193
                        );
6194
                    } else {
6195
                        // after start date and after end date
6196
                        $isVisible = false;
6197
                        $message = sprintf(
6198
                            get_lang('ExerciseWasActivatedFromXToY'),
6199
                            api_convert_and_format_date($this->start_time),
6200
                            api_convert_and_format_date($this->end_time)
6201
                        );
6202
                    }
6203
                } else {
6204
                    if ($nowIsBeforeEndDate) {
6205
                        // before start date and before end date
6206
                        $isVisible = false;
6207
                        $message = sprintf(
6208
                            get_lang('ExerciseWillBeActivatedFromXToY'),
6209
                            api_convert_and_format_date($this->start_time),
6210
                            api_convert_and_format_date($this->end_time)
6211
                        );
6212
                    }
6213
                    // case before start date and after end date is impossible
6214
                }
6215
            } elseif (!$existsStartDate && !$existsEndDate) {
6216
                // doesnt exist start date nor end date
6217
                $isVisible = true;
6218
                $message = '';
6219
            }
6220
        }
6221
6222
        // 4. We check if the student have attempts
6223
        $exerciseAttempts = $this->selectAttempts();
6224
6225
        if ($isVisible) {
6226
            if ($exerciseAttempts > 0) {
6227
                $attemptCount = Event::get_attempt_count_not_finished(
6228
                    api_get_user_id(),
6229
                    $this->id,
6230
                    $lpId,
6231
                    $lpItemId,
6232
                    $lpItemViewId
6233
                );
6234
6235
                if ($attemptCount >= $exerciseAttempts) {
6236
                    $message = sprintf(
6237
                        get_lang('ReachedMaxAttempts'),
6238
                        $this->name,
6239
                        $exerciseAttempts
6240
                    );
6241
                    $isVisible = false;
6242
                }
6243
            }
6244
        }
6245
6246
        $rawMessage = '';
6247
        if (!empty($message)) {
6248
            $rawMessage = $message;
6249
            $message = Display::return_message($message, 'warning', false);
6250
        }
6251
6252
        return [
6253
            'value' => $isVisible,
6254
            'message' => $message,
6255
            'rawMessage' => $rawMessage,
6256
        ];
6257
    }
6258
6259
    public function added_in_lp()
6260
    {
6261
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6262
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6263
                WHERE 
6264
                    c_id = {$this->course_id} AND 
6265
                    item_type = '".TOOL_QUIZ."' AND 
6266
                    path = '{$this->id}'";
6267
        $result = Database::query($sql);
6268
        if (Database::num_rows($result) > 0) {
6269
            return true;
6270
        }
6271
6272
        return false;
6273
    }
6274
6275
    /**
6276
     * Returns an array with this form.
6277
     *
6278
     * @example
6279
     * <code>
6280
     * array (size=3)
6281
     * 999 =>
6282
     * array (size=3)
6283
     * 0 => int 3422
6284
     * 1 => int 3423
6285
     * 2 => int 3424
6286
     * 100 =>
6287
     * array (size=2)
6288
     * 0 => int 3469
6289
     * 1 => int 3470
6290
     * 101 =>
6291
     * array (size=1)
6292
     * 0 => int 3482
6293
     * </code>
6294
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6295
     * this case is special because 999 means "no media".
6296
     *
6297
     * @return array
6298
     */
6299
    public function getMediaList()
6300
    {
6301
        return $this->mediaList;
6302
    }
6303
6304
    /**
6305
     * Is media question activated?
6306
     *
6307
     * @return bool
6308
     */
6309
    public function mediaIsActivated()
6310
    {
6311
        $mediaQuestions = $this->getMediaList();
6312
        $active = false;
6313
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6314
            $media_count = count($mediaQuestions);
6315
            if ($media_count > 1) {
6316
                return true;
6317
            } elseif ($media_count == 1) {
6318
                if (isset($mediaQuestions[999])) {
6319
                    return false;
6320
                } else {
6321
                    return true;
6322
                }
6323
            }
6324
        }
6325
6326
        return $active;
6327
    }
6328
6329
    /**
6330
     * Gets question list from the exercise.
6331
     *
6332
     * @return array
6333
     */
6334
    public function getQuestionList()
6335
    {
6336
        return $this->questionList;
6337
    }
6338
6339
    /**
6340
     * Question list with medias compressed like this.
6341
     *
6342
     * @example
6343
     * <code>
6344
     * array(
6345
     *      question_id_1,
6346
     *      question_id_2,
6347
     *      media_id, <- this media id contains question ids
6348
     *      question_id_3,
6349
     * )
6350
     * </code>
6351
     *
6352
     * @return array
6353
     */
6354
    public function getQuestionListWithMediasCompressed()
6355
    {
6356
        return $this->questionList;
6357
    }
6358
6359
    /**
6360
     * Question list with medias uncompressed like this.
6361
     *
6362
     * @example
6363
     * <code>
6364
     * array(
6365
     *      question_id,
6366
     *      question_id,
6367
     *      question_id, <- belongs to a media id
6368
     *      question_id, <- belongs to a media id
6369
     *      question_id,
6370
     * )
6371
     * </code>
6372
     *
6373
     * @return array
6374
     */
6375
    public function getQuestionListWithMediasUncompressed()
6376
    {
6377
        return $this->questionListUncompressed;
6378
    }
6379
6380
    /**
6381
     * Sets the question list when the exercise->read() is executed.
6382
     *
6383
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
6384
     */
6385
    public function setQuestionList($adminView = false)
6386
    {
6387
        // Getting question list.
6388
        $questionList = $this->selectQuestionList(true, $adminView);
6389
        $this->setMediaList($questionList);
6390
        $this->questionList = $this->transformQuestionListWithMedias(
6391
            $questionList,
6392
            false
6393
        );
6394
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6395
            $questionList,
6396
            true
6397
        );
6398
    }
6399
6400
    /**
6401
     * @params array question list
6402
     * @params bool expand or not question list (true show all questions,
6403
     * false show media question id instead of the question ids)
6404
     */
6405
    public function transformQuestionListWithMedias(
6406
        $question_list,
6407
        $expand_media_questions = false
6408
    ) {
6409
        $new_question_list = [];
6410
        if (!empty($question_list)) {
6411
            $media_questions = $this->getMediaList();
6412
6413
            $media_active = $this->mediaIsActivated($media_questions);
6414
6415
            if ($media_active) {
6416
                $counter = 1;
6417
                foreach ($question_list as $question_id) {
6418
                    $add_question = true;
6419
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6420
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6421
                            $add_question = false;
6422
                            if (!in_array($media_id, $new_question_list)) {
6423
                                $new_question_list[$counter] = $media_id;
6424
                                $counter++;
6425
                            }
6426
                            break;
6427
                        }
6428
                    }
6429
                    if ($add_question) {
6430
                        $new_question_list[$counter] = $question_id;
6431
                        $counter++;
6432
                    }
6433
                }
6434
                if ($expand_media_questions) {
6435
                    $media_key_list = array_keys($media_questions);
6436
                    foreach ($new_question_list as &$question_id) {
6437
                        if (in_array($question_id, $media_key_list)) {
6438
                            $question_id = $media_questions[$question_id];
6439
                        }
6440
                    }
6441
                    $new_question_list = array_flatten($new_question_list);
6442
                }
6443
            } else {
6444
                $new_question_list = $question_list;
6445
            }
6446
        }
6447
6448
        return $new_question_list;
6449
    }
6450
6451
    /**
6452
     * Get question list depend on the random settings.
6453
     *
6454
     * @return array
6455
     */
6456
    public function get_validated_question_list()
6457
    {
6458
        $result = [];
6459
        $isRandomByCategory = $this->isRandomByCat();
6460
        if ($isRandomByCategory == 0) {
6461
            if ($this->isRandom()) {
6462
                $result = $this->getRandomList();
6463
            } else {
6464
                $result = $this->selectQuestionList();
6465
            }
6466
        } else {
6467
            if ($this->isRandom()) {
6468
                // USE question categories
6469
                // get questions by category for this exercise
6470
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6471
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6472
                // value is the array of question id of this category
6473
                $questionList = [];
6474
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6475
                $isRandomByCategory = $this->getRandomByCategory();
6476
                // We sort categories based on the term between [] in the head
6477
                // of the category's description
6478
                /* examples of categories :
6479
                 * [biologie] Maitriser les mecanismes de base de la genetique
6480
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6481
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6482
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6483
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6484
                 * [chimie] Connaître les charges des particules
6485
                 * We want that in the order of the groups defined by the term
6486
                 * between brackets at the beginning of the category title
6487
                */
6488
                // If test option is Grouped By Categories
6489
                if ($isRandomByCategory == 2) {
6490
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6491
                }
6492
                while (list($cat_id, $tabquestion) = each($tabCategoryQuestions)) {
6493
                    $number_of_random_question = $this->random;
6494
                    if ($this->random == -1) {
6495
                        $number_of_random_question = count($this->questionList);
6496
                    }
6497
                    $questionList = array_merge(
6498
                        $questionList,
6499
                        TestCategory::getNElementsFromArray(
6500
                            $tabquestion,
6501
                            $number_of_random_question
6502
                        )
6503
                    );
6504
                }
6505
                // shuffle the question list if test is not grouped by categories
6506
                if ($isRandomByCategory == 1) {
6507
                    shuffle($questionList); // or not
6508
                }
6509
                $result = $questionList;
6510
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
6511
                // Problem, random by category has been selected and
6512
                // we have no $this->isRandom number of question selected
6513
                // Should not happened
6514
            }
6515
        }
6516
6517
        return $result;
6518
    }
6519
6520
    public function get_question_list($expand_media_questions = false)
6521
    {
6522
        $question_list = $this->get_validated_question_list();
6523
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6524
6525
        return $question_list;
6526
    }
6527
6528
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6529
    {
6530
        $new_question_list = [];
6531
        if (!empty($question_list)) {
6532
            $media_questions = $this->getMediaList();
6533
            $media_active = $this->mediaIsActivated($media_questions);
6534
6535
            if ($media_active) {
6536
                $counter = 1;
6537
                foreach ($question_list as $question_id) {
6538
                    $add_question = true;
6539
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6540
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6541
                            $add_question = false;
6542
                            if (!in_array($media_id, $new_question_list)) {
6543
                                $new_question_list[$counter] = $media_id;
6544
                                $counter++;
6545
                            }
6546
                            break;
6547
                        }
6548
                    }
6549
                    if ($add_question) {
6550
                        $new_question_list[$counter] = $question_id;
6551
                        $counter++;
6552
                    }
6553
                }
6554
                if ($expand_media_questions) {
6555
                    $media_key_list = array_keys($media_questions);
6556
                    foreach ($new_question_list as &$question_id) {
6557
                        if (in_array($question_id, $media_key_list)) {
6558
                            $question_id = $media_questions[$question_id];
6559
                        }
6560
                    }
6561
                    $new_question_list = array_flatten($new_question_list);
6562
                }
6563
            } else {
6564
                $new_question_list = $question_list;
6565
            }
6566
        }
6567
6568
        return $new_question_list;
6569
    }
6570
6571
    /**
6572
     * @param int $exe_id
6573
     *
6574
     * @return array
6575
     */
6576
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6577
    {
6578
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6579
        $exe_id = (int) $exe_id;
6580
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
6581
        $result = Database::query($sql_track);
6582
        $new_array = [];
6583
        if (Database::num_rows($result) > 0) {
6584
            $new_array = Database::fetch_array($result, 'ASSOC');
6585
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6586
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6587
            $new_array['duration_formatted'] = '';
6588
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
6589
                $time = api_format_time($new_array['exe_duration'], 'js');
6590
                $new_array['duration_formatted'] = $time;
6591
            }
6592
        }
6593
6594
        return $new_array;
6595
    }
6596
6597
    /**
6598
     * @param int $exeId
6599
     *
6600
     * @return bool
6601
     */
6602
    public function removeAllQuestionToRemind($exeId)
6603
    {
6604
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6605
        $exeId = (int) $exeId;
6606
        if (empty($exeId)) {
6607
            return false;
6608
        }
6609
        $sql = "UPDATE $table 
6610
                SET questions_to_check = '' 
6611
                WHERE exe_id = $exeId ";
6612
        Database::query($sql);
6613
6614
        return true;
6615
    }
6616
6617
    /**
6618
     * @param int   $exeId
6619
     * @param array $questionList
6620
     *
6621
     * @return bool
6622
     */
6623
    public function addAllQuestionToRemind($exeId, $questionList = [])
6624
    {
6625
        $exeId = (int) $exeId;
6626
        if (empty($questionList)) {
6627
            return false;
6628
        }
6629
6630
        $questionListToString = implode(',', $questionList);
6631
        $questionListToString = Database::escape_string($questionListToString);
6632
6633
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6634
        $sql = "UPDATE $table 
6635
                SET questions_to_check = '$questionListToString' 
6636
                WHERE exe_id = $exeId";
6637
        Database::query($sql);
6638
6639
        return true;
6640
    }
6641
6642
    /**
6643
     * @param int    $exe_id
6644
     * @param int    $question_id
6645
     * @param string $action
6646
     */
6647
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6648
    {
6649
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6650
        $question_id = (int) $question_id;
6651
        $exe_id = (int) $exe_id;
6652
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6653
        if ($exercise_info) {
6654
            if (empty($exercise_info['questions_to_check'])) {
6655
                if ($action == 'add') {
6656
                    $sql = "UPDATE $track_exercises 
6657
                            SET questions_to_check = '$question_id' 
6658
                            WHERE exe_id = $exe_id ";
6659
                    Database::query($sql);
6660
                }
6661
            } else {
6662
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6663
                $remind_list_string = '';
6664
                if ($action == 'add') {
6665
                    if (!in_array($question_id, $remind_list)) {
6666
                        $newRemindList = [];
6667
                        $remind_list[] = $question_id;
6668
                        $questionListInSession = Session::read('questionList');
6669
                        if (!empty($questionListInSession)) {
6670
                            foreach ($questionListInSession as $originalQuestionId) {
6671
                                if (in_array($originalQuestionId, $remind_list)) {
6672
                                    $newRemindList[] = $originalQuestionId;
6673
                                }
6674
                            }
6675
                        }
6676
                        $remind_list_string = implode(',', $newRemindList);
6677
                    }
6678
                } elseif ($action == 'delete') {
6679
                    if (!empty($remind_list)) {
6680
                        if (in_array($question_id, $remind_list)) {
6681
                            $remind_list = array_flip($remind_list);
6682
                            unset($remind_list[$question_id]);
6683
                            $remind_list = array_flip($remind_list);
6684
6685
                            if (!empty($remind_list)) {
6686
                                sort($remind_list);
6687
                                array_filter($remind_list);
6688
                                $remind_list_string = implode(',', $remind_list);
6689
                            }
6690
                        }
6691
                    }
6692
                }
6693
                $value = Database::escape_string($remind_list_string);
6694
                $sql = "UPDATE $track_exercises 
6695
                        SET questions_to_check = '$value' 
6696
                        WHERE exe_id = $exe_id ";
6697
                Database::query($sql);
6698
            }
6699
        }
6700
    }
6701
6702
    /**
6703
     * @param string $answer
6704
     *
6705
     * @return mixed
6706
     */
6707
    public function fill_in_blank_answer_to_array($answer)
6708
    {
6709
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6710
        $teacher_answer_list = $teacher_answer_list[0];
6711
6712
        return $teacher_answer_list;
6713
    }
6714
6715
    /**
6716
     * @param string $answer
6717
     *
6718
     * @return string
6719
     */
6720
    public function fill_in_blank_answer_to_string($answer)
6721
    {
6722
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6723
        $result = '';
6724
        if (!empty($teacher_answer_list)) {
6725
            $i = 0;
6726
            foreach ($teacher_answer_list as $teacher_item) {
6727
                $value = null;
6728
                //Cleaning student answer list
6729
                $value = strip_tags($teacher_item);
6730
                $value = api_substr($value, 1, api_strlen($value) - 2);
6731
                $value = explode('/', $value);
6732
                if (!empty($value[0])) {
6733
                    $value = trim($value[0]);
6734
                    $value = str_replace('&nbsp;', '', $value);
6735
                    $result .= $value;
6736
                }
6737
            }
6738
        }
6739
6740
        return $result;
6741
    }
6742
6743
    /**
6744
     * @return string
6745
     */
6746
    public function return_time_left_div()
6747
    {
6748
        $html = '<div id="clock_warning" style="display:none">';
6749
        $html .= Display::return_message(
6750
            get_lang('ReachedTimeLimit'),
6751
            'warning'
6752
        );
6753
        $html .= ' ';
6754
        $html .= sprintf(
6755
            get_lang('YouWillBeRedirectedInXSeconds'),
6756
            '<span id="counter_to_redirect" class="red_alert"></span>'
6757
        );
6758
        $html .= '</div>';
6759
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6760
6761
        return $html;
6762
    }
6763
6764
    /**
6765
     * @return int
6766
     */
6767
    public function get_count_question_list()
6768
    {
6769
        // Real question count
6770
        $question_count = 0;
6771
        $question_list = $this->get_question_list();
6772
        if (!empty($question_list)) {
6773
            $question_count = count($question_list);
6774
        }
6775
6776
        return $question_count;
6777
    }
6778
6779
    /**
6780
     * Get categories added in the exercise--category matrix.
6781
     *
6782
     * @return array
6783
     */
6784
    public function getCategoriesInExercise()
6785
    {
6786
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6787
        if (!empty($this->id)) {
6788
            $sql = "SELECT * FROM $table
6789
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6790
            $result = Database::query($sql);
6791
            $list = [];
6792
            if (Database::num_rows($result)) {
6793
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6794
                    $list[$row['category_id']] = $row;
6795
                }
6796
6797
                return $list;
6798
            }
6799
        }
6800
6801
        return [];
6802
    }
6803
6804
    /**
6805
     * Get total number of question that will be parsed when using the category/exercise.
6806
     *
6807
     * @return int
6808
     */
6809
    public function getNumberQuestionExerciseCategory()
6810
    {
6811
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6812
        if (!empty($this->id)) {
6813
            $sql = "SELECT SUM(count_questions) count_questions
6814
                    FROM $table
6815
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6816
            $result = Database::query($sql);
6817
            if (Database::num_rows($result)) {
6818
                $row = Database::fetch_array($result);
6819
6820
                return $row['count_questions'];
6821
            }
6822
        }
6823
6824
        return 0;
6825
    }
6826
6827
    /**
6828
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
6829
     *
6830
     * @param array $categories
6831
     */
6832
    public function save_categories_in_exercise($categories)
6833
    {
6834
        if (!empty($categories) && !empty($this->id)) {
6835
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6836
            $sql = "DELETE FROM $table
6837
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6838
            Database::query($sql);
6839
            if (!empty($categories)) {
6840
                foreach ($categories as $categoryId => $countQuestions) {
6841
                    $params = [
6842
                        'c_id' => $this->course_id,
6843
                        'exercise_id' => $this->id,
6844
                        'category_id' => $categoryId,
6845
                        'count_questions' => $countQuestions,
6846
                    ];
6847
                    Database::insert($table, $params);
6848
                }
6849
            }
6850
        }
6851
    }
6852
6853
    /**
6854
     * @param array  $questionList
6855
     * @param int    $currentQuestion
6856
     * @param array  $conditions
6857
     * @param string $link
6858
     *
6859
     * @return string
6860
     */
6861
    public function progressExercisePaginationBar(
6862
        $questionList,
6863
        $currentQuestion,
6864
        $conditions,
6865
        $link
6866
    ) {
6867
        $mediaQuestions = $this->getMediaList();
6868
6869
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6870
        $counter = 0;
6871
        $nextValue = 0;
6872
        $wasMedia = false;
6873
        $before = 0;
6874
        $counterNoMedias = 0;
6875
        foreach ($questionList as $questionId) {
6876
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6877
6878
            if (!empty($nextValue)) {
6879
                if ($wasMedia) {
6880
                    $nextValue = $nextValue - $before + 1;
6881
                }
6882
            }
6883
6884
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6885
                $fixedValue = $counterNoMedias;
6886
6887
                $html .= Display::progressPaginationBar(
6888
                    $nextValue,
6889
                    $mediaQuestions[$questionId],
6890
                    $currentQuestion,
6891
                    $fixedValue,
6892
                    $conditions,
6893
                    $link,
6894
                    true,
6895
                    true
6896
                );
6897
6898
                $counter += count($mediaQuestions[$questionId]) - 1;
6899
                $before = count($questionList);
6900
                $wasMedia = true;
6901
                $nextValue += count($questionList);
6902
            } else {
6903
                $html .= Display::parsePaginationItem(
6904
                    $questionId,
6905
                    $isCurrent,
6906
                    $conditions,
6907
                    $link,
6908
                    $counter
6909
                );
6910
                $counter++;
6911
                $nextValue++;
6912
                $wasMedia = false;
6913
            }
6914
            $counterNoMedias++;
6915
        }
6916
        $html .= '</ul></div>';
6917
6918
        return $html;
6919
    }
6920
6921
    /**
6922
     *  Shows a list of numbers that represents the question to answer in a exercise.
6923
     *
6924
     * @param array  $categories
6925
     * @param int    $current
6926
     * @param array  $conditions
6927
     * @param string $link
6928
     *
6929
     * @return string
6930
     */
6931
    public function progressExercisePaginationBarWithCategories(
6932
        $categories,
6933
        $current,
6934
        $conditions = [],
6935
        $link = null
6936
    ) {
6937
        $html = null;
6938
        $counterNoMedias = 0;
6939
        $nextValue = 0;
6940
        $wasMedia = false;
6941
        $before = 0;
6942
6943
        if (!empty($categories)) {
6944
            $selectionType = $this->getQuestionSelectionType();
6945
            $useRootAsCategoryTitle = false;
6946
6947
            // Grouping questions per parent category see BT#6540
6948
            if (in_array(
6949
                $selectionType,
6950
                [
6951
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6952
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
6953
                ]
6954
            )) {
6955
                $useRootAsCategoryTitle = true;
6956
            }
6957
6958
            // If the exercise is set to only show the titles of the categories
6959
            // at the root of the tree, then pre-order the categories tree by
6960
            // removing children and summing their questions into the parent
6961
            // categories
6962
            if ($useRootAsCategoryTitle) {
6963
                // The new categories list starts empty
6964
                $newCategoryList = [];
6965
                foreach ($categories as $category) {
6966
                    $rootElement = $category['root'];
6967
6968
                    if (isset($category['parent_info'])) {
6969
                        $rootElement = $category['parent_info']['id'];
6970
                    }
6971
6972
                    //$rootElement = $category['id'];
6973
                    // If the current category's ancestor was never seen
6974
                    // before, then declare it and assign the current
6975
                    // category to it.
6976
                    if (!isset($newCategoryList[$rootElement])) {
6977
                        $newCategoryList[$rootElement] = $category;
6978
                    } else {
6979
                        // If it was already seen, then merge the previous with
6980
                        // the current category
6981
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6982
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
6983
                        $newCategoryList[$rootElement] = $category;
6984
                    }
6985
                }
6986
                // Now use the newly built categories list, with only parents
6987
                $categories = $newCategoryList;
6988
            }
6989
6990
            foreach ($categories as $category) {
6991
                $questionList = $category['question_list'];
6992
                // Check if in this category there questions added in a media
6993
                $mediaQuestionId = $category['media_question'];
6994
                $isMedia = false;
6995
                $fixedValue = null;
6996
6997
                // Media exists!
6998
                if ($mediaQuestionId != 999) {
6999
                    $isMedia = true;
7000
                    $fixedValue = $counterNoMedias;
7001
                }
7002
7003
                //$categoryName = $category['path']; << show the path
7004
                $categoryName = $category['name'];
7005
7006
                if ($useRootAsCategoryTitle) {
7007
                    if (isset($category['parent_info'])) {
7008
                        $categoryName = $category['parent_info']['title'];
7009
                    }
7010
                }
7011
                $html .= '<div class="row">';
7012
                $html .= '<div class="span2">'.$categoryName.'</div>';
7013
                $html .= '<div class="span8">';
7014
7015
                if (!empty($nextValue)) {
7016
                    if ($wasMedia) {
7017
                        $nextValue = $nextValue - $before + 1;
7018
                    }
7019
                }
7020
                $html .= Display::progressPaginationBar(
7021
                    $nextValue,
7022
                    $questionList,
7023
                    $current,
7024
                    $fixedValue,
7025
                    $conditions,
7026
                    $link,
7027
                    $isMedia,
7028
                    true
7029
                );
7030
                $html .= '</div>';
7031
                $html .= '</div>';
7032
7033
                if ($mediaQuestionId == 999) {
7034
                    $counterNoMedias += count($questionList);
7035
                } else {
7036
                    $counterNoMedias++;
7037
                }
7038
7039
                $nextValue += count($questionList);
7040
                $before = count($questionList);
7041
7042
                if ($mediaQuestionId != 999) {
7043
                    $wasMedia = true;
7044
                } else {
7045
                    $wasMedia = false;
7046
                }
7047
            }
7048
        }
7049
7050
        return $html;
7051
    }
7052
7053
    /**
7054
     * Renders a question list.
7055
     *
7056
     * @param array $questionList    (with media questions compressed)
7057
     * @param int   $currentQuestion
7058
     * @param array $exerciseResult
7059
     * @param array $attemptList
7060
     * @param array $remindList
7061
     */
7062
    public function renderQuestionList(
7063
        $questionList,
7064
        $currentQuestion,
7065
        $exerciseResult,
7066
        $attemptList,
7067
        $remindList
7068
    ) {
7069
        $mediaQuestions = $this->getMediaList();
7070
        $i = 0;
7071
7072
        // Normal question list render (medias compressed)
7073
        foreach ($questionList as $questionId) {
7074
            $i++;
7075
            // For sequential exercises
7076
7077
            if ($this->type == ONE_PER_PAGE) {
7078
                // If it is not the right question, goes to the next loop iteration
7079
                if ($currentQuestion != $i) {
7080
                    continue;
7081
                } else {
7082
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
7083
                        // if the user has already answered this question
7084
                        if (isset($exerciseResult[$questionId])) {
7085
                            echo Display::return_message(
7086
                                get_lang('AlreadyAnswered'),
7087
                                'normal'
7088
                            );
7089
                            break;
7090
                        }
7091
                    }
7092
                }
7093
            }
7094
7095
            // The $questionList contains the media id we check
7096
            // if this questionId is a media question type
7097
            if (isset($mediaQuestions[$questionId]) &&
7098
                $mediaQuestions[$questionId] != 999
7099
            ) {
7100
                // The question belongs to a media
7101
                $mediaQuestionList = $mediaQuestions[$questionId];
7102
                $objQuestionTmp = Question::read($questionId);
7103
7104
                $counter = 1;
7105
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7106
                    echo $objQuestionTmp->show_media_content();
7107
7108
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7109
7110
                    // Show questions that belongs to a media
7111
                    if (!empty($mediaQuestionList)) {
7112
                        // In order to parse media questions we use letters a, b, c, etc.
7113
                        $letterCounter = 97;
7114
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7115
                            $isLastQuestionInMedia = false;
7116
                            if ($counter == $countQuestionsInsideMedia) {
7117
                                $isLastQuestionInMedia = true;
7118
                            }
7119
                            $this->renderQuestion(
7120
                                $questionIdInsideMedia,
7121
                                $attemptList,
7122
                                $remindList,
7123
                                chr($letterCounter),
7124
                                $currentQuestion,
7125
                                $mediaQuestionList,
7126
                                $isLastQuestionInMedia,
7127
                                $questionList
7128
                            );
7129
                            $letterCounter++;
7130
                            $counter++;
7131
                        }
7132
                    }
7133
                } else {
7134
                    $this->renderQuestion(
7135
                        $questionId,
7136
                        $attemptList,
7137
                        $remindList,
7138
                        $i,
7139
                        $currentQuestion,
7140
                        null,
7141
                        null,
7142
                        $questionList
7143
                    );
7144
                    $i++;
7145
                }
7146
            } else {
7147
                // Normal question render.
7148
                $this->renderQuestion(
7149
                    $questionId,
7150
                    $attemptList,
7151
                    $remindList,
7152
                    $i,
7153
                    $currentQuestion,
7154
                    null,
7155
                    null,
7156
                    $questionList
7157
                );
7158
            }
7159
7160
            // For sequential exercises.
7161
            if ($this->type == ONE_PER_PAGE) {
7162
                // quits the loop
7163
                break;
7164
            }
7165
        }
7166
        // end foreach()
7167
7168
        if ($this->type == ALL_ON_ONE_PAGE) {
7169
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7170
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7171
        }
7172
    }
7173
7174
    /**
7175
     * @param int   $questionId
7176
     * @param array $attemptList
7177
     * @param array $remindList
7178
     * @param int   $i
7179
     * @param int   $current_question
7180
     * @param array $questions_in_media
7181
     * @param bool  $last_question_in_media
7182
     * @param array $realQuestionList
7183
     * @param bool  $generateJS
7184
     */
7185
    public function renderQuestion(
7186
        $questionId,
7187
        $attemptList,
7188
        $remindList,
7189
        $i,
7190
        $current_question,
7191
        $questions_in_media = [],
7192
        $last_question_in_media = false,
7193
        $realQuestionList,
7194
        $generateJS = true
7195
    ) {
7196
        // With this option on the question is loaded via AJAX
7197
        //$generateJS = true;
7198
        //$this->loadQuestionAJAX = true;
7199
7200
        if ($generateJS && $this->loadQuestionAJAX) {
7201
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7202
            $params = [
7203
                'questionId' => $questionId,
7204
                'attemptList' => $attemptList,
7205
                'remindList' => $remindList,
7206
                'i' => $i,
7207
                'current_question' => $current_question,
7208
                'questions_in_media' => $questions_in_media,
7209
                'last_question_in_media' => $last_question_in_media,
7210
            ];
7211
            $params = json_encode($params);
7212
7213
            $script = '<script>
7214
            $(function(){
7215
                var params = '.$params.';
7216
                $.ajax({
7217
                    type: "GET",
7218
                    async: false,
7219
                    data: params,
7220
                    url: "'.$url.'",
7221
                    success: function(return_value) {
7222
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7223
                    }
7224
                });
7225
            });
7226
            </script>
7227
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7228
            echo $script;
7229
        } else {
7230
            global $origin;
7231
            $question_obj = Question::read($questionId);
7232
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7233
            $remind_highlight = null;
7234
7235
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7236
            // see #4542 no_remind_highlight class hide with jquery
7237
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7238
                $remind_highlight = 'no_remind_highlight';
7239
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7240
                    return null;
7241
                }
7242
            }
7243
7244
            $attributes = ['id' => 'remind_list['.$questionId.']'];
7245
            if (is_array($remindList) && in_array($questionId, $remindList)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
7246
                //$attributes['checked'] = 1;
7247
                //$remind_highlight = ' remind_highlight ';
7248
            }
7249
7250
            // Showing the question
7251
7252
            $exercise_actions = null;
7253
7254
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7255
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7256
7257
            // Shows the question + possible answers
7258
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7259
            echo $this->showQuestion(
7260
                $question_obj,
7261
                false,
7262
                $origin,
7263
                $i,
7264
                $showTitle,
7265
                false,
7266
                $user_choice,
7267
                false,
7268
                null,
7269
                false,
7270
                $this->getModelType(),
7271
                $this->categoryMinusOne
7272
            );
7273
7274
            // Button save and continue
7275
            switch ($this->type) {
7276
                case ONE_PER_PAGE:
7277
                    $exercise_actions .= $this->show_button(
7278
                        $questionId,
7279
                        $current_question,
7280
                        null,
7281
                        $remindList
7282
                    );
7283
                    break;
7284
                case ALL_ON_ONE_PAGE:
7285
                    if (api_is_allowed_to_session_edit()) {
7286
                        $button = [
7287
                            Display::button(
7288
                                'save_now',
7289
                                get_lang('SaveForNow'),
7290
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7291
                            ),
7292
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7293
                        ];
7294
                        $exercise_actions .= Display::div(
7295
                            implode(PHP_EOL, $button),
7296
                            ['class' => 'exercise_save_now_button']
7297
                        );
7298
                    }
7299
                    break;
7300
            }
7301
7302
            if (!empty($questions_in_media)) {
7303
                $count_of_questions_inside_media = count($questions_in_media);
7304
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7305
                    $button = [
7306
                        Display::button(
7307
                            'save_now',
7308
                            get_lang('SaveForNow'),
7309
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7310
                        ),
7311
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7312
                    ];
7313
                    $exercise_actions = Display::div(
7314
                        implode(PHP_EOL, $button),
7315
                        ['class' => 'exercise_save_now_button']
7316
                    );
7317
                }
7318
7319
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7320
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7321
                }
7322
            }
7323
7324
            // Checkbox review answers
7325
            if ($this->review_answers &&
7326
                !in_array($question_obj->type, Question::question_type_no_review())
7327
            ) {
7328
                $remind_question_div = Display::tag(
7329
                    'label',
7330
                    Display::input(
7331
                        'checkbox',
7332
                        'remind_list['.$questionId.']',
7333
                        '',
7334
                        $attributes
7335
                    ).get_lang('ReviewQuestionLater'),
7336
                    [
7337
                        'class' => 'checkbox',
7338
                        'for' => 'remind_list['.$questionId.']',
7339
                    ]
7340
                );
7341
                $exercise_actions .= Display::div(
7342
                    $remind_question_div,
7343
                    ['class' => 'exercise_save_now_button']
7344
                );
7345
            }
7346
7347
            echo Display::div(' ', ['class' => 'clear']);
7348
7349
            $paginationCounter = null;
7350
            if ($this->type == ONE_PER_PAGE) {
7351
                if (empty($questions_in_media)) {
7352
                    $paginationCounter = Display::paginationIndicator(
7353
                        $current_question,
7354
                        count($realQuestionList)
7355
                    );
7356
                } else {
7357
                    if ($last_question_in_media) {
7358
                        $paginationCounter = Display::paginationIndicator(
7359
                            $current_question,
7360
                            count($realQuestionList)
7361
                        );
7362
                    }
7363
                }
7364
            }
7365
7366
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7367
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7368
            echo '</div>';
7369
        }
7370
    }
7371
7372
    /**
7373
     * Returns an array of categories details for the questions of the current
7374
     * exercise.
7375
     *
7376
     * @return array
7377
     */
7378
    public function getQuestionWithCategories()
7379
    {
7380
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7381
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7382
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7383
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7384
        $sql = "SELECT DISTINCT cat.*
7385
                FROM $TBL_EXERCICE_QUESTION e
7386
                INNER JOIN $TBL_QUESTIONS q
7387
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7388
                INNER JOIN $categoryRelTable catRel
7389
                ON (catRel.question_id = e.question_id)
7390
                INNER JOIN $categoryTable cat
7391
                ON (cat.id = catRel.category_id)
7392
                WHERE
7393
                  e.c_id = {$this->course_id} AND
7394
                  e.exercice_id	= ".intval($this->id);
7395
7396
        $result = Database::query($sql);
7397
        $categoriesInExercise = [];
7398
        if (Database::num_rows($result)) {
7399
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7400
        }
7401
7402
        return $categoriesInExercise;
7403
    }
7404
7405
    /**
7406
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
7407
     */
7408
    public function get_max_score()
7409
    {
7410
        $out_max_score = 0;
7411
        // list of question's id !!! the array key start at 1 !!!
7412
        $questionList = $this->selectQuestionList(true);
7413
7414
        // test is randomQuestions - see field random of test
7415
        if ($this->random > 0 && $this->randomByCat == 0) {
7416
            $numberRandomQuestions = $this->random;
7417
            $questionScoreList = [];
7418
            foreach ($questionList as $questionId) {
7419
                $tmpobj_question = Question::read($questionId);
7420
                if (is_object($tmpobj_question)) {
7421
                    $questionScoreList[] = $tmpobj_question->weighting;
7422
                }
7423
            }
7424
7425
            rsort($questionScoreList);
7426
            // add the first $numberRandomQuestions value of score array to get max_score
7427
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7428
                $out_max_score += $questionScoreList[$i];
7429
            }
7430
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7431
            // test is random by category
7432
            // get the $numberRandomQuestions best score question of each category
7433
            $numberRandomQuestions = $this->random;
7434
            $tab_categories_scores = [];
7435
            foreach ($questionList as $questionId) {
7436
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7437
                if (!is_array($tab_categories_scores[$question_category_id])) {
7438
                    $tab_categories_scores[$question_category_id] = [];
7439
                }
7440
                $tmpobj_question = Question::read($questionId);
7441
                if (is_object($tmpobj_question)) {
7442
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7443
                }
7444
            }
7445
7446
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7447
            while (list($key, $tab_scores) = each($tab_categories_scores)) {
7448
                rsort($tab_scores);
7449
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7450
                    $out_max_score += $tab_scores[$i];
7451
                }
7452
            }
7453
        } else {
7454
            // standard test, just add each question score
7455
            foreach ($questionList as $questionId) {
7456
                $question = Question::read($questionId, $this->course_id);
7457
                $out_max_score += $question->weighting;
7458
            }
7459
        }
7460
7461
        return $out_max_score;
7462
    }
7463
7464
    /**
7465
     * @return string
7466
     */
7467
    public function get_formated_title()
7468
    {
7469
        if (api_get_configuration_value('save_titles_as_html')) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
7470
        }
7471
7472
        return api_html_entity_decode($this->selectTitle());
7473
    }
7474
7475
    /**
7476
     * @param string $title
7477
     *
7478
     * @return string
7479
     */
7480
    public static function get_formated_title_variable($title)
7481
    {
7482
        return api_html_entity_decode($title);
7483
    }
7484
7485
    /**
7486
     * @return string
7487
     */
7488
    public function format_title()
7489
    {
7490
        return api_htmlentities($this->title);
7491
    }
7492
7493
    /**
7494
     * @param string $title
7495
     *
7496
     * @return string
7497
     */
7498
    public static function format_title_variable($title)
7499
    {
7500
        return api_htmlentities($title);
7501
    }
7502
7503
    /**
7504
     * @param int $courseId
7505
     * @param int $sessionId
7506
     *
7507
     * @return array exercises
7508
     */
7509
    public function getExercisesByCourseSession($courseId, $sessionId)
7510
    {
7511
        $courseId = intval($courseId);
7512
        $sessionId = intval($sessionId);
7513
7514
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7515
        $sql = "SELECT * FROM $tbl_quiz cq
7516
                WHERE
7517
                    cq.c_id = %s AND
7518
                    (cq.session_id = %s OR cq.session_id = 0) AND
7519
                    cq.active = 0
7520
                ORDER BY cq.id";
7521
        $sql = sprintf($sql, $courseId, $sessionId);
7522
7523
        $result = Database::query($sql);
7524
7525
        $rows = [];
7526
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7527
            $rows[] = $row;
7528
        }
7529
7530
        return $rows;
7531
    }
7532
7533
    /**
7534
     * @param int   $courseId
7535
     * @param int   $sessionId
7536
     * @param array $quizId
7537
     *
7538
     * @return array exercises
7539
     */
7540
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7541
    {
7542
        if (empty($quizId)) {
7543
            return [];
7544
        }
7545
7546
        $sessionId = intval($sessionId);
7547
        $ids = is_array($quizId) ? $quizId : [$quizId];
7548
        $ids = array_map('intval', $ids);
7549
        $ids = implode(',', $ids);
7550
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7551
        if ($sessionId != 0) {
7552
            $sql = "SELECT * FROM $track_exercises te
7553
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7554
              WHERE
7555
              te.id = %s AND
7556
              te.session_id = %s AND
7557
              cq.id IN (%s)
7558
              ORDER BY cq.id";
7559
7560
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7561
        } else {
7562
            $sql = "SELECT * FROM $track_exercises te
7563
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7564
              WHERE
7565
              te.id = %s AND
7566
              cq.id IN (%s)
7567
              ORDER BY cq.id";
7568
            $sql = sprintf($sql, $courseId, $ids);
7569
        }
7570
        $result = Database::query($sql);
7571
        $rows = [];
7572
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7573
            $rows[] = $row;
7574
        }
7575
7576
        return $rows;
7577
    }
7578
7579
    /**
7580
     * @param $exeId
7581
     * @param $exercise_stat_info
7582
     * @param $remindList
7583
     * @param $currentQuestion
7584
     *
7585
     * @return int|null
7586
     */
7587
    public static function getNextQuestionId(
7588
        $exeId,
7589
        $exercise_stat_info,
7590
        $remindList,
7591
        $currentQuestion
7592
    ) {
7593
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7594
7595
        if (isset($result[$exeId])) {
7596
            $result = $result[$exeId];
7597
        } else {
7598
            return null;
7599
        }
7600
7601
        $data_tracking = $exercise_stat_info['data_tracking'];
7602
        $data_tracking = explode(',', $data_tracking);
7603
7604
        // if this is the final question do nothing.
7605
        if ($currentQuestion == count($data_tracking)) {
7606
            return null;
7607
        }
7608
7609
        $currentQuestion = $currentQuestion - 1;
7610
7611
        if (!empty($result['question_list'])) {
7612
            $answeredQuestions = [];
7613
            foreach ($result['question_list'] as $question) {
7614
                if (!empty($question['answer'])) {
7615
                    $answeredQuestions[] = $question['question_id'];
7616
                }
7617
            }
7618
7619
            // Checking answered questions
7620
            $counterAnsweredQuestions = 0;
7621
            foreach ($data_tracking as $questionId) {
7622
                if (!in_array($questionId, $answeredQuestions)) {
7623
                    if ($currentQuestion != $counterAnsweredQuestions) {
7624
                        break;
7625
                    }
7626
                }
7627
                $counterAnsweredQuestions++;
7628
            }
7629
7630
            $counterRemindListQuestions = 0;
7631
            // Checking questions saved in the reminder list
7632
            if (!empty($remindList)) {
7633
                foreach ($data_tracking as $questionId) {
7634
                    if (in_array($questionId, $remindList)) {
7635
                        // Skip the current question
7636
                        if ($currentQuestion != $counterRemindListQuestions) {
7637
                            break;
7638
                        }
7639
                    }
7640
                    $counterRemindListQuestions++;
7641
                }
7642
7643
                if ($counterRemindListQuestions < $currentQuestion) {
7644
                    return null;
7645
                }
7646
7647
                if (!empty($counterRemindListQuestions)) {
7648
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7649
                        return $counterAnsweredQuestions;
7650
                    } else {
7651
                        return $counterRemindListQuestions;
7652
                    }
7653
                }
7654
            }
7655
7656
            return $counterAnsweredQuestions;
7657
        }
7658
    }
7659
7660
    /**
7661
     * Gets the position of a questionId in the question list.
7662
     *
7663
     * @param $questionId
7664
     *
7665
     * @return int
7666
     */
7667
    public function getPositionInCompressedQuestionList($questionId)
7668
    {
7669
        $questionList = $this->getQuestionListWithMediasCompressed();
7670
        $mediaQuestions = $this->getMediaList();
7671
        $position = 1;
7672
        foreach ($questionList as $id) {
7673
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7674
                $mediaQuestionList = $mediaQuestions[$id];
7675
                if (in_array($questionId, $mediaQuestionList)) {
7676
                    return $position;
7677
                } else {
7678
                    $position++;
7679
                }
7680
            } else {
7681
                if ($id == $questionId) {
7682
                    return $position;
7683
                } else {
7684
                    $position++;
7685
                }
7686
            }
7687
        }
7688
7689
        return 1;
7690
    }
7691
7692
    /**
7693
     * Get the correct answers in all attempts.
7694
     *
7695
     * @param int $learnPathId
7696
     * @param int $learnPathItemId
7697
     *
7698
     * @return array
7699
     */
7700
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7701
    {
7702
        $attempts = Event::getExerciseResultsByUser(
7703
            api_get_user_id(),
7704
            $this->id,
7705
            api_get_course_int_id(),
7706
            api_get_session_id(),
7707
            $learnPathId,
7708
            $learnPathItemId,
7709
            'asc'
7710
        );
7711
7712
        $corrects = [];
7713
7714
        foreach ($attempts as $attempt) {
7715
            foreach ($attempt['question_list'] as $answers) {
7716
                foreach ($answers as $answer) {
7717
                    $objAnswer = new Answer($answer['question_id']);
7718
7719
                    switch ($objAnswer->getQuestionType()) {
7720
                        case FILL_IN_BLANKS:
7721
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7722
                            break;
7723
                        case MATCHING:
7724
                        case DRAGGABLE:
7725
                        case MATCHING_DRAGGABLE:
7726
                            $isCorrect = Matching::isCorrect(
7727
                                $answer['position'],
7728
                                $answer['answer'],
7729
                                $answer['question_id']
7730
                            );
7731
                            break;
7732
                        case ORAL_EXPRESSION:
7733
                            $isCorrect = false;
7734
                            break;
7735
                        default:
7736
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7737
                    }
7738
7739
                    if ($isCorrect) {
7740
                        $corrects[$answer['question_id']][] = $answer;
7741
                    }
7742
                }
7743
            }
7744
        }
7745
7746
        return $corrects;
7747
    }
7748
7749
    /**
7750
     * @return bool
7751
     */
7752
    public function showPreviousButton()
7753
    {
7754
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
7755
        if ($allow === false) {
7756
            return true;
7757
        }
7758
7759
        return $this->showPreviousButton;
7760
    }
7761
7762
    /**
7763
     * @param bool $showPreviousButton
7764
     *
7765
     * @return Exercise
7766
     */
7767
    public function setShowPreviousButton($showPreviousButton)
7768
    {
7769
        $this->showPreviousButton = $showPreviousButton;
7770
7771
        return $this;
7772
    }
7773
7774
    /**
7775
     * @param array $notifications
7776
     */
7777
    public function setNotifications($notifications)
7778
    {
7779
        $this->notifications = $notifications;
7780
    }
7781
7782
    /**
7783
     * @return array
7784
     */
7785
    public function getNotifications()
7786
    {
7787
        return $this->notifications;
7788
    }
7789
7790
    /**
7791
     * @return bool
7792
     */
7793
    public function showExpectedChoice()
7794
    {
7795
        return api_get_configuration_value('show_exercise_expected_choice');
7796
    }
7797
7798
    /**
7799
     * @param string $class
7800
     * @param string $scoreLabel
7801
     * @param string $result
7802
     * @param array
7803
     *
7804
     * @return string
7805
     */
7806
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
7807
    {
7808
        if ($this->showExpectedChoice()) {
7809
            $html = null;
7810
            $hideLabel = api_get_configuration_value('exercise_hide_label');
7811
            $label = '<div class="rib rib-'.$class.'">
7812
                        <h3>'.$scoreLabel.'</h3>
7813
                  </div> 
7814
                  <h4>'.get_lang('Score').': '.$result.'</h4>';
7815
            if ($hideLabel === true) {
7816
                $answerUsed = (int) $array['used'];
7817
                $answerMissing = (int) $array['missing'] - $answerUsed;
7818
                for ($i = 1; $i <= $answerUsed; $i++) {
7819
                    $html .= '<span class="score-img">'.
7820
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
7821
                        '</span>';
7822
                }
7823
                for ($i = 1; $i <= $answerMissing; $i++) {
7824
                    $html .= '<span class="score-img">'.
7825
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
7826
                        '</span>';
7827
                }
7828
                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
7829
                $label .= '<div class="score-limits">';
7830
                $label .= $html;
7831
                $label .= '</div>';
7832
            }
7833
7834
            return '<div class="ribbon">
7835
                '.$label.'
7836
                </div>'
7837
                ;
7838
        } else {
7839
            return '<div class="ribbon">
7840
                        <div class="rib rib-'.$class.'">
7841
                            <h3>'.$scoreLabel.'</h3>
7842
                        </div>
7843
                        <h4>'.get_lang('Score').': '.$result.'</h4>
7844
                    </div>'
7845
                ;
7846
        }
7847
    }
7848
7849
    /**
7850
     * @return int
7851
     */
7852
    public function getAutoLaunch()
7853
    {
7854
        return $this->autolaunch;
7855
    }
7856
7857
    /**
7858
     * Clean auto launch settings for all exercise in course/course-session.
7859
     */
7860
    public function enableAutoLaunch()
7861
    {
7862
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7863
        $sql = "UPDATE $table SET autolaunch = 1
7864
                WHERE iid = ".$this->iId;
7865
        Database::query($sql);
7866
    }
7867
7868
    /**
7869
     * Clean auto launch settings for all exercise in course/course-session.
7870
     */
7871
    public function cleanCourseLaunchSettings()
7872
    {
7873
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
7874
        $sql = "UPDATE $table SET autolaunch = 0  
7875
                WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId;
7876
        Database::query($sql);
7877
    }
7878
7879
    /**
7880
     * Gets the question list ordered by the question_order setting (drag and drop).
7881
     *
7882
     * @return array
7883
     */
7884
    private function getQuestionOrderedList()
7885
    {
7886
        $questionList = [];
7887
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7888
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7889
7890
        // Getting question_order to verify that the question
7891
        // list is correct and all question_order's were set
7892
        $sql = "SELECT DISTINCT e.question_order
7893
                FROM $TBL_EXERCICE_QUESTION e
7894
                INNER JOIN $TBL_QUESTIONS q
7895
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7896
                WHERE
7897
                  e.c_id = {$this->course_id} AND
7898
                  e.exercice_id	= ".$this->id;
7899
7900
        $result = Database::query($sql);
7901
        $count_question_orders = Database::num_rows($result);
7902
7903
        // Getting question list from the order (question list drag n drop interface).
7904
        $sql = "SELECT DISTINCT e.question_id, e.question_order
7905
                FROM $TBL_EXERCICE_QUESTION e
7906
                INNER JOIN $TBL_QUESTIONS q
7907
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7908
                WHERE
7909
                    e.c_id = {$this->course_id} AND
7910
                    e.exercice_id = '".$this->id."'
7911
                ORDER BY question_order";
7912
        $result = Database::query($sql);
7913
7914
        // Fills the array with the question ID for this exercise
7915
        // the key of the array is the question position
7916
        $temp_question_list = [];
7917
        $counter = 1;
7918
        while ($new_object = Database::fetch_object($result)) {
7919
            // Correct order.
7920
            $questionList[$new_object->question_order] = $new_object->question_id;
7921
            // Just in case we save the order in other array
7922
            $temp_question_list[$counter] = $new_object->question_id;
7923
            $counter++;
7924
        }
7925
7926
        if (!empty($temp_question_list)) {
7927
            /* If both array don't match it means that question_order was not correctly set
7928
               for all questions using the default mysql order */
7929
            if (count($temp_question_list) != $count_question_orders) {
7930
                $questionList = $temp_question_list;
7931
            }
7932
        }
7933
7934
        return $questionList;
7935
    }
7936
7937
    /**
7938
     * Select N values from the questions per category array.
7939
     *
7940
     * @param array $categoriesAddedInExercise
7941
     * @param array $question_list
7942
     * @param array $questions_by_category     per category
7943
     * @param bool  $flatResult
7944
     * @param bool  $randomizeQuestions
7945
     *
7946
     * @return array
7947
     */
7948
    private function pickQuestionsPerCategory(
7949
        $categoriesAddedInExercise,
7950
        $question_list,
7951
        &$questions_by_category,
7952
        $flatResult = true,
7953
        $randomizeQuestions = false
7954
    ) {
7955
        $addAll = true;
7956
        $categoryCountArray = [];
7957
7958
        // Getting how many questions will be selected per category.
7959
        if (!empty($categoriesAddedInExercise)) {
7960
            $addAll = false;
7961
            // Parsing question according the category rel exercise settings
7962
            foreach ($categoriesAddedInExercise as $category_info) {
7963
                $category_id = $category_info['category_id'];
7964
                if (isset($questions_by_category[$category_id])) {
7965
                    // How many question will be picked from this category.
7966
                    $count = $category_info['count_questions'];
7967
                    // -1 means all questions
7968
                    $categoryCountArray[$category_id] = $count;
7969
                    if ($count == -1) {
7970
                        $categoryCountArray[$category_id] = 999;
7971
                    }
7972
                }
7973
            }
7974
        }
7975
7976
        if (!empty($questions_by_category)) {
7977
            $temp_question_list = [];
7978
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
7979
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
7980
                    $numberOfQuestions = 0;
7981
                    if (isset($categoryCountArray[$category_id])) {
7982
                        $numberOfQuestions = $categoryCountArray[$category_id];
7983
                    }
7984
                }
7985
7986
                if ($addAll) {
7987
                    $numberOfQuestions = 999;
7988
                }
7989
7990
                if (!empty($numberOfQuestions)) {
7991
                    $elements = TestCategory::getNElementsFromArray(
7992
                        $categoryQuestionList,
7993
                        $numberOfQuestions,
7994
                        $randomizeQuestions
7995
                    );
7996
7997
                    if (!empty($elements)) {
7998
                        $temp_question_list[$category_id] = $elements;
7999
                        $categoryQuestionList = $elements;
8000
                    }
8001
                }
8002
            }
8003
8004
            if (!empty($temp_question_list)) {
8005
                if ($flatResult) {
8006
                    $temp_question_list = array_flatten($temp_question_list);
8007
                }
8008
                $question_list = $temp_question_list;
8009
            }
8010
        }
8011
8012
        return $question_list;
8013
    }
8014
8015
    /**
8016
     * Changes the exercise id.
8017
     *
8018
     * @param int $id - exercise id
8019
     */
8020
    private function updateId($id)
8021
    {
8022
        $this->id = $id;
8023
    }
8024
8025
    /**
8026
     * Sends a notification when a user ends an examn.
8027
     *
8028
     * @param array  $question_list_answers
8029
     * @param string $origin
8030
     * @param int    $exe_id
8031
     */
8032
    private function sendNotificationForOpenQuestions(
8033
        $question_list_answers,
8034
        $origin,
8035
        $exe_id,
8036
        $user_info,
8037
        $url_email,
8038
        $teachers
8039
    ) {
8040
        // Email configuration settings
8041
        $courseCode = api_get_course_id();
8042
        $courseInfo = api_get_course_info($courseCode);
8043
8044
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
8045
                    .get_lang('AttemptDetails').' : <br /><br />'
8046
                    .'<table>'
8047
                        .'<tr>'
8048
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
8049
                            .'<td>&nbsp;<b>#course#</b></td>'
8050
                        .'</tr>'
8051
                        .'<tr>'
8052
                            .'<td>'.get_lang('TestAttempted').'</td>'
8053
                            .'<td>&nbsp;#exercise#</td>'
8054
                        .'</tr>'
8055
                        .'<tr>'
8056
                            .'<td>'.get_lang('StudentName').'</td>'
8057
                            .'<td>&nbsp;#firstName# #lastName#</td>'
8058
                        .'</tr>'
8059
                        .'<tr>'
8060
                            .'<td>'.get_lang('StudentEmail').'</td>'
8061
                            .'<td>&nbsp;#mail#</td>'
8062
                        .'</tr>'
8063
                    .'</table>';
8064
        $open_question_list = null;
8065
        foreach ($question_list_answers as $item) {
8066
            $question = $item['question'];
8067
            $answer = $item['answer'];
8068
            $answer_type = $item['answer_type'];
8069
8070
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
8071
                $open_question_list .=
8072
                    '<tr>'
8073
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
8074
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
8075
                    .'</tr>'
8076
                    .'<tr>'
8077
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
8078
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
8079
                    .'</tr>';
8080
            }
8081
        }
8082
8083
        if (!empty($open_question_list)) {
8084
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
8085
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
8086
            $msg .= $open_question_list;
8087
            $msg .= '</table><br />';
8088
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
8089
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
8090
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
8091
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
8092
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
8093
8094
            if ($origin != 'learnpath') {
8095
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8096
            }
8097
            $msg1 = str_replace("#url#", $url_email, $msg);
8098
            $mail_content = $msg1;
8099
            $subject = get_lang('OpenQuestionsAttempted');
8100
8101
            if (!empty($teachers)) {
8102
                foreach ($teachers as $user_id => $teacher_data) {
8103
                    MessageManager::send_message_simple(
8104
                        $user_id,
8105
                        $subject,
8106
                        $mail_content
8107
                    );
8108
                }
8109
            }
8110
        }
8111
    }
8112
8113
    /**
8114
     * Send notification for oral questions.
8115
     *
8116
     * @param array  $question_list_answers
8117
     * @param string $origin
8118
     * @param int    $exe_id
8119
     * @param array  $user_info
8120
     * @param string $url_email
8121
     * @param array  $teachers
8122
     */
8123
    private function sendNotificationForOralQuestions(
8124
        $question_list_answers,
8125
        $origin,
8126
        $exe_id,
8127
        $user_info,
8128
        $url_email,
8129
        $teachers
8130
    ) {
8131
        // Email configuration settings
8132
        $courseCode = api_get_course_id();
8133
        $courseInfo = api_get_course_info($courseCode);
8134
        $oral_question_list = null;
8135
        foreach ($question_list_answers as $item) {
8136
            $question = $item['question'];
8137
            $file = $item['generated_oral_file'];
8138
            $answer = $item['answer'];
8139
            if ($answer == 0) {
8140
                $answer = '';
8141
            }
8142
            $answer_type = $item['answer_type'];
8143
            if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) {
8144
                if (!empty($file)) {
8145
                    $file = Display::url($file, $file);
8146
                }
8147
                $oral_question_list .= '<br />
8148
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
8149
                    <tr>
8150
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
8151
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
8152
                    </tr>
8153
                    <tr>
8154
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
8155
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
8156
                    </tr></table>';
8157
            }
8158
        }
8159
8160
        if (!empty($oral_question_list)) {
8161
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
8162
                    '.get_lang('AttemptDetails').' : <br /><br />
8163
                    <table>
8164
                        <tr>
8165
                            <td><em>'.get_lang('CourseName').'</em></td>
8166
                            <td>&nbsp;<b>#course#</b></td>
8167
                        </tr>
8168
                        <tr>
8169
                            <td>'.get_lang('TestAttempted').'</td>
8170
                            <td>&nbsp;#exercise#</td>
8171
                        </tr>
8172
                        <tr>
8173
                            <td>'.get_lang('StudentName').'</td>
8174
                            <td>&nbsp;#firstName# #lastName#</td>
8175
                        </tr>
8176
                        <tr>
8177
                            <td>'.get_lang('StudentEmail').'</td>
8178
                            <td>&nbsp;#mail#</td>
8179
                        </tr>
8180
                    </table>';
8181
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
8182
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
8183
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
8184
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
8185
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
8186
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
8187
8188
            if ($origin != 'learnpath') {
8189
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
8190
            }
8191
            $msg1 = str_replace("#url#", $url_email, $msg);
8192
            $mail_content = $msg1;
8193
            $subject = get_lang('OralQuestionsAttempted');
8194
8195
            if (!empty($teachers)) {
8196
                foreach ($teachers as $user_id => $teacher_data) {
8197
                    MessageManager::send_message_simple(
8198
                        $user_id,
8199
                        $subject,
8200
                        $mail_content
8201
                    );
8202
                }
8203
            }
8204
        }
8205
    }
8206
8207
    /**
8208
     * Returns an array with the media list.
8209
     *
8210
     * @param array question list
8211
     *
8212
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
8213
     * <code>
8214
     * array (size=2)
8215
     *  999 =>
8216
     *    array (size=3)
8217
     *      0 => int 7
8218
     *      1 => int 6
8219
     *      2 => int 3254
8220
     *  100 =>
8221
     *   array (size=1)
8222
     *      0 => int 5
8223
     *  </code>
8224
     */
8225
    private function setMediaList($questionList)
8226
    {
8227
        $mediaList = [];
8228
        if (!empty($questionList)) {
8229
            foreach ($questionList as $questionId) {
8230
                $objQuestionTmp = Question::read($questionId, $this->course_id);
8231
8232
                // If a media question exists
8233
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
8234
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
8235
                } else {
8236
                    // Always the last item
8237
                    $mediaList[999][] = $objQuestionTmp->id;
8238
                }
8239
            }
8240
        }
8241
        $this->mediaList = $mediaList;
8242
    }
8243
8244
    /**
8245
     * Get the title without HTML tags.
8246
     *
8247
     * @return string
8248
     */
8249
    private function getUnformattedTitle()
8250
    {
8251
        return strip_tags(api_html_entity_decode($this->title));
8252
    }
8253
}
8254