Test Setup Failed
Push — master ( f71949...6c6bd7 )
by Julito
55:21
created

Exercise::getExerciseAndResult()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
907
                );
908
909
                $question_list = $this->pickQuestionsPerCategory(
910
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 895 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
911
                    $question_list,
912
                    $questions_by_category,
913
                    true,
914
                    false
915
                );
916
                break;
917
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
918 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
919
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
920
                    $this,
921
                    $this->course['real_id'],
922
                    null,
923
                    true,
924
                    true
925
                );
926
                $questions_by_category = TestCategory::getQuestionsByCat(
927
                    $this->id,
928
                    $question_list,
929
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 919 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
930
                );
931
                $question_list = $this->pickQuestionsPerCategory(
932
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 919 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
933
                    $question_list,
934
                    $questions_by_category,
935
                    true,
936
                    false
937
                );
938
                break;
939 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
940
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
941
                    $this,
942
                    $this->course['real_id'],
943
                    'title DESC',
944
                    false,
945
                    true
946
                );
947
                $questions_by_category = TestCategory::getQuestionsByCat(
948
                    $this->id,
949
                    $question_list,
950
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 940 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
951
                );
952
                $question_list = $this->pickQuestionsPerCategory(
953
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 940 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
954
                    $question_list,
955
                    $questions_by_category,
956
                    true,
957
                    true
958
                );
959
                break;
960
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
961 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
962
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
963
                    $this,
964
                    $this->course['real_id'],
965
                    null,
966
                    true,
967
                    true
968
                );
969
970
                $questions_by_category = TestCategory::getQuestionsByCat(
971
                    $this->id,
972
                    $question_list,
973
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 962 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
974
                );
975
976
                $question_list = $this->pickQuestionsPerCategory(
977
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 962 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
978
                    $question_list,
979
                    $questions_by_category,
980
                    true,
981
                    true
982
                );
983
                break;
984
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
985
                break;
986
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
987
                break;
988 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
989
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
990
                    $this,
991
                    $this->course['real_id'],
992
                    'root ASC, lft ASC',
993
                    false,
994
                    true
995
                );
996
                $questions_by_category = TestCategory::getQuestionsByCat(
997
                    $this->id,
998
                    $question_list,
999
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 989 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1000
                );
1001
                $question_list = $this->pickQuestionsPerCategory(
1002
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 989 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1003
                    $question_list,
1004
                    $questions_by_category,
1005
                    true,
1006
                    false
1007
                );
1008
                break;
1009 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
1010
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
1011
                    $this,
1012
                    $this->course['real_id'],
1013
                    'root, lft ASC',
1014
                    false,
1015
                    true
1016
                );
1017
                $questions_by_category = TestCategory::getQuestionsByCat(
1018
                    $this->id,
1019
                    $question_list,
1020
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 1010 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1021
                );
1022
                $question_list = $this->pickQuestionsPerCategory(
1023
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 1010 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1024
                    $question_list,
1025
                    $questions_by_category,
1026
                    true,
1027
                    true
1028
                );
1029
                break;
1030
        }
1031
1032
        $result['question_list'] = isset($question_list) ? $question_list : array();
1033
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
1034
1035
        // Adding category info in the category list with question list:
1036
        if (!empty($questions_by_category)) {
1037
            $newCategoryList = array();
1038
            foreach ($questions_by_category as $categoryId => $questionList) {
1039
                $cat = new TestCategory();
1040
                $cat = $cat->getCategory($categoryId);
1041
1042
                $cat = (array) $cat;
1043
                $cat['iid'] = $cat['id'];
1044
                $categoryParentInfo = null;
1045
                // Parent is not set no loop here
1046
                if (!empty($cat['parent_id'])) {
1047
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1048
                        $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
0 ignored issues
show
Bug introduced by
The variable $em does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1049
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1050
                    } else {
1051
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1052
                    }
1053
                    $path = $repo->getPath($categoryEntity);
0 ignored issues
show
Bug introduced by
The variable $repo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1054
                    $index = 0;
1055
                    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...
1056
                        //$index = 1;
1057
                    }
1058
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent*/
1059
                    foreach ($path as $categoryParent) {
1060
                        $visibility = $categoryParent->getVisibility();
1061
                        if ($visibility == 0) {
1062
                            $categoryParentId = $categoryId;
1063
                            $categoryTitle = $cat['title'];
1064
                            if (count($path) > 1) {
1065
                                continue;
1066
                            }
1067
                        } else {
1068
                            $categoryParentId = $categoryParent->getIid();
1069
                            $categoryTitle = $categoryParent->getTitle();
1070
                        }
1071
1072
                        $categoryParentInfo['id'] = $categoryParentId;
1073
                        $categoryParentInfo['iid'] = $categoryParentId;
1074
                        $categoryParentInfo['parent_path'] = null;
1075
                        $categoryParentInfo['title'] = $categoryTitle;
1076
                        $categoryParentInfo['name'] = $categoryTitle;
1077
                        $categoryParentInfo['parent_id'] = null;
1078
                        break;
1079
                    }
1080
                }
1081
                $cat['parent_info'] = $categoryParentInfo;
1082
                $newCategoryList[$categoryId] = array(
1083
                    'category' => $cat,
1084
                    'question_list' => $questionList
1085
                );
1086
            }
1087
1088
            $result['category_with_questions_list'] = $newCategoryList;
1089
        }
1090
1091
        return $result;
1092
    }
1093
1094
    /**
1095
     * returns the array with the question ID list
1096
     * @param   bool    $from_db    Whether the results should be fetched in the database or just from memory
1097
     * @param   bool    $adminView  Whether we should return all questions (admin view) or
1098
     * just a list limited by the max number of random questions
1099
     * @author Olivier Brouckaert
1100
     * @return array - question ID list
1101
     */
1102
    public function selectQuestionList($from_db = false, $adminView = false)
1103
    {
1104
        if ($from_db && !empty($this->id)) {
1105
            $nbQuestions = $this->getQuestionCount();
1106
            $questionSelectionType = $this->getQuestionSelectionType();
1107
1108
            switch ($questionSelectionType) {
1109
                case EX_Q_SELECTION_ORDERED:
1110
                    $questionList = $this->getQuestionOrderedList();
1111
                    break;
1112
                case EX_Q_SELECTION_RANDOM:
1113
                    // Not a random exercise, or if there are not at least 2 questions
1114
                    if ($this->random == 0 || $nbQuestions < 2) {
1115
                        $questionList = $this->getQuestionOrderedList();
1116
                    } else {
1117
                        $questionList = $this->selectRandomList($adminView);
1118
                    }
1119
                    break;
1120
                default:
1121
                    $questionList = $this->getQuestionOrderedList();
1122
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1123
                        $questionList,
1124
                        $questionSelectionType
1125
                    );
1126
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1127
                    $questionList = $result['question_list'];
1128
                    break;
1129
            }
1130
1131
            return $questionList;
1132
        }
1133
1134
        return $this->questionList;
1135
    }
1136
1137
    /**
1138
     * returns the number of questions in this exercise
1139
     *
1140
     * @author Olivier Brouckaert
1141
     * @return integer - number of questions
1142
     */
1143
    public function selectNbrQuestions()
1144
    {
1145
        return sizeof($this->questionList);
1146
    }
1147
1148
    /**
1149
     * @return int
1150
     */
1151
    public function selectPropagateNeg()
1152
    {
1153
        return $this->propagate_neg;
1154
    }
1155
1156
    /**
1157
     * @return int
1158
     */
1159
    public function selectSaveCorrectAnswers()
1160
    {
1161
        return $this->saveCorrectAnswers;
1162
    }
1163
1164
    /**
1165
     * Selects questions randomly in the question list
1166
     *
1167
     * @author Olivier Brouckaert
1168
     * @author Hubert Borderiou 15 nov 2011
1169
     * @param    bool $adminView Whether we should return all
1170
     * questions (admin view) or just a list limited by the max number of random questions
1171
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1172
     * without randomizing, otherwise, returns the list with questions selected randomly
1173
     */
1174
    public function selectRandomList($adminView = false)
1175
    {
1176
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1177
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1178
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1179
1180
        $randomLimit = "ORDER BY RAND() LIMIT $random";
1181
        // Random all questions so no limit
1182
        if ($random == -1 or $adminView === true) {
1183
            // If viewing it as admin for edition, don't show it randomly, use title + id
1184
            $randomLimit = 'ORDER BY e.question_order';
1185
        }
1186
1187
        $sql = "SELECT e.question_id
1188
                FROM $TBL_EXERCISE_QUESTION e 
1189
                INNER JOIN $TBL_QUESTIONS q
1190
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1191
                WHERE 
1192
                    e.c_id = {$this->course_id} AND 
1193
                    e.exercice_id = '".Database::escape_string($this->id)."'
1194
                    $randomLimit ";
1195
        $result = Database::query($sql);
1196
        $questionList = array();
1197
        while ($row = Database::fetch_object($result)) {
1198
            $questionList[] = $row->question_id;
1199
        }
1200
1201
        return $questionList;
1202
    }
1203
1204
    /**
1205
     * returns 'true' if the question ID is in the question list
1206
     *
1207
     * @author Olivier Brouckaert
1208
     * @param integer $questionId - question ID
1209
     * @return boolean - true if in the list, otherwise false
1210
     */
1211
    public function isInList($questionId)
1212
    {
1213
        if (is_array($this->questionList)) {
1214
            return in_array($questionId, $this->questionList);
1215
        } else {
1216
            return false;
1217
        }
1218
    }
1219
1220
    /**
1221
     * changes the exercise title
1222
     *
1223
     * @author Olivier Brouckaert
1224
     * @param string $title - exercise title
1225
     */
1226
    public function updateTitle($title)
1227
    {
1228
        $this->title = $this->exercise = $title;
1229
    }
1230
1231
    /**
1232
     * changes the exercise max attempts
1233
     *
1234
     * @param int $attempts - exercise max attempts
1235
     */
1236
    public function updateAttempts($attempts)
1237
    {
1238
        $this->attempts = $attempts;
1239
    }
1240
1241
    /**
1242
     * changes the exercise feedback type
1243
     *
1244
     * @param int $feedback_type
1245
     */
1246
    public function updateFeedbackType($feedback_type)
1247
    {
1248
        $this->feedback_type = $feedback_type;
1249
    }
1250
1251
    /**
1252
     * changes the exercise description
1253
     *
1254
     * @author Olivier Brouckaert
1255
     * @param string $description - exercise description
1256
     */
1257
    public function updateDescription($description)
1258
    {
1259
        $this->description = $description;
1260
    }
1261
1262
    /**
1263
     * changes the exercise expired_time
1264
     *
1265
     * @author Isaac flores
1266
     * @param int $expired_time The expired time of the quiz
1267
     */
1268
    public function updateExpiredTime($expired_time)
1269
    {
1270
        $this->expired_time = $expired_time;
1271
    }
1272
1273
    /**
1274
     * @param $value
1275
     */
1276
    public function updatePropagateNegative($value)
1277
    {
1278
        $this->propagate_neg = $value;
1279
    }
1280
1281
    /**
1282
     * @param $value int
1283
     */
1284
    public function updateSaveCorrectAnswers($value)
1285
    {
1286
        $this->saveCorrectAnswers = $value;
1287
    }
1288
1289
    /**
1290
     * @param $value
1291
     */
1292
    public function updateReviewAnswers($value)
1293
    {
1294
        $this->review_answers = isset($value) && $value ? true : false;
1295
    }
1296
1297
    /**
1298
     * @param $value
1299
     */
1300
    public function updatePassPercentage($value)
1301
    {
1302
        $this->pass_percentage = $value;
1303
    }
1304
1305
    /**
1306
     * @param string $text
1307
     */
1308
    public function updateEmailNotificationTemplate($text)
1309
    {
1310
        $this->emailNotificationTemplate = $text;
1311
    }
1312
1313
    /**
1314
     * @param string $text
1315
     */
1316
    public function updateEmailNotificationTemplateToUser($text)
1317
    {
1318
        $this->emailNotificationTemplateToUser = $text;
1319
    }
1320
1321
    /**
1322
     * @param string $value
1323
     */
1324
    public function setNotifyUserByEmail($value)
1325
    {
1326
        $this->notifyUserByEmail = $value;
1327
    }
1328
1329
    /**
1330
     * @param int $value
1331
     */
1332
    public function updateEndButton($value)
1333
    {
1334
        $this->endButton = (int) $value;
1335
    }
1336
1337
    /**
1338
     * @param string $value
1339
     */
1340
    public function setOnSuccessMessage($value)
1341
    {
1342
        $this->onSuccessMessage = $value;
1343
    }
1344
1345
    /**
1346
     * @param string $value
1347
     */
1348
    public function setOnFailedMessage($value)
1349
    {
1350
        $this->onFailedMessage = $value;
1351
    }
1352
1353
    /**
1354
     * @param $value
1355
     */
1356
    public function setModelType($value)
1357
    {
1358
        $this->modelType = (int) $value;
1359
    }
1360
1361
    /**
1362
     * @param int $value
1363
     */
1364
    public function setQuestionSelectionType($value)
1365
    {
1366
        $this->questionSelectionType = (int) $value;
1367
    }
1368
1369
    /**
1370
     * @return int
1371
     */
1372
    public function getQuestionSelectionType()
1373
    {
1374
        return $this->questionSelectionType;
1375
    }
1376
1377
    /**
1378
     * @param array $categories
1379
     */
1380
    public function updateCategories($categories)
1381
    {
1382
        if (!empty($categories)) {
1383
            $categories = array_map('intval', $categories);
1384
            $this->categories = $categories;
1385
        }
1386
    }
1387
1388
    /**
1389
     * changes the exercise sound file
1390
     *
1391
     * @author Olivier Brouckaert
1392
     * @param string $sound - exercise sound file
1393
     * @param string $delete - ask to delete the file
1394
     */
1395
    public function updateSound($sound, $delete)
1396
    {
1397
        global $audioPath, $documentPath;
1398
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1399
1400
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1401
            $this->sound = $sound['name'];
1402
1403
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1404
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1405
                        WHERE 
1406
                            c_id = ".$this->course_id." AND 
1407
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1408
                $result = Database::query($sql);
1409
1410
                if (!Database::num_rows($result)) {
1411
                    $id = add_document(
1412
                        $this->course,
1413
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1414
                        'file',
1415
                        $sound['size'],
1416
                        $sound['name']
1417
                    );
1418
                    api_item_property_update(
1419
                        $this->course,
1420
                        TOOL_DOCUMENT,
1421
                        $id,
1422
                        'DocumentAdded',
1423
                        api_get_user_id()
1424
                    );
1425
                    item_property_update_on_folder(
1426
                        $this->course,
1427
                        str_replace($documentPath, '', $audioPath),
1428
                        api_get_user_id()
1429
                    );
1430
                }
1431
            }
1432
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1433
            $this->sound = '';
1434
        }
1435
    }
1436
1437
    /**
1438
     * changes the exercise type
1439
     *
1440
     * @author Olivier Brouckaert
1441
     * @param integer $type - exercise type
1442
     */
1443
    public function updateType($type)
1444
    {
1445
        $this->type = $type;
1446
    }
1447
1448
    /**
1449
     * sets to 0 if questions are not selected randomly
1450
     * if questions are selected randomly, sets the draws
1451
     *
1452
     * @author Olivier Brouckaert
1453
     * @param integer $random - 0 if not random, otherwise the draws
1454
     */
1455
    public function setRandom($random)
1456
    {
1457
        /*if ($random == 'all') {
1458
            $random = $this->selectNbrQuestions();
1459
        }*/
1460
        $this->random = $random;
1461
    }
1462
1463
    /**
1464
     * sets to 0 if answers are not selected randomly
1465
     * if answers are selected randomly
1466
     * @author Juan Carlos Rana
1467
     * @param integer $random_answers - random answers
1468
     */
1469
    public function updateRandomAnswers($random_answers)
1470
    {
1471
        $this->random_answers = $random_answers;
1472
    }
1473
1474
    /**
1475
     * enables the exercise
1476
     *
1477
     * @author Olivier Brouckaert
1478
     */
1479
    public function enable()
1480
    {
1481
        $this->active = 1;
1482
    }
1483
1484
    /**
1485
     * disables the exercise
1486
     *
1487
     * @author Olivier Brouckaert
1488
     */
1489
    public function disable()
1490
    {
1491
        $this->active = 0;
1492
    }
1493
1494
    /**
1495
     * Set disable results
1496
     */
1497
    public function disable_results()
1498
    {
1499
        $this->results_disabled = true;
1500
    }
1501
1502
    /**
1503
     * Enable results
1504
     */
1505
    public function enable_results()
1506
    {
1507
        $this->results_disabled = false;
1508
    }
1509
1510
    /**
1511
     * @param int $results_disabled
1512
     */
1513
    public function updateResultsDisabled($results_disabled)
1514
    {
1515
        $this->results_disabled = (int) $results_disabled;
1516
    }
1517
1518
    /**
1519
     * updates the exercise in the data base
1520
     * @param string $type_e
1521
     * @author Olivier Brouckaert
1522
     */
1523
    public function save($type_e = '')
1524
    {
1525
        $_course = $this->course;
1526
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1527
1528
        $id = $this->id;
1529
        $exercise = $this->exercise;
1530
        $description = $this->description;
1531
        $sound = $this->sound;
1532
        $type = $this->type;
1533
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1534
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1535
        $random = $this->random;
1536
        $random_answers = $this->random_answers;
1537
        $active = $this->active;
1538
        $propagate_neg = (int) $this->propagate_neg;
1539
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1540
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1541
        $randomByCat = intval($this->randomByCat);
1542
        $text_when_finished = $this->text_when_finished;
1543
        $display_category_name = intval($this->display_category_name);
1544
        $pass_percentage = intval($this->pass_percentage);
1545
        $session_id = $this->sessionId;
1546
1547
        //If direct we do not show results
1548
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1549
            $results_disabled = 0;
1550
        } else {
1551
            $results_disabled = intval($this->results_disabled);
1552
        }
1553
1554
        $expired_time = intval($this->expired_time);
1555
1556
        // Exercise already exists
1557
        if ($id) {
1558
            // we prepare date in the database using the api_get_utc_datetime() function
1559
            if (!empty($this->start_time)) {
1560
                $start_time = $this->start_time;
1561
            } else {
1562
                $start_time = null;
1563
            }
1564
1565
            if (!empty($this->end_time)) {
1566
                $end_time = $this->end_time;
1567
            } else {
1568
                $end_time = null;
1569
            }
1570
1571
            $params = [
1572
                'title' => $exercise,
1573
                'description' => $description,
1574
            ];
1575
1576
            $paramsExtra = [];
1577
            if ($type_e != 'simple') {
1578
                $paramsExtra = [
1579
                    'sound' => $sound,
1580
                    'type' => $type,
1581
                    'random' => $random,
1582
                    'random_answers' => $random_answers,
1583
                    'active' => $active,
1584
                    'feedback_type' => $feedback_type,
1585
                    'start_time' => $start_time,
1586
                    'end_time' => $end_time,
1587
                    'max_attempt' => $attempts,
1588
                    'expired_time' => $expired_time,
1589
                    'propagate_neg' => $propagate_neg,
1590
                    'save_correct_answers' => $saveCorrectAnswers,
1591
                    'review_answers' => $review_answers,
1592
                    'random_by_category' => $randomByCat,
1593
                    'text_when_finished' => $text_when_finished,
1594
                    'display_category_name' => $display_category_name,
1595
                    'pass_percentage' => $pass_percentage,
1596
                    'results_disabled' => $results_disabled,
1597
                    'question_selection_type' => $this->getQuestionSelectionType(),
1598
                    'hide_question_title' => $this->getHideQuestionTitle()
1599
                ];
1600
            }
1601
1602
            $params = array_merge($params, $paramsExtra);
1603
1604
            Database::update(
1605
                $TBL_EXERCISES,
1606
                $params,
1607
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1608
            );
1609
1610
            // update into the item_property table
1611
            api_item_property_update(
1612
                $_course,
1613
                TOOL_QUIZ,
1614
                $id,
1615
                'QuizUpdated',
1616
                api_get_user_id()
1617
            );
1618
1619
            if (api_get_setting('search_enabled') == 'true') {
1620
                $this->search_engine_edit();
1621
            }
1622
        } else {
1623
            // Creates a new exercise
1624
1625
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1626
            // for date because, bellow, we call function api_set_default_visibility()
1627
            // In this function, api_set_default_visibility,
1628
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1629
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1630
            if (!empty($this->start_time)) {
1631
                $start_time = $this->start_time;
1632
            } else {
1633
                $start_time = null;
1634
            }
1635
1636
            if (!empty($this->end_time)) {
1637
                $end_time = $this->end_time;
1638
            } else {
1639
                $end_time = null;
1640
            }
1641
1642
            $params = [
1643
                'c_id' => $this->course_id,
1644
                'start_time' => $start_time,
1645
                'end_time' => $end_time,
1646
                'title' => $exercise,
1647
                'description' => $description,
1648
                'sound' => $sound,
1649
                'type' => $type,
1650
                'random' => $random,
1651
                'random_answers' => $random_answers,
1652
                'active' => $active,
1653
                'results_disabled' => $results_disabled,
1654
                'max_attempt' => $attempts,
1655
                'feedback_type' => $feedback_type,
1656
                'expired_time' => $expired_time,
1657
                'session_id' => $session_id,
1658
                'review_answers' => $review_answers,
1659
                'random_by_category' => $randomByCat,
1660
                'text_when_finished' => $text_when_finished,
1661
                'display_category_name' => $display_category_name,
1662
                'pass_percentage' => $pass_percentage,
1663
                'save_correct_answers' => (int) $saveCorrectAnswers,
1664
                'propagate_neg' => $propagate_neg,
1665
                'hide_question_title' => $this->getHideQuestionTitle()
1666
            ];
1667
1668
            $this->id = Database::insert($TBL_EXERCISES, $params);
1669
1670
            if ($this->id) {
1671
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1672
                Database::query($sql);
1673
1674
                $sql = "UPDATE $TBL_EXERCISES
1675
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1676
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1677
                Database::query($sql);
1678
1679
                // insert into the item_property table
1680
                api_item_property_update(
1681
                    $this->course,
1682
                    TOOL_QUIZ,
1683
                    $this->id,
1684
                    'QuizAdded',
1685
                    api_get_user_id()
1686
                );
1687
1688
                // This function save the quiz again, carefull about start_time
1689
                // and end_time if you remove this line (see above)
1690
                api_set_default_visibility(
1691
                    $this->id,
1692
                    TOOL_QUIZ,
1693
                    null,
1694
                    $this->course
1695
                );
1696
1697
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1698
                    $this->search_engine_save();
1699
                }
1700
            }
1701
        }
1702
1703
        $this->save_categories_in_exercise($this->categories);
1704
1705
        // Updates the question position
1706
        $this->update_question_positions();
1707
    }
1708
1709
    /**
1710
     * Updates question position
1711
     */
1712
    public function update_question_positions()
1713
    {
1714
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1715
        //Fixes #3483 when updating order
1716
        $question_list = $this->selectQuestionList(true);
1717
        if (!empty($question_list)) {
1718
            foreach ($question_list as $position => $questionId) {
1719
                $sql = "UPDATE $table SET
1720
                            question_order ='".intval($position)."'
1721
                        WHERE
1722
                            c_id = ".$this->course_id." AND
1723
                            question_id = ".intval($questionId)." AND
1724
                            exercice_id=".intval($this->id);
1725
                Database::query($sql);
1726
            }
1727
        }
1728
    }
1729
1730
    /**
1731
     * Adds a question into the question list
1732
     *
1733
     * @author Olivier Brouckaert
1734
     * @param integer $questionId - question ID
1735
     * @return boolean - true if the question has been added, otherwise false
1736
     */
1737
    public function addToList($questionId)
1738
    {
1739
        // checks if the question ID is not in the list
1740
        if (!$this->isInList($questionId)) {
1741
            // selects the max position
1742
            if (!$this->selectNbrQuestions()) {
1743
                $pos = 1;
1744
            } else {
1745
                if (is_array($this->questionList)) {
1746
                    $pos = max(array_keys($this->questionList)) + 1;
1747
                }
1748
            }
1749
            $this->questionList[$pos] = $questionId;
1750
1751
            return true;
1752
        }
1753
1754
        return false;
1755
    }
1756
1757
    /**
1758
     * removes a question from the question list
1759
     *
1760
     * @author Olivier Brouckaert
1761
     * @param integer $questionId - question ID
1762
     * @return boolean - true if the question has been removed, otherwise false
1763
     */
1764
    public function removeFromList($questionId)
1765
    {
1766
        // searches the position of the question ID in the list
1767
        $pos = array_search($questionId, $this->questionList);
1768
        // question not found
1769
        if ($pos === false) {
1770
            return false;
1771
        } else {
1772
            // dont reduce the number of random question if we use random by category option, or if
1773
            // random all questions
1774
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1775
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1776
                    $this->random -= 1;
1777
                    $this->save();
1778
                }
1779
            }
1780
            // deletes the position from the array containing the wanted question ID
1781
            unset($this->questionList[$pos]);
1782
1783
            return true;
1784
        }
1785
    }
1786
1787
    /**
1788
     * deletes the exercise from the database
1789
     * Notice : leaves the question in the data base
1790
     *
1791
     * @author Olivier Brouckaert
1792
     */
1793
    public function delete()
1794
    {
1795
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1796
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1797
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1798
        Database::query($sql);
1799
1800
        api_item_property_update(
1801
            $this->course,
1802
            TOOL_QUIZ,
1803
            $this->id,
1804
            'QuizDeleted',
1805
            api_get_user_id()
1806
        );
1807
        api_item_property_update(
1808
            $this->course,
1809
            TOOL_QUIZ,
1810
            $this->id,
1811
            'delete',
1812
            api_get_user_id()
1813
        );
1814
1815
        if (api_get_setting('search_enabled') == 'true' &&
1816
            extension_loaded('xapian')
1817
        ) {
1818
            $this->search_engine_delete();
1819
        }
1820
    }
1821
1822
    /**
1823
     * Creates the form to create / edit an exercise
1824
     * @param FormValidator $form
1825
     */
1826
    public function createForm($form, $type = 'full')
1827
    {
1828
        if (empty($type)) {
1829
            $type = 'full';
1830
        }
1831
1832
        // form title
1833
        if (!empty($_GET['exerciseId'])) {
1834
            $form_title = get_lang('ModifyExercise');
1835
        } else {
1836
            $form_title = get_lang('NewEx');
1837
        }
1838
1839
        $form->addElement('header', $form_title);
1840
1841
        // Title.
1842
        if (api_get_configuration_value('save_titles_as_html')) {
1843
            $form->addHtmlEditor(
1844
                'exerciseTitle',
1845
                get_lang('ExerciseName'),
1846
                false,
1847
                false,
1848
                ['ToolbarSet' => 'Minimal']
1849
            );
1850
        } else {
1851
            $form->addElement(
1852
                'text',
1853
                'exerciseTitle',
1854
                get_lang('ExerciseName'),
1855
                array('id' => 'exercise_title')
1856
            );
1857
        }
1858
1859
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1860
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1861
1862
        $editor_config = array(
1863
            'ToolbarSet' => 'TestQuestionDescription',
1864
            'Width' => '100%',
1865
            'Height' => '150',
1866
        );
1867
        if (is_array($type)) {
1868
            $editor_config = array_merge($editor_config, $type);
1869
        }
1870
1871
        $form->addHtmlEditor(
1872
            'exerciseDescription',
1873
            get_lang('ExerciseDescription'),
1874
            false,
1875
            false,
1876
            $editor_config
1877
        );
1878
1879
        if ($type == 'full') {
1880
            //Can't modify a DirectFeedback question
1881
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1882
                // feedback type
1883
                $radios_feedback = array();
1884
                $radios_feedback[] = $form->createElement(
1885
                    'radio',
1886
                    'exerciseFeedbackType',
1887
                    null,
1888
                    get_lang('ExerciseAtTheEndOfTheTest'),
1889
                    '0',
1890
                    array(
1891
                        'id' => 'exerciseType_0',
1892
                        'onclick' => 'check_feedback()',
1893
                    )
1894
                );
1895
1896 View Code Duplication
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1897
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1898
                    if ($this->selectNbrQuestions() == 0) {
1899
                        $radios_feedback[] = $form->createElement(
1900
                            'radio',
1901
                            'exerciseFeedbackType',
1902
                            null,
1903
                            get_lang('DirectFeedback'),
1904
                            '1',
1905
                            array(
1906
                                'id' => 'exerciseType_1',
1907
                                'onclick' => 'check_direct_feedback()',
1908
                            )
1909
                        );
1910
                    }
1911
                }
1912
1913
                $radios_feedback[] = $form->createElement(
1914
                    'radio',
1915
                    'exerciseFeedbackType',
1916
                    null,
1917
                    get_lang('NoFeedback'),
1918
                    '2',
1919
                    array('id' => 'exerciseType_2')
1920
                );
1921
                $form->addGroup(
1922
                    $radios_feedback,
1923
                    null,
1924
                    array(
1925
                        get_lang('FeedbackType'),
1926
                        get_lang('FeedbackDisplayOptions'),
1927
                    )
1928
                );
1929
1930
                // Type of results display on the final page
1931
                $radios_results_disabled = array();
1932
                $radios_results_disabled[] = $form->createElement(
1933
                    'radio',
1934
                    'results_disabled',
1935
                    null,
1936
                    get_lang('ShowScoreAndRightAnswer'),
1937
                    '0',
1938
                    array('id' => 'result_disabled_0')
1939
                );
1940
                $radios_results_disabled[] = $form->createElement(
1941
                    'radio',
1942
                    'results_disabled',
1943
                    null,
1944
                    get_lang('DoNotShowScoreNorRightAnswer'),
1945
                    '1',
1946
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1947
                );
1948
                $radios_results_disabled[] = $form->createElement(
1949
                    'radio',
1950
                    'results_disabled',
1951
                    null,
1952
                    get_lang('OnlyShowScore'),
1953
                    '2',
1954
                    array('id' => 'result_disabled_2')
1955
                );
1956
1957
                $radios_results_disabled[] = $form->createElement(
1958
                    'radio',
1959
                    'results_disabled',
1960
                    null,
1961
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1962
                    '4',
1963
                    array('id' => 'result_disabled_4')
1964
                );
1965
1966
                $form->addGroup(
1967
                    $radios_results_disabled,
1968
                    null,
1969
                    get_lang('ShowResultsToStudents')
1970
                );
1971
1972
                // Type of questions disposition on page
1973
                $radios = array();
1974
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1975
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'), '2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1976
1977
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1978
1979
            } else {
1980
                // if is Direct feedback but has not questions we can allow to modify the question type
1981
                if ($this->selectNbrQuestions() == 0) {
1982
                    // feedback type
1983
                    $radios_feedback = array();
1984
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'), '0', array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
1985
1986 View Code Duplication
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
1987
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1', 'onclick' => 'check_direct_feedback()'));
1988
                    }
1989
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'), '2', array('id' =>'exerciseType_2'));
1990
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'), get_lang('FeedbackDisplayOptions')));
1991
1992
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
1993
                    $radios_results_disabled = array();
1994
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1995
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'), '1', array('id'=>'result_disabled_1', 'onclick' => 'check_results_disabled()'));
1996
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'), '2', array('id'=>'result_disabled_2', 'onclick' => 'check_results_disabled()'));
1997
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
1998
1999
                    // Type of questions disposition on page
2000
                    $radios = array();
2001
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
2002
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'), '2');
2003
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2004
                } else {
2005
                    //Show options freeze
2006
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
2007
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'), '1', array('id'=>'result_disabled_1', 'onclick' => 'check_results_disabled()'));
2008
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'), '2', array('id'=>'result_disabled_2', 'onclick' => 'check_results_disabled()'));
2009
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
2010
                    $result_disable_group->freeze();
2011
2012
                    //we force the options to the DirectFeedback exercisetype
2013
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2014
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2015
2016
                    // Type of questions disposition on page
2017
                    $radios[] = $form->createElement(
2018
                        'radio',
2019
                        'exerciseType',
2020
                        null,
2021
                        get_lang('SimpleExercise'),
2022
                        '1',
2023
                        array(
2024
                            'onclick' => 'check_per_page_all()',
2025
                            'id' => 'option_page_all',
2026
                        )
2027
                    );
2028
                    $radios[] = $form->createElement(
2029
                        'radio',
2030
                        'exerciseType',
2031
                        null,
2032
                        get_lang('SequentialExercise'),
2033
                        '2',
2034
                        array(
2035
                            'onclick' => 'check_per_page_one()',
2036
                            'id' => 'option_page_one',
2037
                        )
2038
                    );
2039
2040
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2041
                    $type_group->freeze();
2042
                }
2043
            }
2044
2045
            $option = array(
2046
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2047
                //  Defined by user
2048
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2049
                // 1-10, All
2050
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2051
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2052
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2053
                // A 123 B 456 C 78 (0, 1, all)
2054
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2055
                // C 78 B 456 A 123
2056
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2057
                // A 321 B 654 C 87
2058
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2059
                // C 87 B 654 A 321
2060
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2061
                /*    B 456 C 78 A 123
2062
                        456 78 123
2063
                        123 456 78
2064
                */
2065
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2066
                /*
2067
                    A 123 B 456 C 78
2068
                    B 456 C 78 A 123
2069
                    B 654 C 87 A 321
2070
                    654 87 321
2071
                    165 842 73
2072
                */
2073
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2074
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2075
            );
2076
2077
            $form->addElement(
2078
                'select',
2079
                'question_selection_type',
2080
                array(get_lang('QuestionSelection')),
2081
                $option,
2082
                array(
2083
                    'id' => 'questionSelection',
2084
                    'onchange' => 'checkQuestionSelection()'
2085
                )
2086
            );
2087
2088
            $displayMatrix = 'none';
2089
            $displayRandom = 'none';
2090
            $selectionType = $this->getQuestionSelectionType();
2091
            switch ($selectionType) {
2092
                case EX_Q_SELECTION_RANDOM:
2093
                    $displayRandom = 'block';
2094
                    break;
2095
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2096
                    $displayMatrix = 'block';
2097
                    break;
2098
            }
2099
2100
            $form->addElement(
2101
                'html',
2102
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2103
            );
2104
            // Number of random question.
2105
            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2106
            $option = range(0, $max);
2107
            $option[0] = get_lang('No');
2108
            $option[-1] = get_lang('AllQuestionsShort');
2109
            $form->addElement(
2110
                'select',
2111
                'randomQuestions',
2112
                array(
2113
                    get_lang('RandomQuestions'),
2114
                    get_lang('RandomQuestionsHelp')
2115
                ),
2116
                $option,
2117
                array('id' => 'randomQuestions')
2118
            );
2119
            $form->addElement('html', '</div>');
2120
2121
            $form->addElement(
2122
                'html',
2123
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2124
            );
2125
2126
            // Category selection.
2127
            $cat = new TestCategory();
2128
            $cat_form = $cat->returnCategoryForm($this);
2129
            if (empty($cat_form)) {
2130
                $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>';
2131
            }
2132
            $form->addElement('label', null, $cat_form);
2133
            $form->addElement('html', '</div>');
2134
2135
            // Category name.
2136
            $radio_display_cat_name = array(
2137
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2138
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2139
            );
2140
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2141
2142
            // Random answers.
2143
            $radios_random_answers = array(
2144
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2145
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2146
            );
2147
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2148
2149
            // Hide question title.
2150
            $group = array(
2151
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2152
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2153
            );
2154
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2155
2156
            // Attempts
2157
            $attempt_option = range(0, 10);
2158
            $attempt_option[0] = get_lang('Infinite');
2159
2160
            $form->addElement(
2161
                'select',
2162
                'exerciseAttempts',
2163
                get_lang('ExerciseAttempts'),
2164
                $attempt_option,
2165
                ['id' => 'exerciseAttempts']
2166
            );
2167
2168
            // Exercise time limit
2169
            $form->addElement('checkbox', 'activate_start_date_check', null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2170
2171
            $var = self::selectTimeLimit();
2172
2173
            if (!empty($this->start_time)) {
2174
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2175
            } else {
2176
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2177
            }
2178
2179
            $form->addElement('date_time_picker', 'start_time');
2180
            $form->addElement('html', '</div>');
2181
            $form->addElement('checkbox', 'activate_end_date_check', null, get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2182
2183
            if (!empty($this->end_time)) {
2184
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2185
            } else {
2186
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2187
            }
2188
2189
            $form->addElement('date_time_picker', 'end_time');
2190
            $form->addElement('html', '</div>');
2191
2192
            $display = 'block';
2193
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2194
            $form->addCheckBox(
2195
                'save_correct_answers',
2196
                null,
2197
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2198
            );
2199
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2200
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2201
2202
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2203
2204
            // Timer control
2205
            //$time_hours_option = range(0,12);
2206
            //$time_minutes_option = range(0,59);
2207
            $form->addElement(
2208
                'checkbox',
2209
                'enabletimercontrol',
2210
                null,
2211
                get_lang('EnableTimerControl'),
2212
                array(
2213
                    'onclick' => 'option_time_expired()',
2214
                    'id' => 'enabletimercontrol',
2215
                    'onload' => 'check_load_time()',
2216
                )
2217
            );
2218
2219
            $expired_date = (int) $this->selectExpiredTime();
2220
2221
            if (($expired_date != '0')) {
2222
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2223
            } else {
2224
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2225
            }
2226
            $form->addText(
2227
                'enabletimercontroltotalminutes',
2228
                get_lang('ExerciseTotalDurationInMinutes'),
2229
                false,
2230
                [
2231
                    'id' => 'enabletimercontroltotalminutes',
2232
                    'cols-size' => [2, 2, 8]
2233
                ]
2234
            );
2235
            $form->addElement('html', '</div>');
2236
            $form->addElement(
2237
                'text',
2238
                'pass_percentage',
2239
                array(get_lang('PassPercentage'), null, '%'),
2240
                array('id' => 'pass_percentage')
2241
            );
2242
2243
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2244
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2245
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2246
2247
            // add the text_when_finished textbox
2248
            $form->addHtmlEditor(
2249
                'text_when_finished',
2250
                get_lang('TextWhenFinished'),
2251
                false,
2252
                false,
2253
                $editor_config
2254
            );
2255
2256
            $form->addCheckBox('update_title_in_lps', null, get_lang('UpdateTitleInLps'));
2257
2258
            $defaults = array();
2259
            if (api_get_setting('search_enabled') === 'true') {
2260
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2261
2262
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2263
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2264
                $specific_fields = get_specific_field_list();
2265
2266
                foreach ($specific_fields as $specific_field) {
2267
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2268
                    $filter = array(
2269
                        'c_id' => api_get_course_int_id(),
2270
                        'field_id' => $specific_field['id'],
2271
                        'ref_id' => $this->id,
2272
                        'tool_id' => "'".TOOL_QUIZ."'"
2273
                    );
2274
                    $values = get_specific_field_values_list($filter, array('value'));
2275
                    if (!empty($values)) {
2276
                        $arr_str_values = array();
2277
                        foreach ($values as $value) {
2278
                            $arr_str_values[] = $value['value'];
2279
                        }
2280
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2281
                    }
2282
                }
2283
            }
2284
2285
            $form->addElement('html', '</div>'); //End advanced setting
2286
            $form->addElement('html', '</div>');
2287
        }
2288
2289
        // submit
2290
        if (isset($_GET['exerciseId'])) {
2291
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2292
        } else {
2293
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2294
        }
2295
2296
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2297
2298
        if ($type == 'full') {
2299
            // rules
2300
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2301
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2302
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2303
        }
2304
2305
        // defaults
2306
        if ($type == 'full') {
2307
            if ($this->id > 0) {
2308
                if ($this->random > $this->selectNbrQuestions()) {
2309
                    $defaults['randomQuestions'] = $this->selectNbrQuestions();
2310
                } else {
2311
                    $defaults['randomQuestions'] = $this->random;
2312
                }
2313
2314
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2315
                $defaults['exerciseType'] = $this->selectType();
2316
                $defaults['exerciseTitle'] = $this->get_formated_title();
2317
                $defaults['exerciseDescription'] = $this->selectDescription();
2318
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2319
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2320
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2321
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2322
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2323
                $defaults['review_answers'] = $this->review_answers;
2324
                $defaults['randomByCat'] = $this->selectRandomByCat();
2325
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2326
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2327
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2328
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2329
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2330
2331
                if (!empty($this->start_time)) {
2332
                    $defaults['activate_start_date_check'] = 1;
2333
                }
2334
                if (!empty($this->end_time)) {
2335
                    $defaults['activate_end_date_check'] = 1;
2336
                }
2337
2338
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2339
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2340
2341
                // Get expired time
2342
                if ($this->expired_time != '0') {
2343
                    $defaults['enabletimercontrol'] = 1;
2344
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2345
                } else {
2346
                    $defaults['enabletimercontroltotalminutes'] = 0;
2347
                }
2348
            } else {
2349
                $defaults['exerciseType'] = 2;
2350
                $defaults['exerciseAttempts'] = 0;
2351
                $defaults['randomQuestions'] = 0;
2352
                $defaults['randomAnswers'] = 0;
2353
                $defaults['exerciseDescription'] = '';
2354
                $defaults['exerciseFeedbackType'] = 0;
2355
                $defaults['results_disabled'] = 0;
2356
                $defaults['randomByCat'] = 0;
2357
                $defaults['text_when_finished'] = '';
2358
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2359
                $defaults['display_category_name'] = 1;
2360
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2361
                $defaults['pass_percentage'] = '';
2362
                $defaults['end_button'] = $this->selectEndButton();
2363
                $defaults['question_selection_type'] = 1;
2364
                $defaults['hide_question_title'] = 0;
2365
                $defaults['on_success_message'] = null;
2366
                $defaults['on_failed_message'] = null;
2367
            }
2368
        } else {
2369
            $defaults['exerciseTitle'] = $this->selectTitle();
2370
            $defaults['exerciseDescription'] = $this->selectDescription();
2371
        }
2372
        if (api_get_setting('search_enabled') === 'true') {
2373
            $defaults['index_document'] = 'checked="checked"';
2374
        }
2375
        $form->setDefaults($defaults);
2376
2377
        // Freeze some elements.
2378
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2379
            $elementsToFreeze = array(
2380
                'randomQuestions',
2381
                //'randomByCat',
2382
                'exerciseAttempts',
2383
                'propagate_neg',
2384
                'enabletimercontrol',
2385
                'review_answers'
2386
            );
2387
2388
            foreach ($elementsToFreeze as $elementName) {
2389
                /** @var HTML_QuickForm_element $element */
2390
                $element = $form->getElement($elementName);
2391
                $element->freeze();
2392
            }
2393
        }
2394
    }
2395
2396
    /**
2397
     * function which process the creation of exercises
2398
     * @param FormValidator $form
2399
     * @param string
2400
     */
2401
    public function processCreation($form, $type = '')
2402
    {
2403
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2404
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2405
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2406
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2407
        $this->updateType($form->getSubmitValue('exerciseType'));
2408
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2409
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2410
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2411
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2412
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2413
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2414
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2415
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2416
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2417
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2418
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2419
        $this->updateCategories($form->getSubmitValue('category'));
2420
        $this->updateEndButton($form->getSubmitValue('end_button'));
2421
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2422
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2423
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2424
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2425
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2426
        $this->setModelType($form->getSubmitValue('model_type'));
2427
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2428
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2429
        $this->sessionId = api_get_session_id();
2430
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2431
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2432
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2433
2434
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2435
            $start_time = $form->getSubmitValue('start_time');
2436
            $this->start_time = api_get_utc_datetime($start_time);
2437
        } else {
2438
            $this->start_time = null;
2439
        }
2440
2441
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2442
            $end_time = $form->getSubmitValue('end_time');
2443
            $this->end_time = api_get_utc_datetime($end_time);
2444
        } else {
2445
            $this->end_time = null;
2446
        }
2447
2448
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2449
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2450
            if ($this->expired_time == 0) {
2451
                $this->expired_time = $expired_total_time;
2452
            }
2453
        } else {
2454
            $this->expired_time = 0;
2455
        }
2456
2457
        if ($form->getSubmitValue('randomAnswers') == 1) {
2458
            $this->random_answers = 1;
2459
        } else {
2460
            $this->random_answers = 0;
2461
        }
2462
2463
        // Update title in all LPs that have this quiz added
2464
        if ($form->getSubmitValue('update_title_in_lps') == 1) {
2465
            $courseId = api_get_course_int_id();
2466
            $table = Database::get_course_table(TABLE_LP_ITEM);
2467
            $sql = "SELECT * FROM $table 
2468
                    WHERE 
2469
                        c_id = $courseId AND 
2470
                        item_type = 'quiz' AND 
2471
                        path = '".$this->id."'
2472
                    ";
2473
            $result = Database::query($sql);
2474
            $items = Database::store_result($result);
2475
            if (!empty($items)) {
2476
                foreach ($items as $item) {
2477
                    $itemId = $item['iid'];
2478
                    $sql = "UPDATE $table SET title = '".$this->title."'                             
2479
                            WHERE iid = $itemId AND c_id = $courseId ";
2480
                    Database::query($sql);
2481
                }
2482
            }
2483
        }
2484
2485
        $this->save($type);
2486
    }
2487
2488
    function search_engine_save()
2489
    {
2490
        if ($_POST['index_document'] != 1) {
2491
            return;
2492
        }
2493
        $course_id = api_get_course_id();
2494
2495
        require_once api_get_path(LIBRARY_PATH).'search/ChamiloIndexer.class.php';
2496
        require_once api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php';
2497
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2498
2499
        $specific_fields = get_specific_field_list();
2500
        $ic_slide = new IndexableChunk();
2501
2502
        $all_specific_terms = '';
2503 View Code Duplication
        foreach ($specific_fields as $specific_field) {
2504
            if (isset($_REQUEST[$specific_field['code']])) {
2505
                $sterms = trim($_REQUEST[$specific_field['code']]);
2506
                if (!empty($sterms)) {
2507
                    $all_specific_terms .= ' '.$sterms;
2508
                    $sterms = explode(',', $sterms);
2509
                    foreach ($sterms as $sterm) {
2510
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2511
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2512
                    }
2513
                }
2514
            }
2515
        }
2516
2517
        // build the chunk to index
2518
        $ic_slide->addValue("title", $this->exercise);
2519
        $ic_slide->addCourseId($course_id);
2520
        $ic_slide->addToolId(TOOL_QUIZ);
2521
        $xapian_data = array(
2522
            SE_COURSE_ID => $course_id,
2523
            SE_TOOL_ID => TOOL_QUIZ,
2524
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id),
2525
            SE_USER => (int) api_get_user_id(),
2526
        );
2527
        $ic_slide->xapian_data = serialize($xapian_data);
2528
        $exercise_description = $all_specific_terms.' '.$this->description;
2529
        $ic_slide->addValue("content", $exercise_description);
2530
2531
        $di = new ChamiloIndexer();
2532
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2533
        $di->connectDb(NULL, NULL, $lang);
2534
        $di->addChunk($ic_slide);
2535
2536
        //index and return search engine document id
2537
        $did = $di->index();
2538
        if ($did) {
2539
            // save it to db
2540
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2541
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2542
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2543
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2544
            Database::query($sql);
2545
        }
2546
    }
2547
2548
    function search_engine_edit()
2549
    {
2550
        // update search enchine and its values table if enabled
2551
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2552
            $course_id = api_get_course_id();
2553
2554
            // actually, it consists on delete terms from db,
2555
            // insert new ones, create a new search engine document, and remove the old one
2556
            // get search_did
2557
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2558
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2559
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2560
            $res = Database::query($sql);
2561
2562
            if (Database::num_rows($res) > 0) {
2563
                require_once(api_get_path(LIBRARY_PATH).'search/ChamiloIndexer.class.php');
2564
                require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
2565
                require_once(api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php');
2566
2567
                $se_ref = Database::fetch_array($res);
2568
                $specific_fields = get_specific_field_list();
2569
                $ic_slide = new IndexableChunk();
2570
2571
                $all_specific_terms = '';
2572
                foreach ($specific_fields as $specific_field) {
2573
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2574
                    if (isset($_REQUEST[$specific_field['code']])) {
2575
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2576
                        $all_specific_terms .= ' '.$sterms;
2577
                        $sterms = explode(',', $sterms);
2578
                        foreach ($sterms as $sterm) {
2579
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2580
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2581
                        }
2582
                    }
2583
                }
2584
2585
                // build the chunk to index
2586
                $ic_slide->addValue("title", $this->exercise);
2587
                $ic_slide->addCourseId($course_id);
2588
                $ic_slide->addToolId(TOOL_QUIZ);
2589
                $xapian_data = array(
2590
                    SE_COURSE_ID => $course_id,
2591
                    SE_TOOL_ID => TOOL_QUIZ,
2592
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id),
2593
                    SE_USER => (int) api_get_user_id(),
2594
                );
2595
                $ic_slide->xapian_data = serialize($xapian_data);
2596
                $exercise_description = $all_specific_terms.' '.$this->description;
2597
                $ic_slide->addValue("content", $exercise_description);
2598
2599
                $di = new ChamiloIndexer();
2600
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2601
                $di->connectDb(null, null, $lang);
2602
                $di->remove_document((int) $se_ref['search_did']);
2603
                $di->addChunk($ic_slide);
2604
2605
                //index and return search engine document id
2606
                $did = $di->index();
2607
                if ($did) {
2608
                    // save it to db
2609
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2610
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2611
                    Database::query($sql);
2612
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2613
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2614
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2615
                    Database::query($sql);
2616
                }
2617
            } else {
2618
                $this->search_engine_save();
2619
            }
2620
        }
2621
    }
2622
2623
    function search_engine_delete()
2624
    {
2625
        // remove from search engine if enabled
2626
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2627
            $course_id = api_get_course_id();
2628
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2629
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
2630
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2631
            $res = Database::query($sql);
2632
            if (Database::num_rows($res) > 0) {
2633
                $row = Database::fetch_array($res);
2634
                require_once(api_get_path(LIBRARY_PATH).'search/ChamiloIndexer.class.php');
2635
                $di = new ChamiloIndexer();
2636
                $di->remove_document((int) $row['search_did']);
2637
                unset($di);
2638
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2639
                foreach ($this->questionList as $question_i) {
2640
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2641
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2642
                    $qres = Database::query($sql);
2643
                    if (Database::num_rows($qres) > 0) {
2644
                        $qrow = Database::fetch_array($qres);
2645
                        $objQuestion = Question::getInstance($qrow['type']);
2646
                        $objQuestion = Question::read((int) $question_i);
2647
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2648
                        unset($objQuestion);
2649
                    }
2650
                }
2651
            }
2652
            $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
2653
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2654
            Database::query($sql);
2655
2656
            // remove terms from db
2657
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2658
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2659
        }
2660
    }
2661
2662
    public function selectExpiredTime()
2663
    {
2664
        return $this->expired_time;
2665
    }
2666
2667
    /**
2668
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2669
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2670
     * Works with exercises in sessions
2671
     * @param bool $cleanLpTests
2672
     * @param string $cleanResultBeforeDate
2673
     *
2674
     * @return int quantity of user's exercises deleted
2675
     */
2676
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2677
    {
2678
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2679
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2680
2681
        $sql_where = '  AND
2682
                        orig_lp_id = 0 AND
2683
                        orig_lp_item_id = 0';
2684
2685
        // if we want to delete results from LP too
2686
        if ($cleanLpTests) {
2687
            $sql_where = "";
2688
        }
2689
2690
        // if we want to delete attempts before date $cleanResultBeforeDate
2691
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2692
2693
        if (!empty($cleanResultBeforeDate)) {
2694
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2695
            if (api_is_valid_date($cleanResultBeforeDate)) {
2696
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2697
            } else {
2698
                return 0;
2699
            }
2700
        }
2701
2702
        $sql = "SELECT exe_id
2703
                FROM $table_track_e_exercises
2704
                WHERE
2705
                    c_id = ".api_get_course_int_id()." AND
2706
                    exe_exo_id = ".$this->id." AND
2707
                    session_id = ".api_get_session_id()." ".
2708
                    $sql_where;
2709
2710
        $result   = Database::query($sql);
2711
        $exe_list = Database::store_result($result);
2712
2713
        // deleting TRACK_E_ATTEMPT table
2714
        // check if exe in learning path or not
2715
        $i = 0;
2716
        if (is_array($exe_list) && count($exe_list) > 0) {
2717
            foreach ($exe_list as $item) {
2718
                $sql = "DELETE FROM $table_track_e_attempt
2719
                        WHERE exe_id = '".$item['exe_id']."'";
2720
                Database::query($sql);
2721
                $i++;
2722
            }
2723
        }
2724
2725
        $session_id = api_get_session_id();
2726
        // delete TRACK_E_EXERCISES table
2727
        $sql = "DELETE FROM $table_track_e_exercises
2728
                WHERE c_id = ".api_get_course_int_id()."
2729
                AND exe_exo_id = ".$this->id."
2730
                $sql_where
2731
                AND session_id = ".$session_id."";
2732
        Database::query($sql);
2733
2734
        Event::addEvent(
2735
            LOG_EXERCISE_RESULT_DELETE,
2736
            LOG_EXERCISE_ID,
2737
            $this->id,
2738
            null,
2739
            null,
2740
            api_get_course_int_id(),
2741
            $session_id
2742
        );
2743
2744
        return $i;
2745
    }
2746
2747
    /**
2748
     * Copies an exercise (duplicate all questions and answers)
2749
     */
2750
    public function copy_exercise()
2751
    {
2752
        $exercise_obj = $this;
2753
2754
        // force the creation of a new exercise
2755
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2756
        //Hides the new exercise
2757
        $exercise_obj->updateStatus(false);
2758
        $exercise_obj->updateId(0);
2759
        $exercise_obj->save();
2760
2761
        $new_exercise_id = $exercise_obj->selectId();
2762
        $question_list 	 = $exercise_obj->selectQuestionList();
2763
2764
        if (!empty($question_list)) {
2765
            //Question creation
2766
2767
            foreach ($question_list as $old_question_id) {
2768
                $old_question_obj = Question::read($old_question_id);
2769
                $new_id = $old_question_obj->duplicate();
2770
                if ($new_id) {
2771
                    $new_question_obj = Question::read($new_id);
2772
2773
                    if (isset($new_question_obj) && $new_question_obj) {
2774
                        $new_question_obj->addToList($new_exercise_id);
2775
                        // This should be moved to the duplicate function
2776
                        $new_answer_obj = new Answer($old_question_id);
2777
                        $new_answer_obj->read();
2778
                        $new_answer_obj->duplicate($new_question_obj);
2779
                    }
2780
                }
2781
            }
2782
        }
2783
    }
2784
2785
    /**
2786
     * Changes the exercise id
2787
     *
2788
     * @param int $id - exercise id
2789
     */
2790
    private function updateId($id)
2791
    {
2792
        $this->id = $id;
2793
    }
2794
2795
    /**
2796
     * Changes the exercise status
2797
     *
2798
     * @param string $status - exercise status
2799
     */
2800
    function updateStatus($status)
2801
    {
2802
        $this->active = $status;
2803
    }
2804
2805
    /**
2806
     * @param int $lp_id
2807
     * @param int $lp_item_id
2808
     * @param int $lp_item_view_id
2809
     * @param string $status
2810
     * @return array
2811
     */
2812
    public function get_stat_track_exercise_info(
2813
        $lp_id = 0,
2814
        $lp_item_id = 0,
2815
        $lp_item_view_id = 0,
2816
        $status = 'incomplete'
2817
    ) {
2818
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2819
        if (empty($lp_id)) {
2820
            $lp_id = 0;
2821
        }
2822
        if (empty($lp_item_id)) {
2823
            $lp_item_id = 0;
2824
        }
2825
        if (empty($lp_item_view_id)) {
2826
            $lp_item_view_id = 0;
2827
        }
2828
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
2829
					   exe_user_id 			= ' . "'".api_get_user_id()."'".' AND
2830
					   c_id                 = ' . api_get_course_int_id().' AND
2831
					   status 				= ' . "'".Database::escape_string($status)."'".' AND
2832
					   orig_lp_id 			= ' . "'".$lp_id."'".' AND
2833
					   orig_lp_item_id 		= ' . "'".$lp_item_id."'".' AND
2834
                       orig_lp_item_view_id = ' . "'".$lp_item_view_id."'".' AND
2835
					   session_id 			= ' . "'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
2836
2837
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2838
2839
        $result = Database::query($sql_track);
2840
        $new_array = array();
2841
        if (Database::num_rows($result) > 0) {
2842
            $new_array = Database::fetch_array($result, 'ASSOC');
2843
            $new_array['num_exe'] = Database::num_rows($result);
2844
        }
2845
2846
        return $new_array;
2847
    }
2848
2849
    /**
2850
     * Saves a test attempt
2851
     *
2852
     * @param int  $clock_expired_time clock_expired_time
2853
     * @param int  int lp id
2854
     * @param int  int lp item id
2855
     * @param int  int lp item_view id
2856
     * @param array $questionList
2857
     * @param float $weight
2858
     *
2859
     * @return int
2860
     */
2861
    public function save_stat_track_exercise_info(
2862
        $clock_expired_time = 0,
2863
        $safe_lp_id = 0,
2864
        $safe_lp_item_id = 0,
2865
        $safe_lp_item_view_id = 0,
2866
        $questionList = array(),
2867
        $weight = 0
2868
    ) {
2869
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2870
        $safe_lp_id = intval($safe_lp_id);
2871
        $safe_lp_item_id = intval($safe_lp_item_id);
2872
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2873
2874
        if (empty($safe_lp_id)) {
2875
            $safe_lp_id = 0;
2876
        }
2877
        if (empty($safe_lp_item_id)) {
2878
            $safe_lp_item_id = 0;
2879
        }
2880
        if (empty($clock_expired_time)) {
2881
            $clock_expired_time = null;
2882
        }
2883
2884
        $questionList = array_map('intval', $questionList);
2885
2886
        $params = array(
2887
            'exe_exo_id' => $this->id,
2888
            'exe_user_id' => api_get_user_id(),
2889
            'c_id' => api_get_course_int_id(),
2890
            'status' =>  'incomplete',
2891
            'session_id'  => api_get_session_id(),
2892
            'data_tracking'  => implode(',', $questionList),
2893
            'start_date' => api_get_utc_datetime(),
2894
            'orig_lp_id' => $safe_lp_id,
2895
            'orig_lp_item_id'  => $safe_lp_item_id,
2896
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2897
            'exe_weighting'=> $weight,
2898
            'user_ip' => api_get_real_ip(),
2899
            'exe_date' => api_get_utc_datetime(),
2900
            'exe_result' => 0,
2901
            'steps_counter' => 0,
2902
            'exe_duration' => 0,
2903
            'expired_time_control' => $clock_expired_time,
2904
            'questions_to_check' => ''
2905
        );
2906
2907
        $id = Database::insert($track_exercises, $params);
2908
2909
        return $id;
2910
    }
2911
2912
    /**
2913
     * @param int $question_id
2914
     * @param int $questionNum
2915
     * @param array $questions_in_media
2916
     * @param string $currentAnswer
2917
     * @return string
2918
     */
2919
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2920
    {
2921
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2922
        $nbrQuestions = $this->get_count_question_list();
2923
        $all_button = [];
2924
        $html = $label = '';
2925
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
2926
2927
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2928
            $urlTitle = get_lang('ContinueTest');
2929
2930
            if ($questionNum == count($this->questionList)) {
2931
                $urlTitle = get_lang('EndTest');
2932
            }
2933
2934
            $html .= Display::url(
2935
                $urlTitle,
2936
                'exercise_submit_modal.php?'.http_build_query([
2937
                    'learnpath_id' => $safe_lp_id,
2938
                    'learnpath_item_id' => $safe_lp_item_id,
2939
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2940
                    'origin' => $origin,
2941
                    'hotspot' => $hotspot_get,
2942
                    'nbrQuestions' => $nbrQuestions,
2943
                    'num' => $questionNum,
2944
                    'exerciseType' => $this->type,
2945
                    'exerciseId' => $this->id
2946
                ]),
2947
                [
2948
                    'class' => 'ajax btn btn-default',
2949
                    'data-title' => $urlTitle,
2950
                    'data-size' => 'md'
2951
                ]
2952
            );
2953
            $html .= '<br />';
2954
        } else {
2955
            // User
2956
            if (api_is_allowed_to_session_edit()) {
2957
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2958 View Code Duplication
                    if ($this->review_answers) {
2959
                        $label = get_lang('ReviewQuestions');
2960
                        $class = 'btn btn-success';
2961
                    } else {
2962
                        $label = get_lang('EndTest');
2963
                        $class = 'btn btn-warning';
2964
                    }
2965
                } else {
2966
                    $label = get_lang('NextQuestion');
2967
                    $class = 'btn btn-primary';
2968
                }
2969
				$class .= ' question-validate-btn'; // used to select it with jquery
2970
                if ($this->type == ONE_PER_PAGE) {
2971
                    if ($questionNum != 1) {
2972
                        $prev_question = $questionNum - 2;
2973
                        $all_button[] = Display::button(
2974
                            'previous_question_and_save',
2975
                            get_lang('PreviousQuestion'),
2976
                            [
2977
                                'type' => 'button',
2978
                                'class' => 'btn btn-default',
2979
                                'data-prev' => $prev_question,
2980
                                'data-question' => $question_id
2981
                            ]
2982
                        );
2983
                    }
2984
2985
                    //Next question
2986
                    if (!empty($questions_in_media)) {
2987
                        $all_button[] = Display::button(
2988
                            'save_question_list',
2989
                            $label,
2990
                            [
2991
                                'type' => 'button',
2992
                                'class' => $class,
2993
                                'data-list' => implode(",", $questions_in_media)
2994
                            ]
2995
                        );
2996
                    } else {
2997
                        $all_button[] = Display::button(
2998
                            'save_now',
2999
                            $label,
3000
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3001
                        );
3002
                    }
3003
                    $all_button[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3004
3005
                    $html .= implode(PHP_EOL, $all_button);
3006
                } else {
3007 View Code Duplication
                    if ($this->review_answers) {
3008
                        $all_label = get_lang('ReviewQuestions');
3009
                        $class = 'btn btn-success';
3010
                    } else {
3011
                        $all_label = get_lang('EndTest');
3012
                        $class = 'btn btn-warning';
3013
                    }
3014
					$class .= ' question-validate-btn'; // used to select it with jquery
3015
                    $all_button[] = Display::button(
3016
                        'validate_all',
3017
                        $all_label,
3018
                        ['type' => 'button', 'class' => $class]
3019
                    );
3020
                    $all_button[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_reponse']);
3021
                    $html .= implode(PHP_EOL, $all_button);
3022
                }
3023
            }
3024
        }
3025
3026
        return $html;
3027
    }
3028
3029
    /**
3030
     * So the time control will work
3031
     *
3032
     * @param string $time_left
3033
     * @return string
3034
     */
3035
    public function show_time_control_js($time_left)
3036
    {
3037
        $time_left = intval($time_left);
3038
        return "<script>
3039
3040
            function get_expired_date_string(expired_time) {
3041
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3042
                var day, month, year, hours, minutes, seconds, date_string;
3043
                var obj_date = new Date(expired_time);
3044
                day     = obj_date.getDate();
3045
                if (day < 10) day = '0' + day;
3046
                    month   = obj_date.getMonth();
3047
                    year    = obj_date.getFullYear();
3048
                    hours   = obj_date.getHours();
3049
                if (hours < 10) hours = '0' + hours;
3050
                minutes = obj_date.getMinutes();
3051
                if (minutes < 10) minutes = '0' + minutes;
3052
                seconds = obj_date.getSeconds();
3053
                if (seconds < 10) seconds = '0' + seconds;
3054
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3055
                return date_string;
3056
            }
3057
3058
            function open_clock_warning() {
3059
                $('#clock_warning').dialog({
3060
                    modal:true,
3061
                    height:250,
3062
                    closeOnEscape: false,
3063
                    resizable: false,
3064
                    buttons: {
3065
                        '".addslashes(get_lang("EndTest"))."': function() {
3066
                            $('#clock_warning').dialog('close');
3067
                        }
3068
                    },
3069
                    close: function() {
3070
                        send_form();
3071
                    }
3072
                });
3073
                $('#clock_warning').dialog('open');
3074
3075
                $('#counter_to_redirect').epiclock({
3076
                    mode: $.epiclock.modes.countdown,
3077
                    offset: {seconds: 5},
3078
                    format: 's'
3079
                }).bind('timer', function () {
3080
                    send_form();
3081
                });
3082
            }
3083
3084
            function send_form() {
3085
                if ($('#exercise_form').length) {
3086
                    $('#exercise_form').submit();
3087
                } else {
3088
                    // In exercise_reminder.php
3089
                    final_submit();
3090
                }
3091
            }
3092
3093
            function onExpiredTimeExercise() {
3094
                $('#wrapper-clock').hide();
3095
                //$('#exercise_form').hide();
3096
                $('#expired-message-id').show();
3097
3098
                //Fixes bug #5263
3099
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3100
                open_clock_warning();
3101
            }
3102
3103
			$(document).ready(function() {
3104
				var current_time = new Date().getTime();
3105
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3106
				var expired_time = current_time + (time_left*1000);
3107
				var expired_date = get_expired_date_string(expired_time);
3108
3109
                $('#exercise_clock_warning').epiclock({
3110
                    mode: $.epiclock.modes.countdown,
3111
                    offset: {seconds: time_left},
3112
                    format: 'x:i:s',
3113
                    renderer: 'minute'
3114
                }).bind('timer', function () {
3115
                    onExpiredTimeExercise();
3116
                });
3117
	       		$('#submit_save').click(function () {});
3118
	    });
3119
	    </script>";
3120
    }
3121
3122
    /**
3123
     * Lp javascript for hotspots
3124
     */
3125
    public function show_lp_javascript()
3126
    {
3127
        return '';
3128
    }
3129
3130
    /**
3131
     * This function was originally found in the exercise_show.php
3132
     * @param int $exeId
3133
     * @param int $questionId
3134
     * @param int $choice the user selected
3135
     * @param string $from  function is called from 'exercise_show' or 'exercise_result'
3136
     * @param array $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3137
     * @param bool $saved_results save results in the DB or just show the reponse
3138
     * @param bool $from_database gets information from DB or from the current selection
3139
     * @param bool $show_result show results or not
3140
     * @param int $propagate_neg
3141
     * @param array $hotspot_delineation_result
3142
     * @param bool $showTotalScoreAndUserChoicesInLastAttempt
3143
     * @todo    reduce parameters of this function
3144
     * @return  string  html code
3145
     */
3146
    public function manage_answer(
3147
        $exeId,
3148
        $questionId,
3149
        $choice,
3150
        $from = 'exercise_show',
3151
        $exerciseResultCoordinates = [],
3152
        $saved_results = true,
3153
        $from_database = false,
3154
        $show_result = true,
3155
        $propagate_neg = 0,
3156
        $hotspot_delineation_result = [],
3157
        $showTotalScoreAndUserChoicesInLastAttempt = true
3158
    ) {
3159
        global $debug;
3160
        //needed in order to use in the exercise_attempt() for the time
3161
        global $learnpath_id, $learnpath_item_id;
3162
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3163
        $em = Database::getManager();
3164
        $feedback_type = $this->selectFeedbackType();
3165
        $results_disabled = $this->selectResultsDisabled();
3166
3167
        if ($debug) {
3168
            error_log("<------ manage_answer ------> ");
3169
            error_log('exe_id: '.$exeId);
3170
            error_log('$from:  '.$from);
3171
            error_log('$saved_results: '.intval($saved_results));
3172
            error_log('$from_database: '.intval($from_database));
3173
            error_log('$show_result: '.$show_result);
3174
            error_log('$propagate_neg: '.$propagate_neg);
3175
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3176
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3177
            error_log('$learnpath_id: '.$learnpath_id);
3178
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3179
            error_log('$choice: '.print_r($choice, 1));
3180
        }
3181
3182
        $final_overlap = 0;
3183
        $final_missing = 0;
3184
        $final_excess = 0;
3185
        $overlap_color = 0;
3186
        $missing_color = 0;
3187
        $excess_color = 0;
3188
        $threadhold1 = 0;
3189
        $threadhold2 = 0;
3190
        $threadhold3 = 0;
3191
        $arrques = null;
3192
        $arrans  = null;
3193
        $questionId = intval($questionId);
3194
        $exeId = intval($exeId);
3195
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3196
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3197
3198
        // Creates a temporary Question object
3199
        $course_id = $this->course_id;
3200
        $objQuestionTmp = Question::read($questionId, $course_id);
3201
3202
        if ($objQuestionTmp === false) {
3203
            return false;
3204
        }
3205
3206
        $questionName = $objQuestionTmp->selectTitle();
3207
        $questionWeighting = $objQuestionTmp->selectWeighting();
3208
        $answerType = $objQuestionTmp->selectType();
3209
        $quesId = $objQuestionTmp->selectId();
3210
        $extra = $objQuestionTmp->extra;
0 ignored issues
show
Bug introduced by
The property extra does not seem to exist in Question.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3211
        $next = 1; //not for now
3212
        $totalWeighting = 0;
3213
        $totalScore = 0;
3214
3215
        // Extra information of the question
3216
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE && !empty($extra)) {
3217
            $extra = explode(':', $extra);
3218
            if ($debug) {
3219
                error_log(print_r($extra, 1));
3220
            }
3221
            // Fixes problems with negatives values using intval
3222
            $true_score = floatval(trim($extra[0]));
3223
            $false_score = floatval(trim($extra[1]));
3224
            $doubt_score = floatval(trim($extra[2]));
3225
        }
3226
3227
        // Construction of the Answer object
3228
        $objAnswerTmp = new Answer($questionId);
3229
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3230
3231
        if ($debug) {
3232
            error_log('Count of answers: '.$nbrAnswers);
3233
            error_log('$answerType: '.$answerType);
3234
        }
3235
3236
        if ($answerType == FREE_ANSWER ||
3237
            $answerType == ORAL_EXPRESSION ||
3238
            $answerType == CALCULATED_ANSWER ||
3239
            $answerType == ANNOTATION
3240
        ) {
3241
            $nbrAnswers = 1;
3242
        }
3243
3244
        if ($answerType == ORAL_EXPRESSION) {
3245
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3246
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3247
3248
            $objQuestionTmp->initFile(
3249
                api_get_session_id(),
3250
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3251
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3252
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3253
            );
3254
3255
            //probably this attempt came in an exercise all question by page
3256
            if ($feedback_type == 0) {
3257
                $objQuestionTmp->replaceWithRealExe($exeId);
3258
            }
3259
        }
3260
3261
        $user_answer = '';
3262
        // Get answer list for matching
3263
        $sql = "SELECT id_auto, id, answer
3264
                FROM $table_ans
3265
                WHERE c_id = $course_id AND question_id = $questionId";
3266
        $res_answer = Database::query($sql);
3267
3268
        $answerMatching = array();
3269
        while ($real_answer = Database::fetch_array($res_answer)) {
3270
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3271
        }
3272
3273
        $real_answers = array();
3274
        $quiz_question_options = Question::readQuestionOption(
3275
            $questionId,
3276
            $course_id
3277
        );
3278
3279
        $organs_at_risk_hit = 0;
3280
        $questionScore = 0;
3281
        $answer_correct_array = array();
3282
        $orderedHotspots = [];
3283
3284
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3285
            $orderedHotspots = $em
3286
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3287
                ->findBy([
3288
                        'hotspotQuestionId' => $questionId,
3289
                        'cId' => $course_id,
3290
                        'hotspotExeId' => $exeId
3291
                    ],
3292
                    ['hotspotAnswerId' => 'ASC']
3293
                );
3294
        }
3295
3296
        if ($debug) error_log('Start answer loop ');
3297
3298
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3299
            $answer = $objAnswerTmp->selectAnswer($answerId);
3300
            $answerComment = $objAnswerTmp->selectComment($answerId);
3301
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3302
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3303
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3304
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3305
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3306
3307
            if ($debug) {
3308
                error_log("answer auto id: $answerAutoId ");
3309
                error_log("answer correct: $answerCorrect ");
3310
            }
3311
3312
            // Delineation
3313
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3314
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3315
3316
            switch ($answerType) {
3317
                // for unique answer
3318
                case UNIQUE_ANSWER:
3319
                case UNIQUE_ANSWER_IMAGE:
3320
                case UNIQUE_ANSWER_NO_OPTION:
3321
                case READING_COMPREHENSION:
3322
                    if ($from_database) {
3323
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3324
                                WHERE
3325
                                    exe_id = '".$exeId."' AND
3326
                                    question_id= '".$questionId."'";
3327
                        $result = Database::query($sql);
3328
                        $choice = Database::result($result, 0, "answer");
3329
3330
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3331
                        if ($studentChoice) {
3332
                            $questionScore += $answerWeighting;
3333
                            $totalScore += $answerWeighting;
3334
                        }
3335
                    } else {
3336
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3337
                        if ($studentChoice) {
3338
                            $questionScore += $answerWeighting;
3339
                            $totalScore += $answerWeighting;
3340
                        }
3341
                    }
3342
                    break;
3343
                // for multiple answers
3344
                case MULTIPLE_ANSWER_TRUE_FALSE:
3345
                    if ($from_database) {
3346
                        $choice = array();
3347
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3348
                                WHERE
3349
                                    exe_id = $exeId AND
3350
                                    question_id = ".$questionId;
3351
3352
                        $result = Database::query($sql);
3353 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3354
                            $ind = $row['answer'];
3355
                            $values = explode(':', $ind);
3356
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3357
                            $option = isset($values[1]) ? $values[1] : '';
3358
                            $choice[$my_answer_id] = $option;
3359
                        }
3360
                    }
3361
3362
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3363
3364
                    if (!empty($studentChoice)) {
3365
                        if ($studentChoice == $answerCorrect) {
3366
                            $questionScore += $true_score;
3367
                        } else {
3368
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3369
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3370
                            ) {
3371
                                $questionScore += $doubt_score;
3372
                            } else {
3373
                                $questionScore += $false_score;
3374
                            }
3375
                        }
3376
                    } else {
3377
                        // If no result then the user just hit don't know
3378
                        $studentChoice = 3;
3379
                        $questionScore += $doubt_score;
3380
                    }
3381
                    $totalScore = $questionScore;
3382
                    break;
3383 View Code Duplication
                case MULTIPLE_ANSWER: //2
3384
                    if ($from_database) {
3385
                        $choice = array();
3386
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3387
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3388
                        $resultans = Database::query($sql);
3389
                        while ($row = Database::fetch_array($resultans)) {
3390
                            $ind = $row['answer'];
3391
                            $choice[$ind] = 1;
3392
                        }
3393
3394
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3395
                        $real_answers[$answerId] = (bool) $studentChoice;
3396
3397
                        if ($studentChoice) {
3398
                            $questionScore += $answerWeighting;
3399
                        }
3400
                    } else {
3401
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3402
                        $real_answers[$answerId] = (bool) $studentChoice;
3403
3404
                        if (isset($studentChoice)) {
3405
                            $questionScore += $answerWeighting;
3406
                        }
3407
                    }
3408
                    $totalScore += $answerWeighting;
3409
3410
                    if ($debug) error_log("studentChoice: $studentChoice");
3411
                    break;
3412 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
3413
                    if ($from_database) {
3414
                        $choice = array();
3415
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3416
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3417
                        $resultans = Database::query($sql);
3418
                        while ($row = Database::fetch_array($resultans)) {
3419
                            $ind = $row['answer'];
3420
                            $choice[$ind] = 1;
3421
                        }
3422
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3423
                        $real_answers[$answerId] = (bool) $studentChoice;
3424
                        if ($studentChoice) {
3425
                            $questionScore += $answerWeighting;
3426
                        }
3427
                    } else {
3428
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3429
                        if (isset($studentChoice)) {
3430
                            $questionScore += $answerWeighting;
3431
                        }
3432
                        $real_answers[$answerId] = (bool) $studentChoice;
3433
                    }
3434
                    $totalScore += $answerWeighting;
3435
                    if ($debug) error_log("studentChoice: $studentChoice");
3436
                    break;
3437
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3438
                    if ($from_database) {
3439
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3440
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3441
                        $resultans = Database::query($sql);
3442 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3443
                            $ind = $row['answer'];
3444
                            $result = explode(':', $ind);
3445
                            if (isset($result[0])) {
3446
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3447
                                $option = isset($result[1]) ? $result[1] : '';
3448
                                $choice[$my_answer_id] = $option;
3449
                            }
3450
                        }
3451
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3452
3453
                        if ($answerCorrect == $studentChoice) {
3454
                            //$answerCorrect = 1;
3455
                            $real_answers[$answerId] = true;
3456
                        } else {
3457
                            //$answerCorrect = 0;
3458
                            $real_answers[$answerId] = false;
3459
                        }
3460
                    } else {
3461
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3462
                        if ($answerCorrect == $studentChoice) {
3463
                            //$answerCorrect = 1;
3464
                            $real_answers[$answerId] = true;
3465
                        } else {
3466
                            //$answerCorrect = 0;
3467
                            $real_answers[$answerId] = false;
3468
                        }
3469
                    }
3470
                    break;
3471
                case MULTIPLE_ANSWER_COMBINATION:
3472
                    if ($from_database) {
3473
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3474
                                WHERE exe_id = $exeId AND question_id= $questionId";
3475
                        $resultans = Database::query($sql);
3476
                        while ($row = Database::fetch_array($resultans)) {
3477
                            $ind = $row['answer'];
3478
                            $choice[$ind] = 1;
3479
                        }
3480
3481
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3482
3483 View Code Duplication
                        if ($answerCorrect == 1) {
3484
                            if ($studentChoice) {
3485
                                $real_answers[$answerId] = true;
3486
                            } else {
3487
                                $real_answers[$answerId] = false;
3488
                            }
3489
                        } else {
3490
                            if ($studentChoice) {
3491
                                $real_answers[$answerId] = false;
3492
                            } else {
3493
                                $real_answers[$answerId] = true;
3494
                            }
3495
                        }
3496
                    } else {
3497
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3498
3499 View Code Duplication
                        if ($answerCorrect == 1) {
3500
                            if ($studentChoice) {
3501
                                $real_answers[$answerId] = true;
3502
                            } else {
3503
                                $real_answers[$answerId] = false;
3504
                            }
3505
                        } else {
3506
                            if ($studentChoice) {
3507
                                $real_answers[$answerId] = false;
3508
                            } else {
3509
                                $real_answers[$answerId] = true;
3510
                            }
3511
                        }
3512
                    }
3513
                    break;
3514
                case FILL_IN_BLANKS:
3515
                    $str = '';
3516
                    $answerFromDatabase = '';
3517
                    if ($from_database) {
3518
                        $sql = "SELECT answer
3519
                                FROM $TBL_TRACK_ATTEMPT
3520
                                WHERE
3521
                                    exe_id = $exeId AND
3522
                                    question_id= ".intval($questionId);
3523
                        $result = Database::query($sql);
3524
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3525
                    }
3526
3527
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3528
                        // the question is encoded like this
3529
                        // [A] B [C] D [E] F::10,10,10@1
3530
                        // number 1 before the "@" means that is a switchable fill in blank question
3531
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3532
                        // means that is a normal fill blank question
3533
                        // first we explode the "::"
3534
                        $pre_array = explode('::', $answer);
3535
3536
                        // is switchable fill blank or not
3537
                        $last = count($pre_array) - 1;
3538
                        $is_set_switchable = explode('@', $pre_array[$last]);
3539
                        $switchable_answer_set = false;
3540
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3541
                            $switchable_answer_set = true;
3542
                        }
3543
                        $answer = '';
3544
                        for ($k = 0; $k < $last; $k++) {
3545
                            $answer .= $pre_array[$k];
3546
                        }
3547
                        // splits weightings that are joined with a comma
3548
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3549
                        // we save the answer because it will be modified
3550
                        $temp = $answer;
3551
                        $answer = '';
3552
                        $j = 0;
3553
                        //initialise answer tags
3554
                        $user_tags = $correct_tags = $real_text = array();
3555
                        // the loop will stop at the end of the text
3556
                        while (1) {
3557
                            // quits the loop if there are no more blanks (detect '[')
3558
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3559
                                // adds the end of the text
3560
                                $answer = $temp;
3561
                                $real_text[] = $answer;
3562
                                break; //no more "blanks", quit the loop
3563
                            }
3564
                            // adds the piece of text that is before the blank
3565
                            //and ends with '[' into a general storage array
3566
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3567
                            $answer .= api_substr($temp, 0, $pos + 1);
3568
                            //take the string remaining (after the last "[" we found)
3569
                            $temp = api_substr($temp, $pos + 1);
3570
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3571
                            if (($pos = api_strpos($temp, ']')) === false) {
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3569 can also be of type false; however, api_strpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3572
                                // adds the end of the text
3573
                                $answer .= $temp;
3574
                                break;
3575
                            }
3576
                            if ($from_database) {
3577
                                $str = $answerFromDatabase;
3578
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3579
                                $str = str_replace('\r\n', '', $str);
3580
3581
                                $choice = $arr[1];
3582
                                if (isset($choice[$j])) {
3583
                                    $tmp = api_strrpos($choice[$j], ' / ');
3584
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
0 ignored issues
show
Bug introduced by
It seems like $tmp defined by api_strrpos($choice[$j], ' / ') on line 3583 can also be of type double or false; however, api_substr() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3585
                                    $choice[$j] = trim($choice[$j]);
3586
                                    // Needed to let characters ' and " to work as part of an answer
3587
                                    $choice[$j] = stripslashes($choice[$j]);
3588
                                } else {
3589
                                    $choice[$j] = null;
3590
                                }
3591
                            } else {
3592
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3593
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3594
                            }
3595
3596
                            $user_tags[] = $choice[$j];
3597
                            //put the contents of the [] answer tag into correct_tags[]
3598
                            $correct_tags[] = api_substr($temp, 0, $pos);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3569 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3599
                            $j++;
3600
                            $temp = api_substr($temp, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3600 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3601
                        }
3602
                        $answer = '';
3603
                        $real_correct_tags = $correct_tags;
3604
                        $chosen_list = array();
3605
3606
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3607
                            if ($i == 0) {
3608
                                $answer .= $real_text[0];
3609
                            }
3610
                            if (!$switchable_answer_set) {
3611
                                // Needed to parse ' and " characters
3612
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3613 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3614
                                    // gives the related weighting to the student
3615
                                    $questionScore += $answerWeighting[$i];
3616
                                    // increments total score
3617
                                    $totalScore += $answerWeighting[$i];
3618
                                    // adds the word in green at the end of the string
3619
                                    $answer .= $correct_tags[$i];
3620
                                } elseif (!empty($user_tags[$i])) {
3621
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3622
                                    // adds the word in red at the end of the string, and strikes it
3623
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3624
                                } else {
3625
                                    // adds a tabulation if no word has been typed by the student
3626
                                    $answer .= ''; // remove &nbsp; that causes issue
3627
                                }
3628
                            } else {
3629
                                // switchable fill in the blanks
3630
                                if (in_array($user_tags[$i], $correct_tags)) {
3631
                                    $chosen_list[] = $user_tags[$i];
3632
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3633
                                    // gives the related weighting to the student
3634
                                    $questionScore += $answerWeighting[$i];
3635
                                    // increments total score
3636
                                    $totalScore += $answerWeighting[$i];
3637
                                    // adds the word in green at the end of the string
3638
                                    $answer .= $user_tags[$i];
3639
                                } elseif (!empty ($user_tags[$i])) {
3640
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3641
                                    // adds the word in red at the end of the string, and strikes it
3642
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3643
                                } else {
3644
                                    // adds a tabulation if no word has been typed by the student
3645
                                    $answer .= ''; // remove &nbsp; that causes issue
3646
                                }
3647
                            }
3648
3649
                            // adds the correct word, followed by ] to close the blank
3650
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3651
                            if (isset($real_text[$i + 1])) {
3652
                                $answer .= $real_text[$i + 1];
3653
                            }
3654
                        }
3655
                    } else {
3656
                        // insert the student result in the track_e_attempt table, field answer
3657
                        // $answer is the answer like in the c_quiz_answer table for the question
3658
                        // student data are choice[]
3659
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3660
                            $answer
3661
                        );
3662
3663
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3664
                        $answerWeighting = $listCorrectAnswers['tabweighting'];
3665
                        // user choices is an array $choice
3666
3667
                        // get existing user data in n the BDD
3668
                        if ($from_database) {
3669
                            $listStudentResults = FillBlanks::getAnswerInfo(
3670
                                $answerFromDatabase,
3671
                                true
3672
                            );
3673
                            $choice = $listStudentResults['studentanswer'];
3674
                        }
3675
3676
                        // loop other all blanks words
3677
                        if (!$switchableAnswerSet) {
3678
                            // not switchable answer, must be in the same place than teacher order
3679
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3680
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3681
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3682
3683
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3684
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3685
                                // ENT_QUOTES is used in order to transform ' to &#039;
3686
                                if (!$from_database) {
3687
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3688
                                }
3689
3690
                                $isAnswerCorrect = 0;
3691 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
3692
                                    // gives the related weighting to the student
3693
                                    $questionScore += $answerWeighting[$i];
3694
                                    // increments total score
3695
                                    $totalScore += $answerWeighting[$i];
3696
                                    $isAnswerCorrect = 1;
3697
                                }
3698
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3699
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3700
                            }
3701
                        } else {
3702
                            // switchable answer
3703
                            $listStudentAnswerTemp = $choice;
3704
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3705
                            // for every teacher answer, check if there is a student answer
3706
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3707
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3708
                                $found = false;
3709
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3710
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3711
                                    if (!$found) {
3712 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3713
                                            $studentAnswer,
3714
                                            $correctAnswer
3715
                                        )
3716
                                        ) {
3717
                                            $questionScore += $answerWeighting[$i];
3718
                                            $totalScore += $answerWeighting[$i];
3719
                                            $listTeacherAnswerTemp[$j] = '';
3720
                                            $found = true;
3721
                                        }
3722
                                    }
3723
                                }
3724
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3725
                                if (!$found) {
3726
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3727
                                } else {
3728
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3729
                                }
3730
                            }
3731
                        }
3732
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3733
                            $listCorrectAnswers
3734
                        );
3735
                    }
3736
                    break;
3737
                case CALCULATED_ANSWER:
3738
                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
3739
                    $preArray = explode('@@', $answer);
3740
                    $last = count($preArray) - 1;
3741
                    $answer = '';
3742
                    for ($k = 0; $k < $last; $k++) {
3743
                        $answer .= $preArray[$k];
3744
                    }
3745
                    $answerWeighting = array($answerWeighting);
3746
                    // we save the answer because it will be modified
3747
                    $temp = $answer;
3748
                    $answer = '';
3749
                    $j = 0;
3750
                    //initialise answer tags
3751
                    $userTags = $correctTags = $realText = array();
3752
                    // the loop will stop at the end of the text
3753
                    while (1) {
3754
                        // quits the loop if there are no more blanks (detect '[')
3755
                        if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3756
                            // adds the end of the text
3757
                            $answer = $temp;
3758
                            $realText[] = $answer;
3759
                            break; //no more "blanks", quit the loop
3760
                        }
3761
                        // adds the piece of text that is before the blank
3762
                        //and ends with '[' into a general storage array
3763
                        $realText[] = api_substr($temp, 0, $pos + 1);
3764
                        $answer .= api_substr($temp, 0, $pos + 1);
3765
                        //take the string remaining (after the last "[" we found)
3766
                        $temp = api_substr($temp, $pos + 1);
3767
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3768
                        if (($pos = api_strpos($temp, ']')) === false) {
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3766 can also be of type false; however, api_strpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3769
                            // adds the end of the text
3770
                            $answer .= $temp;
3771
                            break;
3772
                        }
3773
                        if ($from_database) {
3774
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3775
                                          WHERE
3776
                                            exe_id = '".$exeId."' AND
3777
                                            question_id= ".intval($questionId);
3778
                            $resfill = Database::query($queryfill);
3779
                            $str = Database::result($resfill, 0, 'answer');
3780
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3781
                            $str = str_replace('\r\n', '', $str);
3782
                            $choice = $arr[1];
3783
                            if (isset($choice[$j])) {
3784
                                $tmp = api_strrpos($choice[$j], ' / ');
3785
3786
                                if ($tmp) {
3787
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3788
                                } else {
3789
                                    $tmp = ltrim($tmp, '[');
3790
                                    $tmp = rtrim($tmp, ']');
3791
                                }
3792
3793
                                $choice[$j] = trim($choice[$j]);
3794
                                // Needed to let characters ' and " to work as part of an answer
3795
                                $choice[$j] = stripslashes($choice[$j]);
3796
                            } else {
3797
                                $choice[$j] = null;
3798
                            }
3799
                        } else {
3800
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3801
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3802
                        }
3803
                        $userTags[] = $choice[$j];
3804
                        //put the contents of the [] answer tag into correct_tags[]
3805
                        $correctTags[] = api_substr($temp, 0, $pos);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3766 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3806
                        $j++;
3807
                        $temp = api_substr($temp, $pos + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3807 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3808
                    }
3809
                    $answer = '';
3810
                    $realCorrectTags = $correctTags;
3811
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3812
                        if ($i == 0) {
3813
                            $answer .= $realText[0];
3814
                        }
3815
                        // Needed to parse ' and " characters
3816
                        $userTags[$i] = stripslashes($userTags[$i]);
3817 View Code Duplication
                        if ($correctTags[$i] == $userTags[$i]) {
3818
                            // gives the related weighting to the student
3819
                            $questionScore += $answerWeighting[$i];
3820
                            // increments total score
3821
                            $totalScore += $answerWeighting[$i];
3822
                            // adds the word in green at the end of the string
3823
                            $answer .= $correctTags[$i];
3824
                        } elseif (!empty($userTags[$i])) {
3825
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3826
                            // adds the word in red at the end of the string, and strikes it
3827
                            $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
3828
                        } else {
3829
                            // adds a tabulation if no word has been typed by the student
3830
                            $answer .= ''; // remove &nbsp; that causes issue
3831
                        }
3832
                        // adds the correct word, followed by ] to close the blank
3833
3834
                        if (
3835
                            $this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM
3836
                        ) {
3837
                            $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
3838
                        }
3839
3840
                        $answer .= ']';
3841
3842
                        if (isset($realText[$i + 1])) {
3843
                            $answer .= $realText[$i + 1];
3844
                        }
3845
                    }
3846
                    break;
3847
                case FREE_ANSWER:
3848
                    if ($from_database) {
3849
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
3850
                                 WHERE 
3851
                                    exe_id = $exeId AND 
3852
                                    question_id= ".$questionId;
3853
                        $resq = Database::query($sql);
3854
                        $data = Database::fetch_array($resq);
3855
3856
                        $choice = $data['answer'];
3857
                        $choice = str_replace('\r\n', '', $choice);
3858
                        $choice = stripslashes($choice);
3859
                        $questionScore = $data['marks'];
3860
3861
                        if ($questionScore == -1) {
3862
                            $totalScore += 0;
3863
                        } else {
3864
                            $totalScore += $questionScore;
3865
                        }
3866
                        if ($questionScore == '') {
3867
                            $questionScore = 0;
3868
                        }
3869
                        $arrques = $questionName;
3870
                        $arrans  = $choice;
3871
                    } else {
3872
                        $studentChoice = $choice;
3873
                        if ($studentChoice) {
3874
                            //Fixing negative puntation see #2193
3875
                            $questionScore = 0;
3876
                            $totalScore += 0;
3877
                        }
3878
                    }
3879
                    break;
3880
                case ORAL_EXPRESSION:
3881
                    if ($from_database) {
3882
                        $query = "SELECT answer, marks 
3883
                                  FROM $TBL_TRACK_ATTEMPT
3884
                                  WHERE 
3885
                                        exe_id = $exeId AND 
3886
                                        question_id = $questionId
3887
                                 ";
3888
                        $resq = Database::query($query);
3889
                        $row = Database::fetch_assoc($resq);
3890
                        $choice = $row['answer'];
3891
                        $choice = str_replace('\r\n', '', $choice);
3892
                        $choice = stripslashes($choice);
3893
                        $questionScore = $row['marks'];
3894
                        if ($questionScore == -1) {
3895
                            $totalScore += 0;
3896
                        } else {
3897
                            $totalScore += $questionScore;
3898
                        }
3899
                        $arrques = $questionName;
3900
                        $arrans  = $choice;
3901
                    } else {
3902
                        $studentChoice = $choice;
3903
                        if ($studentChoice) {
3904
                            //Fixing negative puntation see #2193
3905
                            $questionScore = 0;
3906
                            $totalScore += 0;
3907
                        }
3908
                    }
3909
                    break;
3910
                case DRAGGABLE:
3911
                    //no break
3912
                case MATCHING_DRAGGABLE:
3913
                    //no break
3914
                case MATCHING:
3915
                    if ($from_database) {
3916
                        $sql = "SELECT id, answer, id_auto
3917
                                FROM $table_ans
3918
                                WHERE
3919
                                    c_id = $course_id AND
3920
                                    question_id = $questionId AND
3921
                                    correct = 0
3922
                                ";
3923
                        $res_answer = Database::query($sql);
3924
                        // Getting the real answer
3925
                        $real_list = array();
3926
                        while ($real_answer = Database::fetch_array($res_answer)) {
3927
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3928
                        }
3929
3930
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
3931
                                FROM $table_ans
3932
                                WHERE
3933
                                    c_id = $course_id AND
3934
                                    question_id = $questionId AND
3935
                                    correct <> 0
3936
                                ORDER BY id_auto";
3937
                        $res_answers = Database::query($sql);
3938
3939
                        $questionScore = 0;
3940
3941
                        while ($a_answers = Database::fetch_array($res_answers)) {
3942
                            $i_answer_id = $a_answers['id']; //3
3943
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
3944
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3945
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3946
3947
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3948
                                    WHERE
3949
                                        exe_id = '$exeId' AND
3950
                                        question_id = '$questionId' AND
3951
                                        position = '$i_answer_id_auto'";
3952
3953
                            $res_user_answer = Database::query($sql);
3954
3955
                            if (Database::num_rows($res_user_answer) > 0) {
3956
                                //  rich - good looking
3957
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3958
                            } else {
3959
                                $s_user_answer = 0;
3960
                            }
3961
3962
                            $i_answerWeighting = $a_answers['ponderation'];
3963
3964
                            $user_answer = '';
3965
                            if (!empty($s_user_answer)) {
3966
                                if ($answerType == DRAGGABLE) {
3967
                                    if ($s_user_answer == $i_answer_correct_answer) {
3968
                                        $questionScore += $i_answerWeighting;
3969
                                        $totalScore += $i_answerWeighting;
3970
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3971
                                    } else {
3972
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3973
                                    }
3974
                                } else {
3975
                                    if ($s_user_answer == $i_answer_correct_answer) {
3976
                                        $questionScore += $i_answerWeighting;
3977
                                        $totalScore += $i_answerWeighting;
3978
3979
                                        // Try with id
3980
                                        if (isset($real_list[$i_answer_id])) {
3981
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3982
                                        }
3983
3984
                                        // Try with $i_answer_id_auto
3985
                                        if (empty($user_answer)) {
3986
                                            if (isset($real_list[$i_answer_id_auto])) {
3987
                                                $user_answer = Display::span(
3988
                                                    $real_list[$i_answer_id_auto]
3989
                                                );
3990
                                            }
3991
                                        }
3992
                                    } else {
3993
                                        $user_answer = Display::span(
3994
                                            $real_list[$s_user_answer],
3995
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3996
                                        );
3997
                                    }
3998
                                }
3999
                            } elseif ($answerType == DRAGGABLE) {
4000
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4001
                            } else {
4002
                                $user_answer = Display::span(
4003
                                    get_lang('Incorrect').' &nbsp;',
4004
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4005
                                );
4006
                            }
4007
4008
                            if ($show_result) {
4009
                                if ($showTotalScoreAndUserChoicesInLastAttempt === false) {
4010
                                    $user_answer = '';
4011
                                }
4012
                                echo '<tr>';
4013
                                echo '<td>'.$s_answer_label.'</td>';
4014
                                echo '<td>'.$user_answer;
4015
4016
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4017
                                    if (isset($real_list[$i_answer_correct_answer]) &&
4018
                                        $showTotalScoreAndUserChoicesInLastAttempt === true
4019
                                    ) {
4020
                                        echo Display::span(
4021
                                            $real_list[$i_answer_correct_answer],
4022
                                            ['style' => 'color: #008000; font-weight: bold;']
4023
                                        );
4024
                                    }
4025
                                }
4026
                                echo '</td>';
4027
                                echo '</tr>';
4028
                            }
4029
                        }
4030
                        break(2); // break the switch and the "for" condition
4031
                    } else {
4032
                        if ($answerCorrect) {
4033
                            if (isset($choice[$answerAutoId]) &&
4034
                                $answerCorrect == $choice[$answerAutoId]
4035
                            ) {
4036
                                $questionScore += $answerWeighting;
4037
                                $totalScore += $answerWeighting;
4038
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4039
                            } else {
4040
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4041
                                    $user_answer = Display::span(
4042
                                        $answerMatching[$choice[$answerAutoId]],
4043
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4044
                                    );
4045
                                }
4046
                            }
4047
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4048
                        }
4049
                    }
4050
                    break;
4051
                case HOT_SPOT:
4052
                    if ($from_database) {
4053
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4054
                        // Check auto id
4055
                        $sql = "SELECT hotspot_correct
4056
                                FROM $TBL_TRACK_HOTSPOT
4057
                                WHERE
4058
                                    hotspot_exe_id = $exeId AND
4059
                                    hotspot_question_id= $questionId AND
4060
                                    hotspot_answer_id = ".intval($answerAutoId)."
4061
                                ORDER BY hotspot_id ASC";
4062
                        $result = Database::query($sql);
4063
                        if (Database::num_rows($result)) {
4064
                            $studentChoice = Database::result(
4065
                                $result,
4066
                                0,
4067
                                'hotspot_correct'
4068
                            );
4069
4070
                            if ($studentChoice) {
4071
                                $questionScore += $answerWeighting;
4072
                                $totalScore += $answerWeighting;
4073
                            }
4074
                        } else {
4075
                            // If answer.id is different:
4076
                            $sql = "SELECT hotspot_correct
4077
                                FROM $TBL_TRACK_HOTSPOT
4078
                                WHERE
4079
                                    hotspot_exe_id = $exeId AND
4080
                                    hotspot_question_id= $questionId AND
4081
                                    hotspot_answer_id = ".intval($answerId)."
4082
                                ORDER BY hotspot_id ASC";
4083
                            $result = Database::query($sql);
4084
4085
                            if (Database::num_rows($result)) {
4086
                                $studentChoice = Database::result(
4087
                                    $result,
4088
                                    0,
4089
                                    'hotspot_correct'
4090
                                );
4091
4092
                                if ($studentChoice) {
4093
                                    $questionScore += $answerWeighting;
4094
                                    $totalScore += $answerWeighting;
4095
                                }
4096
                            } else {
4097
                                // check answer.iid
4098
                                if (!empty($answerIid)) {
4099
                                    $sql = "SELECT hotspot_correct
4100
                                            FROM $TBL_TRACK_HOTSPOT
4101
                                            WHERE
4102
                                                hotspot_exe_id = $exeId AND
4103
                                                hotspot_question_id= $questionId AND
4104
                                                hotspot_answer_id = ".intval($answerIid)."
4105
                                            ORDER BY hotspot_id ASC";
4106
                                    $result = Database::query($sql);
4107
4108
                                    $studentChoice = Database::result(
4109
                                        $result,
4110
                                        0,
4111
                                        'hotspot_correct'
4112
                                    );
4113
4114
                                    if ($studentChoice) {
4115
                                        $questionScore += $answerWeighting;
4116
                                        $totalScore += $answerWeighting;
4117
                                    }
4118
                                }
4119
                            }
4120
                        }
4121
                    } else {
4122
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4123
                            $choice[$answerAutoId] = 0;
4124
                            $choice[$answerIid] = 0;
4125
                        } else {
4126
                            $studentChoice = $choice[$answerAutoId];
4127
                            if (empty($studentChoice)) {
4128
                                $studentChoice = $choice[$answerIid];
4129
                            }
4130
                            $choiceIsValid = false;
4131
                            if (!empty($studentChoice)) {
4132
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4133
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4134
                                $choicePoint = Geometry::decodePoint($studentChoice);
4135
4136
                                switch ($hotspotType) {
4137
                                    case 'square':
4138
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4139
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4140
                                        break;
4141
                                    case 'circle':
4142
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4143
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4144
                                        break;
4145
                                    case 'poly':
4146
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4147
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4148
                                        break;
4149
                                }
4150
                            }
4151
4152
                            $choice[$answerAutoId] = 0;
4153
                            if ($choiceIsValid) {
4154
                                $questionScore += $answerWeighting;
4155
                                $totalScore += $answerWeighting;
4156
                                $choice[$answerAutoId] = 1;
4157
                                $choice[$answerIid] = 1;
4158
                            }
4159
                        }
4160
                    }
4161
                    break;
4162
                // @todo never added to chamilo
4163
                //for hotspot with fixed order
4164
                case HOT_SPOT_ORDER:
4165
                    $studentChoice = $choice['order'][$answerId];
4166
                    if ($studentChoice == $answerId) {
4167
                        $questionScore  += $answerWeighting;
4168
                        $totalScore     += $answerWeighting;
4169
                        $studentChoice = true;
4170
                    } else {
4171
                        $studentChoice = false;
4172
                    }
4173
                    break;
4174
                // for hotspot with delineation
4175
                case HOT_SPOT_DELINEATION:
4176
                    if ($from_database) {
4177
                        // getting the user answer
4178
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4179
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4180
                                    FROM $TBL_TRACK_HOTSPOT
4181
                                    WHERE
4182
                                        hotspot_exe_id = '".$exeId."' AND
4183
                                        hotspot_question_id= '".$questionId."' AND
4184
                                        hotspot_answer_id='1'";
4185
                        //by default we take 1 because it's a delineation
4186
                        $resq = Database::query($query);
4187
                        $row = Database::fetch_array($resq, 'ASSOC');
4188
4189
                        $choice = $row['hotspot_correct'];
4190
                        $user_answer = $row['hotspot_coordinate'];
4191
4192
                        // THIS is very important otherwise the poly_compile will throw an error!!
4193
                        // round-up the coordinates
4194
                        $coords = explode('/', $user_answer);
4195
                        $user_array = '';
4196 View Code Duplication
                        foreach ($coords as $coord) {
4197
                            list($x, $y) = explode(';', $coord);
4198
                            $user_array .= round($x).';'.round($y).'/';
4199
                        }
4200
                        $user_array = substr($user_array, 0, -1);
4201
                    } else {
4202
                        if (!empty($studentChoice)) {
4203
                            $newquestionList[] = $questionId;
4204
                        }
4205
4206
                        if ($answerId === 1) {
4207
                            $studentChoice = $choice[$answerId];
4208
                            $questionScore += $answerWeighting;
4209
4210
                            if ($hotspot_delineation_result[1] == 1) {
4211
                                $totalScore += $answerWeighting; //adding the total
4212
                            }
4213
                        }
4214
                    }
4215
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4216
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4217
                    break;
4218
                case ANNOTATION:
4219
                    if ($from_database) {
4220
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4221
                                 WHERE 
4222
                                    exe_id = $exeId AND 
4223
                                    question_id= ".$questionId;
4224
                        $resq = Database::query($sql);
4225
                        $data = Database::fetch_array($resq);
4226
4227
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4228
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4229
4230
                        $arrques = $questionName;
4231
                        break;
4232
                    }
4233
4234
                    $studentChoice = $choice;
4235
4236
                    if ($studentChoice) {
4237
                        $questionScore = 0;
4238
                        $totalScore += 0;
4239
                    }
4240
                    break;
4241
            } // end switch Answertype
4242
4243
            if ($show_result) {
4244
                if ($debug) error_log('Showing questions $from '.$from);
4245
                if ($from == 'exercise_result') {
4246
                    //display answers (if not matching type, or if the answer is correct)
4247
                    if (
4248
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4249
                        $answerCorrect
4250
                    ) {
4251
                        if (
4252
                            in_array(
4253
                                $answerType,
4254
                                array(
4255
                                    UNIQUE_ANSWER,
4256
                                    UNIQUE_ANSWER_IMAGE,
4257
                                    UNIQUE_ANSWER_NO_OPTION,
4258
                                    MULTIPLE_ANSWER,
4259
                                    MULTIPLE_ANSWER_COMBINATION,
4260
                                    GLOBAL_MULTIPLE_ANSWER,
4261
                                    READING_COMPREHENSION,
4262
                                )
4263
                            )
4264
                        ) {
4265
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4266
                                $feedback_type,
4267
                                $answerType,
4268
                                $studentChoice,
4269
                                $answer,
4270
                                $answerComment,
4271
                                $answerCorrect,
4272
                                0,
4273
                                0,
4274
                                0,
4275
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4276
                                $showTotalScoreAndUserChoicesInLastAttempt
4277
                            );
4278
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4279
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4280
                                $feedback_type,
4281
                                $answerType,
4282
                                $studentChoice,
4283
                                $answer,
4284
                                $answerComment,
4285
                                $answerCorrect,
4286
                                0,
4287
                                $questionId,
4288
                                0,
4289
                                $results_disabled,
4290
                                $showTotalScoreAndUserChoicesInLastAttempt
4291
                            );
4292
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4293
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4294
                                $feedback_type,
4295
                                $answerType,
4296
                                $studentChoice,
4297
                                $answer,
4298
                                $answerComment,
4299
                                $answerCorrect,
4300
                                0,
4301
                                0,
4302
                                0,
4303
                                $results_disabled,
4304
                                $showTotalScoreAndUserChoicesInLastAttempt
4305
                            );
4306
                        } elseif ($answerType == FILL_IN_BLANKS) {
4307
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4308
                                $feedback_type,
4309
                                $answer,
4310
                                0,
4311
                                0,
4312
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::d...fill_in_blanks_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4313
                                '',
4314
                                $showTotalScoreAndUserChoicesInLastAttempt
4315
                            );
4316
                        } elseif ($answerType == CALCULATED_ANSWER) {
4317
                            ExerciseShowFunctions::display_calculated_answer(
4318
                                $feedback_type,
4319
                                $answer,
4320
                                0,
4321
                                0,
4322
                                $results_disabled,
4323
                                $showTotalScoreAndUserChoicesInLastAttempt
4324
                            );
4325
                        } elseif ($answerType == FREE_ANSWER) {
4326
                            ExerciseShowFunctions::display_free_answer(
4327
                                $feedback_type,
4328
                                $choice,
4329
                                $exeId,
4330
                                $questionId,
4331
                                $questionScore,
4332
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::display_free_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4333
                            );
4334
                        } elseif ($answerType == ORAL_EXPRESSION) {
4335
                            // to store the details of open questions in an array to be used in mail
4336
                            /** @var OralExpression $objQuestionTmp */
4337
                            ExerciseShowFunctions::display_oral_expression_answer(
4338
                                $feedback_type,
4339
                                $choice,
4340
                                0,
4341
                                0,
4342
                                $objQuestionTmp->getFileUrl(true),
4343
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4344
                            );
4345
                        } elseif ($answerType == HOT_SPOT) {
4346
                            /**
4347
                             * @var int $correctAnswerId
4348
                             * @var TrackEHotspot $hotspot
4349
                             */
4350
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4351
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4352
                                    break;
4353
                                }
4354
                            }
4355
4356
                            ExerciseShowFunctions::display_hotspot_answer(
4357
                                $feedback_type,
4358
                                ++$correctAnswerId,
4359
                                $answer,
4360
                                $studentChoice,
4361
                                $answerComment,
4362
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::display_hotspot_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4363
                                $correctAnswerId,
4364
                                $showTotalScoreAndUserChoicesInLastAttempt
4365
                            );
4366
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4367
                            ExerciseShowFunctions::display_hotspot_order_answer(
4368
                                $feedback_type,
4369
                                $answerId,
4370
                                $answer,
4371
                                $studentChoice,
4372
                                $answerComment
4373
                            );
4374
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4375
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4376
4377
                            //round-up the coordinates
4378
                            $coords = explode('/', $user_answer);
4379
                            $user_array = '';
4380 View Code Duplication
                            foreach ($coords as $coord) {
4381
                                list($x, $y) = explode(';', $coord);
4382
                                $user_array .= round($x).';'.round($y).'/';
4383
                            }
4384
                            $user_array = substr($user_array, 0, -1);
4385
4386 View Code Duplication
                            if ($next) {
4387
                                $user_answer = $user_array;
4388
                                // we compare only the delineation not the other points
4389
                                $answer_question = $_SESSION['hotspot_coord'][1];
4390
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4391
4392
                                //calculating the area
4393
                                $poly_user = convert_coordinates($user_answer, '/');
4394
                                $poly_answer = convert_coordinates($answer_question, '|');
4395
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4396
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4397
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4398
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4399
4400
                                $overlap = $poly_results['both'];
4401
                                $poly_answer_area = $poly_results['s1'];
4402
                                $poly_user_area = $poly_results['s2'];
4403
                                $missing = $poly_results['s1Only'];
4404
                                $excess = $poly_results['s2Only'];
4405
4406
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4407
                                // //this is an area in pixels
4408
                                if ($debug > 0) {
4409
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4410
                                }
4411
4412
                                if ($overlap < 1) {
4413
                                    //shortcut to avoid complicated calculations
4414
                                    $final_overlap = 0;
4415
                                    $final_missing = 100;
4416
                                    $final_excess = 100;
4417
                                } else {
4418
                                    // the final overlap is the percentage of the initial polygon
4419
                                    // that is overlapped by the user's polygon
4420
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4421
                                    if ($debug > 1) {
4422
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4423
                                    }
4424
                                    // the final missing area is the percentage of the initial polygon
4425
                                    // that is not overlapped by the user's polygon
4426
                                    $final_missing = 100 - $final_overlap;
4427
                                    if ($debug > 1) {
4428
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4429
                                    }
4430
                                    // the final excess area is the percentage of the initial polygon's size
4431
                                    // that is covered by the user's polygon outside of the initial polygon
4432
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4433
                                    if ($debug > 1) {
4434
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4435
                                    }
4436
                                }
4437
4438
                                //checking the destination parameters parsing the "@@"
4439
                                $destination_items = explode(
4440
                                    '@@',
4441
                                    $answerDestination
4442
                                );
4443
                                $threadhold_total = $destination_items[0];
4444
                                $threadhold_items = explode(
4445
                                    ';',
4446
                                    $threadhold_total
4447
                                );
4448
                                $threadhold1 = $threadhold_items[0]; // overlap
4449
                                $threadhold2 = $threadhold_items[1]; // excess
4450
                                $threadhold3 = $threadhold_items[2]; //missing
4451
4452
                                // if is delineation
4453
                                if ($answerId === 1) {
4454
                                    //setting colors
4455
                                    if ($final_overlap >= $threadhold1) {
4456
                                        $overlap_color = true; //echo 'a';
4457
                                    }
4458
                                    //echo $excess.'-'.$threadhold2;
4459
                                    if ($final_excess <= $threadhold2) {
4460
                                        $excess_color = true; //echo 'b';
4461
                                    }
4462
                                    //echo '--------'.$missing.'-'.$threadhold3;
4463
                                    if ($final_missing <= $threadhold3) {
4464
                                        $missing_color = true; //echo 'c';
4465
                                    }
4466
4467
                                    // if pass
4468
                                    if (
4469
                                        $final_overlap >= $threadhold1 &&
4470
                                        $final_missing <= $threadhold3 &&
4471
                                        $final_excess <= $threadhold2
4472
                                    ) {
4473
                                        $next = 1; //go to the oars
4474
                                        $result_comment = get_lang('Acceptable');
4475
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4476
                                    } else {
4477
                                        $next = 0;
4478
                                        $result_comment = get_lang('Unacceptable');
4479
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4480
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4481
                                        //checking the destination parameters parsing the "@@"
4482
                                        $destination_items = explode('@@', $answerDestination);
4483
                                    }
4484
                                } elseif ($answerId > 1) {
4485
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4486
                                        if ($debug > 0) {
4487
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4488
                                        }
4489
                                        //type no error shouldn't be treated
4490
                                        $next = 1;
4491
                                        continue;
4492
                                    }
4493
                                    if ($debug > 0) {
4494
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4495
                                    }
4496
                                    //check the intersection between the oar and the user
4497
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4498
                                    //echo 'official';print_r($x_list);print_r($y_list);
4499
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4500
                                    $inter = $result['success'];
4501
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4502
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4503
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4504
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4505
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4506
4507
                                    if ($overlap == false) {
4508
                                        //all good, no overlap
4509
                                        $next = 1;
4510
                                        continue;
4511
                                    } else {
4512
                                        if ($debug > 0) {
4513
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4514
                                        }
4515
                                        $organs_at_risk_hit++;
4516
                                        //show the feedback
4517
                                        $next = 0;
4518
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4519
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4520
4521
                                        $destination_items = explode('@@', $answerDestination);
4522
                                        $try_hotspot = $destination_items[1];
4523
                                        $lp_hotspot = $destination_items[2];
4524
                                        $select_question_hotspot = $destination_items[3];
4525
                                        $url_hotspot = $destination_items[4];
4526
                                    }
4527
                                }
4528
                            } else {	// the first delineation feedback
4529
                                if ($debug > 0) {
4530
                                    error_log(__LINE__.' first', 0);
4531
                                }
4532
                            }
4533
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4534
                            echo '<tr>';
4535
                            echo Display::tag('td', $answerMatching[$answerId]);
4536
                            echo Display::tag(
4537
                                'td',
4538
                                "$user_answer / ".Display::tag(
4539
                                    'strong',
4540
                                    $answerMatching[$answerCorrect],
4541
                                    ['style' => 'color: #008000; font-weight: bold;']
4542
                                )
4543
                            );
4544
                            echo '</tr>';
4545
                        } elseif ($answerType == ANNOTATION) {
4546
                            ExerciseShowFunctions::displayAnnotationAnswer(
4547
                                $feedback_type,
4548
                                $exeId,
4549
                                $questionId,
4550
                                $questionScore,
4551
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::displayAnnotationAnswer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4552
                            );
4553
                        }
4554
                    }
4555
                } else {
4556
                    if ($debug) error_log('Showing questions $from '.$from);
4557
4558
                    switch ($answerType) {
4559
                        case UNIQUE_ANSWER:
4560
                            //no break
4561
                        case UNIQUE_ANSWER_IMAGE:
4562
                            //no break
4563
                        case UNIQUE_ANSWER_NO_OPTION:
4564
                            //no break
4565
                        case MULTIPLE_ANSWER:
4566
                            //no break
4567
                        case GLOBAL_MULTIPLE_ANSWER :
4568
                            //no break
4569
                        case MULTIPLE_ANSWER_COMBINATION:
4570
                            //no break
4571
                        case READING_COMPREHENSION:
4572
                            if ($answerId == 1) {
4573
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4574
                                    $feedback_type,
4575
                                    $answerType,
4576
                                    $studentChoice,
4577
                                    $answer,
4578
                                    $answerComment,
4579
                                    $answerCorrect,
4580
                                    $exeId,
4581
                                    $questionId,
4582
                                    $answerId,
4583
                                    $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4584
                                    $showTotalScoreAndUserChoicesInLastAttempt
4585
                                );
4586
                            } else {
4587
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4588
                                    $feedback_type,
4589
                                    $answerType,
4590
                                    $studentChoice,
4591
                                    $answer,
4592
                                    $answerComment,
4593
                                    $answerCorrect,
4594
                                    $exeId,
4595
                                    $questionId,
4596
                                    '',
4597
                                    $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4598
                                    $showTotalScoreAndUserChoicesInLastAttempt
4599
                                );
4600
                            }
4601
                            break;
4602 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4603
                            if ($answerId == 1) {
4604
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4605
                                    $feedback_type,
4606
                                    $answerType,
4607
                                    $studentChoice,
4608
                                    $answer,
4609
                                    $answerComment,
4610
                                    $answerCorrect,
4611
                                    $exeId,
4612
                                    $questionId,
4613
                                    $answerId,
4614
                                    $results_disabled,
4615
                                    $showTotalScoreAndUserChoicesInLastAttempt
4616
                                );
4617
                            } else {
4618
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4619
                                    $feedback_type,
4620
                                    $answerType,
4621
                                    $studentChoice,
4622
                                    $answer,
4623
                                    $answerComment,
4624
                                    $answerCorrect,
4625
                                    $exeId,
4626
                                    $questionId,
4627
                                    '',
4628
                                    $results_disabled,
4629
                                    $showTotalScoreAndUserChoicesInLastAttempt
4630
                                );
4631
                            }
4632
                            break;
4633 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4634
                            if ($answerId == 1) {
4635
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4636
                                    $feedback_type,
4637
                                    $answerType,
4638
                                    $studentChoice,
4639
                                    $answer,
4640
                                    $answerComment,
4641
                                    $answerCorrect,
4642
                                    $exeId,
4643
                                    $questionId,
4644
                                    $answerId,
4645
                                    $results_disabled,
4646
                                    $showTotalScoreAndUserChoicesInLastAttempt
4647
                                );
4648
                            } else {
4649
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4650
                                    $feedback_type,
4651
                                    $answerType,
4652
                                    $studentChoice,
4653
                                    $answer,
4654
                                    $answerComment,
4655
                                    $answerCorrect,
4656
                                    $exeId,
4657
                                    $questionId,
4658
                                    '',
4659
                                    $results_disabled,
4660
                                    $showTotalScoreAndUserChoicesInLastAttempt
4661
                                );
4662
                            }
4663
                            break;
4664
                        case FILL_IN_BLANKS:
4665
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4666
                                $feedback_type,
4667
                                $answer,
4668
                                $exeId,
4669
                                $questionId,
4670
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::d...fill_in_blanks_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4671
                                $str,
4672
                                $showTotalScoreAndUserChoicesInLastAttempt
4673
                            );
4674
                            break;
4675
                        case CALCULATED_ANSWER:
4676
                            ExerciseShowFunctions::display_calculated_answer(
4677
                                $feedback_type,
4678
                                $answer,
4679
                                $exeId,
4680
                                $questionId,
4681
                                $results_disabled,
4682
                                '',
4683
                                $showTotalScoreAndUserChoicesInLastAttempt
4684
                            );
4685
                            break;
4686
                        case FREE_ANSWER:
4687
                            echo ExerciseShowFunctions::display_free_answer(
4688
                                $feedback_type,
4689
                                $choice,
4690
                                $exeId,
4691
                                $questionId,
4692
                                $questionScore,
4693
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::display_free_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4694
                            );
4695
                            break;
4696
                        case ORAL_EXPRESSION:
4697
                            echo '<tr>
4698
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4699
                                    $feedback_type,
4700
                                    $choice,
4701
                                    $exeId,
4702
                                    $questionId,
4703
                                    $objQuestionTmp->getFileUrl(),
4704
                                    $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4705
                                ).'</td>
4706
                                </tr>
4707
                                </table>';
4708
                            break;
4709
                        case HOT_SPOT:
4710
                            ExerciseShowFunctions::display_hotspot_answer(
4711
                                $feedback_type,
4712
                                $answerId,
4713
                                $answer,
4714
                                $studentChoice,
4715
                                $answerComment,
4716
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::display_hotspot_answer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4717
                                $answerId,
4718
                                $showTotalScoreAndUserChoicesInLastAttempt
4719
                            );
4720
                            break;
4721
                        case HOT_SPOT_DELINEATION:
4722
                            $user_answer = $user_array;
4723 View Code Duplication
                            if ($next) {
4724
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4725
                                // Save into db
4726
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4727
                                 * hotspot_user_id,
4728
                                 *  hotspot_course_code,
4729
                                 *  hotspot_exe_id,
4730
                                 *  hotspot_question_id,
4731
                                 *  hotspot_answer_id,
4732
                                 *  hotspot_correct,
4733
                                 *  hotspot_coordinate
4734
                                 *  )
4735
                                VALUES (
4736
                                 * '".Database::escape_string($_user['user_id'])."',
4737
                                 *  '".Database::escape_string($_course['id'])."',
4738
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4739
                                 *  '".Database::escape_string($answerId)."',
4740
                                 *  '".Database::escape_string($studentChoice)."',
4741
                                 *  '".Database::escape_string($user_array)."')";
4742
                                $result = Database::query($sql,__FILE__,__LINE__);
4743
                                 */
4744
                                $user_answer = $user_array;
4745
                                // we compare only the delineation not the other points
4746
                                $answer_question = $_SESSION['hotspot_coord'][1];
4747
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4748
4749
                                // calculating the area
4750
                                $poly_user = convert_coordinates($user_answer, '/');
4751
                                $poly_answer = convert_coordinates($answer_question, '|');
4752
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4753
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4754
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4755
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4756
4757
                                $overlap = $poly_results['both'];
4758
                                $poly_answer_area = $poly_results['s1'];
4759
                                $poly_user_area = $poly_results['s2'];
4760
                                $missing = $poly_results['s1Only'];
4761
                                $excess = $poly_results['s2Only'];
4762
                                if ($debug > 0) {
4763
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4764
                                }
4765
                                if ($overlap < 1) {
4766
                                    //shortcut to avoid complicated calculations
4767
                                    $final_overlap = 0;
4768
                                    $final_missing = 100;
4769
                                    $final_excess = 100;
4770
                                } else {
4771
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4772
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4773
                                    if ($debug > 1) {
4774
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4775
                                    }
4776
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4777
                                    $final_missing = 100 - $final_overlap;
4778
                                    if ($debug > 1) {
4779
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4780
                                    }
4781
                                    // 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
4782
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4783
                                    if ($debug > 1) {
4784
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4785
                                    }
4786
                                }
4787
4788
                                // Checking the destination parameters parsing the "@@"
4789
                                $destination_items = explode('@@', $answerDestination);
4790
                                $threadhold_total = $destination_items[0];
4791
                                $threadhold_items = explode(';', $threadhold_total);
4792
                                $threadhold1 = $threadhold_items[0]; // overlap
4793
                                $threadhold2 = $threadhold_items[1]; // excess
4794
                                $threadhold3 = $threadhold_items[2]; //missing
4795
                                // if is delineation
4796
                                if ($answerId === 1) {
4797
                                    //setting colors
4798
                                    if ($final_overlap >= $threadhold1) {
4799
                                        $overlap_color = true; //echo 'a';
4800
                                    }
4801
                                    //echo $excess.'-'.$threadhold2;
4802
                                    if ($final_excess <= $threadhold2) {
4803
                                        $excess_color = true; //echo 'b';
4804
                                    }
4805
                                    //echo '--------'.$missing.'-'.$threadhold3;
4806
                                    if ($final_missing <= $threadhold3) {
4807
                                        $missing_color = true; //echo 'c';
4808
                                    }
4809
4810
                                    // if pass
4811
                                    if ($final_overlap >= $threadhold1 &&
4812
                                        $final_missing <= $threadhold3 &&
4813
                                        $final_excess <= $threadhold2
4814
                                    ) {
4815
                                        $next = 1; //go to the oars
4816
                                        $result_comment = get_lang('Acceptable');
4817
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4818
                                    } else {
4819
                                        $next = 0;
4820
                                        $result_comment = get_lang('Unacceptable');
4821
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4822
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4823
                                        //checking the destination parameters parsing the "@@"
4824
                                        $destination_items = explode('@@', $answerDestination);
4825
                                    }
4826
                                } elseif ($answerId > 1) {
4827
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4828
                                        if ($debug > 0) {
4829
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4830
                                        }
4831
                                        //type no error shouldn't be treated
4832
                                        $next = 1;
4833
                                        continue;
4834
                                    }
4835
                                    if ($debug > 0) {
4836
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4837
                                    }
4838
                                    //check the intersection between the oar and the user
4839
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4840
                                    //echo 'official';print_r($x_list);print_r($y_list);
4841
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4842
                                    $inter = $result['success'];
4843
4844
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4845
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4846
4847
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4848
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4849
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4850
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4851
4852
                                    if ($overlap == false) {
4853
                                        //all good, no overlap
4854
                                        $next = 1;
4855
                                        continue;
4856
                                    } else {
4857
                                        if ($debug > 0) {
4858
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4859
                                        }
4860
                                        $organs_at_risk_hit++;
4861
                                        //show the feedback
4862
                                        $next = 0;
4863
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4864
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4865
4866
                                        $destination_items = explode('@@', $answerDestination);
4867
                                        $try_hotspot = $destination_items[1];
4868
                                        $lp_hotspot = $destination_items[2];
4869
                                        $select_question_hotspot = $destination_items[3];
4870
                                        $url_hotspot = $destination_items[4];
4871
                                    }
4872
                                }
4873
                            } else {	// the first delineation feedback
4874
                                if ($debug > 0) {
4875
                                    error_log(__LINE__.' first', 0);
4876
                                }
4877
                            }
4878
                            break;
4879
                        case HOT_SPOT_ORDER:
4880
                            ExerciseShowFunctions::display_hotspot_order_answer(
4881
                                $feedback_type,
4882
                                $answerId,
4883
                                $answer,
4884
                                $studentChoice,
4885
                                $answerComment
4886
                            );
4887
                            break;
4888
                        case DRAGGABLE:
4889
                            //no break
4890
                        case MATCHING_DRAGGABLE:
4891
                            //no break
4892
                        case MATCHING:
4893
                            echo '<tr>';
4894
                            echo Display::tag('td', $answerMatching[$answerId]);
4895
                            echo Display::tag(
4896
                                'td',
4897
                                "$user_answer / ".Display::tag(
4898
                                    'strong',
4899
                                    $answerMatching[$answerCorrect],
4900
                                    ['style' => 'color: #008000; font-weight: bold;']
4901
                                )
4902
                            );
4903
                            echo '</tr>';
4904
4905
                            break;
4906
                        case ANNOTATION:
4907
                            ExerciseShowFunctions::displayAnnotationAnswer(
4908
                                $feedback_type,
4909
                                $exeId,
4910
                                $questionId,
4911
                                $questionScore,
4912
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3165 can also be of type boolean; however, ExerciseShowFunctions::displayAnnotationAnswer() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4913
                            );
4914
                            break;
4915
                    }
4916
                }
4917
            }
4918
            if ($debug) error_log(' ------ ');
4919
        } // end for that loops over all answers of the current question
4920
4921
        if ($debug) error_log('-- end answer loop --');
4922
4923
        $final_answer = true;
4924
4925
        foreach ($real_answers as $my_answer) {
4926
            if (!$my_answer) {
4927
                $final_answer = false;
4928
            }
4929
        }
4930
4931
        //we add the total score after dealing with the answers
4932
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4933
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4934
        ) {
4935
            if ($final_answer) {
4936
                //getting only the first score where we save the weight of all the question
4937
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4938
                $questionScore += $answerWeighting;
4939
                $totalScore += $answerWeighting;
4940
            }
4941
        }
4942
4943
        //Fixes multiple answer question in order to be exact
4944
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4945
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4946
            $diff = @array_diff($answer_correct_array, $real_answers);
4947
4948
            // All good answers or nothing works like exact
4949
4950
            $counter = 1;
4951
            $correct_answer = true;
4952
            foreach ($real_answers as $my_answer) {
4953
                if ($debug)
4954
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4955
                if ($my_answer != $answer_correct_array[$counter]) {
4956
                    $correct_answer = false;
4957
                    break;
4958
                }
4959
                $counter++;
4960
            }
4961
4962
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4963
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4964
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4965
4966
            if ($correct_answer == false) {
4967
                $questionScore = 0;
4968
            }
4969
4970
            // This makes the result non exact
4971
            if (!empty($diff)) {
4972
                $questionScore = 0;
4973
            }
4974
        }*/
4975
4976
        $extra_data = array(
4977
            'final_overlap' => $final_overlap,
4978
            'final_missing' => $final_missing,
4979
            'final_excess' => $final_excess,
4980
            'overlap_color' => $overlap_color,
4981
            'missing_color' => $missing_color,
4982
            'excess_color' => $excess_color,
4983
            'threadhold1' => $threadhold1,
4984
            'threadhold2' => $threadhold2,
4985
            'threadhold3' => $threadhold3,
4986
        );
4987
        if ($from == 'exercise_result') {
4988
            // if answer is hotspot. To the difference of exercise_show.php,
4989
            //  we use the results from the session (from_db=0)
4990
            // TODO Change this, because it is wrong to show the user
4991
            //  some results that haven't been stored in the database yet
4992
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
4993
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4994
                $my_exe_id = 0;
4995
                $from_database = 0;
4996
                if ($answerType == HOT_SPOT_DELINEATION) {
4997
                    if (0) {
4998
                        if ($overlap_color) {
4999
                            $overlap_color = 'green';
5000
                        } else {
5001
                            $overlap_color = 'red';
5002
                        }
5003
                        if ($missing_color) {
5004
                            $missing_color = 'green';
5005
                        } else {
5006
                            $missing_color = 'red';
5007
                        }
5008
                        if ($excess_color) {
5009
                            $excess_color = 'green';
5010
                        } else {
5011
                            $excess_color = 'red';
5012
                        }
5013
                        if (!is_numeric($final_overlap)) {
5014
                            $final_overlap = 0;
5015
                        }
5016
                        if (!is_numeric($final_missing)) {
5017
                            $final_missing = 0;
5018
                        }
5019
                        if (!is_numeric($final_excess)) {
5020
                            $final_excess = 0;
5021
                        }
5022
5023
                        if ($final_overlap > 100) {
5024
                            $final_overlap = 100;
5025
                        }
5026
5027
                        $table_resume = '<table class="data_table">
5028
                                <tr class="row_odd" >
5029
                                    <td></td>
5030
                                    <td ><b>' . get_lang('Requirements').'</b></td>
5031
                                    <td><b>' . get_lang('YourAnswer').'</b></td>
5032
                                </tr>
5033
                                <tr class="row_even">
5034
                                    <td><b>' . get_lang('Overlap').'</b></td>
5035
                                    <td>' . get_lang('Min').' '.$threadhold1.'</td>
5036
                                    <td><div style="color:' . $overlap_color.'">'
5037
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5038
                                </tr>
5039
                                <tr>
5040
                                    <td><b>' . get_lang('Excess').'</b></td>
5041
                                    <td>' . get_lang('Max').' '.$threadhold2.'</td>
5042
                                    <td><div style="color:' . $excess_color.'">'
5043
                                        . (($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5044
                                </tr>
5045
                                <tr class="row_even">
5046
                                    <td><b>' . get_lang('Missing').'</b></td>
5047
                                    <td>' . get_lang('Max').' '.$threadhold3.'</td>
5048
                                    <td><div style="color:' . $missing_color.'">'
5049
                                        . (($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5050
                                </tr>
5051
                            </table>';
5052 View Code Duplication
                        if ($next == 0) {
5053
                            $try = $try_hotspot;
5054
                            $lp = $lp_hotspot;
5055
                            $destinationid = $select_question_hotspot;
5056
                            $url = $url_hotspot;
5057
                        } else {
5058
                            //show if no error
5059
                            //echo 'no error';
5060
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5061
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5062
                        }
5063
5064
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5065
                            <p style="text-align:center">';
5066
5067
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5068
                        $message .= $table_resume;
5069
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5070
                        if ($organs_at_risk_hit > 0) {
5071
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5072
                        }
5073
                        $message .= '<p>'.$comment.'</p>';
5074
                        echo $message;
5075
                    } else {
5076
                        echo $hotspot_delineation_result[0]; //prints message
5077
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5078
                    }
5079
5080
                    //save the score attempts
5081
                    if (1) {
5082
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5083
                        $final_answer = $hotspot_delineation_result[1];
5084
                        if ($final_answer == 0) {
5085
                            $questionScore = 0;
5086
                        }
5087
                        // we always insert the answer_id 1 = delineation
5088
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5089
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5090
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5091
                        Event::saveExerciseAttemptHotspot(
5092
                            $exeId,
5093
                            $quesId,
5094
                            1,
5095
                            $hotspotValue,
5096
                            $exerciseResultCoordinates[$quesId]
5097
                        );
5098
                    } else {
5099
                        if ($final_answer == 0) {
5100
                            $questionScore = 0;
5101
                            $answer = 0;
5102
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5103
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5104
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5105
                                    Event::saveExerciseAttemptHotspot(
5106
                                        $exeId,
5107
                                        $quesId,
5108
                                        $idx,
5109
                                        0,
5110
                                        $val
5111
                                    );
5112
                                }
5113
                            }
5114
                        } else {
5115
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5116
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5117
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5118
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5119
                                    Event::saveExerciseAttemptHotspot(
5120
                                        $exeId,
5121
                                        $quesId,
5122
                                        $idx,
5123
                                        $hotspotValue,
5124
                                        $val
5125
                                    );
5126
                                }
5127
                            }
5128
                        }
5129
                    }
5130
                    $my_exe_id = $exeId;
5131
                }
5132
            }
5133
5134
            $relPath = api_get_path(WEB_CODE_PATH);
5135
5136
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5137
                // We made an extra table for the answers
5138
5139
                if ($show_result) {
5140
5141
                    //	if ($origin != 'learnpath') {
5142
                    echo '</table></td></tr>';
5143
                    echo "
5144
                        <tr>
5145
                            <td colspan=\"2\">
5146
                                <p><em>" . get_lang('HotSpot')."</em></p>
5147
                                <div id=\"hotspot-solution-$questionId\"></div>
5148
                                <script>
5149
                                    $(document).on('ready', function () {
5150
                                        new HotspotQuestion({
5151
                                            questionId: $questionId,
5152
                                            exerciseId: $exeId,
5153
                                            selector: '#hotspot-solution-$questionId',
5154
                                            for: 'solution',
5155
                                            relPath: '$relPath'
5156
                                        });
5157
                                    });
5158
                                </script>
5159
                            </td>
5160
                        </tr>
5161
                    ";
5162
                    //	}
5163
                }
5164
            } elseif ($answerType == ANNOTATION) {
5165
                if ($show_result) {
5166
                    echo '
5167
                        <p><em>' . get_lang('Annotation').'</em></p>
5168
                        <div id="annotation-canvas-'.$questionId.'"></div>
5169
                        <script>
5170
                            AnnotationQuestion({
5171
                                questionId: parseInt('.$questionId.'),
5172
                                exerciseId: parseInt('.$exeId.'),
5173
                                relPath: \''.$relPath.'\'
5174
                            });
5175
                        </script>
5176
                    ';
5177
                }
5178
            }
5179
5180
            //if ($origin != 'learnpath') {
5181
            if ($show_result && $answerType != ANNOTATION) {
5182
                echo '</table>';
5183
            }
5184
            //	}
5185
        }
5186
        unset($objAnswerTmp);
5187
5188
        $totalWeighting += $questionWeighting;
5189
        // Store results directly in the database
5190
        // For all in one page exercises, the results will be
5191
        // stored by exercise_results.php (using the session)
5192
5193
        if ($saved_results) {
5194
            if ($debug) error_log("Save question results $saved_results");
5195
            if ($debug) error_log(print_r($choice, 1));
5196
5197
            if (empty($choice)) {
5198
                $choice = 0;
5199
            }
5200
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5201
                if ($choice != 0) {
5202
                    $reply = array_keys($choice);
5203
                    for ($i = 0; $i < sizeof($reply); $i++) {
5204
                        $ans = $reply[$i];
5205
                        Event::saveQuestionAttempt(
5206
                            $questionScore,
5207
                            $ans.':'.$choice[$ans],
5208
                            $quesId,
5209
                            $exeId,
5210
                            $i,
5211
                            $this->id
5212
                        );
5213
                        if ($debug) {
5214
                            error_log('result =>'.$questionScore.' '.$ans.':'.$choice[$ans]);
5215
                        }
5216
                    }
5217
                } else {
5218
                    Event::saveQuestionAttempt(
5219
                        $questionScore,
5220
                        0,
5221
                        $quesId,
5222
                        $exeId,
5223
                        0,
5224
                        $this->id
5225
                    );
5226
                }
5227
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5228
                if ($choice != 0) {
5229
                    $reply = array_keys($choice);
5230
5231
                    if ($debug) {
5232
                        error_log("reply ".print_r($reply, 1)."");
5233
                    }
5234 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5235
                        $ans = $reply[$i];
5236
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5237
                    }
5238
                } else {
5239
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5240
                }
5241
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5242
                if ($choice != 0) {
5243
                    $reply = array_keys($choice);
5244 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5245
                        $ans = $reply[$i];
5246
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5247
                    }
5248
                } else {
5249
                    Event::saveQuestionAttempt(
5250
                        $questionScore,
5251
                        0,
5252
                        $quesId,
5253
                        $exeId,
5254
                        0,
5255
                        $this->id
5256
                    );
5257
                }
5258
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5259
                if (isset($matching)) {
5260
                    foreach ($matching as $j => $val) {
5261
                        Event::saveQuestionAttempt(
5262
                            $questionScore,
5263
                            $val,
5264
                            $quesId,
5265
                            $exeId,
5266
                            $j,
5267
                            $this->id
5268
                        );
5269
                    }
5270
                }
5271
            } elseif ($answerType == FREE_ANSWER) {
5272
                $answer = $choice;
5273
                Event::saveQuestionAttempt(
5274
                    $questionScore,
5275
                    $answer,
5276
                    $quesId,
5277
                    $exeId,
5278
                    0,
5279
                    $this->id
5280
                );
5281
            } elseif ($answerType == ORAL_EXPRESSION) {
5282
                $answer = $choice;
5283
                Event::saveQuestionAttempt(
5284
                    $questionScore,
5285
                    $answer,
5286
                    $quesId,
5287
                    $exeId,
5288
                    0,
5289
                    $this->id,
5290
                    false,
5291
                    $objQuestionTmp->getAbsoluteFilePath()
5292
                );
5293
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION])) {
5294
                $answer = $choice;
5295
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5296
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5297
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5298
                $answer = [];
5299
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5300
                    Database::delete(
5301
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5302
                        [
5303
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5304
                                $exeId,
5305
                                $questionId,
5306
                                api_get_course_int_id()
5307
                            ]
5308
                        ]
5309
                    );
5310
5311
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5312
                        $answer[] = $val;
5313
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5314
                        Event::saveExerciseAttemptHotspot(
5315
                            $exeId,
5316
                            $quesId,
5317
                            $idx,
5318
                            $hotspotValue,
5319
                            $val,
5320
                            false,
5321
                            $this->id
5322
                        );
5323
                    }
5324
                }
5325
5326
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5327
            } else {
5328
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5329
            }
5330
        }
5331
5332
        if ($propagate_neg == 0 && $questionScore < 0) {
5333
            $questionScore = 0;
5334
        }
5335
5336
        if ($saved_results) {
5337
            $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5338
            $sql = 'UPDATE '.$stat_table.' SET
5339
                        exe_result = exe_result + ' . floatval($questionScore).'
5340
                    WHERE exe_id = ' . $exeId;
5341
            Database::query($sql);
5342
        }
5343
5344
        $return_array = array(
5345
            'score' => $questionScore,
5346
            'weight' => $questionWeighting,
5347
            'extra' => $extra_data,
5348
            'open_question' => $arrques,
5349
            'open_answer' => $arrans,
5350
            'answer_type' => $answerType,
5351
        );
5352
5353
        return $return_array;
5354
    }
5355
5356
    /**
5357
     * Sends a notification when a user ends an examn
5358
     *
5359
     * @param string $type 'start' or 'end' of an exercise
5360
     * @param array $question_list_answers
5361
     * @param string $origin
5362
     * @param int $exe_id
5363
     * @param float $score
5364
     * @param float $weight
5365
     * @return bool
5366
     */
5367
    public function send_mail_notification_for_exam(
5368
        $type = 'end',
5369
        $question_list_answers,
5370
        $origin,
5371
        $exe_id,
5372
        $score = null,
5373
        $weight  = null
5374
    ) {
5375
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5376
5377
        if (empty($setting)) {
5378
            return false;
5379
        }
5380
5381
        // Email configuration settings
5382
        $courseCode = api_get_course_id();
5383
        $courseInfo = api_get_course_info($courseCode);
5384
5385
        if (empty($courseInfo)) {
5386
            return false;
5387
        }
5388
5389
        $sessionId = api_get_session_id();
5390
5391
        $sendStart = false;
5392
        $sendEnd = false;
5393
        $sendEndOpenQuestion = false;
5394
        $sendEndOralQuestion = false;
5395
5396
        foreach ($setting as $option) {
5397
            switch ($option) {
5398
                case 0:
5399
                    return false;
5400
                    break;
5401
                case 1: // End
5402
                    if ($type == 'end') {
5403
                        $sendEnd = true;
5404
                    }
5405
                    break;
5406
                case 2: // start
5407
                    if ($type == 'start') {
5408
                        $sendStart = true;
5409
                    }
5410
                    break;
5411
                case 3: // end + open
5412
                    if ($type == 'end') {
5413
                        $sendEndOpenQuestion = true;
5414
                    }
5415
                    break;
5416
                case 4: // end + oral
5417
                    if ($type == 'end') {
5418
                        $sendEndOralQuestion = true;
5419
                    }
5420
                    break;
5421
            }
5422
        }
5423
5424
        $user_info = api_get_user_info(api_get_user_id());
5425
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.api_get_cidreq().'&id_session='.$sessionId.'&id='.$exe_id.'&action=qualify';
5426
5427
        if (!empty($sessionId)) {
5428
            $addGeneralCoach = true;
5429
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5430
            if ($setting === true) {
5431
                $addGeneralCoach = false;
5432
            }
5433
            $teachers = CourseManager::get_coach_list_from_course_code(
5434
                $courseCode,
5435
                $sessionId,
5436
                $addGeneralCoach
5437
            );
5438
        } else {
5439
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5440
        }
5441
5442
        if ($sendEndOpenQuestion) {
5443
            $this->send_notification_for_open_questions(
5444
                $question_list_answers,
5445
                $origin,
5446
                $exe_id,
5447
                $user_info,
5448
                $url,
5449
                $teachers
5450
            );
5451
        }
5452
5453
        if ($sendEndOralQuestion) {
5454
            $this->send_notification_for_oral_questions(
5455
                $question_list_answers,
5456
                $origin,
5457
                $exe_id,
5458
                $user_info,
5459
                $url,
5460
                $teachers
5461
            );
5462
        }
5463
5464
        if (!$sendEnd && !$sendStart) {
5465
            return false;
5466
        }
5467
5468
5469
        $scoreLabel = '';
5470
        if ($sendEnd && api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true) {
5471
            $scoreLabel = ExerciseLib::show_score($score, $weight, false, true);
5472
            $scoreLabel = "<tr>
5473
                            <td>".get_lang('Score')."</td>
5474
                            <td>&nbsp;$scoreLabel</td>
5475
                        </tr>";
5476
        }
5477
5478
        if ($sendEnd) {
5479
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5480
        } else {
5481
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5482
        }
5483
5484
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5485
                    <table>
5486
                        <tr>
5487
                            <td>'.get_lang('CourseName').'</td>
5488
                            <td>#course#</td>
5489
                        </tr>
5490
                        <tr>
5491
                            <td>'.get_lang('Exercise').'</td>
5492
                            <td>&nbsp;#exercise#</td>
5493
                        </tr>
5494
                        <tr>
5495
                            <td>'.get_lang('StudentName').'</td>
5496
                            <td>&nbsp;#student_complete_name#</td>
5497
                        </tr>
5498
                        <tr>
5499
                            <td>'.get_lang('StudentEmail').'</td>
5500
                            <td>&nbsp;#email#</td>
5501
                        </tr>
5502
                        '.$scoreLabel.'
5503
                    </table>';
5504
5505
        $variables = [
5506
            '#email#' => $user_info['email'],
5507
            '#exercise#' => $this->exercise,
5508
            '#student_complete_name#' => $user_info['complete_name'],
5509
            '#course#' => $courseInfo['title']
5510
        ];
5511
         if ($origin != 'learnpath' && $sendEnd) {
5512
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5513
            $variables['#url#'] = $url;
5514
        }
5515
5516
        $mail_content = str_replace(array_keys($variables), array_values($variables), $msg);
5517
5518
        if ($sendEnd) {
5519
            $subject = get_lang('ExerciseAttempted');
5520
        } else {
5521
            $subject = get_lang('StudentStartExercise');
5522
        }
5523
5524
        if (!empty($teachers)) {
5525
            foreach ($teachers as $user_id => $teacher_data) {
5526
                MessageManager::send_message_simple(
5527
                    $user_id,
5528
                    $subject,
5529
                    $mail_content
5530
                );
5531
            }
5532
        }
5533
    }
5534
5535
    /**
5536
     * Sends a notification when a user ends an examn
5537
     * @param array $question_list_answers
5538
     * @param string $origin
5539
     * @param int $exe_id
5540
     * @return null
5541
     */
5542
    private function send_notification_for_open_questions(
5543
        $question_list_answers,
5544
        $origin,
5545
        $exe_id,
5546
        $user_info,
5547
        $url_email,
5548
        $teachers
5549
    ) {
5550
        // Email configuration settings
5551
        $courseCode = api_get_course_id();
5552
        $course_info = api_get_course_info($courseCode);
5553
5554
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5555
                    .get_lang('AttemptDetails').' : <br /><br />'
5556
                    .'<table>'
5557
                        .'<tr>'
5558
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5559
                            .'<td>&nbsp;<b>#course#</b></td>'
5560
                        .'</tr>'
5561
                        .'<tr>'
5562
                            .'<td>'.get_lang('TestAttempted').'</td>'
5563
                            .'<td>&nbsp;#exercise#</td>'
5564
                        .'</tr>'
5565
                        .'<tr>'
5566
                            .'<td>'.get_lang('StudentName').'</td>'
5567
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5568
                        .'</tr>'
5569
                        .'<tr>'
5570
                            .'<td>'.get_lang('StudentEmail').'</td>'
5571
                            .'<td>&nbsp;#mail#</td>'
5572
                        .'</tr>'
5573
                    .'</table>';
5574
        $open_question_list = null;
5575 View Code Duplication
        foreach ($question_list_answers as $item) {
5576
            $question = $item['question'];
5577
            $answer = $item['answer'];
5578
            $answer_type = $item['answer_type'];
5579
5580
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5581
                $open_question_list .=
5582
                    '<tr>'
5583
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5584
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5585
                    .'</tr>'
5586
                    .'<tr>'
5587
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5588
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5589
                    .'</tr>';
5590
            }
5591
        }
5592
5593
        if (!empty($open_question_list)) {
5594
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5595
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5596
            $msg .= $open_question_list;
5597
            $msg .= '</table><br />';
5598
5599
5600
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5601
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5602
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5603
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5604
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5605
5606
            if ($origin != 'learnpath') {
5607
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5608
            }
5609
            $msg1 = str_replace("#url#", $url_email, $msg);
5610
            $mail_content = $msg1;
5611
            $subject = get_lang('OpenQuestionsAttempted');
5612
5613
            if (!empty($teachers)) {
5614
                foreach ($teachers as $user_id => $teacher_data) {
5615
                    MessageManager::send_message_simple(
5616
                        $user_id,
5617
                        $subject,
5618
                        $mail_content
5619
                    );
5620
                }
5621
            }
5622
        }
5623
    }
5624
5625
    /**
5626
     * Send notification for oral questions
5627
     * @param array $question_list_answers
5628
     * @param string $origin
5629
     * @param int $exe_id
5630
     * @return null
5631
     */
5632
    private function send_notification_for_oral_questions(
5633
        $question_list_answers,
5634
        $origin,
5635
        $exe_id,
5636
        $user_info,
5637
        $url_email,
5638
        $teachers
5639
    ) {
5640
        // Email configuration settings
5641
        $courseCode = api_get_course_id();
5642
        $course_info = api_get_course_info($courseCode);
5643
5644
        $oral_question_list = null;
5645 View Code Duplication
        foreach ($question_list_answers as $item) {
5646
            $question = $item['question'];
5647
            $answer = $item['answer'];
5648
            $answer_type = $item['answer_type'];
5649
5650
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5651
                $oral_question_list .= '<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5652
                    .'<tr>'
5653
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5654
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5655
                    .'</tr>'
5656
                    .'<tr>'
5657
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5658
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5659
                    .'</tr></table>';
5660
            }
5661
        }
5662
5663
        if (!empty($oral_question_list)) {
5664
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5665
                    '.get_lang('AttemptDetails').' : <br /><br />'
5666
                    .'<table>'
5667
                        .'<tr>'
5668
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5669
                            .'<td>&nbsp;<b>#course#</b></td>'
5670
                        .'</tr>'
5671
                        .'<tr>'
5672
                            .'<td>'.get_lang('TestAttempted').'</td>'
5673
                            .'<td>&nbsp;#exercise#</td>'
5674
                        .'</tr>'
5675
                        .'<tr>'
5676
                            .'<td>'.get_lang('StudentName').'</td>'
5677
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5678
                        .'</tr>'
5679
                        .'<tr>'
5680
                            .'<td>'.get_lang('StudentEmail').'</td>'
5681
                            .'<td>&nbsp;#mail#</td>'
5682
                        .'</tr>'
5683
                    .'</table>';
5684
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
5685
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5686
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5687
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5688
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5689
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5690
5691
            if ($origin != 'learnpath') {
5692
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5693
            }
5694
            $msg1 = str_replace("#url#", $url_email, $msg);
5695
            $mail_content = $msg1;
5696
            $subject = get_lang('OralQuestionsAttempted');
5697
5698
            if (!empty($teachers)) {
5699
                foreach ($teachers as $user_id => $teacher_data) {
5700
                    MessageManager::send_message_simple(
5701
                        $user_id,
5702
                        $subject,
5703
                        $mail_content
5704
                    );
5705
                }
5706
            }
5707
        }
5708
    }
5709
5710
    /**
5711
     * @param array $user_data result of api_get_user_info()
5712
     * @param string $start_date
5713
     * @param null $duration
5714
     * @param string $ip Optional. The user IP
5715
     * @return string
5716
     */
5717
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5718
    {
5719
        $array = array();
5720
5721
        if (!empty($user_data)) {
5722
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5723
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5724
            if (!empty($user_data['official_code'])) {
5725
                $array[] = array(
5726
                    'title' => get_lang('OfficialCode'),
5727
                    'content' => $user_data['official_code']
5728
                );
5729
            }
5730
        }
5731
        // Description can be very long and is generally meant to explain
5732
        //   rules *before* the exam. Leaving here to make display easier if
5733
        //   necessary
5734
        /*
5735
        if (!empty($this->description)) {
5736
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5737
        }
5738
        */
5739 View Code Duplication
        if (!empty($start_date)) {
5740
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5741
        }
5742
5743 View Code Duplication
        if (!empty($duration)) {
5744
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5745
        }
5746
5747 View Code Duplication
        if (!empty($ip)) {
5748
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5749
        }
5750
5751
        $icon = Display::return_icon('test-quiz.png', get_lang('Result'), null, ICON_SIZE_MEDIUM);
5752
5753
        $html = '<div class="question-result">';
5754
5755
        if (api_get_configuration_value('save_titles_as_html')) {
5756
            $html .= $this->get_formated_title();
5757
            $html .= Display::page_header(get_lang('Result'));
5758
        } else {
5759
            $html .= Display::page_header(
5760
                $icon.PHP_EOL.$this->exercise.' : '.get_lang('Result')
5761
            );
5762
        }
5763
5764
        $html .= Display::description($array);
5765
        $html .= "</div>";
5766
        return $html;
5767
    }
5768
5769
    /**
5770
     * Create a quiz from quiz data
5771
     * @param string  Title
5772
     * @param int     Time before it expires (in minutes)
5773
     * @param int     Type of exercise
5774
     * @param int     Whether it's randomly picked questions (1) or not (0)
5775
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5776
     * @param int     Whether the results are show to the user (0) or not (1)
5777
     * @param int     Maximum number of attempts (0 if no limit)
5778
     * @param int     Feedback type
5779
     * @todo this was function was added due the import exercise via CSV
5780
     * @return    int New exercise ID
5781
     */
5782
    public function createExercise(
5783
        $title,
5784
        $expired_time = 0,
5785
        $type = 2,
5786
        $random = 0,
5787
        $active = 1,
5788
        $results_disabled = 0,
5789
        $max_attempt = 0,
5790
        $feedback = 3,
5791
        $propagateNegative = 0
5792
    ) {
5793
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5794
        $type = intval($type);
5795
        $random = intval($random);
5796
        $active = intval($active);
5797
        $results_disabled = intval($results_disabled);
5798
        $max_attempt = intval($max_attempt);
5799
        $feedback = intval($feedback);
5800
        $expired_time = intval($expired_time);
5801
        $title = Database::escape_string($title);
5802
        $propagateNegative = intval($propagateNegative);
5803
        $sessionId = api_get_session_id();
5804
        $course_id = api_get_course_int_id();
5805
        // Save a new quiz
5806
        $sql = "INSERT INTO $tbl_quiz (
5807
                c_id,
5808
                title,
5809
                type,
5810
                random,
5811
                active,
5812
                results_disabled,
5813
                max_attempt,
5814
                start_time,
5815
                end_time,
5816
                feedback_type,
5817
                expired_time,
5818
                session_id,
5819
                propagate_neg
5820
            )
5821
            VALUES (
5822
                '$course_id',
5823
                '$title',
5824
                $type,
5825
                $random,
5826
                $active,
5827
                $results_disabled,
5828
                $max_attempt,
5829
                '',
5830
                '',
5831
                $feedback,
5832
                $expired_time,
5833
                $sessionId,
5834
                $propagateNegative
5835
            )";
5836
        Database::query($sql);
5837
        $quiz_id = Database::insert_id();
5838
5839
        if ($quiz_id) {
5840
5841
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5842
            Database::query($sql);
5843
        }
5844
5845
        return $quiz_id;
5846
    }
5847
5848
    function process_geometry()
5849
    {
5850
5851
    }
5852
5853
    /**
5854
     * Returns the exercise result
5855
     * @param 	int		attempt id
5856
     * @return 	float 	exercise result
5857
     */
5858
    public function get_exercise_result($exe_id)
5859
    {
5860
        $result = array();
5861
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5862
5863
        if (!empty($track_exercise_info)) {
5864
            $totalScore = 0;
5865
            $objExercise = new Exercise();
5866
            $objExercise->read($track_exercise_info['exe_exo_id']);
5867
            if (!empty($track_exercise_info['data_tracking'])) {
5868
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5869
            }
5870
            foreach ($question_list as $questionId) {
5871
                $question_result = $objExercise->manage_answer(
5872
                    $exe_id,
5873
                    $questionId,
5874
                    '',
5875
                    'exercise_show',
5876
                    array(),
5877
                    false,
5878
                    true,
5879
                    false,
5880
                    $objExercise->selectPropagateNeg()
5881
                );
5882
                $totalScore += $question_result['score'];
5883
            }
5884
5885
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5886
                $totalScore = 0;
5887
            }
5888
            $result = array(
5889
                'score' => $totalScore,
5890
                'weight' => $track_exercise_info['exe_weighting']
5891
            );
5892
        }
5893
        return $result;
5894
    }
5895
5896
    /**
5897
     * Checks if the exercise is visible due a lot of conditions
5898
     * visibility, time limits, student attempts
5899
     * Return associative array
5900
     * value : true if execise visible
5901
     * message : HTML formated message
5902
     * rawMessage : text message
5903
     * @param int $lpId
5904
     * @param int $lpItemId
5905
     * @param int $lpItemViewId
5906
     * @param bool $filterByAdmin
5907
     * @return array
5908
     */
5909
    public function is_visible(
5910
        $lpId = 0,
5911
        $lpItemId = 0,
5912
        $lpItemViewId = 0,
5913
        $filterByAdmin = true
5914
    ) {
5915
        // 1. By default the exercise is visible
5916
        $isVisible = true;
5917
        $message = null;
5918
5919
        // 1.1 Admins and teachers can access to the exercise
5920
        if ($filterByAdmin) {
5921
            if (api_is_platform_admin() || api_is_course_admin()) {
5922
                return array('value' => true, 'message' => '');
5923
            }
5924
        }
5925
5926
        // Deleted exercise.
5927 View Code Duplication
        if ($this->active == -1) {
5928
            return array(
5929
                'value' => false,
5930
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5931
                'rawMessage' => get_lang('ExerciseNotFound')
5932
            );
5933
        }
5934
5935
        // Checking visibility in the item_property table.
5936
        $visibility = api_get_item_visibility(
5937
            api_get_course_info(),
5938
            TOOL_QUIZ,
5939
            $this->id,
5940
            api_get_session_id()
5941
        );
5942
5943
        if ($visibility == 0 || $visibility == 2) {
5944
            $this->active = 0;
5945
        }
5946
5947
        // 2. If the exercise is not active.
5948
        if (empty($lpId)) {
5949
            // 2.1 LP is OFF
5950 View Code Duplication
            if ($this->active == 0) {
5951
                return array(
5952
                    'value' => false,
5953
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5954
                    'rawMessage' => get_lang('ExerciseNotFound')
5955
                );
5956
            }
5957
        } else {
5958
            // 2.1 LP is loaded
5959
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5960
                return array(
5961
                    'value' => false,
5962
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5963
                    'rawMessage' => get_lang('ExerciseNotFound')
5964
                );
5965
            }
5966
        }
5967
5968
        //3. We check if the time limits are on
5969
        if (!empty($this->start_time) || !empty($this->end_time)) {
5970
            $limitTimeExists = true;
5971
        } else {
5972
            $limitTimeExists = false;
5973
        }
5974
5975
        if ($limitTimeExists) {
5976
            $timeNow = time();
5977
5978
            $existsStartDate = false;
5979
            $nowIsAfterStartDate = true;
5980
            $existsEndDate = false;
5981
            $nowIsBeforeEndDate = true;
5982
5983
            if (!empty($this->start_time)) {
5984
                $existsStartDate = true;
5985
            }
5986
5987
            if (!empty($this->end_time)) {
5988
                $existsEndDate = true;
5989
            }
5990
5991
            // check if we are before-or-after end-or-start date
5992
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5993
                $nowIsAfterStartDate = false;
5994
            }
5995
5996
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5997
                $nowIsBeforeEndDate = false;
5998
            }
5999
6000
            // lets check all cases
6001
            if ($existsStartDate && !$existsEndDate) {
6002
                // exists start date and dont exists end date
6003
                if ($nowIsAfterStartDate) {
6004
                    // after start date, no end date
6005
                    $isVisible = true;
6006
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
6007
                        api_convert_and_format_date($this->start_time));
6008
                } else {
6009
                    // before start date, no end date
6010
                    $isVisible = false;
6011
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
6012
                        api_convert_and_format_date($this->start_time));
6013
            }
6014
            } elseif (!$existsStartDate && $existsEndDate) {
6015
                // doesnt exist start date, exists end date
6016
                if ($nowIsBeforeEndDate) {
6017
                    // before end date, no start date
6018
                    $isVisible = true;
6019
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
6020
                        api_convert_and_format_date($this->end_time));
6021
                } else {
6022
                    // after end date, no start date
6023
                    $isVisible = false;
6024
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
6025
                        api_convert_and_format_date($this->end_time));
6026
                }
6027
            } elseif ($existsStartDate && $existsEndDate) {
6028
                // exists start date and end date
6029
                if ($nowIsAfterStartDate) {
6030
                    if ($nowIsBeforeEndDate) {
6031
                        // after start date and before end date
6032
                        $isVisible = true;
6033
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
6034
                            api_convert_and_format_date($this->start_time),
6035
                            api_convert_and_format_date($this->end_time));
6036 View Code Duplication
                    } else {
6037
                        // after start date and after end date
6038
                        $isVisible = false;
6039
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
6040
                            api_convert_and_format_date($this->start_time),
6041
                            api_convert_and_format_date($this->end_time));
6042
                    }
6043 View Code Duplication
                } else {
6044
                    if ($nowIsBeforeEndDate) {
6045
                        // before start date and before end date
6046
                        $isVisible = false;
6047
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
6048
                            api_convert_and_format_date($this->start_time),
6049
                            api_convert_and_format_date($this->end_time));
6050
                    }
6051
                    // case before start date and after end date is impossible
6052
                }
6053
            } elseif (!$existsStartDate && !$existsEndDate) {
6054
                // doesnt exist start date nor end date
6055
                $isVisible = true;
6056
                $message = "";
6057
            }
6058
        }
6059
6060
        // 4. We check if the student have attempts
6061
        $exerciseAttempts = $this->selectAttempts();
6062
6063
        if ($isVisible) {
6064
            if ($exerciseAttempts > 0) {
6065
6066
                $attemptCount = Event::get_attempt_count_not_finished(
6067
                    api_get_user_id(),
6068
                    $this->id,
6069
                    $lpId,
6070
                    $lpItemId,
6071
                    $lpItemViewId
6072
                );
6073
6074
                if ($attemptCount >= $exerciseAttempts) {
6075
                    $message = sprintf(
6076
                        get_lang('ReachedMaxAttempts'),
6077
                        $this->name,
6078
                        $exerciseAttempts
6079
                    );
6080
                    $isVisible = false;
6081
                }
6082
            }
6083
        }
6084
6085
        $rawMessage = "";
6086
        if (!empty($message)) {
6087
            $rawMessage = $message;
6088
            $message = Display::return_message($message, 'warning', false);
6089
        }
6090
6091
        return array(
6092
            'value' => $isVisible,
6093
            'message' => $message,
6094
            'rawMessage' => $rawMessage
6095
        );
6096
    }
6097
6098
    public function added_in_lp()
6099
    {
6100
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6101
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6102
            WHERE c_id = {$this->course_id} AND item_type = '".TOOL_QUIZ."' AND path = '{$this->id}'";
6103
        $result = Database::query($sql);
6104
        if (Database::num_rows($result) > 0) {
6105
            return true;
6106
        }
6107
        return false;
6108
    }
6109
6110
    /**
6111
     * Returns an array with the media list
6112
     * @param array question list
6113
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
6114
     * <code>
6115
     * array (size=2)
6116
     *  999 =>
6117
     *    array (size=3)
6118
     *      0 => int 7
6119
     *      1 => int 6
6120
     *      2 => int 3254
6121
     *  100 =>
6122
     *   array (size=1)
6123
     *      0 => int 5
6124
     *  </code>
6125
     * @return array
6126
     */
6127
    private function setMediaList($questionList)
6128
    {
6129
        $mediaList = array();
6130
        if (!empty($questionList)) {
6131
            foreach ($questionList as $questionId) {
6132
                $objQuestionTmp = Question::read($questionId, $this->course_id);
6133
6134
                // If a media question exists
6135
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
6136
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
6137
                } else {
6138
                    //Always the last item
6139
                    $mediaList[999][] = $objQuestionTmp->id;
6140
                }
6141
            }
6142
        }
6143
        $this->mediaList = $mediaList;
6144
    }
6145
6146
    /**
6147
     * Returns an array with this form
6148
     * @example
6149
     * <code>
6150
     * array (size=3)
6151
    999 =>
6152
    array (size=3)
6153
    0 => int 3422
6154
    1 => int 3423
6155
    2 => int 3424
6156
    100 =>
6157
    array (size=2)
6158
    0 => int 3469
6159
    1 => int 3470
6160
    101 =>
6161
    array (size=1)
6162
    0 => int 3482
6163
     * </code>
6164
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6165
     * this case is special because 999 means "no media".
6166
     * @return array
6167
     */
6168
    public function getMediaList()
6169
    {
6170
        return $this->mediaList;
6171
    }
6172
6173
    /**
6174
     * Is media question activated?
6175
     * @return bool
6176
     */
6177
    public function mediaIsActivated()
6178
    {
6179
        $mediaQuestions = $this->getMediaList();
6180
        $active = false;
6181
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6182
            $media_count = count($mediaQuestions);
6183
            if ($media_count > 1) {
6184
                return true;
6185
            } elseif ($media_count == 1) {
6186
                if (isset($mediaQuestions[999])) {
6187
                    return false;
6188
                } else {
6189
                    return true;
6190
                }
6191
            }
6192
        }
6193
6194
        return $active;
6195
    }
6196
6197
    /**
6198
     * Gets question list from the exercise
6199
     *
6200
     * @return array
6201
     */
6202
    public function getQuestionList()
6203
    {
6204
        return $this->questionList;
6205
    }
6206
6207
    /**
6208
     * Question list with medias compressed like this
6209
     * @example
6210
     * <code>
6211
     * array(
6212
     *      question_id_1,
6213
     *      question_id_2,
6214
     *      media_id, <- this media id contains question ids
6215
     *      question_id_3,
6216
     * )
6217
     * </code>
6218
     * @return array
6219
     */
6220
    public function getQuestionListWithMediasCompressed()
6221
    {
6222
        return $this->questionList;
6223
    }
6224
6225
    /**
6226
     * Question list with medias uncompressed like this
6227
     * @example
6228
     * <code>
6229
     * array(
6230
     *      question_id,
6231
     *      question_id,
6232
     *      question_id, <- belongs to a media id
6233
     *      question_id, <- belongs to a media id
6234
     *      question_id,
6235
     * )
6236
     * </code>
6237
     * @return array
6238
     */
6239
    public function getQuestionListWithMediasUncompressed()
6240
    {
6241
        return $this->questionListUncompressed;
6242
    }
6243
6244
    /**
6245
     * Sets the question list when the exercise->read() is executed
6246
     * @param   bool    $adminView  Whether to view the set the list of *all* questions or just the normal student view
6247
     */
6248
    public function setQuestionList($adminView = false)
6249
    {
6250
        // Getting question list.
6251
        $questionList = $this->selectQuestionList(true, $adminView);
6252
6253
        $this->setMediaList($questionList);
6254
6255
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
6256
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
6257
    }
6258
6259
    /**
6260
     *
6261
     * @params array question list
6262
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
6263
     *
6264
     **/
6265 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
6266
    {
6267
        $new_question_list = array();
6268
        if (!empty($question_list)) {
6269
            $media_questions = $this->getMediaList();
6270
6271
            $media_active = $this->mediaIsActivated($media_questions);
6272
6273
            if ($media_active) {
6274
                $counter = 1;
6275
                foreach ($question_list as $question_id) {
6276
                    $add_question = true;
6277
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6278
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6279
                            $add_question = false;
6280
                            if (!in_array($media_id, $new_question_list)) {
6281
                                $new_question_list[$counter] = $media_id;
6282
                                $counter++;
6283
                            }
6284
                            break;
6285
                        }
6286
                    }
6287
                    if ($add_question) {
6288
                        $new_question_list[$counter] = $question_id;
6289
                        $counter++;
6290
                    }
6291
                }
6292
                if ($expand_media_questions) {
6293
                    $media_key_list = array_keys($media_questions);
6294
                    foreach ($new_question_list as &$question_id) {
6295
                        if (in_array($question_id, $media_key_list)) {
6296
                            $question_id = $media_questions[$question_id];
6297
                        }
6298
                    }
6299
                    $new_question_list = array_flatten($new_question_list);
6300
                }
6301
            } else {
6302
                $new_question_list = $question_list;
6303
            }
6304
        }
6305
6306
        return $new_question_list;
6307
    }
6308
6309
    /**
6310
     * Get question list depend on the random settings.
6311
     *
6312
     * @return array
6313
     */
6314
    public function get_validated_question_list()
6315
    {
6316
        $tabres = array();
6317
        $isRandomByCategory = $this->isRandomByCat();
6318
        if ($isRandomByCategory == 0) {
6319
            if ($this->isRandom()) {
6320
                $tabres = $this->selectRandomList();
6321
            } else {
6322
                $tabres = $this->selectQuestionList();
6323
            }
6324
        } else {
6325
            if ($this->isRandom()) {
6326
                // USE question categories
6327
                // get questions by category for this exercise
6328
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6329
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6330
                // value is the array of question id of this category
6331
                $questionList = array();
6332
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6333
                $isRandomByCategory = $this->selectRandomByCat();
6334
                // We sort categories based on the term between [] in the head
6335
                // of the category's description
6336
                /* examples of categories :
6337
                 * [biologie] Maitriser les mecanismes de base de la genetique
6338
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6339
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6340
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6341
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6342
                 * [chimie] Connaître les charges des particules
6343
                 * We want that in the order of the groups defined by the term
6344
                 * between brackets at the beginning of the category title
6345
                */
6346
                // If test option is Grouped By Categories
6347
                if ($isRandomByCategory == 2) {
6348
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6349
                }
6350
                while (list($cat_id, $tabquestion) = each($tabCategoryQuestions)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $cat_id is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
6351
                    $number_of_random_question = $this->random;
6352
                    if ($this->random == -1) {
6353
                        $number_of_random_question = count($this->questionList);
6354
                    }
6355
                    $questionList = array_merge(
6356
                        $questionList,
6357
                        TestCategory::getNElementsFromArray(
6358
                            $tabquestion,
6359
                            $number_of_random_question
6360
                        )
6361
                    );
6362
                }
6363
                // shuffle the question list if test is not grouped by categories
6364
                if ($isRandomByCategory == 1) {
6365
                    shuffle($questionList); // or not
6366
                }
6367
                $tabres = $questionList;
6368
            } 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...
6369
                // Problem, random by category has been selected and
6370
                // we have no $this->isRandom number of question selected
6371
                // Should not happened
6372
            }
6373
        }
6374
        return $tabres;
6375
    }
6376
6377
    function get_question_list($expand_media_questions = false)
6378
    {
6379
        $question_list = $this->get_validated_question_list();
6380
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6381
        return $question_list;
6382
    }
6383
6384 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6385
    {
6386
        $new_question_list = array();
6387
        if (!empty($question_list)) {
6388
            $media_questions = $this->getMediaList();
6389
            $media_active = $this->mediaIsActivated($media_questions);
6390
6391
            if ($media_active) {
6392
                $counter = 1;
6393
                foreach ($question_list as $question_id) {
6394
                    $add_question = true;
6395
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6396
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6397
                            $add_question = false;
6398
                            if (!in_array($media_id, $new_question_list)) {
6399
                                $new_question_list[$counter] = $media_id;
6400
                                $counter++;
6401
                            }
6402
                            break;
6403
                        }
6404
                    }
6405
                    if ($add_question) {
6406
                        $new_question_list[$counter] = $question_id;
6407
                        $counter++;
6408
                    }
6409
                }
6410
                if ($expand_media_questions) {
6411
                    $media_key_list = array_keys($media_questions);
6412
                    foreach ($new_question_list as &$question_id) {
6413
                        if (in_array($question_id, $media_key_list)) {
6414
                            $question_id = $media_questions[$question_id];
6415
                        }
6416
                    }
6417
                    $new_question_list = array_flatten($new_question_list);
6418
                }
6419
            } else {
6420
                $new_question_list = $question_list;
6421
            }
6422
        }
6423
        return $new_question_list;
6424
    }
6425
6426
    /**
6427
     * @param int $exe_id
6428
     * @return array|mixed
6429
     */
6430
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6431
    {
6432
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6433
        $exe_id = intval($exe_id);
6434
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6435
        $result = Database::query($sql_track);
6436
        $new_array = array();
6437
        if (Database::num_rows($result) > 0) {
6438
            $new_array = Database::fetch_array($result, 'ASSOC');
6439
6440
            $new_array['duration'] = null;
6441
6442
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6443
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6444
6445
            if (!empty($start_date) && !empty($end_date)) {
6446
                $start_date = api_strtotime($start_date, 'UTC');
0 ignored issues
show
Bug introduced by
It seems like $start_date defined by api_strtotime($start_date, 'UTC') on line 6446 can also be of type object<DateTime>; however, api_strtotime() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
6447
                $end_date = api_strtotime($end_date, 'UTC');
0 ignored issues
show
Bug introduced by
It seems like $end_date defined by api_strtotime($end_date, 'UTC') on line 6447 can also be of type object<DateTime>; however, api_strtotime() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
6448
                if ($start_date && $end_date) {
6449
                    $mytime = $end_date - $start_date;
6450
                    $new_learnpath_item = new learnpathItem(null);
6451
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6452
                    $h = get_lang('h');
6453
                    $time_attemp = str_replace('NaN', '00'.$h.'00\'00"', $time_attemp);
6454
                    $new_array['duration'] = $time_attemp;
6455
                }
6456
            }
6457
        }
6458
        return $new_array;
6459
    }
6460
6461
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6462
    {
6463
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6464
        $question_id = intval($question_id);
6465
        $exe_id = intval($exe_id);
6466
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6467
        if ($exercise_info) {
6468
6469
            if (empty($exercise_info['questions_to_check'])) {
6470
                if ($action == 'add') {
6471
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6472
                    Database::query($sql);
6473
                }
6474
            } else {
6475
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6476
6477
                $remind_list_string = '';
6478
                if ($action == 'add') {
6479
                    if (!in_array($question_id, $remind_list)) {
6480
                        $remind_list[] = $question_id;
6481
                        if (!empty($remind_list)) {
6482
                            sort($remind_list);
6483
                            array_filter($remind_list);
6484
                        }
6485
                        $remind_list_string = implode(',', $remind_list);
6486
                    }
6487
                } elseif ($action == 'delete') {
6488
                    if (!empty($remind_list)) {
6489
                        if (in_array($question_id, $remind_list)) {
6490
                            $remind_list = array_flip($remind_list);
6491
                            unset($remind_list[$question_id]);
6492
                            $remind_list = array_flip($remind_list);
6493
6494
                            if (!empty($remind_list)) {
6495
                                sort($remind_list);
6496
                                array_filter($remind_list);
6497
                                $remind_list_string = implode(',', $remind_list);
6498
                            }
6499
                        }
6500
                    }
6501
                }
6502
                $remind_list_string = Database::escape_string($remind_list_string);
6503
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6504
                Database::query($sql);
6505
            }
6506
        }
6507
    }
6508
6509
    public function fill_in_blank_answer_to_array($answer)
6510
    {
6511
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6512
        $teacher_answer_list = $teacher_answer_list[0];
6513
        return $teacher_answer_list;
6514
    }
6515
6516
    public function fill_in_blank_answer_to_string($answer)
6517
    {
6518
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6519
        $result = '';
6520
        if (!empty($teacher_answer_list)) {
6521
            $i = 0;
6522
            foreach ($teacher_answer_list as $teacher_item) {
6523
                $value = null;
6524
                //Cleaning student answer list
6525
                $value = strip_tags($teacher_item);
6526
                $value = api_substr($value, 1, api_strlen($value) - 2);
6527
                $value = explode('/', $value);
6528
                if (!empty($value[0])) {
6529
                    $value = trim($value[0]);
6530
                    $value = str_replace('&nbsp;', '', $value);
6531
                    $result .= $value;
6532
                }
6533
            }
6534
        }
6535
        return $result;
6536
    }
6537
6538
    public function return_time_left_div()
6539
    {
6540
        $html = '<div id="clock_warning" style="display:none">';
6541
        $html .= Display::return_message(
6542
            get_lang('ReachedTimeLimit'),
6543
            'warning'
6544
        );
6545
        $html .= ' ';
6546
        $html .= sprintf(
6547
            get_lang('YouWillBeRedirectedInXSeconds'),
6548
            '<span id="counter_to_redirect" class="red_alert"></span>'
6549
        );
6550
        $html .= '</div>';
6551
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6552
        return $html;
6553
    }
6554
6555
    function get_count_question_list()
6556
    {
6557
        //Real question count
6558
        $question_count = 0;
6559
        $question_list = $this->get_question_list();
6560
        if (!empty($question_list)) {
6561
            $question_count = count($question_list);
6562
        }
6563
        return $question_count;
6564
    }
6565
6566
    function get_exercise_list_ordered()
6567
    {
6568
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6569
        $course_id = api_get_course_int_id();
6570
        $session_id = api_get_session_id();
6571
        $sql = "SELECT exercise_id, exercise_order
6572
                FROM $table_exercise_order
6573
                WHERE c_id = $course_id AND session_id = $session_id
6574
                ORDER BY exercise_order";
6575
        $result = Database::query($sql);
6576
        $list = array();
6577
        if (Database::num_rows($result)) {
6578
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6579
                $list[$row['exercise_order']] = $row['exercise_id'];
6580
            }
6581
        }
6582
        return $list;
6583
    }
6584
6585
    /**
6586
     * Get categories added in the exercise--category matrix
6587
     * @return bool
6588
     */
6589
    public function get_categories_in_exercise()
6590
    {
6591
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6592
        if (!empty($this->id)) {
6593
            $sql = "SELECT * FROM $table
6594
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6595
            $result = Database::query($sql);
6596
            $list = array();
6597
            if (Database::num_rows($result)) {
6598
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6599
                    $list[$row['category_id']] = $row;
6600
                }
6601
                return $list;
6602
            }
6603
        }
6604
        return false;
6605
    }
6606
6607
    /**
6608
     * @param null $order
6609
     * @return bool
6610
     */
6611
    public function get_categories_with_name_in_exercise($order = null)
6612
    {
6613
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6614
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6615
        $sql = "SELECT * FROM $table qc
6616
                INNER JOIN $table_category c
6617
                ON (category_id = c.iid)
6618
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6619
        if (!empty($order)) {
6620
            $sql .= "ORDER BY $order ";
6621
        }
6622
        $result = Database::query($sql);
6623
        if (Database::num_rows($result)) {
6624
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6625
                $list[$row['category_id']] = $row;
6626
            }
6627
            return $list;
6628
        }
6629
        return false;
6630
    }
6631
6632
    /**
6633
     * Get total number of question that will be parsed when using the category/exercise
6634
     */
6635 View Code Duplication
    public function getNumberQuestionExerciseCategory()
6636
    {
6637
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6638
        if (!empty($this->id)) {
6639
            $sql = "SELECT SUM(count_questions) count_questions
6640
                    FROM $table
6641
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6642
            $result = Database::query($sql);
6643
            if (Database::num_rows($result)) {
6644
                $row = Database::fetch_array($result);
6645
                return $row['count_questions'];
6646
            }
6647
        }
6648
        return 0;
6649
    }
6650
6651
    /**
6652
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6653
     * @param array $categories
6654
     */
6655
    public function save_categories_in_exercise($categories)
6656
    {
6657
        if (!empty($categories) && !empty($this->id)) {
6658
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6659
            $sql = "DELETE FROM $table
6660
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6661
            Database::query($sql);
6662
            if (!empty($categories)) {
6663
                foreach ($categories as $category_id => $count_questions) {
6664
                    $params = array(
6665
                        'c_id' => $this->course_id,
6666
                        'exercise_id' => $this->id,
6667
                        'category_id' => $category_id,
6668
                        'count_questions' => $count_questions
6669
                    );
6670
                    Database::insert($table, $params);
6671
                }
6672
            }
6673
        }
6674
    }
6675
6676
    /**
6677
     * @param array $questionList
6678
     * @param int $currentQuestion
6679
     * @param array $conditions
6680
     * @param string $link
6681
     * @return string
6682
     */
6683
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6684
    {
6685
        $mediaQuestions = $this->getMediaList();
6686
6687
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6688
        $counter = 0;
6689
        $nextValue = 0;
6690
        $wasMedia = false;
6691
        $before = 0;
6692
        $counterNoMedias = 0;
6693
        foreach ($questionList as $questionId) {
6694
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6695
6696
            if (!empty($nextValue)) {
6697
                if ($wasMedia) {
6698
                    $nextValue = $nextValue - $before + 1;
6699
                }
6700
            }
6701
6702
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6703
                $fixedValue = $counterNoMedias;
6704
6705
                $html .= Display::progressPaginationBar(
6706
                    $nextValue,
6707
                    $mediaQuestions[$questionId],
6708
                    $currentQuestion,
6709
                    $fixedValue,
6710
                    $conditions,
6711
                    $link,
6712
                    true,
6713
                    true
6714
                );
6715
6716
                $counter += count($mediaQuestions[$questionId]) - 1;
6717
                $before = count($questionList);
6718
                $wasMedia = true;
6719
                $nextValue += count($questionList);
6720
            } else {
6721
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6722
                $counter++;
6723
                $nextValue++;
6724
                $wasMedia = false;
6725
            }
6726
            $counterNoMedias++;
6727
        }
6728
        $html .= '</ul></div>';
6729
        return $html;
6730
    }
6731
6732
6733
    /**
6734
     *  Shows a list of numbers that represents the question to answer in a exercise
6735
     *
6736
     * @param array $categories
6737
     * @param int $current
6738
     * @param array $conditions
6739
     * @param string $link
6740
     * @return string
6741
     */
6742
    public function progressExercisePaginationBarWithCategories(
6743
        $categories,
6744
        $current,
6745
        $conditions = array(),
6746
        $link = null
6747
    ) {
6748
        $html = null;
6749
        $counterNoMedias = 0;
6750
        $nextValue = 0;
6751
        $wasMedia = false;
6752
        $before = 0;
6753
6754
        if (!empty($categories)) {
6755
            $selectionType = $this->getQuestionSelectionType();
6756
            $useRootAsCategoryTitle = false;
6757
6758
            // Grouping questions per parent category see BT#6540
6759
6760
            if (in_array(
6761
                $selectionType,
6762
                array(
6763
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6764
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6765
                )
6766
            )) {
6767
                $useRootAsCategoryTitle = true;
6768
            }
6769
6770
            // If the exercise is set to only show the titles of the categories
6771
            // at the root of the tree, then pre-order the categories tree by
6772
            // removing children and summing their questions into the parent
6773
            // categories
6774
6775
            if ($useRootAsCategoryTitle) {
6776
                // The new categories list starts empty
6777
                $newCategoryList = array();
6778
                foreach ($categories as $category) {
6779
                    $rootElement = $category['root'];
6780
6781
                    if (isset($category['parent_info'])) {
6782
                        $rootElement = $category['parent_info']['id'];
6783
                    }
6784
6785
                    //$rootElement = $category['id'];
6786
                    // If the current category's ancestor was never seen
6787
                    // before, then declare it and assign the current
6788
                    // category to it.
6789
                    if (!isset($newCategoryList[$rootElement])) {
6790
                        $newCategoryList[$rootElement] = $category;
6791
                    } else {
6792
                        // If it was already seen, then merge the previous with
6793
                        // the current category
6794
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6795
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
6796
                        $newCategoryList[$rootElement] = $category;
6797
                    }
6798
                }
6799
                // Now use the newly built categories list, with only parents
6800
                $categories = $newCategoryList;
6801
            }
6802
6803
            foreach ($categories as $category) {
6804
                $questionList = $category['question_list'];
6805
                // Check if in this category there questions added in a media
6806
                $mediaQuestionId = $category['media_question'];
6807
                $isMedia = false;
6808
                $fixedValue = null;
6809
6810
                // Media exists!
6811
                if ($mediaQuestionId != 999) {
6812
                    $isMedia = true;
6813
                    $fixedValue = $counterNoMedias;
6814
                }
6815
6816
                //$categoryName = $category['path']; << show the path
6817
                $categoryName = $category['name'];
6818
6819
                if ($useRootAsCategoryTitle) {
6820
                    if (isset($category['parent_info'])) {
6821
                        $categoryName = $category['parent_info']['title'];
6822
                    }
6823
                }
6824
                $html .= '<div class="row">';
6825
                $html .= '<div class="span2">'.$categoryName.'</div>';
6826
                $html .= '<div class="span8">';
6827
6828
                if (!empty($nextValue)) {
6829
                    if ($wasMedia) {
6830
                        $nextValue = $nextValue - $before + 1;
6831
                    }
6832
                }
6833
                $html .= Display::progressPaginationBar(
6834
                    $nextValue,
6835
                    $questionList,
6836
                    $current,
6837
                    $fixedValue,
6838
                    $conditions,
6839
                    $link,
6840
                    $isMedia,
6841
                    true
6842
                );
6843
                $html .= '</div>';
6844
                $html .= '</div>';
6845
6846
                if ($mediaQuestionId == 999) {
6847
                    $counterNoMedias += count($questionList);
6848
                } else {
6849
                    $counterNoMedias++;
6850
                }
6851
6852
                $nextValue += count($questionList);
6853
                $before = count($questionList);
6854
6855
                if ($mediaQuestionId != 999) {
6856
                    $wasMedia = true;
6857
                } else {
6858
                    $wasMedia = false;
6859
                }
6860
6861
            }
6862
        }
6863
        return $html;
6864
    }
6865
6866
    /**
6867
     * Renders a question list
6868
     *
6869
     * @param array $questionList (with media questions compressed)
6870
     * @param int $currentQuestion
6871
     * @param array $exerciseResult
6872
     * @param array $attemptList
6873
     * @param array $remindList
6874
     */
6875
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6876
    {
6877
        $mediaQuestions = $this->getMediaList();
6878
        $i = 0;
6879
6880
        // Normal question list render (medias compressed)
6881
        foreach ($questionList as $questionId) {
6882
            $i++;
6883
            // For sequential exercises
6884
6885
            if ($this->type == ONE_PER_PAGE) {
6886
                // If it is not the right question, goes to the next loop iteration
6887
                if ($currentQuestion != $i) {
6888
                    continue;
6889
                } else {
6890
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6891
                        // if the user has already answered this question
6892
                        if (isset($exerciseResult[$questionId])) {
6893
                            echo Display::return_message(get_lang('AlreadyAnswered'), 'normal');
6894
                            break;
6895
                        }
6896
                    }
6897
                }
6898
            }
6899
6900
            // The $questionList contains the media id we check if this questionId is a media question type
6901
6902
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6903
6904
                // The question belongs to a media
6905
                $mediaQuestionList = $mediaQuestions[$questionId];
6906
                $objQuestionTmp = Question::read($questionId);
6907
6908
                $counter = 1;
6909
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6910
                    echo $objQuestionTmp->show_media_content();
6911
6912
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6913
6914
                    // Show questions that belongs to a media
6915
                    if (!empty($mediaQuestionList)) {
6916
                        // In order to parse media questions we use letters a, b, c, etc.
6917
                        $letterCounter = 97;
6918
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6919
                            $isLastQuestionInMedia = false;
6920
                            if ($counter == $countQuestionsInsideMedia) {
6921
                                $isLastQuestionInMedia = true;
6922
                            }
6923
                            $this->renderQuestion(
6924
                                $questionIdInsideMedia,
6925
                                $attemptList,
6926
                                $remindList,
6927
                                chr($letterCounter),
6928
                                $currentQuestion,
6929
                                $mediaQuestionList,
6930
                                $isLastQuestionInMedia,
6931
                                $questionList
6932
                            );
6933
                            $letterCounter++;
6934
                            $counter++;
6935
                        }
6936
                    }
6937
                } else {
6938
                    $this->renderQuestion(
6939
                        $questionId,
6940
                        $attemptList,
6941
                        $remindList,
6942
                        $i,
6943
                        $currentQuestion,
6944
                        null,
6945
                        null,
6946
                        $questionList
6947
                    );
6948
                    $i++;
6949
                }
6950
            } else {
6951
                // Normal question render.
6952
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6953
            }
6954
6955
            // For sequential exercises.
6956
            if ($this->type == ONE_PER_PAGE) {
6957
                // quits the loop
6958
                break;
6959
            }
6960
        }
6961
        // end foreach()
6962
6963
        if ($this->type == ALL_ON_ONE_PAGE) {
6964
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
0 ignored issues
show
Bug introduced by
The variable $questionId seems to be defined by a foreach iteration on line 6881. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
6965
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6966
        }
6967
    }
6968
6969
    /**
6970
     * @param int $questionId
6971
     * @param array $attemptList
6972
     * @param array $remindList
6973
     * @param int $i
6974
     * @param int $current_question
6975
     * @param array $questions_in_media
6976
     * @param bool $last_question_in_media
6977
     * @param array $realQuestionList
6978
     * @param bool $generateJS
6979
     * @return null
6980
     */
6981
    public function renderQuestion(
6982
        $questionId,
6983
        $attemptList,
6984
        $remindList,
6985
        $i,
6986
        $current_question,
6987
        $questions_in_media = array(),
6988
        $last_question_in_media = false,
6989
        $realQuestionList,
6990
        $generateJS = true
6991
    ) {
6992
6993
        // With this option on the question is loaded via AJAX
6994
        //$generateJS = true;
6995
        //$this->loadQuestionAJAX = true;
6996
6997
        if ($generateJS && $this->loadQuestionAJAX) {
6998
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
6999
            $params = array(
7000
                'questionId' => $questionId,
7001
                'attemptList'=> $attemptList,
7002
                'remindList' => $remindList,
7003
                'i' => $i,
7004
                'current_question' => $current_question,
7005
                'questions_in_media' => $questions_in_media,
7006
                'last_question_in_media' => $last_question_in_media
7007
            );
7008
            $params = json_encode($params);
7009
7010
            $script = '<script>
7011
            $(function(){
7012
                var params = '.$params.';
7013
                $.ajax({
7014
                    type: "GET",
7015
                    async: false,
7016
                    data: params,
7017
                    url: "'.$url.'",
7018
                    success: function(return_value) {
7019
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7020
                    }
7021
                });
7022
            });
7023
            </script>
7024
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7025
            echo $script;
7026
        } else {
7027
7028
            global $origin;
7029
            $question_obj = Question::read($questionId);
7030
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7031
7032
            $remind_highlight = null;
7033
7034
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
7035
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7036
                $remind_highlight = 'no_remind_highlight';
7037
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7038
                    return null;
7039
                }
7040
            }
7041
7042
            $attributes = array('id' =>'remind_list['.$questionId.']');
7043
            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...
7044
                //$attributes['checked'] = 1;
7045
                //$remind_highlight = ' remind_highlight ';
7046
            }
7047
7048
            // Showing the question
7049
7050
            $exercise_actions = null;
7051
7052
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7053
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7054
7055
            // Shows the question + possible answers
7056
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7057
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
7058
7059
            // Button save and continue
7060 View Code Duplication
            switch ($this->type) {
7061
                case ONE_PER_PAGE:
7062
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
7063
                    break;
7064
                case ALL_ON_ONE_PAGE:
7065
                    $button = [
7066
                        Display::button(
7067
                            'save_now',
7068
                            get_lang('SaveForNow'),
7069
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7070
                        ),
7071
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>'
7072
                    ];
7073
                    $exercise_actions .= Display::div(
7074
                        implode(PHP_EOL, $button),
7075
                        array('class'=>'exercise_save_now_button')
7076
                    );
7077
                    break;
7078
            }
7079
7080
            if (!empty($questions_in_media)) {
7081
                $count_of_questions_inside_media = count($questions_in_media);
7082
                if ($count_of_questions_inside_media > 1) {
7083
                    $button = [
7084
                        Display::button(
7085
                            'save_now',
7086
                            get_lang('SaveForNow'),
7087
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7088
                        ),
7089
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;'
7090
                    ];
7091
                    $exercise_actions = Display::div(
7092
                        implode(PHP_EOL, $button),
7093
                        array('class'=>'exercise_save_now_button')
7094
                    );
7095
                }
7096
7097
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7098
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7099
                }
7100
            }
7101
7102
            // Checkbox review answers
7103
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
7104
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
7105
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
7106
            }
7107
7108
            echo Display::div(' ', array('class'=>'clear'));
7109
7110
            $paginationCounter = null;
7111
            if ($this->type == ONE_PER_PAGE) {
7112
                if (empty($questions_in_media)) {
7113
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
7114
                } else {
7115
                    if ($last_question_in_media) {
7116
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
7117
                    }
7118
                }
7119
            }
7120
7121
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7122
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
7123
            echo '</div>';
7124
        }
7125
    }
7126
7127
    /**
7128
     * Returns an array of categories details for the questions of the current
7129
     * exercise.
7130
     * @return array
7131
     */
7132
    public function getQuestionWithCategories()
7133
    {
7134
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7135
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7136
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7137
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7138
        $sql = "SELECT DISTINCT cat.*
7139
                FROM $TBL_EXERCICE_QUESTION e
7140
                INNER JOIN $TBL_QUESTIONS q
7141
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7142
                INNER JOIN $categoryRelTable catRel
7143
                ON (catRel.question_id = e.question_id)
7144
                INNER JOIN $categoryTable cat
7145
                ON (cat.id = catRel.category_id)
7146
                WHERE
7147
                  e.c_id = {$this->course_id} AND
7148
                  e.exercice_id	= ".intval($this->id);
7149
7150
        $result = Database::query($sql);
7151
        $categoriesInExercise = array();
7152
        if (Database::num_rows($result)) {
7153
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7154
        }
7155
7156
        return $categoriesInExercise;
7157
    }
7158
7159
    /**
7160
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
7161
     */
7162
    public function get_max_score()
7163
    {
7164
        $out_max_score = 0;
7165
        // list of question's id !!! the array key start at 1 !!!
7166
        $questionList = $this->selectQuestionList(true);
7167
7168
        // test is randomQuestions - see field random of test
7169
        if ($this->random > 0 && $this->randomByCat == 0) {
7170
            $numberRandomQuestions = $this->random;
7171
            $questionScoreList = array();
7172
            for ($i = 1; $i <= count($questionList); $i++) {
7173
                if (isset($questionList[$i])) {
7174
                    $tmpobj_question = Question::read($questionList[$i]);
7175
                    if (is_object($tmpobj_question)) {
7176
                        $questionScoreList[] = $tmpobj_question->weighting;
7177
                    }
7178
                }
7179
            }
7180
            rsort($questionScoreList);
7181
            // add the first $numberRandomQuestions value of score array to get max_score
7182
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7183
                $out_max_score += $questionScoreList[$i];
7184
            }
7185
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7186
            // test is random by category
7187
            // get the $numberRandomQuestions best score question of each category
7188
7189
            $numberRandomQuestions = $this->random;
7190
            $tab_categories_scores = array();
7191
            for ($i = 1; $i <= count($questionList); $i++) {
7192
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
7193
                if (!is_array($tab_categories_scores[$question_category_id])) {
7194
                    $tab_categories_scores[$question_category_id] = array();
7195
                }
7196
                $tmpobj_question = Question::read($questionList[$i]);
7197
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7198
            }
7199
7200
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7201
            while (list($key, $tab_scores) = each($tab_categories_scores)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $key is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
7202
                rsort($tab_scores);
7203
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7204
                    $out_max_score += $tab_scores[$i];
7205
                }
7206
            }
7207
        } else {
7208
            // standard test, just add each question score
7209
            foreach ($questionList as $questionId) {
7210
                $question = Question::read($questionId, $this->course_id);
7211
                $out_max_score += $question->weighting;
7212
            }
7213
        }
7214
7215
        return $out_max_score;
7216
    }
7217
7218
    /**
7219
    * @return string
7220
    */
7221
    public function get_formated_title()
7222
    {
7223
        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...
7224
7225
        }
7226
        return api_html_entity_decode($this->selectTitle());
7227
    }
7228
7229
    /**
7230
     * @param $in_title
7231
     * @return string
7232
     */
7233
    public static function get_formated_title_variable($in_title)
7234
    {
7235
        return api_html_entity_decode($in_title);
7236
    }
7237
7238
    /**
7239
     * @return string
7240
     */
7241
    public function format_title()
7242
    {
7243
        return api_htmlentities($this->title);
7244
    }
7245
7246
    /**
7247
     * @param $in_title
7248
     * @return string
7249
     */
7250
    public static function format_title_variable($in_title)
7251
    {
7252
        return api_htmlentities($in_title);
7253
    }
7254
7255
    /**
7256
     * @param int $courseId
7257
     * @param int $sessionId
7258
     * @return array exercises
7259
     */
7260 View Code Duplication
    public function getExercisesByCourseSession($courseId, $sessionId)
7261
    {
7262
        $courseId = intval($courseId);
7263
        $sessionId = intval($sessionId);
7264
7265
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7266
        $sql = "SELECT * FROM $tbl_quiz cq
7267
                WHERE
7268
                    cq.c_id = %s AND
7269
                    (cq.session_id = %s OR cq.session_id = 0) AND
7270
                    cq.active = 0
7271
                ORDER BY cq.id";
7272
        $sql = sprintf($sql, $courseId, $sessionId);
7273
7274
        $result = Database::query($sql);
7275
7276
        $rows = array();
7277
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7278
            $rows[] = $row;
7279
        }
7280
7281
        return $rows;
7282
    }
7283
7284
    /**
7285
     *
7286
     * @param int $courseId
7287
     * @param int $sessionId
7288
     * @param array $quizId
7289
     * @return array exercises
7290
     */
7291
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
7292
    {
7293
        if (empty($quizId)) {
7294
            return array();
7295
        }
7296
7297
        $sessionId = intval($sessionId);
7298
7299
        $ids = is_array($quizId) ? $quizId : array($quizId);
7300
        $ids = array_map('intval', $ids);
7301
        $ids = implode(',', $ids);
7302
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7303
        if ($sessionId != 0) {
7304
            $sql = "SELECT * FROM $track_exercises te
7305
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7306
              WHERE
7307
              te.id = %s AND
7308
              te.session_id = %s AND
7309
              cq.id IN (%s)
7310
              ORDER BY cq.id";
7311
7312
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7313
        } else {
7314
            $sql = "SELECT * FROM $track_exercises te
7315
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7316
              WHERE
7317
              te.id = %s AND
7318
              cq.id IN (%s)
7319
              ORDER BY cq.id";
7320
            $sql = sprintf($sql, $courseId, $ids);
7321
        }
7322
        $result = Database::query($sql);
7323
        $rows = array();
7324
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7325
            $rows[] = $row;
7326
        }
7327
7328
        return $rows;
7329
    }
7330
7331
    /**
7332
     * @param $exeId
7333
     * @param $exercise_stat_info
7334
     * @param $remindList
7335
     * @param $currentQuestion
7336
     * @return int|null
7337
     */
7338
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
7339
    {
7340
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7341
7342
        if (isset($result[$exeId])) {
7343
            $result = $result[$exeId];
7344
        } else {
7345
            return null;
7346
        }
7347
7348
        $data_tracking  = $exercise_stat_info['data_tracking'];
7349
        $data_tracking  = explode(',', $data_tracking);
7350
7351
        // if this is the final question do nothing.
7352
        if ($currentQuestion == count($data_tracking)) {
7353
            return null;
7354
        }
7355
7356
        $currentQuestion = $currentQuestion - 1;
7357
7358
        if (!empty($result['question_list'])) {
7359
            $answeredQuestions = array();
7360
7361
            foreach ($result['question_list'] as $question) {
7362
                if (!empty($question['answer'])) {
7363
                    $answeredQuestions[] = $question['question_id'];
7364
                }
7365
            }
7366
7367
            // Checking answered questions
7368
7369
            $counterAnsweredQuestions = 0;
7370
            foreach ($data_tracking as $questionId) {
7371
                if (!in_array($questionId, $answeredQuestions)) {
7372
                    if ($currentQuestion != $counterAnsweredQuestions) {
7373
                        break;
7374
                    }
7375
                }
7376
                $counterAnsweredQuestions++;
7377
            }
7378
7379
            $counterRemindListQuestions = 0;
7380
            // Checking questions saved in the reminder list
7381
7382
            if (!empty($remindList)) {
7383
                foreach ($data_tracking as $questionId) {
7384
                    if (in_array($questionId, $remindList)) {
7385
                        // Skip the current question
7386
                        if ($currentQuestion != $counterRemindListQuestions) {
7387
                            break;
7388
                        }
7389
                    }
7390
                    $counterRemindListQuestions++;
7391
                }
7392
7393
                if ($counterRemindListQuestions < $currentQuestion) {
7394
                    return null;
7395
                }
7396
7397
                if (!empty($counterRemindListQuestions)) {
7398
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7399
                        return $counterAnsweredQuestions;
7400
                    } else {
7401
                        return $counterRemindListQuestions;
7402
                    }
7403
                }
7404
            }
7405
7406
            return $counterAnsweredQuestions;
7407
        }
7408
    }
7409
7410
    /**
7411
     * Gets the position of a questionId in the question list
7412
     * @param $questionId
7413
     * @return int
7414
     */
7415
    public function getPositionInCompressedQuestionList($questionId)
7416
    {
7417
        $questionList = $this->getQuestionListWithMediasCompressed();
7418
        $mediaQuestions = $this->getMediaList();
7419
        $position = 1;
7420
        foreach ($questionList as $id) {
7421
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7422
                $mediaQuestionList = $mediaQuestions[$id];
7423
                if (in_array($questionId, $mediaQuestionList)) {
7424
                    return $position;
7425
                } else {
7426
                    $position++;
7427
                }
7428
            } else {
7429
                if ($id == $questionId) {
7430
                    return $position;
7431
                } else {
7432
                    $position++;
7433
                }
7434
            }
7435
        }
7436
        return 1;
7437
    }
7438
7439
    /**
7440
     * Get the correct answers in all attempts
7441
     * @param int $learnPathId
7442
     * @param int $learnPathItemId
7443
     * @return array
7444
     */
7445
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7446
    {
7447
        $attempts = Event::getExerciseResultsByUser(
7448
            api_get_user_id(),
7449
            $this->id,
7450
            api_get_course_int_id(),
7451
            api_get_session_id(),
7452
            $learnPathId,
7453
            $learnPathItemId,
7454
            'asc'
7455
        );
7456
7457
        $corrects = [];
7458
7459
        foreach ($attempts as $attempt) {
7460
            foreach ($attempt['question_list'] as $answers) {
7461
                foreach ($answers as $answer) {
7462
                    $objAnswer = new Answer($answer['question_id']);
7463
7464
                    switch ($objAnswer->getQuestionType()) {
7465
                        case FILL_IN_BLANKS:
7466
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7467
                            break;
7468
                        case MATCHING:
7469
                            //no break
7470
                        case DRAGGABLE:
7471
                            //no break
7472
                        case MATCHING_DRAGGABLE:
7473
                            $isCorrect = Matching::isCorrect(
7474
                                $answer['position'],
7475
                                $answer['answer'],
7476
                                $answer['question_id']
7477
                            );
7478
                            break;
7479
                        default:
7480
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7481
                    }
7482
7483
                    if ($isCorrect) {
7484
                        $corrects[$answer['question_id']][] = $answer;
7485
                    }
7486
                }
7487
            }
7488
        }
7489
7490
        return $corrects;
7491
    }
7492
7493
    /**
7494
     * Get the title without HTML tags
7495
     * @return string
7496
     */
7497
    private function getUnformattedTitle()
7498
    {
7499
        return strip_tags(api_html_entity_decode($this->title));
7500
    }
7501
}
7502