Completed
Push — master ( 9b8b24...6e1754 )
by Julito
58:58
created

Exercise::setGlobalCategoryId()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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