Test Failed
Push — master ( 482637...7bef58 )
by Julito
33:32
created

Exercise::setRandom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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