Completed
Push — 1.10.x ( dc201d...fe0e5a )
by Angel Fernando Quiroz
130:27 queued 85:01
created

Exercise::fill_in_blank_answer_to_array()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
6
/**
7
 * Class Exercise
8
 *
9
 * Allows to instantiate an object of type Exercise
10
 * @package chamilo.exercise
11
 * @author Olivier Brouckaert
12
 * @author Julio Montoya Cleaning exercises
13
 * Modified by Hubert Borderiou #294
14
 */
15
class Exercise
16
{
17
    public $id;
18
    public $name;
19
    public $title;
20
    public $exercise;
21
    public $description;
22
    public $sound;
23
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
24
    public $random;
25
    public $random_answers;
26
    public $active;
27
    public $timeLimit;
28
    public $attempts;
29
    public $feedback_type;
30
    public $end_time;
31
    public $start_time;
32
    public $questionList;  // array with the list of this exercise's questions
33
    /* including question list of the media */
34
    public $questionListUncompressed;
35
    public $results_disabled;
36
    public $expired_time;
37
    public $course;
38
    public $course_id;
39
    public $propagate_neg;
40
    public $review_answers;
41
    public $randomByCat;
42
    public $text_when_finished;
43
    public $display_category_name;
44
    public $pass_percentage;
45
    public $edit_exercise_in_lp = false;
46
    public $is_gradebook_locked = false;
47
    public $exercise_was_added_in_lp = false;
48
    public $lpList = array();
49
    public $force_edit_exercise_in_lp = false;
50
    public $categories;
51
    public $categories_grouping = true;
52
    public $endButton = 0;
53
    public $categoryWithQuestionList;
54
    public $mediaList;
55
    public $loadQuestionAJAX = false;
56
    // Notification send to the teacher.
57
    public $emailNotificationTemplate = null;
58
    // Notification send to the student.
59
    public $emailNotificationTemplateToUser = null;
60
    public $countQuestions = 0;
61
    public $fastEdition = false;
62
    public $modelType = 1;
63
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
64
    public $hideQuestionTitle = 0;
65
    public $scoreTypeModel = 0;
66
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
67
    public $globalCategoryId = null;
68
    public $onSuccessMessage = null;
69
    public $onFailedMessage = null;
70
    public $emailAlert;
71
    public $notifyUserByEmail = 0;
72
    public $sessionId = 0;
73
    public $specialCategoryOrders = false;
74
    public $quizRelCategoryTable = false;
75
    // CREATE TABLE c_quiz_rel_category (iid BIGINT AUTO_INCREMENT NOT NULL, c_id INT NOT NULL, category_id INT NOT NULL, exercise_id INT NOT NULL, count_questions INT NOT NULL, PRIMARY KEY(iid));
76
    // ALTER TABLE c_quiz ADD COLUMN question_selection_type INT;
77
78
    /**
79
     * Constructor of the class
80
     *
81
     * @author Olivier Brouckaert
82
     */
83
    public function __construct($course_id = null)
84
    {
85
        $this->id = 0;
86
        $this->exercise = '';
87
        $this->description = '';
88
        $this->sound = '';
89
        $this->type = ALL_ON_ONE_PAGE;
90
        $this->random = 0;
91
        $this->random_answers = 0;
92
        $this->active = 1;
93
        $this->questionList = array();
94
        $this->timeLimit = 0;
95
        $this->end_time = '0000-00-00 00:00:00';
96
        $this->start_time = '0000-00-00 00:00:00';
97
        $this->results_disabled = 1;
98
        $this->expired_time = '0000-00-00 00:00:00';
99
        $this->propagate_neg = 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
106
        $this->modelType = 1;
107
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
108
        $this->endButton = 0;
109
        $this->scoreTypeModel = 0;
110
        $this->globalCategoryId = null;
111
112
        if (!empty($course_id)) {
113
            $course_info = api_get_course_info_by_id($course_id);
114
        } else {
115
            $course_info = api_get_course_info();
116
        }
117
        $this->course_id = $course_info['real_id'];
118
        $this->course = $course_info;
119
        $this->specialCategoryOrders = api_get_configuration_value('exercise_enable_category_order');
120
    }
121
122
    /**
123
     * Reads exercise information from the data base
124
     *
125
     * @author Olivier Brouckaert
126
     * @param integer $id - exercise Id
127
     *
128
     * @return boolean - true if exercise exists, otherwise false
129
     */
130
    public function read($id, $parseQuestionList = true)
131
    {
132
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
133
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
134
135
        $id  = intval($id);
136
        if (empty($this->course_id)) {
137
138
            return false;
139
        }
140
        $sql = "SELECT * FROM $TBL_EXERCISES 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->randomByCat = $object->random_by_category;
163
            $this->text_when_finished = $object->text_when_finished;
164
            $this->display_category_name = $object->display_category_name;
165
            $this->pass_percentage = $object->pass_percentage;
166
            $this->sessionId = $object->session_id;
167
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
168
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
169
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
170
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
171
172
            $sql = "SELECT lp_id, max_score
173
                    FROM $table_lp_item
174
                    WHERE   c_id = {$this->course_id} AND
175
                            item_type = '".TOOL_QUIZ."' AND
176
                            path = '".$id."'";
177
            $result = Database::query($sql);
178
179
            if (Database::num_rows($result) > 0) {
180
                $this->exercise_was_added_in_lp = true;
181
                $this->lpList = Database::store_result($result, 'ASSOC');
182
            }
183
184
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
185
186
            if ($this->exercise_was_added_in_lp) {
187
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
188
            } else {
189
                $this->edit_exercise_in_lp = true;
190
            }
191
192
            if ($object->end_time != '0000-00-00 00:00:00') {
193
                $this->end_time = $object->end_time;
194
            }
195
            if ($object->start_time != '0000-00-00 00:00:00') {
196
                $this->start_time = $object->start_time;
197
            }
198
199
            //control time
200
            $this->expired_time = $object->expired_time;
201
202
            //Checking if question_order is correctly set
203
204
            //$this->questionList     = $this->selectQuestionList(true);
205
            if ($parseQuestionList) {
206
                $this->setQuestionList();
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
        return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
232
    }
233
234
    /**
235
     * returns the exercise ID
236
     *
237
     * @author Olivier Brouckaert
238
     * @return int - exercise ID
239
     */
240
    public function selectId()
241
    {
242
        return $this->id;
243
    }
244
245
    /**
246
     * returns the exercise title
247
     *
248
     * @author Olivier Brouckaert
249
     * @return string - exercise title
250
     */
251
    public function selectTitle()
252
    {
253
        return $this->exercise;
254
    }
255
256
    /**
257
     * returns the number of attempts setted
258
     *
259
     * @return int - exercise attempts
260
     */
261
    public function selectAttempts()
262
    {
263
        return $this->attempts;
264
    }
265
266
    /** returns the number of FeedbackType  *
267
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
268
     * @return int - exercise attempts
269
     */
270
    public function selectFeedbackType()
271
    {
272
        return $this->feedback_type;
273
    }
274
275
    /**
276
     * returns the time limit
277
     */
278
    public function selectTimeLimit()
279
    {
280
        return $this->timeLimit;
281
    }
282
283
    /**
284
     * returns the exercise description
285
     *
286
     * @author Olivier Brouckaert
287
     * @return string - exercise description
288
     */
289
    public function selectDescription()
290
    {
291
        return $this->description;
292
    }
293
294
    /**
295
     * returns the exercise sound file
296
     *
297
     * @author Olivier Brouckaert
298
     * @return string - exercise description
299
     */
300
    public function selectSound()
301
    {
302
        return $this->sound;
303
    }
304
305
    /**
306
     * returns the exercise type
307
     *
308
     * @author Olivier Brouckaert
309
     * @return integer - exercise type
310
     */
311
    public function selectType()
312
    {
313
        return $this->type;
314
    }
315
316
    /**
317
     * @return int
318
     */
319
    public function getModelType()
320
    {
321
        return $this->modelType;
322
    }
323
324
    /**
325
     * @return int
326
     */
327
    public function selectEndButton()
328
    {
329
        return $this->endButton;
330
    }
331
332
    /**
333
     * @return string
334
     */
335
    public function getOnSuccessMessage()
336
    {
337
        return $this->onSuccessMessage;
338
    }
339
340
    /**
341
     * @return string
342
     */
343
    public function getOnFailedMessage()
344
    {
345
        return $this->onFailedMessage;
346
    }
347
348
    /**
349
     * @author hubert borderiou 30-11-11
350
     * @return integer : do we display the question category name for students
351
     */
352
    public function selectDisplayCategoryName()
353
    {
354
        return $this->display_category_name;
355
    }
356
357
    /**
358
     * @return int
359
     */
360
    public function selectPassPercentage()
361
    {
362
        return $this->pass_percentage;
363
    }
364
365
    /**
366
     *
367
     * Modify object to update the switch display_category_name
368
     * @author hubert borderiou 30-11-11
369
     * @param int $in_txt is an integer 0 or 1
370
     */
371
    public function updateDisplayCategoryName($in_txt)
372
    {
373
        $this->display_category_name = $in_txt;
374
    }
375
376
    /**
377
     * @author hubert borderiou 28-11-11
378
     * @return string html text : the text to display ay the end of the test.
379
     */
380
    public function selectTextWhenFinished()
381
    {
382
        return $this->text_when_finished;
383
    }
384
385
    /**
386
     * @author hubert borderiou 28-11-11
387
     * @return string  html text : update the text to display ay the end of the test.
388
     */
389
    public function updateTextWhenFinished($in_txt)
390
    {
391
        $this->text_when_finished = $in_txt;
392
    }
393
394
    /**
395
     * return 1 or 2 if randomByCat
396
     * @author hubert borderiou
397
     * @return integer - quiz random by category
398
     */
399
    public function selectRandomByCat()
400
    {
401
        return $this->randomByCat;
402
    }
403
404
    /**
405
     * return 0 if no random by cat
406
     * return 1 if random by cat, categories shuffled
407
     * return 2 if random by cat, categories sorted by alphabetic order
408
     * @author hubert borderiou
409
     * @return integer - quiz random by category
410
     */
411
    public function isRandomByCat()
412
    {
413
        /*$res = 0;
414
        if ($this->randomByCat == 1) {
415
            $res = 1;
416
        } else if ($this->randomByCat == 2) {
417
            $res = 2;
418
        }
419
        */
420
421
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
422
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
423
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
424
        } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
425
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
426
        }
427
428
        return $res;
429
    }
430
431
    /**
432
     * return nothing
433
     * update randomByCat value for object
434
     * @param int $random
435
     *
436
     * @author hubert borderiou
437
     */
438
    public function updateRandomByCat($random)
439
    {
440
        if ($this->specialCategoryOrders) {
441
            if (in_array($random, 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
        } else {
452
            if ($random == 1) {
453
                $this->randomByCat = 1;
454
            } else if ($random == 2) {
455
                $this->randomByCat = 2;
456
            } else {
457
                $this->randomByCat = 0;
458
            }
459
        }
460
    }
461
462
    /**
463
     * Tells if questions are selected randomly, and if so returns the draws
464
     *
465
     * @author Carlos Vargas
466
     * @return integer - results disabled exercise
467
     */
468
    public function selectResultsDisabled()
469
    {
470
        return $this->results_disabled;
471
    }
472
473
    /**
474
     * tells if questions are selected randomly, and if so returns the draws
475
     *
476
     * @author Olivier Brouckaert
477
     * @return integer - 0 if not random, otherwise the draws
478
     */
479
    public function isRandom()
480
    {
481
        if($this->random > 0 || $this->random == -1) {
482
            return true;
483
        } else {
484
            return false;
485
        }
486
    }
487
488
    /**
489
     * returns random answers status.
490
     *
491
     * @author Juan Carlos Rana
492
     */
493
    public function selectRandomAnswers()
494
    {
495
        return $this->random_answers;
496
    }
497
498
    /**
499
     * Same as isRandom() but has a name applied to values different than 0 or 1
500
     */
501
    public function getShuffle()
502
    {
503
        return $this->random;
504
    }
505
506
    /**
507
     * returns the exercise status (1 = enabled ; 0 = disabled)
508
     *
509
     * @author Olivier Brouckaert
510
     * @return boolean - true if enabled, otherwise false
511
     */
512
    public function selectStatus()
513
    {
514
        return $this->active;
515
    }
516
517
    /**
518
     * If false the question list will be managed as always if true the question will be filtered
519
     * depending of the exercise settings (table c_quiz_rel_category)
520
     * @param bool active or inactive grouping
521
     **/
522
    public function setCategoriesGrouping($status)
523
    {
524
        $this->categories_grouping = (bool) $status;
525
    }
526
527
    /**
528
     * @return int
529
     */
530
    public function getHideQuestionTitle()
531
    {
532
        return $this->hideQuestionTitle;
533
    }
534
535
    /**
536
     * @param $value
537
     */
538
    public function setHideQuestionTitle($value)
539
    {
540
        $this->hideQuestionTitle = intval($value);
541
    }
542
543
    /**
544
     * @return int
545
     */
546
    public function getScoreTypeModel()
547
    {
548
        return $this->scoreTypeModel;
549
    }
550
551
    /**
552
     * @param int $value
553
     */
554
    public function setScoreTypeModel($value)
555
    {
556
        $this->scoreTypeModel = intval($value);
557
    }
558
559
    /**
560
     * @return int
561
     */
562
    public function getGlobalCategoryId()
563
    {
564
        return $this->globalCategoryId;
565
    }
566
567
    /**
568
     * @param int $value
569
     */
570
    public function setGlobalCategoryId($value)
571
    {
572
        if (is_array($value) && isset($value[0])) {
573
            $value = $value[0];
574
        }
575
        $this->globalCategoryId = intval($value);
576
    }
577
578
    /**
579
     *
580
     * @param int $start
581
     * @param int $limit
582
     * @param int $sidx
583
     * @param string $sord
584
     * @param array $where_condition
585
     * @param array $extraFields
586
     */
587
    public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
588
    {
589
        if (!empty($this->id)) {
590
            $category_list = TestCategory::getListOfCategoriesNameForTest($this->id, false);
591
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
592
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
593
594
            $sql = "SELECT q.iid
595
                    FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
596
                        ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
597
					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
598
					";
599
600
            $orderCondition = "ORDER BY question_order";
601
602
            if (!empty($sidx) && !empty($sord)) {
603
                if ($sidx == 'question') {
604
605
                    if (in_array(strtolower($sord), array('desc', 'asc'))) {
606
                        $orderCondition = " ORDER BY q.$sidx $sord";
607
                    }
608
                }
609
            }
610
611
            $sql .= $orderCondition;
612
613
            $limitCondition = null;
614
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($objQuestionTmp->category_list, $category_list);
631
632
                    if (empty($category_labels)) {
633
                        $category_labels = "-";
634
                    }
635
636
                    // Question type
637
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
638
639
                    $question_media = null;
640
                    if (!empty($objQuestionTmp->parent_id)) {
641
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
642
                        $question_media  = Question::getMediaLabel($objQuestionMedia->question);
643
                    }
644
645
                    $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media);
646
647
                    $question = array(
648
                        'id' => $question['iid'],
649
                        'question' => $objQuestionTmp->selectTitle(),
650
                        'type' => $questionType,
651
                        'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
652
                        'score' => $objQuestionTmp->selectWeighting(),
653
                        'level' => $objQuestionTmp->level
654
                    );
655
                    if (!empty($extraFields)) {
656
                        foreach ($extraFields as $extraField) {
657
                            $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
658
                            $stringValue = null;
659
                            if ($value) {
660
                                $stringValue = $value['field_value'];
661
                            }
662
                            $question[$extraField['field_variable']] = $stringValue;
663
                        }
664
                    }
665
                    $questions[] = $question;
666
                }
667
            }
668
            return $questions;
669
        }
670
    }
671
672
    /**
673
     * Get question count per exercise from DB (any special treatment)
674
     * @return int
675
     */
676 View Code Duplication
    public function getQuestionCount()
677
    {
678
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
679
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
680
        $sql = "SELECT count(q.id) as count
681
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
682
                    ON (e.question_id = q.id)
683
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= ".Database::escape_string($this->id);
684
        $result = Database::query($sql);
685
686
        $count = 0;
687
        if (Database::num_rows($result)) {
688
            $row = Database::fetch_array($result);
689
            $count = $row['count'];
690
        }
691
692
        return $count;
693
    }
694
695
    /**
696
     * @return array
697
     */
698 View Code Duplication
    public function getQuestionOrderedListByName()
699
    {
700
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
701
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
702
703
        // Getting question list from the order (question list drag n drop interface ).
704
        $sql = "SELECT e.question_id
705
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
706
                    ON (e.question_id= q.id)
707
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
708
                ORDER BY q.question";
709
        $result = Database::query($sql);
710
        $list = array();
711
        if (Database::num_rows($result)) {
712
            $list = Database::store_result($result, 'ASSOC');
713
        }
714
        return $list;
715
    }
716
717
    /**
718
     * Gets the question list ordered by the question_order setting (drag and drop)
719
     * @return array
720
     */
721
    private function getQuestionOrderedList()
722
    {
723
        $questionList = array();
724
725
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
726
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
727
728
        // Getting question_order to verify that the question
729
        // list is correct and all question_order's were set
730
        $sql = "SELECT DISTINCT e.question_order
731
                FROM $TBL_EXERCICE_QUESTION e
732
                INNER JOIN $TBL_QUESTIONS q
733
                    ON (e.question_id = q.id)
734
                WHERE
735
                  e.c_id = {$this->course_id} AND
736
                  e.exercice_id	= ".Database::escape_string($this->id);
737
738
        $result = Database::query($sql);
739
740
        $count_question_orders = Database::num_rows($result);
741
742
        // Getting question list from the order (question list drag n drop interface ).
743
        $sql = "SELECT DISTINCT e.question_id, e.question_order
744
                FROM $TBL_EXERCICE_QUESTION e
745
                INNER JOIN $TBL_QUESTIONS q
746
                    ON (e.question_id= q.id)
747
                WHERE
748
                    e.c_id = {$this->course_id} AND
749
                    e.exercice_id	= '".Database::escape_string($this->id)."'
750
                ORDER BY question_order";
751
752
        $result = Database::query($sql);
753
754
        // Fills the array with the question ID for this exercise
755
        // the key of the array is the question position
756
        $temp_question_list = array();
757
758
        $counter = 1;
759 View Code Duplication
        while ($new_object = Database::fetch_object($result)) {
760
            // Correct order.
761
            $questionList[$new_object->question_order] = $new_object->question_id;
762
            // Just in case we save the order in other array
763
            $temp_question_list[$counter] = $new_object->question_id;
764
            $counter++;
765
        }
766
767
        if (!empty($temp_question_list)) {
768
            /* If both array don't match it means that question_order was not correctly set
769
               for all questions using the default mysql order */
770
            if (count($temp_question_list) != $count_question_orders) {
771
                $questionList = $temp_question_list;
772
            }
773
        }
774
775
        return $questionList;
776
    }
777
778
    /**
779
     * Select N values from the questions per category array
780
     *
781
     * @param array $categoriesAddedInExercise
782
     * @param array $question_list
783
     * @param array $questions_by_category per category
784
     * @param bool $flatResult
785
     * @param bool $randomizeQuestions
786
     *
787
     * @return array
788
     */
789
    private function pickQuestionsPerCategory(
790
        $categoriesAddedInExercise,
791
        $question_list,
792
        & $questions_by_category,
793
        $flatResult = true,
794
        $randomizeQuestions = false
795
    ) {
796
        $addAll = true;
797
        $categoryCountArray = array();
798
799
        // Getting how many questions will be selected per category.
800
        if (!empty($categoriesAddedInExercise)) {
801
            $addAll = false;
802
            // Parsing question according the category rel exercise settings
803
            foreach ($categoriesAddedInExercise as $category_info) {
804
                $category_id = $category_info['category_id'];
805
                if (isset($questions_by_category[$category_id])) {
806
                    // How many question will be picked from this category.
807
                    $count = $category_info['count_questions'];
808
                    // -1 means all questions
809
                    if ($count == -1) {
810
                        $categoryCountArray[$category_id] = 999;
811
                    } else {
812
                        $categoryCountArray[$category_id] = $count;
813
                    }
814
                }
815
            }
816
        }
817
818
        if (!empty($questions_by_category)) {
819
            $temp_question_list = array();
820
821
            foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
822
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
823
                    if (isset($categoryCountArray[$category_id])) {
824
                        $numberOfQuestions = $categoryCountArray[$category_id];
825
                    } else {
826
                        $numberOfQuestions = 0;
827
                    }
828
                }
829
830
                if ($addAll) {
831
                    $numberOfQuestions = 999;
832
                }
833
834
                if (!empty($numberOfQuestions)) {
835
                    $elements = TestCategory::getNElementsFromArray(
836
                        $categoryQuestionList,
837
                        $numberOfQuestions,
838
                        $randomizeQuestions
839
                    );
840
841
                    if (!empty($elements)) {
842
                        $temp_question_list[$category_id] = $elements;
843
                        $categoryQuestionList = $elements;
844
                    }
845
                }
846
            }
847
848
            if (!empty($temp_question_list)) {
849
                if ($flatResult) {
850
                    $temp_question_list = array_flatten($temp_question_list);
851
                }
852
                $question_list = $temp_question_list;
853
            }
854
        }
855
856
        return $question_list;
857
    }
858
859
    /**
860
     * Selecting question list depending in the exercise-category
861
     * relationship (category table in exercise settings)
862
     *
863
     * @param array $question_list
864
     * @param int $questionSelectionType
865
     * @return array
866
     */
867
    public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
868
    {
869
        $result = array(
870
            'question_list' => array(),
871
            'category_with_questions_list' => array()
872
        );
873
874
        // Order/random categories
875
        $cat = new TestCategory();
876
877
        // Setting category order.
878
879
        switch ($questionSelectionType) {
880
            case EX_Q_SELECTION_ORDERED: // 1
881
            case EX_Q_SELECTION_RANDOM:  // 2
882
                // This options are not allowed here.
883
                break;
884 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
885
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
886
                    $this,
887
                    $this->course['real_id'],
888
                    'title ASC',
889
                    false,
890
                    true
891
                );
892
893
                $questions_by_category = TestCategory::getQuestionsByCat(
894
                    $this->id,
895
                    $question_list,
896
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 885 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...
897
                );
898
899
900
                $question_list = $this->pickQuestionsPerCategory(
901
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 885 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...
902
                    $question_list,
903
                    $questions_by_category,
904
                    true,
905
                    false
906
                );
907
                break;
908
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
909 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
910
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
911
                    $this,
912
                    $this->course['real_id'],
913
                    null,
914
                    true,
915
                    true
916
                );
917
                $questions_by_category = TestCategory::getQuestionsByCat(
918
                    $this->id,
919
                    $question_list,
920
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 910 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...
921
                );
922
                $question_list = $this->pickQuestionsPerCategory(
923
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 910 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...
924
                    $question_list,
925
                    $questions_by_category,
926
                    true,
927
                    false
928
                );
929
            break;
930 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
931
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
932
                    $this,
933
                    $this->course['real_id'],
934
                    'title DESC',
935
                    false,
936
                    true
937
                );
938
                $questions_by_category = TestCategory::getQuestionsByCat(
939
                    $this->id,
940
                    $question_list,
941
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 931 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...
942
                );
943
                $question_list = $this->pickQuestionsPerCategory(
944
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 931 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...
945
                    $question_list,
946
                    $questions_by_category,
947
                    true,
948
                    true
949
                );
950
                break;
951
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
952 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
953
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
954
                    $this,
955
                    $this->course['real_id'],
956
                    null,
957
                    true,
958
                    true
959
                );
960
                $questions_by_category = TestCategory::getQuestionsByCat(
961
                    $this->id,
962
                    $question_list,
963
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 953 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...
964
                );
965
                $question_list = $this->pickQuestionsPerCategory(
966
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 953 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...
967
                    $question_list,
968
                    $questions_by_category,
969
                    true,
970
                    true
971
                );
972
                break;
973
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
974
                break;
975
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
976
                break;
977 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
978
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
979
                    $this,
980
                    $this->course['real_id'],
981
                    'root ASC, lft ASC',
982
                    false,
983
                    true
984
                );
985
                $questions_by_category = TestCategory::getQuestionsByCat(
986
                    $this->id,
987
                    $question_list,
988
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 978 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...
989
                );
990
                $question_list = $this->pickQuestionsPerCategory(
991
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 978 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...
992
                    $question_list,
993
                    $questions_by_category,
994
                    true,
995
                    false
996
                );
997
                break;
998 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
999
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
1000
                    $this,
1001
                    $this->course['real_id'],
1002
                    'root, lft ASC',
1003
                    false,
1004
                    true
1005
                );
1006
                $questions_by_category = TestCategory::getQuestionsByCat(
1007
                    $this->id,
1008
                    $question_list,
1009
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 999 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...
1010
                );
1011
                $question_list = $this->pickQuestionsPerCategory(
1012
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 999 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...
1013
                    $question_list,
1014
                    $questions_by_category,
1015
                    true,
1016
                    true
1017
                );
1018
                break;
1019
        }
1020
1021
        $result['question_list'] = isset($question_list) ? $question_list : array();
1022
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
1023
1024
        // Adding category info in the category list with question list:
1025
1026
        if (!empty($questions_by_category)) {
1027
1028
            /*$em = Database::getManager();
1029
            $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');*/
1030
1031
            $newCategoryList = array();
1032
1033
            foreach ($questions_by_category as $categoryId => $questionList) {
1034
                $cat = new TestCategory($categoryId);
1035
                $cat = (array)$cat;
1036
                $cat['iid'] = $cat['id'];
1037
                //*$cat['name'] = $cat['name'];
1038
1039
                $categoryParentInfo = null;
1040
                // Parent is not set no loop here
1041
                if (!empty($cat['parent_id'])) {
1042
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1043
                        $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...
1044
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1045
                    } else {
1046
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1047
                    }
1048
                    $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...
1049
                    $index = 0;
1050
                    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...
1051
                        //$index = 1;
1052
                    }
1053
                    /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
1054
1055
                    foreach ($path as $categoryParent) {
1056
                        $visibility = $categoryParent->getVisibility();
1057
1058
                        if ($visibility == 0) {
1059
                            $categoryParentId = $categoryId;
1060
                            $categoryTitle = $cat['title'];
1061
                            if (count($path) > 1) {
1062
                                continue;
1063
                            }
1064
                        } else {
1065
                            $categoryParentId = $categoryParent->getIid();
1066
                            $categoryTitle = $categoryParent->getTitle();
1067
                        }
1068
1069
                        $categoryParentInfo['id'] = $categoryParentId;
1070
                        $categoryParentInfo['iid'] = $categoryParentId;
1071
                        $categoryParentInfo['parent_path'] = null;
1072
                        $categoryParentInfo['title'] = $categoryTitle;
1073
                        $categoryParentInfo['name'] = $categoryTitle;
1074
                        $categoryParentInfo['parent_id'] = null;
1075
                        break;
1076
                    }
1077
                }
1078
                $cat['parent_info'] = $categoryParentInfo;
1079
                $newCategoryList[$categoryId] = array(
1080
                    'category' => $cat,
1081
                    'question_list' => $questionList
1082
                );
1083
            }
1084
1085
            $result['category_with_questions_list'] = $newCategoryList;
1086
        }
1087
        return $result;
1088
    }
1089
1090
    /**
1091
     * returns the array with the question ID list
1092
     *
1093
     * @author Olivier Brouckaert
1094
     * @return array - question ID list
1095
     */
1096
    public function selectQuestionList($from_db = false)
1097
    {
1098
        if ($this->specialCategoryOrders == false) {
1099
            if ($from_db && !empty($this->id)) {
1100
                $TBL_EXERCISE_QUESTION  = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1101
                $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1102
1103
                $sql = "SELECT DISTINCT e.question_order
1104
                        FROM $TBL_EXERCISE_QUESTION e
1105
                        INNER JOIN $TBL_QUESTIONS  q
1106
                        ON (e.question_id = q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
1107
					    WHERE e.exercice_id	= ".intval($this->id);
1108
                $result = Database::query($sql);
1109
1110
                $count_question_orders = Database::num_rows($result);
1111
1112
                $sql = "SELECT DISTINCT e.question_id, e.question_order
1113
                        FROM $TBL_EXERCISE_QUESTION e
1114
                        INNER JOIN $TBL_QUESTIONS  q
1115
                        ON (e.question_id= q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
1116
					    WHERE e.exercice_id	= ".intval($this->id)."
1117
					    ORDER BY question_order";
1118
                $result = Database::query($sql);
1119
1120
                // fills the array with the question ID for this exercise
1121
                // the key of the array is the question position
1122
                $temp_question_list = array();
1123
                $counter = 1;
1124
                $question_list = array();
1125
1126 View Code Duplication
                while ($new_object = Database::fetch_object($result)) {
1127
                    $question_list[$new_object->question_order]=  $new_object->question_id;
1128
                    $temp_question_list[$counter] = $new_object->question_id;
1129
                    $counter++;
1130
                }
1131
1132
                if (!empty($temp_question_list)) {
1133
                    if (count($temp_question_list) != $count_question_orders) {
1134
                        $question_list = $temp_question_list;
1135
                    }
1136
                }
1137
1138
                return $question_list;
1139
            }
1140
1141
            return $this->questionList;
1142
        } else {
1143
1144
            if ($from_db && !empty($this->id)) {
1145
1146
                $nbQuestions = $this->getQuestionCount();
1147
                $questionSelectionType = $this->getQuestionSelectionType();
1148
1149
                switch ($questionSelectionType) {
1150
                    case EX_Q_SELECTION_ORDERED:
1151
                        $questionList = $this->getQuestionOrderedList();
1152
                        break;
1153
                    case EX_Q_SELECTION_RANDOM:
1154
                        // Not a random exercise, or if there are not at least 2 questions
1155
                        if ($this->random == 0 || $nbQuestions < 2) {
1156
                            $questionList = $this->getQuestionOrderedList();
1157
                        } else {
1158
                            $questionList = $this->selectRandomList();
1159
                        }
1160
                        break;
1161
                    default:
1162
                        $questionList = $this->getQuestionOrderedList();
1163
                        $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1164
                            $questionList,
1165
                            $questionSelectionType
1166
                        );
1167
                        $this->categoryWithQuestionList = $result['category_with_questions_list'];
1168
                        $questionList = $result['question_list'];
1169
                        break;
1170
                }
1171
1172
                return $questionList;
1173
            }
1174
            return $this->questionList;
1175
        }
1176
    }
1177
1178
    /**
1179
     * returns the number of questions in this exercise
1180
     *
1181
     * @author Olivier Brouckaert
1182
     * @return integer - number of questions
1183
     */
1184
    public function selectNbrQuestions()
1185
    {
1186
        return sizeof($this->questionList);
1187
    }
1188
1189
    /**
1190
     * @return int
1191
     */
1192
    public function selectPropagateNeg()
1193
    {
1194
        return $this->propagate_neg;
1195
    }
1196
1197
    /**
1198
     * Selects questions randomly in the question list
1199
     *
1200
     * @author Olivier Brouckaert
1201
     * @author Hubert Borderiou 15 nov 2011
1202
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1203
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1204
     */
1205
    public function selectRandomList()
1206
    {
1207
        /*$nbQuestions	= $this->selectNbrQuestions();
1208
        $temp_list		= $this->questionList;
1209
1210
        //Not a random exercise, or if there are not at least 2 questions
1211
        if($this->random == 0 || $nbQuestions < 2) {
1212
            return $this->questionList;
1213
        }
1214
        if ($nbQuestions != 0) {
1215
            shuffle($temp_list);
1216
            $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
1217
            $my_question_list = array();
1218
            // $this->random == -1 if random with all questions
1219
            if ($this->random > 0) {
1220
                $i = 0;
1221
                foreach ($my_random_list as $item) {
1222
                    if ($i < $this->random) {
1223
                        $my_question_list[$i] = $item;
1224
                    } else {
1225
                        break;
1226
                    }
1227
                    $i++;
1228
                }
1229
            } else {
1230
                $my_question_list = $my_random_list;
1231
            }
1232
            return $my_question_list;
1233
        }*/
1234
1235
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1236
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1237
1238
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1239
1240
        $randomLimit = "LIMIT $random";
1241
        // Random all questions so no limit
1242
        if ($random == -1) {
1243
            $randomLimit = null;
1244
        }
1245
1246
        // @todo improve this query
1247
        $sql = "SELECT e.question_id
1248
                FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q
1249
                    ON (e.question_id= q.iid)
1250
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1251
                ORDER BY RAND()
1252
                $randomLimit ";
1253
        $result = Database::query($sql);
1254
        $questionList = array();
1255
        while ($row = Database::fetch_object($result)) {
1256
            $questionList[] = $row->question_id;
1257
        }
1258
        return $questionList;
1259
    }
1260
1261
    /**
1262
     * returns 'true' if the question ID is in the question list
1263
     *
1264
     * @author Olivier Brouckaert
1265
     * @param integer $questionId - question ID
1266
     * @return boolean - true if in the list, otherwise false
1267
     */
1268
    public function isInList($questionId)
1269
    {
1270
        if (is_array($this->questionList))
1271
            return in_array($questionId,$this->questionList);
1272
        else
1273
            return false;
1274
    }
1275
1276
    /**
1277
     * changes the exercise title
1278
     *
1279
     * @author Olivier Brouckaert
1280
     * @param string $title - exercise title
1281
     */
1282
    public function updateTitle($title)
1283
    {
1284
        $this->exercise=$title;
1285
    }
1286
1287
    /**
1288
     * changes the exercise max attempts
1289
     *
1290
     * @param int $attempts - exercise max attempts
1291
     */
1292
    public function updateAttempts($attempts)
1293
    {
1294
        $this->attempts=$attempts;
1295
    }
1296
1297
    /**
1298
     * changes the exercise feedback type
1299
     *
1300
     * @param int $feedback_type
1301
     */
1302
    public function updateFeedbackType($feedback_type)
1303
    {
1304
        $this->feedback_type=$feedback_type;
1305
    }
1306
1307
    /**
1308
     * changes the exercise description
1309
     *
1310
     * @author Olivier Brouckaert
1311
     * @param string $description - exercise description
1312
     */
1313
    public function updateDescription($description)
1314
    {
1315
        $this->description=$description;
1316
    }
1317
1318
    /**
1319
     * changes the exercise expired_time
1320
     *
1321
     * @author Isaac flores
1322
     * @param int $expired_time The expired time of the quiz
1323
     */
1324
    public function updateExpiredTime($expired_time)
1325
    {
1326
        $this->expired_time = $expired_time;
1327
    }
1328
1329
    /**
1330
     * @param $value
1331
     */
1332
    public function updatePropagateNegative($value)
1333
    {
1334
        $this->propagate_neg = $value;
1335
    }
1336
1337
    /**
1338
     * @param $value
1339
     */
1340
    public function updateReviewAnswers($value)
1341
    {
1342
        $this->review_answers = isset($value) && $value ? true : false;
1343
    }
1344
1345
    /**
1346
     * @param $value
1347
     */
1348
    public function updatePassPercentage($value)
1349
    {
1350
        $this->pass_percentage = $value;
1351
    }
1352
1353
1354
    /**
1355
     * @param string $text
1356
     */
1357
    public function updateEmailNotificationTemplate($text)
1358
    {
1359
        $this->emailNotificationTemplate = $text;
1360
    }
1361
1362
    /**
1363
     * @param string $text
1364
     */
1365
    public function updateEmailNotificationTemplateToUser($text)
1366
    {
1367
        $this->emailNotificationTemplateToUser = $text;
1368
    }
1369
1370
    /**
1371
     * @param string $value
1372
     */
1373
    public function setNotifyUserByEmail($value)
1374
    {
1375
        $this->notifyUserByEmail = $value;
0 ignored issues
show
Documentation Bug introduced by
The property $notifyUserByEmail was declared of type integer, but $value is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1376
    }
1377
1378
1379
    /**
1380
     * @param int $value
1381
     */
1382
    public function updateEndButton($value)
1383
    {
1384
        $this->endButton = intval($value);
1385
    }
1386
1387
    /**
1388
     * @param string $value
1389
     */
1390
    public function setOnSuccessMessage($value)
1391
    {
1392
        $this->onSuccessMessage = $value;
1393
    }
1394
1395
    /**
1396
     * @param string $value
1397
     */
1398
    public function setOnFailedMessage($value)
1399
    {
1400
        $this->onFailedMessage = $value;
1401
    }
1402
1403
    /**
1404
     * @param $value
1405
     */
1406
    public function setModelType($value)
1407
    {
1408
        $this->modelType = intval($value);
1409
    }
1410
1411
    /**
1412
     * @param intval $value
1413
     */
1414
    public function setQuestionSelectionType($value)
1415
    {
1416
        $this->questionSelectionType = intval($value);
1417
    }
1418
1419
    /**
1420
     * @return int
1421
     */
1422
    public function getQuestionSelectionType()
1423
    {
1424
        return $this->questionSelectionType;
1425
    }
1426
1427
    /**
1428
     * @param array $categories
1429
     */
1430
    public function updateCategories($categories)
1431
    {
1432
        if (!empty($categories)) {
1433
            $categories = array_map('intval', $categories);
1434
            $this->categories = $categories;
1435
        }
1436
    }
1437
1438
    /**
1439
     * changes the exercise sound file
1440
     *
1441
     * @author Olivier Brouckaert
1442
     * @param string $sound - exercise sound file
1443
     * @param string $delete - ask to delete the file
1444
     */
1445
    public function updateSound($sound,$delete)
1446
    {
1447
        global $audioPath, $documentPath;
1448
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1449
1450
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1451
            $this->sound=$sound['name'];
1452
1453
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1454
                $query = "SELECT 1 FROM $TBL_DOCUMENT
1455
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1456
                $result=Database::query($query);
1457
1458
                if (!Database::num_rows($result)) {
1459
                    $id = add_document(
1460
                        $this->course,
1461
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1462
                        'file',
1463
                        $sound['size'],
1464
                        $sound['name']
1465
                    );
1466
                    api_item_property_update(
1467
                        $this->course,
1468
                        TOOL_DOCUMENT,
1469
                        $id,
1470
                        'DocumentAdded',
1471
                        api_get_user_id()
1472
                    );
1473
                    item_property_update_on_folder(
1474
                        $this->course,
1475
                        str_replace($documentPath, '', $audioPath),
1476
                        api_get_user_id()
1477
                    );
1478
                }
1479
            }
1480
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1481
            $this->sound='';
1482
        }
1483
    }
1484
1485
    /**
1486
     * changes the exercise type
1487
     *
1488
     * @author Olivier Brouckaert
1489
     * @param integer $type - exercise type
1490
     */
1491
    public function updateType($type)
1492
    {
1493
        $this->type=$type;
1494
    }
1495
1496
    /**
1497
     * sets to 0 if questions are not selected randomly
1498
     * if questions are selected randomly, sets the draws
1499
     *
1500
     * @author Olivier Brouckaert
1501
     * @param integer $random - 0 if not random, otherwise the draws
1502
     */
1503
    public function setRandom($random)
1504
    {
1505
        /*if ($random == 'all') {
1506
            $random = $this->selectNbrQuestions();
1507
        }*/
1508
        $this->random = $random;
1509
    }
1510
1511
    /**
1512
     * sets to 0 if answers are not selected randomly
1513
     * if answers are selected randomly
1514
     * @author Juan Carlos Rana
1515
     * @param integer $random_answers - random answers
1516
     */
1517
    public function updateRandomAnswers($random_answers)
1518
    {
1519
        $this->random_answers = $random_answers;
1520
    }
1521
1522
    /**
1523
     * enables the exercise
1524
     *
1525
     * @author Olivier Brouckaert
1526
     */
1527
    public function enable()
1528
    {
1529
        $this->active=1;
1530
    }
1531
1532
    /**
1533
     * disables the exercise
1534
     *
1535
     * @author Olivier Brouckaert
1536
     */
1537
    public function disable()
1538
    {
1539
        $this->active=0;
1540
    }
1541
1542
    /**
1543
     * Set disable results
1544
     */
1545
    public function disable_results()
1546
    {
1547
        $this->results_disabled = true;
1548
    }
1549
1550
    /**
1551
     * Enable results
1552
     */
1553
    public function enable_results()
1554
    {
1555
        $this->results_disabled = false;
1556
    }
1557
1558
    /**
1559
     * @param int $results_disabled
1560
     */
1561
    public function updateResultsDisabled($results_disabled)
1562
    {
1563
        $this->results_disabled = intval($results_disabled);
1564
    }
1565
1566
    /**
1567
     * updates the exercise in the data base
1568
     *
1569
     * @author Olivier Brouckaert
1570
     */
1571
    public function save($type_e = '')
1572
    {
1573
        $_course = $this->course;
1574
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1575
1576
        $id = $this->id;
1577
        $exercise = $this->exercise;
1578
        $description = $this->description;
1579
        $sound = $this->sound;
1580
        $type = $this->type;
1581
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1582
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1583
        $random = $this->random;
1584
        $random_answers = $this->random_answers;
1585
        $active = $this->active;
1586
        $propagate_neg = $this->propagate_neg;
1587
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1588
        $randomByCat = intval($this->randomByCat);
1589
        $text_when_finished = $this->text_when_finished;
1590
        $display_category_name = intval($this->display_category_name);
1591
        $pass_percentage = intval($this->pass_percentage);
1592
        $session_id = $this->sessionId;
1593
1594
        //If direct we do not show results
1595
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1596
            $results_disabled = 0;
1597
        } else {
1598
            $results_disabled = intval($this->results_disabled);
1599
        }
1600
1601
        $expired_time = intval($this->expired_time);
1602
1603
        // Exercise already exists
1604
        if ($id) {
1605
            // we prepare date in the database using the api_get_utc_datetime() function
1606 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1607
                $start_time = $this->start_time;
1608
            } else {
1609
                $start_time = '0000-00-00 00:00:00';
1610
            }
1611
1612 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1613
                $end_time = $this->end_time;
1614
            } else {
1615
                $end_time = '0000-00-00 00:00:00';
1616
            }
1617
1618
            $params = [
1619
                'title' => $exercise,
1620
                'description' => $description,
1621
            ];
1622
1623
            $paramsExtra = [];
1624
            if ($type_e != 'simple') {
1625
                $paramsExtra = [
1626
                    'sound' => $sound,
1627
                    'type' => $type,
1628
                    'random' => $random,
1629
                    'random_answers' => $random_answers,
1630
                    'active' => $active,
1631
                    'feedback_type' => $feedback_type,
1632
                    'start_time' => $start_time,
1633
                    'end_time' => $end_time,
1634
                    'max_attempt' => $attempts,
1635
                    'expired_time' => $expired_time,
1636
                    'propagate_neg' => $propagate_neg,
1637
                    'review_answers' => $review_answers,
1638
                    'random_by_category' => $randomByCat,
1639
                    'text_when_finished' => $text_when_finished,
1640
                    'display_category_name' => $display_category_name,
1641
                    'pass_percentage' => $pass_percentage,
1642
                    'results_disabled' => $results_disabled,
1643
                ];
1644
            }
1645
1646
            $params = array_merge($params, $paramsExtra);
1647
1648
            if ($this->specialCategoryOrders) {
1649
                $params['question_selection_type'] = intval($this->getQuestionSelectionType());
1650
            }
1651
1652
            Database::update(
1653
                $TBL_EXERCISES,
1654
                $params,
1655
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1656
            );
1657
1658
            // update into the item_property table
1659
            api_item_property_update(
1660
                $_course,
1661
                TOOL_QUIZ,
1662
                $id,
1663
                'QuizUpdated',
1664
                api_get_user_id()
1665
            );
1666
1667
            if (api_get_setting('search_enabled')=='true') {
1668
                $this->search_engine_edit();
1669
            }
1670
        } else {
1671
            // Creates a new exercise
1672
1673
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1674
            // for date because, bellow, we call function api_set_default_visibility()
1675
            // In this function, api_set_default_visibility,
1676
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1677
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1678 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1679
                $start_time = $this->start_time;
1680
            } else {
1681
                $start_time = '0000-00-00 00:00:00';
1682
            }
1683
1684 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1685
                $end_time = $this->end_time;
1686
            } else {
1687
                $end_time = '0000-00-00 00:00:00';
1688
            }
1689
1690
            $params = [
1691
                'c_id' => $this->course_id,
1692
                'start_time' => $start_time,
1693
                'end_time' => $end_time,
1694
                'title' => $exercise,
1695
                'description' => $description,
1696
                'sound' => $sound,
1697
                'type' => $type,
1698
                'random' => $random,
1699
                'random_answers' => $random_answers,
1700
                'active' => $active,
1701
                'results_disabled' => $results_disabled,
1702
                'max_attempt' => $attempts,
1703
                'feedback_type' => $feedback_type,
1704
                'expired_time' => $expired_time,
1705
                'session_id' => $session_id,
1706
                'review_answers' => $review_answers,
1707
                'random_by_category' => $randomByCat,
1708
                'text_when_finished' => $text_when_finished,
1709
                'display_category_name' => $display_category_name,
1710
                'pass_percentage' => $pass_percentage
1711
            ];
1712
1713
            $this->id = Database::insert($TBL_EXERCISES, $params);
1714
1715
            if ($this->id) {
1716
1717
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1718
                Database::query($sql);
1719
1720
                if ($this->specialCategoryOrders) {
1721
                    $sql = "UPDATE $TBL_EXERCISES
1722
                            SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1723
                            WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1724
                    Database::query($sql);
1725
                }
1726
1727
                // insert into the item_property table
1728
                api_item_property_update(
1729
                    $this->course,
1730
                    TOOL_QUIZ,
1731
                    $this->id,
1732
                    'QuizAdded',
1733
                    api_get_user_id()
1734
                );
1735
1736
                // This function save the quiz again, carefull about start_time
1737
                // and end_time if you remove this line (see above)
1738
                api_set_default_visibility(
1739
                    $this->id,
1740
                    TOOL_QUIZ,
1741
                    null,
1742
                    $this->course
1743
                );
1744
1745
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1746
                    $this->search_engine_save();
1747
                }
1748
            }
1749
        }
1750
1751
        $this->save_categories_in_exercise($this->categories);
1752
1753
        // Updates the question position
1754
        $this->update_question_positions();
1755
    }
1756
1757
    /**
1758
     * Updates question position
1759
     */
1760
    public function update_question_positions()
1761
    {
1762
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1763
        //Fixes #3483 when updating order
1764
        $question_list = $this->selectQuestionList(true);
1765
        if (!empty($question_list)) {
1766
            foreach ($question_list as $position => $questionId) {
1767
                $sql = "UPDATE $quiz_question_table SET
1768
                        question_order ='".intval($position)."'
1769
                        WHERE
1770
                            c_id = ".$this->course_id." AND
1771
                            question_id = ".intval($questionId)." AND
1772
                            exercice_id=".intval($this->id);
1773
                Database::query($sql);
1774
            }
1775
        }
1776
    }
1777
1778
    /**
1779
     * Adds a question into the question list
1780
     *
1781
     * @author Olivier Brouckaert
1782
     * @param integer $questionId - question ID
1783
     * @return boolean - true if the question has been added, otherwise false
1784
     */
1785
    public function addToList($questionId)
1786
    {
1787
        // checks if the question ID is not in the list
1788
        if (!$this->isInList($questionId)) {
1789
            // selects the max position
1790
            if (!$this->selectNbrQuestions()) {
1791
                $pos = 1;
1792
            } else {
1793
                if (is_array($this->questionList)) {
1794
                    $pos = max(array_keys($this->questionList)) + 1;
1795
                }
1796
            }
1797
            $this->questionList[$pos] = $questionId;
1798
1799
            return true;
1800
        }
1801
1802
        return false;
1803
    }
1804
1805
    /**
1806
     * removes a question from the question list
1807
     *
1808
     * @author Olivier Brouckaert
1809
     * @param integer $questionId - question ID
1810
     * @return boolean - true if the question has been removed, otherwise false
1811
     */
1812
    public function removeFromList($questionId)
1813
    {
1814
        // searches the position of the question ID in the list
1815
        $pos = array_search($questionId,$this->questionList);
1816
1817
        // question not found
1818
        if ($pos === false) {
1819
            return false;
1820
        } else {
1821
            // dont reduce the number of random question if we use random by category option, or if
1822
            // random all questions
1823
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1824
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1825
                    $this->random -= 1;
1826
                    $this->save();
1827
                }
1828
            }
1829
            // deletes the position from the array containing the wanted question ID
1830
            unset($this->questionList[$pos]);
1831
            return true;
1832
        }
1833
    }
1834
1835
    /**
1836
     * deletes the exercise from the database
1837
     * Notice : leaves the question in the data base
1838
     *
1839
     * @author Olivier Brouckaert
1840
     */
1841
    public function delete()
1842
    {
1843
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1844
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1845
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id)."";
1846
        Database::query($sql);
1847
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
1848
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
1849
1850
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) {
1851
            $this->search_engine_delete();
1852
        }
1853
    }
1854
1855
    /**
1856
     * Creates the form to create / edit an exercise
1857
     * @param FormValidator $form
1858
     */
1859
    public function createForm($form, $type='full')
1860
    {
1861
        if (empty($type)) {
1862
            $type = 'full';
1863
        }
1864
1865
        // form title
1866
        if (!empty($_GET['exerciseId'])) {
1867
            $form_title = get_lang('ModifyExercise');
1868
        } else {
1869
            $form_title = get_lang('NewEx');
1870
        }
1871
1872
        $form->addElement('header', $form_title);
1873
1874
        // Title.
1875
        $form->addElement(
1876
            'text',
1877
            'exerciseTitle',
1878
            get_lang('ExerciseName'),
1879
            array('id' => 'exercise_title')
1880
        );
1881
1882
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1883
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1884
1885
        $editor_config = array(
1886
            'ToolbarSet' => 'TestQuestionDescription',
1887
            'Width' => '100%',
1888
            'Height' => '150',
1889
        );
1890
        if (is_array($type)){
1891
            $editor_config = array_merge($editor_config, $type);
1892
        }
1893
1894
        $form->addHtmlEditor(
1895
            'exerciseDescription',
1896
            get_lang('ExerciseDescription'),
1897
            false,
1898
            false,
1899
            $editor_config
1900
        );
1901
1902
        if ($type == 'full') {
1903
            //Can't modify a DirectFeedback question
1904
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1905
                // feedback type
1906
                $radios_feedback = array();
1907
                $radios_feedback[] = $form->createElement(
1908
                    'radio',
1909
                    'exerciseFeedbackType',
1910
                    null,
1911
                    get_lang('ExerciseAtTheEndOfTheTest'),
1912
                    '0',
1913
                    array(
1914
                        'id' => 'exerciseType_0',
1915
                        'onclick' => 'check_feedback()',
1916
                    )
1917
                );
1918
1919 View Code Duplication
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1920
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1921
                    if ($this->selectNbrQuestions() == 0) {
1922
                        $radios_feedback[] = $form->createElement(
1923
                            'radio',
1924
                            'exerciseFeedbackType',
1925
                            null,
1926
                            get_lang('DirectFeedback'),
1927
                            '1',
1928
                            array(
1929
                                'id' => 'exerciseType_1',
1930
                                'onclick' => 'check_direct_feedback()',
1931
                            )
1932
                        );
1933
                    }
1934
                }
1935
1936
                $radios_feedback[] = $form->createElement(
1937
                    'radio',
1938
                    'exerciseFeedbackType',
1939
                    null,
1940
                    get_lang('NoFeedback'),
1941
                    '2',
1942
                    array('id' => 'exerciseType_2')
1943
                );
1944
                $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')), '');
1945
1946
                // Type of results display on the final page
1947
                $radios_results_disabled = array();
1948
                $radios_results_disabled[] = $form->createElement(
1949
                    'radio',
1950
                    'results_disabled',
1951
                    null,
1952
                    get_lang('ShowScoreAndRightAnswer'),
1953
                    '0',
1954
                    array('id' => 'result_disabled_0')
1955
                );
1956
                $radios_results_disabled[] = $form->createElement(
1957
                    'radio',
1958
                    'results_disabled',
1959
                    null,
1960
                    get_lang('DoNotShowScoreNorRightAnswer'),
1961
                    '1',
1962
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1963
                );
1964
                $radios_results_disabled[] = $form->createElement(
1965
                    'radio',
1966
                    'results_disabled',
1967
                    null,
1968
                    get_lang('OnlyShowScore'),
1969
                    '2',
1970
                    array('id' => 'result_disabled_2')
1971
                );
1972
                //$radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ExamModeWithFinalScoreShowOnlyFinalScoreWithCategoriesIfAvailable'),  '3', array('id'=>'result_disabled_3','onclick' => 'check_results_disabled()'));
1973
1974
                $radios_results_disabled[] = $form->createElement(
1975
                    'radio',
1976
                    'results_disabled',
1977
                    null,
1978
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1979
                    '4',
1980
                    array('id' => 'result_disabled_4')
1981
                );
1982
1983
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
1984
1985
                // Type of questions disposition on page
1986
                $radios = array();
1987
1988
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1989
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1990
1991
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
1992
1993
            } else {
1994
                // if is Directfeedback but has not questions we can allow to modify the question type
1995
                if ($this->selectNbrQuestions() == 0) {
1996
1997
                    // feedback type
1998
                    $radios_feedback = array();
1999
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
2000
2001 View Code Duplication
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
2002
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
2003
                    }
2004
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
2005
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
2006
2007
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
2008
                    $radios_results_disabled = array();
2009
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
2010
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
2011
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
2012
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
2013
2014
                    // Type of questions disposition on page
2015
                    $radios = array();
2016
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
2017
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
2018
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2019
2020
                } else {
2021
                    //Show options freeze
2022
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
2023
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
2024
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
2025
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
2026
                    $result_disable_group->freeze();
2027
2028
                    //we force the options to the DirectFeedback exercisetype
2029
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2030
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2031
2032
                    // Type of questions disposition on page
2033
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
2034
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
2035
2036
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
2037
                    $type_group->freeze();
2038
                }
2039
            }
2040
2041
            if ($this->specialCategoryOrders) {
2042
                $option = array(
2043
                    EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2044
                    //  defined by user
2045
                    EX_Q_SELECTION_RANDOM => get_lang('Random'),
2046
                    // 1-10, All
2047
                    'per_categories' => '--------'.get_lang(
2048
                            'UsingCategories'
2049
                        ).'----------',
2050
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(
2053
                        'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
2054
                    ),
2055
                    // A 123 B 456 C 78 (0, 1, all)
2056
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
2057
                        'RandomCategoriesWithQuestionsOrdered'
2058
                    ),
2059
                    // C 78 B 456 A 123
2060
2061
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
2062
                        'OrderedCategoriesAlphabeticallyWithRandomQuestions'
2063
                    ),
2064
                    // A 321 B 654 C 87
2065
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
2066
                        'RandomCategoriesWithRandomQuestions'
2067
                    ),
2068
                    //C 87 B 654 A 321
2069
2070
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2071
                    /*    B 456 C 78 A 123
2072
                            456 78 123
2073
                            123 456 78
2074
                    */
2075
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2076
                    /*
2077
                        A 123 B 456 C 78
2078
                        B 456 C 78 A 123
2079
                        B 654 C 87 A 321
2080
                        654 87 321
2081
                        165 842 73
2082
                    */
2083
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2084
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2085
                );
2086
2087
                $form->addElement(
2088
                    'select',
2089
                    'question_selection_type',
2090
                    array(get_lang('QuestionSelection')),
2091
                    $option,
2092
                    array(
2093
                        'id' => 'questionSelection',
2094
                        'onclick' => 'checkQuestionSelection()'
2095
                    )
2096
                );
2097
2098
                $displayMatrix = 'none';
2099
                $displayRandom = 'none';
2100
                $selectionType = $this->getQuestionSelectionType();
2101
                switch ($selectionType) {
2102
                    case EX_Q_SELECTION_RANDOM:
2103
                        $displayRandom = 'block';
2104
                        break;
2105
                    case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2106
                        $displayMatrix = 'block';
2107
                        break;
2108
                }
2109
2110
                $form->addElement(
2111
                    'html',
2112
                    '<div id="hidden_random" style="display:'.$displayRandom.'">'
2113
                );
2114
                // Number of random question.
2115
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2116
                $option = range(0, $max);
2117
                $option[0] = get_lang('No');
2118
                $option[-1] = get_lang('AllQuestionsShort');
2119
                $form->addElement(
2120
                    'select',
2121
                    'randomQuestions',
2122
                    array(
2123
                        get_lang('RandomQuestions'),
2124
                        get_lang('RandomQuestionsHelp')
2125
                    ),
2126
                    $option,
2127
                    array('id' => 'randomQuestions')
2128
                );
2129
                $form->addElement('html', '</div>');
2130
2131
                $form->addElement(
2132
                    'html',
2133
                    '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2134
                );
2135
2136
                // Category selection.
2137
                $cat = new TestCategory();
2138
                $cat_form = $cat->returnCategoryForm($this);
2139
                $form->addElement('label', null, $cat_form);
2140
                $form->addElement('html', '</div>');
2141
2142
                // Category name.
2143
                $radio_display_cat_name = array(
2144
                    $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2145
                    $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2146
                );
2147
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');
2148
2149
                // Random answers.
2150
                $radios_random_answers = array(
2151
                    $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2152
                    $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2153
                );
2154
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2155
2156
                // Hide question title.
2157
                $group = array(
2158
                    $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2159
                    $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2160
                );
2161
                $form->addGroup($group, null, get_lang('HideQuestionTitle'), '');
2162
            } else {
2163
2164
                // number of random question
2165
2166
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
2167
                $option = range(0, $max);
2168
                $option[0] = get_lang('No');
2169
                $option[-1] = get_lang('AllQuestionsShort');
2170
                $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
2171
2172
                // Random answers
2173
                $radios_random_answers = array();
2174
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
2175
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
2176
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2177
2178
                // Random by category
2179
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2180
                $radiocat = array();
2181
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
2182
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
2183
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
2184
                $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
2185
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2186
2187
                // add the radio display the category name for student
2188
                $radio_display_cat_name = array();
2189
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
2190
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
2191
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');
2192
            }
2193
            // Attempts
2194
            $attempt_option = range(0, 10);
2195
            $attempt_option[0] = get_lang('Infinite');
2196
2197
            $form->addElement(
2198
                'select',
2199
                'exerciseAttempts',
2200
                get_lang('ExerciseAttempts'),
2201
                $attempt_option,
2202
                ['id' => 'exerciseAttempts']
2203
            );
2204
2205
            // Exercise time limit
2206
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2207
2208
            $var = Exercise::selectTimeLimit();
2209
2210
            if (($this->start_time != '0000-00-00 00:00:00'))
2211
                $form->addElement('html','<div id="start_date_div" style="display:block;">');
2212
            else
2213
                $form->addElement('html','<div id="start_date_div" style="display:none;">');
2214
2215
            $form->addElement('date_time_picker', 'start_time');
2216
2217
            $form->addElement('html','</div>');
2218
2219
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2220
2221
            if (($this->end_time != '0000-00-00 00:00:00'))
2222
                $form->addElement('html','<div id="end_date_div" style="display:block;">');
2223
            else
2224
                $form->addElement('html','<div id="end_date_div" style="display:none;">');
2225
2226
            $form->addElement('date_time_picker', 'end_time');
2227
            $form->addElement('html','</div>');
2228
2229
            //$check_option=$this->selectType();
2230
            $diplay = 'block';
2231
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2232
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2233
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2234
2235
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2236
2237
            //Timer control
2238
            //$time_hours_option = range(0,12);
2239
            //$time_minutes_option = range(0,59);
2240
            $form->addElement(
2241
                'checkbox',
2242
                'enabletimercontrol',
2243
                null,
2244
                get_lang('EnableTimerControl'),
2245
                array(
2246
                    'onclick' => 'option_time_expired()',
2247
                    'id' => 'enabletimercontrol',
2248
                    'onload' => 'check_load_time()',
2249
                )
2250
            );
2251
            $expired_date = (int)$this->selectExpiredTime();
2252
2253
            if (($expired_date!='0')) {
2254
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2255
            } else {
2256
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2257
            }
2258
            $form->addText(
2259
                'enabletimercontroltotalminutes',
2260
                get_lang('ExerciseTotalDurationInMinutes'),
2261
                false,
2262
                [
2263
                    'id' => 'enabletimercontroltotalminutes',
2264
                    'cols-size' => [2, 2, 8]
2265
                ]
2266
            );
2267
            $form->addElement('html','</div>');
2268
2269
            $form->addElement(
2270
                'text',
2271
                'pass_percentage',
2272
                array(get_lang('PassPercentage'), null, '%'),
2273
                array('id' => 'pass_percentage')
2274
            );
2275
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2276
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2277
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2278
2279
            // add the text_when_finished textbox
2280
            $form->addHtmlEditor(
2281
                'text_when_finished',
2282
                get_lang('TextWhenFinished'),
2283
                false,
2284
                false,
2285
                $editor_config
2286
            );
2287
2288
            $defaults = array();
2289
2290
            if (api_get_setting('search_enabled') === 'true') {
2291
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2292
2293
                $form->addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
2294
                $form->addElement ('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
2295
2296
                $specific_fields = get_specific_field_list();
2297
2298
                foreach ($specific_fields as $specific_field) {
2299
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2300
                    $filter = array(
2301
                        'c_id' => api_get_course_int_id(),
2302
                        'field_id' => $specific_field['id'],
2303
                        'ref_id' => $this->id,
2304
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2305
                    );
2306
                    $values = get_specific_field_values_list($filter, array('value'));
2307 View Code Duplication
                    if ( !empty($values) ) {
2308
                        $arr_str_values = array();
2309
                        foreach ($values as $value) {
2310
                            $arr_str_values[] = $value['value'];
2311
                        }
2312
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2313
                    }
2314
                }
2315
                //$form->addElement ('html','</div>');
2316
            }
2317
2318
            $form->addElement('html','</div>');  //End advanced setting
2319
            $form->addElement('html','</div>');
2320
        }
2321
2322
        // submit
2323
        if (isset($_GET['exerciseId'])) {
2324
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2325
        } else {
2326
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2327
        }
2328
2329
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2330
2331
        if ($type == 'full') {
2332
            // rules
2333
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2334
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2335
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2336
        }
2337
2338
        // defaults
2339
        if ($type=='full') {
2340
            if ($this->id > 0) {
2341
                if ($this->random > $this->selectNbrQuestions()) {
2342
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2343
                } else {
2344
                    $defaults['randomQuestions'] = $this->random;
2345
                }
2346
2347
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2348
                $defaults['exerciseType'] = $this->selectType();
2349
                $defaults['exerciseTitle'] = $this->get_formated_title();
2350
                $defaults['exerciseDescription'] = $this->selectDescription();
2351
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2352
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2353
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2354
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2355
                $defaults['review_answers'] = $this->review_answers;
2356
                $defaults['randomByCat'] = $this->selectRandomByCat();
2357
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2358
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2359
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2360
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2361
2362
                if (($this->start_time != '0000-00-00 00:00:00')) {
2363
                    $defaults['activate_start_date_check'] = 1;
2364
                }
2365
                if ($this->end_time != '0000-00-00 00:00:00') {
2366
                    $defaults['activate_end_date_check'] = 1;
2367
                }
2368
2369
                $defaults['start_time'] = ($this->start_time!='0000-00-00 00:00:00') ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2370
                $defaults['end_time'] = ($this->end_time!='0000-00-00 00:00:00') ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2371
2372
                // Get expired time
2373
                if ($this->expired_time != '0') {
2374
                    $defaults['enabletimercontrol'] = 1;
2375
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2376
                } else {
2377
                    $defaults['enabletimercontroltotalminutes'] = 0;
2378
                }
2379
            } else {
2380
                $defaults['exerciseType'] = 2;
2381
                $defaults['exerciseAttempts'] = 0;
2382
                $defaults['randomQuestions'] = 0;
2383
                $defaults['randomAnswers'] = 0;
2384
                $defaults['exerciseDescription'] = '';
2385
                $defaults['exerciseFeedbackType'] = 0;
2386
                $defaults['results_disabled'] = 0;
2387
                $defaults['randomByCat'] = 0;
2388
                $defaults['text_when_finished'] = "";
2389
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2390
                $defaults['display_category_name'] = 1;
2391
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2392
                $defaults['pass_percentage'] = '';
2393
                $defaults['end_button'] = $this->selectEndButton();
2394
                $defaults['question_selection_type'] = 1;
2395
                $defaults['hide_question_title'] = 0;
2396
                $defaults['on_success_message'] = null;
2397
                $defaults['on_failed_message'] = null;
2398
            }
2399
        } else {
2400
            $defaults['exerciseTitle'] = $this->selectTitle();
2401
            $defaults['exerciseDescription'] = $this->selectDescription();
2402
        }
2403
        if (api_get_setting('search_enabled') === 'true') {
2404
            $defaults['index_document'] = 'checked="checked"';
2405
        }
2406
        $form->setDefaults($defaults);
2407
2408
        // Freeze some elements.
2409
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2410
            $elementsToFreeze = array(
2411
                'randomQuestions',
2412
                //'randomByCat',
2413
                'exerciseAttempts',
2414
                'propagate_neg',
2415
                'enabletimercontrol',
2416
                'review_answers'
2417
            );
2418
2419
            foreach ($elementsToFreeze as $elementName) {
2420
                /** @var HTML_QuickForm_element $element */
2421
                $element = $form->getElement($elementName);
2422
                $element->freeze();
2423
            }
2424
2425
            //$radioCatGroup->freeze();
2426
        }
2427
    }
2428
2429
    /**
2430
     * function which process the creation of exercises
2431
     * @param FormValidator $form
2432
     * @param string
2433
     */
2434
    function processCreation($form, $type = '')
2435
    {
2436
        $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
2437
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2438
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2439
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2440
        $this->updateType($form->getSubmitValue('exerciseType'));
2441
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2442
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2443
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2444
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2445
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2446
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2447
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2448
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2449
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2450
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2451
        $this->updateCategories($form->getSubmitValue('category'));
2452
        $this->updateEndButton($form->getSubmitValue('end_button'));
2453
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2454
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2455
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2456
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2457
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2458
        $this->setModelType($form->getSubmitValue('model_type'));
2459
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2460
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2461
        $this->sessionId = api_get_session_id();
2462
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2463
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2464
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2465
2466
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2467
            $start_time = $form->getSubmitValue('start_time');
2468
            $this->start_time = api_get_utc_datetime($start_time);
2469
        } else {
2470
            $this->start_time = '0000-00-00 00:00:00';
2471
        }
2472
2473
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2474
            $end_time = $form->getSubmitValue('end_time');
2475
            $this->end_time = api_get_utc_datetime($end_time);
2476
        } else {
2477
            $this->end_time   = '0000-00-00 00:00:00';
2478
        }
2479
2480
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2481
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2482
            if ($this->expired_time == 0) {
2483
                $this->expired_time = $expired_total_time;
2484
            }
2485
        } else {
2486
            $this->expired_time = 0;
2487
        }
2488
2489
        if ($form->getSubmitValue('randomAnswers') == 1) {
2490
            $this->random_answers=1;
2491
        } else {
2492
            $this->random_answers=0;
2493
        }
2494
        $this->save($type);
2495
    }
2496
2497
    function search_engine_save()
2498
    {
2499
        if ($_POST['index_document'] != 1) {
2500
            return;
2501
        }
2502
        $course_id = api_get_course_id();
2503
2504
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2505
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2506
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2507
2508
        $specific_fields = get_specific_field_list();
2509
        $ic_slide = new IndexableChunk();
2510
2511
        $all_specific_terms = '';
2512 View Code Duplication
        foreach ($specific_fields as $specific_field) {
2513
            if (isset($_REQUEST[$specific_field['code']])) {
2514
                $sterms = trim($_REQUEST[$specific_field['code']]);
2515
                if (!empty($sterms)) {
2516
                    $all_specific_terms .= ' '. $sterms;
2517
                    $sterms = explode(',', $sterms);
2518
                    foreach ($sterms as $sterm) {
2519
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2520
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2521
                    }
2522
                }
2523
            }
2524
        }
2525
2526
        // build the chunk to index
2527
        $ic_slide->addValue("title", $this->exercise);
2528
        $ic_slide->addCourseId($course_id);
2529
        $ic_slide->addToolId(TOOL_QUIZ);
2530
        $xapian_data = array(
2531
            SE_COURSE_ID => $course_id,
2532
            SE_TOOL_ID => TOOL_QUIZ,
2533
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2534
            SE_USER => (int)api_get_user_id(),
2535
        );
2536
        $ic_slide->xapian_data = serialize($xapian_data);
2537
        $exercise_description = $all_specific_terms .' '. $this->description;
2538
        $ic_slide->addValue("content", $exercise_description);
2539
2540
        $di = new ChamiloIndexer();
2541
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2542
        $di->connectDb(NULL, NULL, $lang);
2543
        $di->addChunk($ic_slide);
2544
2545
        //index and return search engine document id
2546
        $did = $di->index();
2547
        if ($did) {
2548
            // save it to db
2549
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2550
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2551
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2552
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2553
            Database::query($sql);
2554
        }
2555
    }
2556
2557
    function search_engine_edit()
2558
    {
2559
        // update search enchine and its values table if enabled
2560
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
2561
            $course_id = api_get_course_id();
2562
2563
            // actually, it consists on delete terms from db,
2564
            // insert new ones, create a new search engine document, and remove the old one
2565
            // get search_did
2566
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2567
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2568
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2569
            $res = Database::query($sql);
2570
2571
            if (Database::num_rows($res) > 0) {
2572
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2573
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2574
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2575
2576
                $se_ref = Database::fetch_array($res);
2577
                $specific_fields = get_specific_field_list();
2578
                $ic_slide = new IndexableChunk();
2579
2580
                $all_specific_terms = '';
2581
                foreach ($specific_fields as $specific_field) {
2582
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2583
                    if (isset($_REQUEST[$specific_field['code']])) {
2584
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2585
                        $all_specific_terms .= ' '. $sterms;
2586
                        $sterms = explode(',', $sterms);
2587
                        foreach ($sterms as $sterm) {
2588
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2589
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2590
                        }
2591
                    }
2592
                }
2593
2594
                // build the chunk to index
2595
                $ic_slide->addValue("title", $this->exercise);
2596
                $ic_slide->addCourseId($course_id);
2597
                $ic_slide->addToolId(TOOL_QUIZ);
2598
                $xapian_data = array(
2599
                    SE_COURSE_ID => $course_id,
2600
                    SE_TOOL_ID => TOOL_QUIZ,
2601
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2602
                    SE_USER => (int)api_get_user_id(),
2603
                );
2604
                $ic_slide->xapian_data = serialize($xapian_data);
2605
                $exercise_description = $all_specific_terms .' '. $this->description;
2606
                $ic_slide->addValue("content", $exercise_description);
2607
2608
                $di = new ChamiloIndexer();
2609
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2610
                $di->connectDb(NULL, NULL, $lang);
2611
                $di->remove_document((int)$se_ref['search_did']);
2612
                $di->addChunk($ic_slide);
2613
2614
                //index and return search engine document id
2615
                $did = $di->index();
2616
                if ($did) {
2617
                    // save it to db
2618
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2619
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2620
                    Database::query($sql);
2621
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2622
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2623
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2624
                    Database::query($sql);
2625
                }
2626
            } else {
2627
                $this->search_engine_save();
2628
            }
2629
        }
2630
2631
    }
2632
2633
    function search_engine_delete()
2634
    {
2635
        // remove from search engine if enabled
2636
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
2637
            $course_id = api_get_course_id();
2638
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2639
            $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';
2640
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2641
            $res = Database::query($sql);
2642
            if (Database::num_rows($res) > 0) {
2643
                $row = Database::fetch_array($res);
2644
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2645
                $di = new ChamiloIndexer();
2646
                $di->remove_document((int)$row['search_did']);
2647
                unset($di);
2648
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2649
                foreach ( $this->questionList as $question_i) {
2650
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2651
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2652
                    $qres = Database::query($sql);
2653
                    if (Database::num_rows($qres) > 0) {
2654
                        $qrow = Database::fetch_array($qres);
2655
                        $objQuestion = Question::getInstance($qrow['type']);
2656
                        $objQuestion = Question::read((int)$question_i);
2657
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2658
                        unset($objQuestion);
2659
                    }
2660
                }
2661
            }
2662
            $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';
2663
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2664
            Database::query($sql);
2665
2666
            // remove terms from db
2667
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2668
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2669
        }
2670
    }
2671
2672
    function selectExpiredTime()
2673
    {
2674
        return $this->expired_time;
2675
    }
2676
2677
    /**
2678
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2679
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2680
     * Works with exercises in sessions
2681
     * @param bool $cleanLpTests
2682
     * @param string $cleanResultBeforeDate
2683
     *
2684
     * @return int quantity of user's exercises deleted
2685
     */
2686
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2687
    {
2688
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2689
        $table_track_e_attempt   = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2690
2691
        $sql_where = '  AND
2692
                        orig_lp_id = 0 AND
2693
                        orig_lp_item_id = 0';
2694
2695
        // if we want to delete results from LP too
2696
        if ($cleanLpTests) {
2697
            $sql_where = "";
2698
        }
2699
2700
        // if we want to delete attempts before date $cleanResultBeforeDate
2701
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2702
2703
        if (!empty($cleanResultBeforeDate)) {
2704
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2705
            if (api_is_valid_date($cleanResultBeforeDate)) {
2706
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2707
            } else {
2708
                return 0;
2709
            }
2710
        }
2711
2712
        $sql = "SELECT exe_id
2713
                FROM $table_track_e_exercises
2714
                WHERE
2715
                    c_id = ".api_get_course_int_id()." AND
2716
                    exe_exo_id = ".$this->id." AND
2717
                    session_id = ".api_get_session_id()." ".
2718
                    $sql_where;
2719
2720
        $result   = Database::query($sql);
2721
        $exe_list = Database::store_result($result);
2722
2723
        // deleting TRACK_E_ATTEMPT table
2724
        // check if exe in learning path or not
2725
        $i = 0;
2726
        if (is_array($exe_list) && count($exe_list) > 0) {
2727
            foreach ($exe_list as $item) {
2728
                $sql = "DELETE FROM $table_track_e_attempt
2729
                        WHERE exe_id = '".$item['exe_id']."'";
2730
                Database::query($sql);
2731
                $i++;
2732
            }
2733
        }
2734
2735
        $session_id = api_get_session_id();
2736
        // delete TRACK_E_EXERCISES table
2737
        $sql = "DELETE FROM $table_track_e_exercises
2738
                WHERE c_id = ".api_get_course_int_id()."
2739
                AND exe_exo_id = ".$this->id."
2740
                $sql_where
2741
                AND session_id = ".$session_id."";
2742
        Database::query($sql);
2743
2744
        Event::addEvent(
2745
            LOG_EXERCISE_RESULT_DELETE,
2746
            LOG_EXERCISE_ID,
2747
            $this->id,
2748
            null,
2749
            null,
2750
            api_get_course_int_id(),
2751
            $session_id
2752
        );
2753
2754
        return $i;
2755
    }
2756
2757
    /**
2758
     * Copies an exercise (duplicate all questions and answers)
2759
     */
2760
    public function copy_exercise()
2761
    {
2762
        $exercise_obj= new Exercise();
2763
        $exercise_obj = $this;
2764
2765
        // force the creation of a new exercise
2766
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2767
        //Hides the new exercise
2768
        $exercise_obj->updateStatus(false);
2769
        $exercise_obj->updateId(0);
2770
        $exercise_obj->save();
2771
2772
        $new_exercise_id = $exercise_obj->selectId();
2773
        $question_list 	 = $exercise_obj->selectQuestionList();
2774
2775
        if (!empty($question_list)) {
2776
            //Question creation
2777
2778
            foreach ($question_list as $old_question_id) {
2779
                $old_question_obj = Question::read($old_question_id);
2780
                $new_id = $old_question_obj->duplicate();
2781
                if ($new_id) {
2782
                    $new_question_obj = Question::read($new_id);
2783
2784
                    if (isset($new_question_obj) && $new_question_obj) {
2785
                        $new_question_obj->addToList($new_exercise_id);
2786
                        // This should be moved to the duplicate function
2787
                        $new_answer_obj = new Answer($old_question_id);
2788
                        $new_answer_obj->read();
2789
                        $new_answer_obj->duplicate($new_id);
2790
                    }
2791
                }
2792
            }
2793
        }
2794
    }
2795
2796
    /**
2797
     * Changes the exercise id
2798
     *
2799
     * @param int $id - exercise id
2800
     */
2801
    private function updateId($id)
2802
    {
2803
        $this->id = $id;
2804
    }
2805
2806
    /**
2807
     * Changes the exercise status
2808
     *
2809
     * @param string $status - exercise status
2810
     */
2811
    function updateStatus($status)
2812
    {
2813
        $this->active = $status;
2814
    }
2815
2816
    /**
2817
     * @param int $lp_id
2818
     * @param int $lp_item_id
2819
     * @param int $lp_item_view_id
2820
     * @param string $status
2821
     * @return array
2822
     */
2823
    public function get_stat_track_exercise_info(
2824
        $lp_id = 0,
2825
        $lp_item_id = 0,
2826
        $lp_item_view_id = 0,
2827
        $status = 'incomplete'
2828
    ) {
2829
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2830
        if (empty($lp_id)) {
2831
            $lp_id = 0;
2832
        }
2833
        if (empty($lp_item_id)) {
2834
            $lp_item_id   = 0;
2835
        }
2836
        if (empty($lp_item_view_id)) {
2837
            $lp_item_view_id = 0;
2838
        }
2839
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2840
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2841
					   c_id                 = ' . api_get_course_int_id() . ' AND
2842
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2843
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2844
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2845
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2846
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2847
2848
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2849
2850
        $result = Database::query($sql_track);
2851
        $new_array = array();
2852
        if (Database::num_rows($result) > 0 ) {
2853
            $new_array = Database::fetch_array($result, 'ASSOC');
2854
            $new_array['num_exe'] = Database::num_rows($result);
2855
        }
2856
        return $new_array;
2857
    }
2858
2859
    /**
2860
     * Saves a test attempt
2861
     *
2862
     * @param int  clock_expired_time
2863
     * @param int  int lp id
2864
     * @param int  int lp item id
2865
     * @param int  int lp item_view id
2866
     * @param float $weight
2867
     * @param array question list
2868
     */
2869
    public function save_stat_track_exercise_info(
2870
        $clock_expired_time = 0,
2871
        $safe_lp_id = 0,
2872
        $safe_lp_item_id = 0,
2873
        $safe_lp_item_view_id = 0,
2874
        $questionList = array(),
2875
        $weight = 0
2876
    ) {
2877
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2878
        $safe_lp_id = intval($safe_lp_id);
2879
        $safe_lp_item_id = intval($safe_lp_item_id);
2880
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2881
2882
        if (empty($safe_lp_id)) {
2883
            $safe_lp_id = 0;
2884
        }
2885
        if (empty($safe_lp_item_id)) {
2886
            $safe_lp_item_id = 0;
2887
        }
2888
        if (empty($clock_expired_time)) {
2889
            $clock_expired_time = 0;
2890
        }
2891
2892
        $questionList = array_map('intval', $questionList);
2893
2894
        $params = array(
2895
            'exe_exo_id' => $this->id ,
2896
            'exe_user_id' => api_get_user_id(),
2897
            'c_id' => api_get_course_int_id(),
2898
            'status' =>  'incomplete',
2899
            'session_id'  => api_get_session_id(),
2900
            'data_tracking'  => implode(',', $questionList) ,
2901
            'start_date' => api_get_utc_datetime(),
2902
            'orig_lp_id' => $safe_lp_id,
2903
            'orig_lp_item_id'  => $safe_lp_item_id,
2904
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2905
            'exe_weighting'=> $weight,
2906
            'user_ip' => api_get_real_ip()
2907
        );
2908
2909
        if ($this->expired_time != 0) {
2910
            $params['expired_time_control'] = $clock_expired_time;
2911
        }
2912
2913
        $id = Database::insert($track_exercises, $params);
2914
2915
        return $id;
2916
    }
2917
2918
    /**
2919
     * @param int $question_id
2920
     * @param int $questionNum
2921
     * @param array $questions_in_media
2922
     * @param string $currentAnswer
2923
     * @return string
2924
     */
2925
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2926
    {
2927
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2928
2929
        $nbrQuestions = $this->get_count_question_list();
2930
2931
        $all_button = $html = $label = '';
2932
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2933
2934
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2935
            $urlTitle = get_lang('ContinueTest');
2936
2937
            if ($questionNum == count($this->questionList)) {
2938
                $urlTitle = get_lang('EndTest');
2939
            }
2940
2941
            $html .= Display::url(
2942
                $urlTitle,
2943
                'exercise_submit_modal.php?' . http_build_query([
2944
                    'learnpath_id' => $safe_lp_id,
2945
                    'learnpath_item_id' => $safe_lp_item_id,
2946
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2947
                    'origin' => $origin,
2948
                    'hotspot' => $hotspot_get,
2949
                    'nbrQuestions' => $nbrQuestions,
2950
                    'num' => $questionNum,
2951
                    'exerciseType' => $this->type,
2952
                    'exerciseId' => $this->id
2953
                ]),
2954
                [
2955
                    'class' => 'ajax btn btn-default',
2956
                    'data-title' => $urlTitle,
2957
                    'data-size' => 'md'
2958
                ]
2959
            );
2960
            $html .='<br />';
2961
        } else {
2962
            // User
2963
            if (api_is_allowed_to_session_edit()) {
2964
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2965 View Code Duplication
                    if ($this->review_answers) {
2966
                        $label = get_lang('ReviewQuestions');
2967
                        $class = 'btn btn-success';
2968
                    } else {
2969
                        $label = get_lang('EndTest');
2970
                        $class = 'btn btn-warning';
2971
                    }
2972
                } else {
2973
                    $label = get_lang('NextQuestion');
2974
                    $class = 'btn btn-primary';
2975
                }
2976
				$class .= ' question-validate-btn'; // used to select it with jquery
2977
                if ($this->type == ONE_PER_PAGE) {
2978
                    if ($questionNum != 1) {
2979
                        $prev_question = $questionNum - 2;
2980
                        $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
2981
                    }
2982
2983
                    //Next question
2984
                    if (!empty($questions_in_media)) {
2985
                        $questions_in_media = "['".implode("','",$questions_in_media)."']";
2986
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
2987
                    } else {
2988
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
2989
                    }
2990
                    $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2991
2992
                    $html .= $all_button;
2993
                } else {
2994 View Code Duplication
                    if ($this->review_answers) {
2995
                        $all_label = get_lang('ReviewQuestions');
2996
                        $class = 'btn btn-success';
2997
                    } else {
2998
                        $all_label = get_lang('EndTest');
2999
                        $class = 'btn btn-warning';
3000
                    }
3001
					$class .= ' question-validate-btn'; // used to select it with jquery
3002
                    $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
3003
                    $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
3004
                    $html .= $all_button;
3005
                }
3006
            }
3007
        }
3008
        return $html;
3009
    }
3010
3011
    /**
3012
     * So the time control will work
3013
     *
3014
     * @param string $time_left
3015
     * @return string
3016
     */
3017
    public function show_time_control_js($time_left)
3018
    {
3019
        $time_left = intval($time_left);
3020
        return "<script>
3021
3022
            function get_expired_date_string(expired_time) {
3023
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3024
                var day, month, year, hours, minutes, seconds, date_string;
3025
                var obj_date = new Date(expired_time);
3026
                day     = obj_date.getDate();
3027
                if (day < 10) day = '0' + day;
3028
                    month   = obj_date.getMonth();
3029
                    year    = obj_date.getFullYear();
3030
                    hours   = obj_date.getHours();
3031
                if (hours < 10) hours = '0' + hours;
3032
                minutes = obj_date.getMinutes();
3033
                if (minutes < 10) minutes = '0' + minutes;
3034
                seconds = obj_date.getSeconds();
3035
                if (seconds < 10) seconds = '0' + seconds;
3036
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3037
                return date_string;
3038
            }
3039
3040
            function open_clock_warning() {
3041
                $('#clock_warning').dialog({
3042
                    modal:true,
3043
                    height:250,
3044
                    closeOnEscape: false,
3045
                    resizable: false,
3046
                    buttons: {
3047
                        '".addslashes(get_lang("EndTest"))."': function() {
3048
                            $('#clock_warning').dialog('close');
3049
                        }
3050
                    },
3051
                    close: function() {
3052
                        send_form();
3053
                    }
3054
                });
3055
                $('#clock_warning').dialog('open');
3056
3057
                $('#counter_to_redirect').epiclock({
3058
                    mode: $.epiclock.modes.countdown,
3059
                    offset: {seconds: 5},
3060
                    format: 's'
3061
                }).bind('timer', function () {
3062
                    send_form();
3063
                });
3064
3065
            }
3066
3067
            function send_form() {
3068
                if ($('#exercise_form').length) {
3069
                    $('#exercise_form').submit();
3070
                } else {
3071
                    //In reminder
3072
                    final_submit();
3073
                }
3074
            }
3075
3076
            function onExpiredTimeExercise() {
3077
                $('#wrapper-clock').hide();
3078
                $('#exercise_form').hide();
3079
                $('#expired-message-id').show();
3080
3081
                //Fixes bug #5263
3082
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3083
                open_clock_warning();
3084
            }
3085
3086
			$(document).ready(function() {
3087
3088
				var current_time = new Date().getTime();
3089
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3090
				var expired_time = current_time + (time_left*1000);
3091
				var expired_date = get_expired_date_string(expired_time);
3092
3093
                $('#exercise_clock_warning').epiclock({
3094
                    mode: $.epiclock.modes.countdown,
3095
                    offset: {seconds: time_left},
3096
                    format: 'x:i:s',
3097
                    renderer: 'minute'
3098
                }).bind('timer', function () {
3099
                    onExpiredTimeExercise();
3100
                });
3101
	       		$('#submit_save').click(function () {});
3102
	    });
3103
	    </script>";
3104
    }
3105
3106
    /**
3107
     * Lp javascript for hotspots
3108
     */
3109
    public function show_lp_javascript()
3110
    {
3111
        return "";
3112
    }
3113
3114
    /**
3115
     * This function was originally found in the exercise_show.php
3116
     * @param int       $exeId
3117
     * @param int       $questionId
3118
     * @param int       $choice the user selected
3119
     * @param string    $from  function is called from 'exercise_show' or 'exercise_result'
3120
     * @param array     $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3121
     * @param bool      $saved_results save results in the DB or just show the reponse
3122
     * @param bool      $from_database gets information from DB or from the current selection
3123
     * @param bool      $show_result show results or not
3124
     * @param int       $propagate_neg
3125
     * @param array     $hotspot_delineation_result
3126
     *
3127
     * @todo    reduce parameters of this function
3128
     * @return  string  html code
3129
     */
3130
    public function manage_answer(
3131
        $exeId,
3132
        $questionId,
3133
        $choice,
3134
        $from = 'exercise_show',
3135
        $exerciseResultCoordinates = array(),
3136
        $saved_results = true,
3137
        $from_database = false,
3138
        $show_result = true,
3139
        $propagate_neg = 0,
3140
        $hotspot_delineation_result = array(),
3141
        $showTotalScoreAndUserChoices = false
3142
    ) {
3143
        global $debug;
3144
        //needed in order to use in the exercise_attempt() for the time
3145
        global $learnpath_id, $learnpath_item_id;
3146
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3147
3148
        $em = Database::getManager();
3149
3150
        $feedback_type = $this->selectFeedbackType();
3151
        $results_disabled = $this->selectResultsDisabled();
3152
3153
        if ($debug) {
3154
            error_log("<------ manage_answer ------> ");
3155
            error_log('exe_id: '.$exeId);
3156
            error_log('$from:  '.$from);
3157
            error_log('$saved_results: '.intval($saved_results));
3158
            error_log('$from_database: '.intval($from_database));
3159
            error_log('$show_result: '.$show_result);
3160
            error_log('$propagate_neg: '.$propagate_neg);
3161
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3162
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3163
            error_log('$learnpath_id: '.$learnpath_id);
3164
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3165
            error_log('$choice: '.print_r($choice, 1));
3166
        }
3167
3168
        $extra_data = array();
3169
        $final_overlap = 0;
3170
        $final_missing = 0;
3171
        $final_excess = 0;
3172
        $overlap_color = 0;
3173
        $missing_color = 0;
3174
        $excess_color = 0;
3175
        $threadhold1 = 0;
3176
        $threadhold2 = 0;
3177
        $threadhold3 = 0;
3178
3179
        $arrques = null;
3180
        $arrans  = null;
3181
3182
        $questionId = intval($questionId);
3183
        $exeId = intval($exeId);
3184
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3185
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3186
3187
        // Creates a temporary Question object
3188
        $course_id = $this->course_id;
3189
        $objQuestionTmp = Question::read($questionId, $course_id);
3190
3191
        if ($objQuestionTmp === false) {
3192
            return false;
3193
        }
3194
3195
        $questionName = $objQuestionTmp->selectTitle();
3196
        $questionWeighting = $objQuestionTmp->selectWeighting();
3197
        $answerType = $objQuestionTmp->selectType();
3198
        $quesId = $objQuestionTmp->selectId();
3199
        $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...
3200
3201
        $next = 1; //not for now
3202
3203
        // Extra information of the question
3204
        if (!empty($extra)) {
3205
            $extra = explode(':', $extra);
3206
            if ($debug) {
3207
                error_log(print_r($extra, 1));
3208
            }
3209
            // Fixes problems with negatives values using intval
3210
            $true_score = floatval(trim($extra[0]));
3211
            $false_score = floatval(trim($extra[1]));
3212
            $doubt_score = floatval(trim($extra[2]));
3213
        }
3214
3215
        $totalWeighting = 0;
3216
        $totalScore = 0;
3217
3218
        // Destruction of the Question object
3219
        unset($objQuestionTmp);
3220
3221
        // Construction of the Answer object
3222
        $objAnswerTmp = new Answer($questionId);
3223
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3224
3225
        if ($debug) {
3226
            error_log('Count of answers: '.$nbrAnswers);
3227
            error_log('$answerType: '.$answerType);
3228
        }
3229
3230 View Code Duplication
        if ($answerType == FREE_ANSWER ||
3231
            $answerType == ORAL_EXPRESSION ||
3232
            $answerType == CALCULATED_ANSWER
3233
        ) {
3234
            $nbrAnswers = 1;
3235
        }
3236
3237
        $nano = null;
3238
3239
        if ($answerType == ORAL_EXPRESSION) {
3240
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3241
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3242
3243
            $params = array();
3244
            $params['course_id'] = $course_id;
3245
            $params['session_id'] = api_get_session_id();
3246
            $params['user_id'] = isset($exe_info['exe_user_id'])? $exe_info['exe_user_id'] : api_get_user_id();
3247
            $params['exercise_id'] = isset($exe_info['exe_exo_id'])? $exe_info['exe_exo_id'] : $this->id;
3248
            $params['question_id'] = $questionId;
3249
            $params['exe_id'] = isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId;
3250
3251
            $nano = new Nanogong($params);
3252
3253
            //probably this attempt came in an exercise all question by page
3254
            if ($feedback_type == 0) {
3255
                $nano->replace_with_real_exe($exeId);
3256
            }
3257
        }
3258
3259
        $user_answer = '';
3260
3261
        // Get answer list for matching
3262
        $sql = "SELECT id_auto, id, answer
3263
                FROM $table_ans
3264
                WHERE c_id = $course_id AND question_id = $questionId";
3265
        $res_answer = Database::query($sql);
3266
3267
        $answerMatching = array();
3268
        while ($real_answer = Database::fetch_array($res_answer)) {
3269
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3270
        }
3271
3272
        $real_answers = array();
3273
        $quiz_question_options = Question::readQuestionOption(
3274
            $questionId,
3275
            $course_id
3276
        );
3277
3278
        $organs_at_risk_hit = 0;
3279
        $questionScore = 0;
3280
3281
        if ($debug) error_log('Start answer loop ');
3282
3283
        $orderedHotspots = [];
3284
3285
        if ($answerType == HOT_SPOT) {
3286
            $orderedHotspots = $em
3287
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3288
                ->findBy([
3289
                        'hotspotQuestionId' => $questionId,
3290
                        'cId' => $course_id,
3291
                        'hotspotExeId' => $exeId
3292
                    ],
3293
                    ['hotspotId' => 'ASC']
3294
                );
3295
        }
3296
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3297
            $answer = $objAnswerTmp->selectAnswer($answerId);
3298
            $answerComment = $objAnswerTmp->selectComment($answerId);
3299
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3300
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3301
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3302
3303
            if ($debug) {
3304
                error_log("answer auto id: $answerAutoId ");
3305
                error_log("answer correct: $answerCorrect ");
3306
            }
3307
3308
            // Delineation
3309
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3310
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3311
3312
            switch ($answerType) {
3313
                // for unique answer
3314
                case UNIQUE_ANSWER:
3315
                case UNIQUE_ANSWER_IMAGE:
3316
                case UNIQUE_ANSWER_NO_OPTION:
3317
                    if ($from_database) {
3318
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3319
                                WHERE
3320
                                    exe_id = '".$exeId."' AND
3321
                                    question_id= '".$questionId."'";
3322
                        $result = Database::query($sql);
3323
                        $choice = Database::result($result,0,"answer");
3324
3325
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3326
                        if ($studentChoice) {
3327
                            $questionScore += $answerWeighting;
3328
                            $totalScore += $answerWeighting;
3329
                        }
3330
                    } else {
3331
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3332
                        if ($studentChoice) {
3333
                            $questionScore += $answerWeighting;
3334
                            $totalScore += $answerWeighting;
3335
                        }
3336
                    }
3337
                    break;
3338
                // for multiple answers
3339
                case MULTIPLE_ANSWER_TRUE_FALSE:
3340
                    if ($from_database) {
3341
                        $choice = array();
3342
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3343
                                WHERE
3344
                                    exe_id = $exeId AND
3345
                                    question_id = ".$questionId;
3346
3347
                        $result = Database::query($sql);
3348 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3349
                            $ind = $row['answer'];
3350
                            $values = explode(':', $ind);
3351
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3352
                            $option = isset($values[1]) ? $values[1] : '';
3353
                            $choice[$my_answer_id] = $option;
3354
                        }
3355
                    }
3356
3357
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3358
3359
                    if (!empty($studentChoice)) {
3360
                        if ($studentChoice == $answerCorrect) {
3361
                            $questionScore += $true_score;
3362
                        } else {
3363
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3364
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3365
                            ) {
3366
                                $questionScore += $doubt_score;
3367
                            } else {
3368
                                $questionScore += $false_score;
3369
                            }
3370
                        }
3371
                    } else {
3372
                        // If no result then the user just hit don't know
3373
                        $studentChoice = 3;
3374
                        $questionScore  +=  $doubt_score;
3375
                    }
3376
                    $totalScore = $questionScore;
3377
                    break;
3378 View Code Duplication
                case MULTIPLE_ANSWER: //2
3379
                    if ($from_database) {
3380
                        $choice = array();
3381
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3382
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3383
                        $resultans = Database::query($sql);
3384
                        while ($row = Database::fetch_array($resultans)) {
3385
                            $ind = $row['answer'];
3386
                            $choice[$ind] = 1;
3387
                        }
3388
3389
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3390
                        $real_answers[$answerId] = (bool)$studentChoice;
3391
3392
                        if ($studentChoice) {
3393
                            $questionScore  +=$answerWeighting;
3394
                        }
3395
                    } else {
3396
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3397
                        $real_answers[$answerId] = (bool)$studentChoice;
3398
3399
                        if (isset($studentChoice)) {
3400
                            $questionScore  += $answerWeighting;
3401
                        }
3402
                    }
3403
                    $totalScore += $answerWeighting;
3404
3405
                    if ($debug) error_log("studentChoice: $studentChoice");
3406
                    break;
3407 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
3408
                    if ($from_database) {
3409
                        $choice = array();
3410
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3411
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3412
                        $resultans = Database::query($sql);
3413
                        while ($row = Database::fetch_array($resultans)) {
3414
                            $ind = $row['answer'];
3415
                            $choice[$ind] = 1;
3416
                        }
3417
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3418
                        $real_answers[$answerId] = (bool)$studentChoice;
3419
                        if ($studentChoice) {
3420
                            $questionScore +=$answerWeighting;
3421
                        }
3422
                    } else {
3423
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3424
                        if (isset($studentChoice)) {
3425
                            $questionScore += $answerWeighting;
3426
                        }
3427
                        $real_answers[$answerId] = (bool)$studentChoice;
3428
                    }
3429
                    $totalScore += $answerWeighting;
3430
                    if ($debug) error_log("studentChoice: $studentChoice");
3431
                    break;
3432
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3433
                    if ($from_database) {
3434
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3435
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3436
                        $resultans = Database::query($sql);
3437 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3438
                            $ind = $row['answer'];
3439
                            $result = explode(':',$ind);
3440
                            if (isset($result[0])) {
3441
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3442
                                $option = isset($result[1]) ? $result[1] : '';
3443
                                $choice[$my_answer_id] = $option;
3444
                            }
3445
                        }
3446
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3447
3448
                        if ($answerCorrect == $studentChoice) {
3449
                            //$answerCorrect = 1;
3450
                            $real_answers[$answerId] = true;
3451
                        } else {
3452
                            //$answerCorrect = 0;
3453
                            $real_answers[$answerId] = false;
3454
                        }
3455
                    } else {
3456
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3457
                        if ($answerCorrect == $studentChoice) {
3458
                            //$answerCorrect = 1;
3459
                            $real_answers[$answerId] = true;
3460
                        } else {
3461
                            //$answerCorrect = 0;
3462
                            $real_answers[$answerId] = false;
3463
                        }
3464
                    }
3465
                    break;
3466
                case MULTIPLE_ANSWER_COMBINATION:
3467
                    if ($from_database) {
3468
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3469
                                WHERE exe_id = $exeId AND question_id= $questionId";
3470
                        $resultans = Database::query($sql);
3471
                        while ($row = Database::fetch_array($resultans)) {
3472
                            $ind = $row['answer'];
3473
                            $choice[$ind] = 1;
3474
                        }
3475
3476
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3477
3478 View Code Duplication
                        if ($answerCorrect == 1) {
3479
                            if ($studentChoice) {
3480
                                $real_answers[$answerId] = true;
3481
                            } else {
3482
                                $real_answers[$answerId] = false;
3483
                            }
3484
                        } else {
3485
                            if ($studentChoice) {
3486
                                $real_answers[$answerId] = false;
3487
                            } else {
3488
                                $real_answers[$answerId] = true;
3489
                            }
3490
                        }
3491
                    } else {
3492
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3493 View Code Duplication
                        if ($answerCorrect == 1) {
3494
                            if ($studentChoice) {
3495
                                $real_answers[$answerId] = true;
3496
                            } else {
3497
                                $real_answers[$answerId] = false;
3498
                            }
3499
                        } else {
3500
                            if ($studentChoice) {
3501
                                $real_answers[$answerId] = false;
3502
                            } else {
3503
                                $real_answers[$answerId] = true;
3504
                            }
3505
                        }
3506
                    }
3507
                    break;
3508
                case FILL_IN_BLANKS:
3509
                    $str = '';
3510
                    if ($from_database) {
3511
                        $sql = "SELECT answer
3512
                                FROM $TBL_TRACK_ATTEMPT
3513
                                WHERE
3514
                                    exe_id = $exeId AND
3515
                                    question_id= ".intval($questionId);
3516
                        $result = Database::query($sql);
3517
                        $str = Database::result($result, 0, 'answer');
3518
                    }
3519
3520
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3521
                        // the question is encoded like this
3522
                        // [A] B [C] D [E] F::10,10,10@1
3523
                        // number 1 before the "@" means that is a switchable fill in blank question
3524
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3525
                        // means that is a normal fill blank question
3526
                        // first we explode the "::"
3527
                        $pre_array = explode('::', $answer);
3528
3529
                        // is switchable fill blank or not
3530
                        $last = count($pre_array) - 1;
3531
                        $is_set_switchable = explode('@', $pre_array[$last]);
3532
                        $switchable_answer_set = false;
3533
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3534
                            $switchable_answer_set = true;
3535
                        }
3536
                        $answer = '';
3537
                        for ($k = 0; $k < $last; $k++) {
3538
                            $answer .= $pre_array[$k];
3539
                        }
3540
                        // splits weightings that are joined with a comma
3541
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3542
                        // we save the answer because it will be modified
3543
                        $temp = $answer;
3544
                        $answer = '';
3545
                        $j = 0;
3546
                        //initialise answer tags
3547
                        $user_tags = $correct_tags = $real_text = array();
3548
                        // the loop will stop at the end of the text
3549 View Code Duplication
                        while (1) {
3550
                            // quits the loop if there are no more blanks (detect '[')
3551
                            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 3598 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...
3552
                                // adds the end of the text
3553
                                $answer = $temp;
3554
                                $real_text[] = $answer;
3555
                                break; //no more "blanks", quit the loop
3556
                            }
3557
                            // adds the piece of text that is before the blank
3558
                            //and ends with '[' into a general storage array
3559
                            $real_text[] = api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3598 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...
3560
                            $answer .= api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3598 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...
3561
                            //take the string remaining (after the last "[" we found)
3562
                            $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 3562 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...
3563
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3564
                            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 3562 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...
3565
                                // adds the end of the text
3566
                                $answer .= $temp;
3567
                                break;
3568
                            }
3569
                            if ($from_database) {
3570
                                $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3571
                                              WHERE
3572
                                                exe_id = '".$exeId."' AND
3573
                                                question_id= ".intval($questionId)."";
3574
                                $resfill = Database::query($queryfill);
3575
                                $str = Database::result($resfill, 0, 'answer');
3576
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3577
                                $str = str_replace('\r\n', '', $str);
3578
3579
                                $choice = $arr[1];
3580
                                if (isset($choice[$j])) {
3581
                                    $tmp = api_strrpos($choice[$j], ' / ');
3582
                                    $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 3581 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...
3583
                                    $choice[$j] = trim($choice[$j]);
3584
                                    // Needed to let characters ' and " to work as part of an answer
3585
                                    $choice[$j] = stripslashes($choice[$j]);
3586
                                } else {
3587
                                    $choice[$j] = null;
3588
                                }
3589
                            } else {
3590
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3591
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3592
                            }
3593
3594
                            $user_tags[] = $choice[$j];
3595
                            //put the contents of the [] answer tag into correct_tags[]
3596
                            $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 3562 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...
3597
                            $j++;
3598
                            $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 3598 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
                        }
3600
                        $answer = '';
3601
                        $real_correct_tags = $correct_tags;
3602
                        $chosen_list = array();
3603
3604
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3605
                            if ($i == 0) {
3606
                                $answer .= $real_text[0];
3607
                            }
3608
                            if (!$switchable_answer_set) {
3609
                                // Needed to parse ' and " characters
3610
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3611 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3612
                                    // gives the related weighting to the student
3613
                                    $questionScore += $answerWeighting[$i];
3614
                                    // increments total score
3615
                                    $totalScore += $answerWeighting[$i];
3616
                                    // adds the word in green at the end of the string
3617
                                    $answer .= $correct_tags[$i];
3618
                                } elseif (!empty($user_tags[$i])) {
3619
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3620
                                    // adds the word in red at the end of the string, and strikes it
3621
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3622
                                } else {
3623
                                    // adds a tabulation if no word has been typed by the student
3624
                                    $answer .= ''; // remove &nbsp; that causes issue
3625
                                }
3626
                            } else {
3627
                                // switchable fill in the blanks
3628
                                if (in_array($user_tags[$i], $correct_tags)) {
3629
                                    $chosen_list[] = $user_tags[$i];
3630
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3631
                                    // gives the related weighting to the student
3632
                                    $questionScore += $answerWeighting[$i];
3633
                                    // increments total score
3634
                                    $totalScore += $answerWeighting[$i];
3635
                                    // adds the word in green at the end of the string
3636
                                    $answer .= $user_tags[$i];
3637
                                } elseif (!empty ($user_tags[$i])) {
3638
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3639
                                    // adds the word in red at the end of the string, and strikes it
3640
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3641
                                } else {
3642
                                    // adds a tabulation if no word has been typed by the student
3643
                                    $answer .= '';  // remove &nbsp; that causes issue
3644
                                }
3645
                            }
3646
3647
                            // adds the correct word, followed by ] to close the blank
3648
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3649
                            if (isset($real_text[$i +1])) {
3650
                                $answer .= $real_text[$i +1];
3651
                            }
3652
                        }
3653
                    } else {
3654
                        // insert the student result in the track_e_attempt table, field answer
3655
                        // $answer is the answer like in the c_quiz_answer table for the question
3656
                        // student data are choice[]
3657
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3658
                            $answer
3659
                        );
3660
                        $switchableAnswerSet = $listCorrectAnswers["switchable"];
3661
                        $answerWeighting = $listCorrectAnswers["tabweighting"];
3662
                        // user choices is an array $choice
3663
3664
                        // get existing user data in n the BDD
3665
                        if ($from_database) {
3666
                            $sql = "SELECT answer
3667
                                    FROM $TBL_TRACK_ATTEMPT
3668
                                    WHERE
3669
                                        exe_id = $exeId AND
3670
                                        question_id= ".intval($questionId);
3671
                            $result = Database::query($sql);
3672
                            $str = Database::result($result, 0, 'answer');
3673
                            $listStudentResults = FillBlanks::getAnswerInfo(
3674
                                $str,
3675
                                true
3676
                            );
3677
                            $choice = $listStudentResults['studentanswer'];
3678
                        }
3679
3680
                        // loop other all blanks words
3681
                        if (!$switchableAnswerSet) {
3682
                            // not switchable answer, must be in the same place than teacher order
3683
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3684
                                $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
3685
3686
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3687
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3688
                                if (!$from_database) {
3689
                                    $studentAnswer = htmlentities(
3690
                                        api_utf8_encode($studentAnswer)
3691
                                    );
3692
                                }
3693
3694
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3695
                                $isAnswerCorrect = 0;
3696 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer(
3697
                                    $studentAnswer,
3698
                                    $correctAnswer
3699
                                )
3700
                                ) {
3701
                                    // gives the related weighting to the student
3702
                                    $questionScore += $answerWeighting[$i];
3703
                                    // increments total score
3704
                                    $totalScore += $answerWeighting[$i];
3705
                                    $isAnswerCorrect = 1;
3706
                                }
3707
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3708
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3709
                            }
3710
                        } else {
3711
                            // switchable answer
3712
                            $listStudentAnswerTemp = $choice;
3713
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3714
                            // for every teacher answer, check if there is a student answer
3715
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3716
                                $studentAnswer = trim(
3717
                                    $listStudentAnswerTemp[$i]
3718
                                );
3719
                                $found = false;
3720
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3721
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3722
                                    if (!$found) {
3723 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3724
                                            $studentAnswer,
3725
                                            $correctAnswer
3726
                                        )
3727
                                        ) {
3728
                                            $questionScore += $answerWeighting[$i];
3729
                                            $totalScore += $answerWeighting[$i];
3730
                                            $listTeacherAnswerTemp[$j] = "";
3731
                                            $found = true;
3732
                                        }
3733
                                    }
3734
                                }
3735
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3736
                                if (!$found) {
3737
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3738
                                } else {
3739
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3740
                                }
3741
                            }
3742
                        }
3743
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3744
                            $listCorrectAnswers
3745
                        );
3746
                    }
3747
                    break;
3748
                case CALCULATED_ANSWER:
3749
                    $calculatedAnswerId = Session::read('calculatedAnswerId');
3750
                    $answer = '';
3751
                    if ($calculatedAnswerId) {
3752
                        $calculatedAnswerInfo = Session::read('calculatedAnswerInfo');
3753
                        if (isset($calculatedAnswerInfo[$questionId])) {
3754
                            $answer = $calculatedAnswerInfo[$questionId];
3755
                        } else {
3756
                            $answer = $objAnswerTmp->selectAnswer($calculatedAnswerId[$questionId]);
3757
                        }
3758
                    }
3759
                    $preArray = explode('@@', $answer);
3760
                    $last = count($preArray) - 1;
3761
                    $answer = '';
3762
                    for ($k = 0; $k < $last; $k++) {
3763
                        $answer .= $preArray[$k];
3764
                    }
3765
                    $answerWeighting = array($answerWeighting);
3766
                    // we save the answer because it will be modified
3767
                    $temp = $answer;
3768
                    $answer = '';
3769
                    $j = 0;
3770
                    //initialise answer tags
3771
                    $userTags = $correctTags = $realText = array();
3772
                    // the loop will stop at the end of the text
3773 View Code Duplication
                    while (1) {
3774
                        // quits the loop if there are no more blanks (detect '[')
3775
                        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 3820 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...
3776
                            // adds the end of the text
3777
                            $answer = $temp;
3778
                            $realText[] = $answer;
3779
                            break; //no more "blanks", quit the loop
3780
                        }
3781
                        // adds the piece of text that is before the blank
3782
                        //and ends with '[' into a general storage array
3783
                        $realText[] = api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3820 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...
3784
                        $answer .= api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3820 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...
3785
                        //take the string remaining (after the last "[" we found)
3786
                        $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 3786 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...
3787
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3788
                        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 3786 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...
3789
                            // adds the end of the text
3790
                            $answer .= $temp;
3791
                            break;
3792
                        }
3793
                        if ($from_database) {
3794
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3795
                                          WHERE
3796
                                            exe_id = '".$exeId."' AND
3797
                                            question_id= ".intval($questionId)."";
3798
                            $resfill = Database::query($queryfill);
3799
                            $str = Database::result($resfill, 0, 'answer');
3800
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3801
                            $str = str_replace('\r\n', '', $str);
3802
                            $choice = $arr[1];
3803
                            if (isset($choice[$j])) {
3804
                                $tmp = api_strrpos($choice[$j], ' / ');
3805
                                $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 3804 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...
3806
                                $choice[$j] = trim($choice[$j]);
3807
                                // Needed to let characters ' and " to work as part of an answer
3808
                                $choice[$j] = stripslashes($choice[$j]);
3809
                            } else {
3810
                                $choice[$j] = null;
3811
                            }
3812
                        } else {
3813
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3814
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3815
                        }
3816
                        $userTags[] = $choice[$j];
3817
                        //put the contents of the [] answer tag into correct_tags[]
3818
                        $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 3786 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...
3819
                        $j++;
3820
                        $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 3820 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...
3821
                    }
3822
                    $answer = '';
3823
                    $realCorrectTags = $correctTags;
3824
3825
                    if ($from_database && empty($calculatedAnswerId)) {
3826
                        $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3827
                                      WHERE
3828
                                        exe_id = '".$exeId."' AND
3829
                                        question_id= ".intval($questionId)  ;
3830
                        $resfill = Database::query($queryfill);
3831
                        $rowFill = Database::fetch_assoc($resfill);
3832
                        $answer = $rowFill['answer'];
3833
                        $questionScore = $rowFill['marks'];
3834
                    }
3835
3836
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3837
                        if ($i == 0) {
3838
                            $answer .= $realText[0];
3839
                        }
3840
                        // Needed to parse ' and " characters
3841
                        $userTags[$i] = stripslashes($userTags[$i]);
3842 View Code Duplication
                        if ($correctTags[$i] == $userTags[$i]) {
3843
                            // gives the related weighting to the student
3844
                            $questionScore += $answerWeighting[$i];
3845
                            // increments total score
3846
                            $totalScore += $answerWeighting[$i];
3847
                            // adds the word in green at the end of the string
3848
                            $answer .= $correctTags[$i];
3849
                        } elseif (!empty($userTags[$i])) {
3850
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3851
                            // adds the word in red at the end of the string, and strikes it
3852
                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
3853
                        } else {
3854
                            // adds a tabulation if no word has been typed by the student
3855
                            $answer .= ''; // remove &nbsp; that causes issue
3856
                        }
3857
                        // adds the correct word, followed by ] to close the blank
3858
                        $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>]';
3859
                        if (isset($realText[$i +1])) {
3860
                            $answer .= $realText[$i +1];
3861
                        }
3862
                    }
3863
                    break;
3864
                case FREE_ANSWER:
3865
                    if ($from_database) {
3866
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3867
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3868
                        $resq = Database::query($query);
3869
                        $data = Database::fetch_array($resq);
3870
3871
                        $choice = $data['answer'];
3872
                        $choice = str_replace('\r\n', '', $choice);
3873
                        $choice = stripslashes($choice);
3874
                        $questionScore = $data['marks'];
3875
3876
                        if ($questionScore == -1) {
3877
                            $totalScore+= 0;
3878
                        } else {
3879
                            $totalScore+= $questionScore;
3880
                        }
3881
                        if ($questionScore == '') {
3882
                            $questionScore = 0;
3883
                        }
3884
                        $arrques = $questionName;
3885
                        $arrans = $choice;
3886
                    } else {
3887
                        $studentChoice = $choice;
3888
                        if ($studentChoice) {
3889
                            //Fixing negative puntation see #2193
3890
                            $questionScore = 0;
3891
                            $totalScore += 0;
3892
                        }
3893
                    }
3894
                    break;
3895
                case ORAL_EXPRESSION:
3896
                    if ($from_database) {
3897
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3898
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3899
                        $resq   = Database::query($query);
3900
                        $choice = Database::result($resq,0,'answer');
3901
                        $choice = str_replace('\r\n', '', $choice);
3902
                        $choice = stripslashes($choice);
3903
                        $questionScore = Database::result($resq,0,"marks");
3904
                        if ($questionScore == -1) {
3905
                            $totalScore+=0;
3906
                        } else {
3907
                            $totalScore+=$questionScore;
3908
                        }
3909
                        $arrques = $questionName;
3910
                        $arrans  = $choice;
3911
                    } else {
3912
                        $studentChoice = $choice;
3913
                        if ($studentChoice) {
3914
                            //Fixing negative puntation see #2193
3915
                            $questionScore = 0;
3916
                            $totalScore += 0;
3917
                        }
3918
                    }
3919
                    break;
3920
                case DRAGGABLE:
3921
                    //no break
3922
                case MATCHING_DRAGGABLE:
3923
                    //no break
3924
                case MATCHING:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
3925
                    if ($from_database) {
3926
                        $sql = 'SELECT id, answer, id_auto
3927
                                FROM '.$table_ans.'
3928
                                WHERE
3929
                                    c_id = '.$course_id.' AND
3930
                                    question_id = "'.$questionId.'" AND
3931
                                    correct = 0';
3932
                        $res_answer = Database::query($sql);
3933
                        // Getting the real answer
3934
                        $real_list = array();
3935
                        while ($real_answer = Database::fetch_array($res_answer)) {
3936
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3937
                        }
3938
3939
                        $sql = 'SELECT id, answer, correct, id_auto, ponderation
3940
                                FROM '.$table_ans.'
3941
                                WHERE
3942
                                    c_id = '.$course_id.' AND
3943
                                    question_id="'.$questionId.'" AND
3944
                                    correct <> 0
3945
                                ORDER BY id_auto';
3946
                        $res_answers = Database::query($sql);
3947
3948
                        $questionScore = 0;
3949
3950
                        while ($a_answers = Database::fetch_array($res_answers)) {
3951
                            $i_answer_id = $a_answers['id']; //3
3952
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3953
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3954
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3955
3956
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3957
                                    WHERE
3958
                                        exe_id = '$exeId' AND
3959
                                        question_id = '$questionId' AND
3960
                                        position = '$i_answer_id_auto'";
3961
3962
                            $res_user_answer = Database::query($sql);
3963
3964
                            if (Database::num_rows($res_user_answer) > 0) {
3965
                                //  rich - good looking
3966
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3967
                            } else {
3968
                                $s_user_answer = 0;
3969
                            }
3970
3971
                            $i_answerWeighting = $a_answers['ponderation'];
3972
3973
                            $user_answer = '';
3974
                            if (!empty($s_user_answer)) {
3975
                                if ($answerType == DRAGGABLE) {
3976
                                    if ($s_user_answer == $i_answer_correct_answer) {
3977
                                        $questionScore += $i_answerWeighting;
3978
                                        $totalScore += $i_answerWeighting;
3979
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3980
                                    } else {
3981
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3982
                                    }
3983
                                } else {
3984
                                    if ($s_user_answer == $i_answer_correct_answer) {
3985
                                        $questionScore += $i_answerWeighting;
3986
                                        $totalScore += $i_answerWeighting;
3987
3988
                                        if (isset($real_list[$i_answer_id])) {
3989
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3990
                                        }
3991
                                    } else {
3992
                                        $user_answer = Display::span(
3993
                                            $real_list[$s_user_answer],
3994
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3995
                                        );
3996
                                    }
3997
                                }
3998
                            } elseif ($answerType == DRAGGABLE) {
3999
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4000
                            }
4001
4002
                            if ($show_result) {
4003
                                if ($showTotalScoreAndUserChoices == true) {
4004
                                    $user_answer = '';
4005
                                }
4006
                                echo '<tr>';
4007
                                echo '<td>' . $s_answer_label . '</td>';
4008
                                echo '<td>' . $user_answer;
4009
4010
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4011
                                    if (isset($real_list[$i_answer_correct_answer]) && $showTotalScoreAndUserChoices == false) {
4012
                                        echo Display::span(
4013
                                            $real_list[$i_answer_correct_answer],
4014
                                            ['style' => 'color: #008000; font-weight: bold;']
4015
                                        );
4016
                                    }
4017
                                }
4018
                                echo '</td>';
4019
                                echo '</tr>';
4020
                            }
4021
                        }
4022
                        break(2); // break the switch and the "for" condition
4023
                    } else {
4024
                        if ($answerCorrect) {
4025
                            if (isset($choice[$answerAutoId]) &&
4026
                                $answerCorrect == $choice[$answerAutoId]
4027
                            ) {
4028
                                $questionScore += $answerWeighting;
4029
                                $totalScore += $answerWeighting;
4030
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4031
                            } else {
4032
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4033
                                    $user_answer = Display::span(
4034
                                        $answerMatching[$choice[$answerAutoId]],
4035
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4036
                                    );
4037
                                }
4038
                            }
4039
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4040
                        }
4041
                        break;
4042
                    }
4043
                case HOT_SPOT:
4044
                    if ($from_database) {
4045
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4046
                        $sql = "SELECT hotspot_correct
4047
                                FROM $TBL_TRACK_HOTSPOT
4048
                                WHERE
4049
                                    hotspot_exe_id = '".$exeId."' AND
4050
                                    hotspot_question_id= '".$questionId."' AND
4051
                                    hotspot_answer_id = ".intval($answerAutoId)."";
4052
                        $result = Database::query($sql);
4053
                        $studentChoice = Database::result($result, 0, "hotspot_correct");
4054
4055
                        if ($studentChoice) {
4056
                            $questionScore += $answerWeighting;
4057
                            $totalScore += $answerWeighting;
4058
                        }
4059
                    } else {
4060
                        if (!isset($choice[$answerAutoId])) {
4061
                            $choice[$answerAutoId] = 0;
4062
                        } else {
4063
                            $studentChoice = $choice[$answerAutoId];
4064
4065
                            $choiceIsValid = false;
4066
4067
                            if (!empty($studentChoice)) {
4068
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4069
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4070
                                $choicePoint = Geometry::decodePoint($studentChoice);
4071
4072
                                switch ($hotspotType) {
4073
                                    case 'square':
4074
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4075
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4076
                                        break;
4077
4078
                                    case 'circle':
4079
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4080
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4081
                                        break;
4082
4083
                                    case 'poly':
4084
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4085
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4086
                                        break;
4087
                                }
4088
                            }
4089
4090
                            $choice[$answerAutoId] = 0;
4091
                            if ($choiceIsValid) {
4092
                                $questionScore += $answerWeighting;
4093
                                $totalScore += $answerWeighting;
4094
                                $choice[$answerAutoId] = 1;
4095
                            }
4096
                        }
4097
                    }
4098
                    break;
4099
                // @todo never added to chamilo
4100
                //for hotspot with fixed order
4101
                case HOT_SPOT_ORDER :
4102
                    $studentChoice = $choice['order'][$answerId];
4103
                    if ($studentChoice == $answerId) {
4104
                        $questionScore  += $answerWeighting;
4105
                        $totalScore     += $answerWeighting;
4106
                        $studentChoice = true;
4107
                    } else {
4108
                        $studentChoice = false;
4109
                    }
4110
                    break;
4111
                // for hotspot with delineation
4112
                case HOT_SPOT_DELINEATION :
4113
                    if ($from_database) {
4114
                        // getting the user answer
4115
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4116
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4117
                                    FROM $TBL_TRACK_HOTSPOT
4118
                                    WHERE
4119
                                        hotspot_exe_id = '".$exeId."' AND
4120
                                        hotspot_question_id= '".$questionId."' AND
4121
                                        hotspot_answer_id='1'";
4122
                        //by default we take 1 because it's a delineation
4123
                        $resq = Database::query($query);
4124
                        $row = Database::fetch_array($resq,'ASSOC');
4125
4126
                        $choice = $row['hotspot_correct'];
4127
                        $user_answer = $row['hotspot_coordinate'];
4128
4129
                        // THIS is very important otherwise the poly_compile will throw an error!!
4130
                        // round-up the coordinates
4131
                        $coords = explode('/',$user_answer);
4132
                        $user_array = '';
4133 View Code Duplication
                        foreach ($coords as $coord) {
4134
                            list($x,$y) = explode(';',$coord);
4135
                            $user_array .= round($x).';'.round($y).'/';
4136
                        }
4137
                        $user_array = substr($user_array,0,-1);
4138
                    } else {
4139
                        if (!empty($studentChoice)) {
4140
                            $newquestionList[] = $questionId;
4141
                        }
4142
4143
                        if ($answerId === 1) {
4144
                            $studentChoice = $choice[$answerId];
4145
                            $questionScore += $answerWeighting;
4146
4147
                            if ($hotspot_delineation_result[1]==1) {
4148
                                $totalScore += $answerWeighting; //adding the total
4149
                            }
4150
                        }
4151
                    }
4152
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4153
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4154
                    break;
4155
            } // end switch Answertype
4156
4157
            if ($show_result) {
4158
                if ($debug) error_log('Showing questions $from '.$from);
4159
                if ($from == 'exercise_result') {
4160
                    // display answers (if not matching type, or if the answer is correct)
4161
                    if (
4162
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4163
                        $answerCorrect
4164
                    ) {
4165
                        if (
4166
                            in_array(
4167
                                $answerType,
4168
                                array(
4169
                                    UNIQUE_ANSWER,
4170
                                    UNIQUE_ANSWER_IMAGE,
4171
                                    UNIQUE_ANSWER_NO_OPTION,
4172
                                    MULTIPLE_ANSWER,
4173
                                    MULTIPLE_ANSWER_COMBINATION,
4174
                                    GLOBAL_MULTIPLE_ANSWER
4175
                                )
4176
                            )
4177
                        ) {
4178
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4179
                                $feedback_type,
4180
                                $answerType,
4181
                                $studentChoice,
4182
                                $answer,
4183
                                $answerComment,
4184
                                $answerCorrect,
4185
                                0,
4186
                                0,
4187
                                0,
4188
                                $results_disabled,
4189
                                $showTotalScoreAndUserChoices
4190
                            );
4191
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4192
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4193
                                $feedback_type,
4194
                                $answerType,
4195
                                $studentChoice,
4196
                                $answer,
4197
                                $answerComment,
4198
                                $answerCorrect,
4199
                                0,
4200
                                $questionId,
4201
                                0,
4202
                                $results_disabled,
4203
                                $showTotalScoreAndUserChoices
4204
                            );
4205
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4206
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4207
                                $feedback_type,
4208
                                $answerType,
4209
                                $studentChoice,
4210
                                $answer,
4211
                                $answerComment,
4212
                                $answerCorrect,
4213
                                0,
4214
                                0,
4215
                                0,
4216
                                $results_disabled,
4217
                                $showTotalScoreAndUserChoices
4218
                            );
4219
                        } elseif ($answerType == FILL_IN_BLANKS) {
4220
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4221
                                $feedback_type,
4222
                                $answer,
4223
                                0,
4224
                                0,
4225
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4226
                                '',
4227
                                $showTotalScoreAndUserChoices
4228
                            );
4229
                        } elseif ($answerType == CALCULATED_ANSWER) {
4230
                            ExerciseShowFunctions::display_calculated_answer(
4231
                                $feedback_type,
4232
                                $answer,
4233
                                0,
4234
                                0,
4235
                                $results_disabled,
4236
                                $showTotalScoreAndUserChoices
4237
                            );
4238
                        } elseif ($answerType == FREE_ANSWER) {
4239
                            ExerciseShowFunctions::display_free_answer(
4240
                                $feedback_type,
4241
                                $choice,
4242
                                $exeId,
4243
                                $questionId,
4244
                                $questionScore,
4245
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4246
                            );
4247
                        } elseif ($answerType == ORAL_EXPRESSION) {
4248
                            // to store the details of open questions in an array to be used in mail
4249
                            ExerciseShowFunctions::display_oral_expression_answer(
4250
                                $feedback_type,
4251
                                $choice,
4252
                                0,
4253
                                0,
4254
                                $nano,
0 ignored issues
show
Bug introduced by
It seems like $nano defined by new \Nanogong($params) on line 3251 can also be of type object<Nanogong>; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept 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...
4255
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4256
                            );
4257
                        } elseif ($answerType == HOT_SPOT) {
4258
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4259
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4260
                                    break;
4261
                                }
4262
                            }
4263
4264
                            ExerciseShowFunctions::display_hotspot_answer(
4265
                                $feedback_type,
4266
                                ++$correctAnswerId,
4267
                                $answer,
4268
                                $studentChoice,
4269
                                $answerComment,
4270
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4271
                                $answerId,
4272
                                $showTotalScoreAndUserChoices
4273
                            );
4274
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4275
                            ExerciseShowFunctions::display_hotspot_order_answer(
4276
                                $feedback_type,
4277
                                $answerId,
4278
                                $answer,
4279
                                $studentChoice,
4280
                                $answerComment
4281
                            );
4282
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4283
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4284
4285
                            //round-up the coordinates
4286
                            $coords = explode('/',$user_answer);
4287
                            $user_array = '';
4288 View Code Duplication
                            foreach ($coords as $coord) {
4289
                                list($x,$y) = explode(';',$coord);
4290
                                $user_array .= round($x).';'.round($y).'/';
4291
                            }
4292
                            $user_array = substr($user_array,0,-1);
4293
4294 View Code Duplication
                            if ($next) {
4295
4296
                                $user_answer = $user_array;
4297
4298
                                // we compare only the delineation not the other points
4299
                                $answer_question = $_SESSION['hotspot_coord'][1];
4300
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4301
4302
                                //calculating the area
4303
                                $poly_user = convert_coordinates($user_answer, '/');
4304
                                $poly_answer = convert_coordinates($answer_question, '|');
4305
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4306
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4307
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4308
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4309
4310
                                $overlap = $poly_results['both'];
4311
                                $poly_answer_area = $poly_results['s1'];
4312
                                $poly_user_area = $poly_results['s2'];
4313
                                $missing = $poly_results['s1Only'];
4314
                                $excess = $poly_results['s2Only'];
4315
4316
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4317
                                // //this is an area in pixels
4318
                                if ($debug > 0) {
4319
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4320
                                }
4321
4322
                                if ($overlap < 1) {
4323
                                    //shortcut to avoid complicated calculations
4324
                                    $final_overlap = 0;
4325
                                    $final_missing = 100;
4326
                                    $final_excess = 100;
4327
                                } else {
4328
                                    // the final overlap is the percentage of the initial polygon
4329
                                    // that is overlapped by the user's polygon
4330
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4331
                                    if ($debug > 1) {
4332
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4333
                                    }
4334
                                    // the final missing area is the percentage of the initial polygon
4335
                                    // that is not overlapped by the user's polygon
4336
                                    $final_missing = 100 - $final_overlap;
4337
                                    if ($debug > 1) {
4338
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4339
                                    }
4340
                                    // the final excess area is the percentage of the initial polygon's size
4341
                                    // that is covered by the user's polygon outside of the initial polygon
4342
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4343
                                    if ($debug > 1) {
4344
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4345
                                    }
4346
                                }
4347
4348
                                //checking the destination parameters parsing the "@@"
4349
                                $destination_items= explode('@@', $answerDestination);
4350
                                $threadhold_total = $destination_items[0];
4351
                                $threadhold_items=explode(';',$threadhold_total);
4352
                                $threadhold1 = $threadhold_items[0]; // overlap
4353
                                $threadhold2 = $threadhold_items[1]; // excess
4354
                                $threadhold3 = $threadhold_items[2];	 //missing
4355
4356
                                // if is delineation
4357
                                if ($answerId===1) {
4358
                                    //setting colors
4359
                                    if ($final_overlap>=$threadhold1) {
4360
                                        $overlap_color=true; //echo 'a';
4361
                                    }
4362
                                    //echo $excess.'-'.$threadhold2;
4363
                                    if ($final_excess<=$threadhold2) {
4364
                                        $excess_color=true; //echo 'b';
4365
                                    }
4366
                                    //echo '--------'.$missing.'-'.$threadhold3;
4367
                                    if ($final_missing<=$threadhold3) {
4368
                                        $missing_color=true; //echo 'c';
4369
                                    }
4370
4371
                                    // if pass
4372
                                    if (
4373
                                        $final_overlap >= $threadhold1 &&
4374
                                        $final_missing <= $threadhold3 &&
4375
                                        $final_excess <= $threadhold2
4376
                                    ) {
4377
                                        $next=1; //go to the oars
4378
                                        $result_comment=get_lang('Acceptable');
4379
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4380
                                    } else {
4381
                                        $next=0;
4382
                                        $result_comment=get_lang('Unacceptable');
4383
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4384
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4385
                                        //checking the destination parameters parsing the "@@"
4386
                                        $destination_items= explode('@@', $answerDestination);
4387
                                    }
4388
                                } elseif($answerId>1) {
4389
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4390
                                        if ($debug>0) {
4391
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4392
                                        }
4393
                                        //type no error shouldn't be treated
4394
                                        $next = 1;
4395
                                        continue;
4396
                                    }
4397
                                    if ($debug>0) {
4398
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4399
                                    }
4400
                                    //check the intersection between the oar and the user
4401
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4402
                                    //echo 'official';print_r($x_list);print_r($y_list);
4403
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4404
                                    $inter= $result['success'];
4405
4406
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4407
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4408
4409
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4410
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4411
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4412
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4413
4414
                                    if ($overlap == false) {
4415
                                        //all good, no overlap
4416
                                        $next = 1;
4417
                                        continue;
4418
                                    } else {
4419
                                        if ($debug>0) {
4420
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4421
                                        }
4422
                                        $organs_at_risk_hit++;
4423
                                        //show the feedback
4424
                                        $next=0;
4425
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4426
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4427
4428
                                        $destination_items= explode('@@', $answerDestination);
4429
                                        $try_hotspot=$destination_items[1];
4430
                                        $lp_hotspot=$destination_items[2];
4431
                                        $select_question_hotspot=$destination_items[3];
4432
                                        $url_hotspot=$destination_items[4];
4433
                                    }
4434
                                }
4435
                            } else {	// the first delineation feedback
4436
                                if ($debug>0) {
4437
                                    error_log(__LINE__.' first',0);
4438
                                }
4439
                            }
4440
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4441
                            echo '<tr>';
4442
                            echo Display::tag('td', $answerMatching[$answerId]);
4443
                            echo Display::tag(
4444
                                'td',
4445
                                "$user_answer / " . Display::tag(
4446
                                    'strong',
4447
                                    $answerMatching[$answerCorrect],
4448
                                    ['style' => 'color: #008000; font-weight: bold;']
4449
                                )
4450
                            );
4451
                            echo '</tr>';
4452
                        }
4453
                    }
4454
                } else {
4455
                    if ($debug) error_log('Showing questions $from '.$from);
4456
4457
                    switch ($answerType) {
4458
                        case UNIQUE_ANSWER:
4459
                        case UNIQUE_ANSWER_IMAGE:
4460
                        case UNIQUE_ANSWER_NO_OPTION:
4461
                        case MULTIPLE_ANSWER:
4462
                        case GLOBAL_MULTIPLE_ANSWER :
4463 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4464
                            if ($answerId == 1) {
4465
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4466
                                    $feedback_type,
4467
                                    $answerType,
4468
                                    $studentChoice,
4469
                                    $answer,
4470
                                    $answerComment,
4471
                                    $answerCorrect,
4472
                                    $exeId,
4473
                                    $questionId,
4474
                                    $answerId,
4475
                                    $results_disabled,
4476
                                    $showTotalScoreAndUserChoices
4477
                                );
4478
                            } else {
4479
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4480
                                    $feedback_type,
4481
                                    $answerType,
4482
                                    $studentChoice,
4483
                                    $answer,
4484
                                    $answerComment,
4485
                                    $answerCorrect,
4486
                                    $exeId,
4487
                                    $questionId,
4488
                                    '',
4489
                                    $results_disabled,
4490
                                    $showTotalScoreAndUserChoices
4491
                                );
4492
                            }
4493
                            break;
4494 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4495
                            if ($answerId == 1) {
4496
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4497
                                    $feedback_type,
4498
                                    $answerType,
4499
                                    $studentChoice,
4500
                                    $answer,
4501
                                    $answerComment,
4502
                                    $answerCorrect,
4503
                                    $exeId,
4504
                                    $questionId,
4505
                                    $answerId,
4506
                                    $results_disabled,
4507
                                    $showTotalScoreAndUserChoices
4508
                                );
4509
                            } else {
4510
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4511
                                    $feedback_type,
4512
                                    $answerType,
4513
                                    $studentChoice,
4514
                                    $answer,
4515
                                    $answerComment,
4516
                                    $answerCorrect,
4517
                                    $exeId,
4518
                                    $questionId,
4519
                                    '',
4520
                                    $results_disabled,
4521
                                    $showTotalScoreAndUserChoices
4522
                                );
4523
                            }
4524
                            break;
4525 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4526
                            if ($answerId == 1) {
4527
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4528
                                    $feedback_type,
4529
                                    $answerType,
4530
                                    $studentChoice,
4531
                                    $answer,
4532
                                    $answerComment,
4533
                                    $answerCorrect,
4534
                                    $exeId,
4535
                                    $questionId,
4536
                                    $answerId,
4537
                                    $results_disabled,
4538
                                    $showTotalScoreAndUserChoices
4539
                                );
4540
                            } else {
4541
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4542
                                    $feedback_type,
4543
                                    $answerType,
4544
                                    $studentChoice,
4545
                                    $answer,
4546
                                    $answerComment,
4547
                                    $answerCorrect,
4548
                                    $exeId,
4549
                                    $questionId,
4550
                                    '',
4551
                                    $results_disabled,
4552
                                    $showTotalScoreAndUserChoices
4553
                                );
4554
                            }
4555
                            break;
4556
                        case FILL_IN_BLANKS:
4557
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4558
                                $feedback_type,
4559
                                $answer,
4560
                                $exeId,
4561
                                $questionId,
4562
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4563
                                $str,
4564
                                $showTotalScoreAndUserChoices
4565
                            );
4566
                            break;
4567
                        case CALCULATED_ANSWER:
4568
                            ExerciseShowFunctions::display_calculated_answer(
4569
                                $feedback_type,
4570
                                $answer,
4571
                                $exeId,
4572
                                $questionId,
4573
                                $results_disabled,
4574
                                '',
4575
                                $showTotalScoreAndUserChoices
4576
                            );
4577
                            break;
4578
                        case FREE_ANSWER:
4579
                            echo ExerciseShowFunctions::display_free_answer(
4580
                                $feedback_type,
4581
                                $choice,
4582
                                $exeId,
4583
                                $questionId,
4584
                                $questionScore,
4585
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4586
                            );
4587
                            break;
4588
                        case ORAL_EXPRESSION:
4589
                            echo '<tr>
4590
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4591
                                    $feedback_type,
4592
                                    $choice,
4593
                                    $exeId,
4594
                                    $questionId,
4595
                                    $nano,
0 ignored issues
show
Bug introduced by
It seems like $nano defined by new \Nanogong($params) on line 3251 can also be of type object<Nanogong>; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept 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...
4596
                                    $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4597
                                ) . '</td>
4598
                                </tr>
4599
                                </table>';
4600
                            break;
4601
                        case HOT_SPOT:
4602
                            ExerciseShowFunctions::display_hotspot_answer(
0 ignored issues
show
Bug introduced by
The call to display_hotspot_answer() misses a required argument $showTotalScoreAndUserChoices.

This check looks for function calls that miss required arguments.

Loading history...
4603
                                $feedback_type,
4604
                                $answerId,
4605
                                $answer,
4606
                                $studentChoice,
4607
                                $answerComment,
4608
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3151 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...
4609
                                $answerId
4610
                            );
4611
                            break;
4612
                        case HOT_SPOT_DELINEATION:
4613
                            $user_answer = $user_array;
4614 View Code Duplication
                            if ($next) {
4615
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4616
                                // Save into db
4617
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4618
                                 * hotspot_user_id,
4619
                                 *  hotspot_course_code,
4620
                                 *  hotspot_exe_id,
4621
                                 *  hotspot_question_id,
4622
                                 *  hotspot_answer_id,
4623
                                 *  hotspot_correct,
4624
                                 *  hotspot_coordinate
4625
                                 *  )
4626
                                VALUES (
4627
                                 * '".Database::escape_string($_user['user_id'])."',
4628
                                 *  '".Database::escape_string($_course['id'])."',
4629
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4630
                                 *  '".Database::escape_string($answerId)."',
4631
                                 *  '".Database::escape_string($studentChoice)."',
4632
                                 *  '".Database::escape_string($user_array)."')";
4633
                                $result = Database::query($sql,__FILE__,__LINE__);
4634
                                 */
4635
                                $user_answer = $user_array;
4636
4637
                                // we compare only the delineation not the other points
4638
                                $answer_question = $_SESSION['hotspot_coord'][1];
4639
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4640
4641
                                //calculating the area
4642
                                $poly_user = convert_coordinates($user_answer, '/');
4643
                                $poly_answer = convert_coordinates($answer_question, '|');
4644
4645
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4646
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4647
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4648
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4649
4650
                                $overlap = $poly_results['both'];
4651
                                $poly_answer_area = $poly_results['s1'];
4652
                                $poly_user_area = $poly_results['s2'];
4653
                                $missing = $poly_results['s1Only'];
4654
                                $excess = $poly_results['s2Only'];
4655
4656
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
4657
                                if ($debug > 0) {
4658
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4659
                                }
4660
                                if ($overlap < 1) {
4661
                                    //shortcut to avoid complicated calculations
4662
                                    $final_overlap = 0;
4663
                                    $final_missing = 100;
4664
                                    $final_excess = 100;
4665
                                } else {
4666
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4667
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4668
                                    if ($debug > 1) {
4669
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4670
                                    }
4671
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4672
                                    $final_missing = 100 - $final_overlap;
4673
                                    if ($debug > 1) {
4674
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4675
                                    }
4676
                                    // 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
4677
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4678
                                    if ($debug > 1) {
4679
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4680
                                    }
4681
                                }
4682
4683
                                //checking the destination parameters parsing the "@@"
4684
                                $destination_items = explode('@@', $answerDestination);
4685
                                $threadhold_total = $destination_items[0];
4686
                                $threadhold_items = explode(';', $threadhold_total);
4687
                                $threadhold1 = $threadhold_items[0]; // overlap
4688
                                $threadhold2 = $threadhold_items[1]; // excess
4689
                                $threadhold3 = $threadhold_items[2];  //missing
4690
                                // if is delineation
4691
                                if ($answerId === 1) {
4692
                                    //setting colors
4693
                                    if ($final_overlap >= $threadhold1) {
4694
                                        $overlap_color = true; //echo 'a';
4695
                                    }
4696
                                    //echo $excess.'-'.$threadhold2;
4697
                                    if ($final_excess <= $threadhold2) {
4698
                                        $excess_color = true; //echo 'b';
4699
                                    }
4700
                                    //echo '--------'.$missing.'-'.$threadhold3;
4701
                                    if ($final_missing <= $threadhold3) {
4702
                                        $missing_color = true; //echo 'c';
4703
                                    }
4704
4705
                                    // if pass
4706
                                    if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
4707
                                        $next = 1; //go to the oars
4708
                                        $result_comment = get_lang('Acceptable');
4709
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4710
                                    } else {
4711
                                        $next = 0;
4712
                                        $result_comment = get_lang('Unacceptable');
4713
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4714
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4715
                                        //checking the destination parameters parsing the "@@"
4716
                                        $destination_items = explode('@@', $answerDestination);
4717
                                    }
4718
                                } elseif ($answerId > 1) {
4719
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4720
                                        if ($debug > 0) {
4721
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4722
                                        }
4723
                                        //type no error shouldn't be treated
4724
                                        $next = 1;
4725
                                        continue;
4726
                                    }
4727
                                    if ($debug > 0) {
4728
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4729
                                    }
4730
                                    //check the intersection between the oar and the user
4731
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4732
                                    //echo 'official';print_r($x_list);print_r($y_list);
4733
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4734
                                    $inter = $result['success'];
4735
4736
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4737
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4738
4739
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4740
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4741
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4742
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4743
4744
                                    if ($overlap == false) {
4745
                                        //all good, no overlap
4746
                                        $next = 1;
4747
                                        continue;
4748
                                    } else {
4749
                                        if ($debug > 0) {
4750
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4751
                                        }
4752
                                        $organs_at_risk_hit++;
4753
                                        //show the feedback
4754
                                        $next = 0;
4755
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4756
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4757
4758
                                        $destination_items = explode('@@', $answerDestination);
4759
                                        $try_hotspot = $destination_items[1];
4760
                                        $lp_hotspot = $destination_items[2];
4761
                                        $select_question_hotspot = $destination_items[3];
4762
                                        $url_hotspot=$destination_items[4];
4763
                                    }
4764
                                }
4765
                            } else {	// the first delineation feedback
4766
                                if ($debug > 0) {
4767
                                    error_log(__LINE__ . ' first', 0);
4768
                                }
4769
                            }
4770
                            break;
4771
                        case HOT_SPOT_ORDER:
4772
                            ExerciseShowFunctions::display_hotspot_order_answer(
4773
                                $feedback_type,
4774
                                $answerId,
4775
                                $answer,
4776
                                $studentChoice,
4777
                                $answerComment
4778
                            );
4779
                            break;
4780
                        case DRAGGABLE:
4781
                            //no break
4782
                        case MATCHING_DRAGGABLE:
4783
                            //no break
4784
                        case MATCHING:
4785
                            echo '<tr>';
4786
                            echo Display::tag('td', $answerMatching[$answerId]);
4787
                            echo Display::tag(
4788
                                'td',
4789
                                "$user_answer / " . Display::tag(
4790
                                    'strong',
4791
                                    $answerMatching[$answerCorrect],
4792
                                    ['style' => 'color: #008000; font-weight: bold;']
4793
                                )
4794
                            );
4795
                            echo '</tr>';
4796
4797
                            break;
4798
                    }
4799
                }
4800
            }
4801
            if ($debug) error_log(' ------ ');
4802
        } // end for that loops over all answers of the current question
4803
4804
        if ($debug) error_log('-- end answer loop --');
4805
4806
        $final_answer = true;
4807
4808
        foreach ($real_answers as $my_answer) {
4809
            if (!$my_answer) {
4810
                $final_answer = false;
4811
            }
4812
        }
4813
4814
        //we add the total score after dealing with the answers
4815
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4816
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4817
        ) {
4818
            if ($final_answer) {
4819
                //getting only the first score where we save the weight of all the question
4820
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4821
                $questionScore += $answerWeighting;
4822
                $totalScore += $answerWeighting;
4823
            }
4824
        }
4825
4826
        //Fixes multiple answer question in order to be exact
4827
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4828
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4829
            $diff = @array_diff($answer_correct_array, $real_answers);
4830
4831
            // All good answers or nothing works like exact
4832
4833
            $counter = 1;
4834
            $correct_answer = true;
4835
            foreach ($real_answers as $my_answer) {
4836
                if ($debug)
4837
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4838
                if ($my_answer != $answer_correct_array[$counter]) {
4839
                    $correct_answer = false;
4840
                    break;
4841
                }
4842
                $counter++;
4843
            }
4844
4845
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4846
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4847
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4848
4849
            if ($correct_answer == false) {
4850
                $questionScore = 0;
4851
            }
4852
4853
            // This makes the result non exact
4854
            if (!empty($diff)) {
4855
                $questionScore = 0;
4856
            }
4857
        }*/
4858
4859
        $extra_data = array(
4860
            'final_overlap' => $final_overlap,
4861
            'final_missing'=>$final_missing,
4862
            'final_excess'=> $final_excess,
4863
            'overlap_color' => $overlap_color,
4864
            'missing_color'=>$missing_color,
4865
            'excess_color'=> $excess_color,
4866
            'threadhold1'   => $threadhold1,
4867
            'threadhold2'=>$threadhold2,
4868
            'threadhold3'=> $threadhold3,
4869
        );
4870
        if ($from == 'exercise_result') {
4871
            // if answer is hotspot. To the difference of exercise_show.php,
4872
            //  we use the results from the session (from_db=0)
4873
            // TODO Change this, because it is wrong to show the user
4874
            //  some results that haven't been stored in the database yet
4875
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4876
4877
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4878
4879
                $my_exe_id = 0;
4880
                $from_database = 0;
4881
                if ($answerType == HOT_SPOT_DELINEATION) {
4882
                    if (0) {
4883
                        if ($overlap_color) {
4884
                            $overlap_color='green';
4885
                        } else {
4886
                            $overlap_color='red';
4887
                        }
4888
                        if ($missing_color) {
4889
                            $missing_color='green';
4890
                        } else {
4891
                            $missing_color='red';
4892
                        }
4893
                        if ($excess_color) {
4894
                            $excess_color='green';
4895
                        } else {
4896
                            $excess_color='red';
4897
                        }
4898
                        if (!is_numeric($final_overlap)) {
4899
                            $final_overlap = 0;
4900
                        }
4901
                        if (!is_numeric($final_missing)) {
4902
                            $final_missing = 0;
4903
                        }
4904
                        if (!is_numeric($final_excess)) {
4905
                            $final_excess = 0;
4906
                        }
4907
4908
                        if ($final_overlap>100) {
4909
                            $final_overlap = 100;
4910
                        }
4911
4912
                        $table_resume='<table class="data_table">
4913
                                <tr class="row_odd" >
4914
                                    <td></td>
4915
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4916
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4917
                                </tr>
4918
                                <tr class="row_even">
4919
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4920
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4921
                                    <td><div style="color:' . $overlap_color . '">'
4922
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4923
                                </tr>
4924
                                <tr>
4925
                                    <td><b>' . get_lang('Excess') . '</b></td>
4926
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4927
                                    <td><div style="color:' . $excess_color . '">'
4928
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4929
                                </tr>
4930
                                <tr class="row_even">
4931
                                    <td><b>' . get_lang('Missing') . '</b></td>
4932
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4933
                                    <td><div style="color:' . $missing_color . '">'
4934
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4935
                                </tr>
4936
                            </table>';
4937 View Code Duplication
                        if ($next == 0) {
4938
                            $try = $try_hotspot;
4939
                            $lp = $lp_hotspot;
4940
                            $destinationid = $select_question_hotspot;
4941
                            $url = $url_hotspot;
4942
                        } else {
4943
                            //show if no error
4944
                            //echo 'no error';
4945
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4946
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4947
                        }
4948
4949
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4950
                            <p style="text-align:center">';
4951
4952
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
4953
                        $message .= $table_resume;
4954
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
4955
                        if ($organs_at_risk_hit > 0) {
4956
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
4957
                        }
4958
                        $message .='<p>' . $comment . '</p>';
4959
                        echo $message;
4960
                    } else {
4961
                        echo $hotspot_delineation_result[0]; //prints message
4962
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
4963
                    }
4964
4965
                    //save the score attempts
4966
4967
                    if (1) {
4968
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
4969
                        $final_answer = $hotspot_delineation_result[1];
4970
                        if ($final_answer == 0) {
4971
                            $questionScore = 0;
4972
                        }
4973
                        // we always insert the answer_id 1 = delineation
4974
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
4975
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
4976
                        Event::saveExerciseAttemptHotspot(
4977
                            $exeId,
4978
                            $quesId,
4979
                            1,
4980
                            $hotspot_delineation_result[1],
4981
                            $exerciseResultCoordinates[$quesId]
4982
                        );
4983
                    } else {
4984
                        if ($final_answer==0) {
4985
                            $questionScore = 0;
4986
                            $answer=0;
4987
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
4988 View Code Duplication
                            if (is_array($exerciseResultCoordinates[$quesId])) {
4989
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
4990
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,0,$val);
4991
                                }
4992
                            }
4993
                        } else {
4994
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
4995 View Code Duplication
                            if (is_array($exerciseResultCoordinates[$quesId])) {
4996
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
4997
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,$choice[$idx],$val);
4998
                                }
4999
                            }
5000
                        }
5001
                    }
5002
                    $my_exe_id = $exeId;
5003
                }
5004
            }
5005
5006
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5007
                // We made an extra table for the answers
5008
5009
                if ($show_result) {
5010
                    $relPath = api_get_path(REL_PATH);
5011
                    //	if ($origin != 'learnpath') {
5012
                    echo '</table></td></tr>';
5013
                    echo "
5014
                        <tr>
5015
                            <td colspan=\"2\">
5016
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5017
5018
                                <div id=\"hotspot-solution-$questionId\"></div>
5019
5020
                                <script>
5021
                                    $(document).on('ready', function () {
5022
                                        new HotspotQuestion({
5023
                                            questionId: $questionId,
5024
                                            exerciseId: $exeId,
5025
                                            selector: '#hotspot-solution-$questionId',
5026
                                            for: 'solution',
5027
                                            relPath: '$relPath'
5028
                                        });
5029
                                    });
5030
5031
                                </script>
5032
                            </td>
5033
                        </tr>
5034
                    ";
5035
                    //	}
5036
                }
5037
            }
5038
5039
            //if ($origin != 'learnpath') {
5040
            if ($show_result) {
5041
                echo '</table>';
5042
            }
5043
            //	}
5044
        }
5045
        unset ($objAnswerTmp);
5046
5047
        $totalWeighting += $questionWeighting;
5048
        // Store results directly in the database
5049
        // For all in one page exercises, the results will be
5050
        // stored by exercise_results.php (using the session)
5051
5052
        if ($saved_results) {
5053
            if ($debug) error_log("Save question results $saved_results");
5054
            if ($debug) error_log(print_r($choice ,1 ));
5055
5056
            if (empty($choice)) {
5057
                $choice = 0;
5058
            }
5059
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5060
                if ($choice != 0) {
5061
                    $reply = array_keys($choice);
5062
                    for ($i = 0; $i < sizeof($reply); $i++) {
5063
                        $ans = $reply[$i];
5064
                        Event::saveQuestionAttempt(
5065
                            $questionScore,
5066
                            $ans . ':' . $choice[$ans],
5067
                            $quesId,
5068
                            $exeId,
5069
                            $i,
5070
                            $this->id
5071
                        );
5072
                        if ($debug) {
5073
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5074
                        }
5075
                    }
5076
                } else {
5077
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5078
                }
5079
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5080
                if ($choice != 0) {
5081
                    $reply = array_keys($choice);
5082
5083
                    if ($debug) {
5084
                        error_log("reply " . print_r($reply, 1) . "");
5085
                    }
5086 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5087
                        $ans = $reply[$i];
5088
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5089
                    }
5090
                } else {
5091
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5092
                }
5093
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5094
                if ($choice != 0) {
5095
                    $reply = array_keys($choice);
5096 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5097
                        $ans = $reply[$i];
5098
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5099
                    }
5100
                } else {
5101
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5102
                }
5103
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5104
                if (isset($matching)) {
5105
                    foreach ($matching as $j => $val) {
5106
                        Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
5107
                    }
5108
                }
5109
            } elseif ($answerType == FREE_ANSWER) {
5110
                $answer = $choice;
5111
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5112
            } elseif ($answerType == ORAL_EXPRESSION) {
5113
                $answer = $choice;
5114
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id, $nano);
5115
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5116
                $answer = $choice;
5117
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5118
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5119
            } elseif ($answerType == HOT_SPOT) {
5120
                $answer = [];
5121
5122
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5123
                    Database::delete(
5124
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5125
                        [
5126
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5127
                                $exeId,
5128
                                $questionId,
5129
                                api_get_course_int_id()
5130
                            ]
5131
                        ]
5132
                    );
5133
5134
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5135
                        $answer[] = $val;
5136
5137
                        Event::saveExerciseAttemptHotspot($exeId, $quesId, $idx, $choice[$idx], $val, false, $this->id);
5138
                    }
5139
                }
5140
5141
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5142
            } else {
5143
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5144
            }
5145
        }
5146
5147
        if ($propagate_neg == 0 && $questionScore < 0) {
5148
            $questionScore = 0;
5149
        }
5150
5151
        if ($saved_results) {
5152
            $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5153
            $sql = 'UPDATE ' . $stat_table . ' SET
5154
                        exe_result = exe_result + ' . floatval($questionScore) . '
5155
                    WHERE exe_id = ' . $exeId;
5156
            if ($debug) error_log($sql);
5157
            Database::query($sql);
5158
        }
5159
5160
        $return_array = array(
5161
            'score'         => $questionScore,
5162
            'weight'        => $questionWeighting,
5163
            'extra'         => $extra_data,
5164
            'open_question' => $arrques,
5165
            'open_answer'   => $arrans,
5166
            'answer_type'   => $answerType
5167
        );
5168
5169
        return $return_array;
5170
    }
5171
5172
    /**
5173
     * Sends a notification when a user ends an examn
5174
     *
5175
     */
5176
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id)
5177
    {
5178
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5179
            return null;
5180
        }
5181
        // Email configuration settings
5182
        $courseCode = api_get_course_id();
5183
        $courseInfo = api_get_course_info($courseCode);
5184
        $sessionId = api_get_session_id();
5185
5186
        if (empty($courseInfo)) {
5187
            return false;
5188
        }
5189
5190
        $url_email = api_get_path(WEB_CODE_PATH)
5191
            . 'exercice/exercise_show.php?'
5192
            . api_get_cidreq()
5193
            . '&id_session='
5194
            . $sessionId
5195
            . '&id='
5196
            . $exe_id
5197
            . '&action=qualify';
5198
        $user_info = api_get_user_info(api_get_user_id());
5199
5200
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5201
                    .get_lang('AttemptDetails').' : <br /><br />'.
5202
                    '<table>'
5203
                        .'<tr>'
5204
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5205
                            .'<td>&nbsp;<b>#course#</b></td>'
5206
                        .'</tr>'
5207
                        .'<tr>'
5208
                            .'<td>'.get_lang('TestAttempted').'</td>'
5209
                            .'<td>&nbsp;#exercise#</td>'
5210
                        .'</tr>'
5211
                        .'<tr>'
5212
                            .'<td>'.get_lang('StudentName').'</td>'
5213
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5214
                        .'</tr>'
5215
                        .'<tr>'
5216
                            .'<td>'.get_lang('StudentEmail').'</td>'
5217
                            .'<td>&nbsp;#email#</td>'
5218
                        .'</tr>'
5219
                    .'</table>';
5220
        $open_question_list = null;
5221
5222
        $msg = str_replace("#email#", $user_info['email'], $msg);
5223
        $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5224
        $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5225
        $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5226
        $msg = str_replace("#course#", $courseInfo['name'], $msg1);
5227
5228
        if ($origin != 'learnpath') {
5229
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5230
        }
5231
        $msg1 = str_replace("#url#", $url_email, $msg);
5232
        $mail_content = $msg1;
5233
        $subject = get_lang('ExerciseAttempted');
5234
5235
        if (!empty($sessionId)) {
5236
            $teachers = CourseManager::get_coach_list_from_course_code($courseCode, $sessionId);
5237
        } else {
5238
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5239
        }
5240
5241
        if (!empty($teachers)) {
5242
            foreach ($teachers as $user_id => $teacher_data) {
5243
                MessageManager::send_message_simple(
5244
                    $user_id,
5245
                    $subject,
5246
                    $mail_content
5247
                );
5248
            }
5249
        }
5250
    }
5251
5252
    /**
5253
     * Sends a notification when a user ends an examn
5254
     *
5255
     */
5256
    function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5257
    {
5258
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5259
            return null;
5260
        }
5261
        // Email configuration settings
5262
        $courseCode     = api_get_course_id();
5263
        $course_info    = api_get_course_info($courseCode);
5264
5265
        $url_email = api_get_path(WEB_CODE_PATH)
5266
            . 'exercice/exercise_show.php?'
5267
            . api_get_cidreq()
5268
            . '&id_session='
5269
            . api_get_session_id()
5270
            . '&id='
5271
            . $exe_id
5272
            . '&action=qualify';
5273
        $user_info = api_get_user_info(api_get_user_id());
5274
5275
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5276
                    .get_lang('AttemptDetails').' : <br /><br />'
5277
                    .'<table>'
5278
                        .'<tr>'
5279
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5280
                            .'<td>&nbsp;<b>#course#</b></td>'
5281
                        .'</tr>'
5282
                        .'<tr>'
5283
                            .'<td>'.get_lang('TestAttempted').'</td>'
5284
                            .'<td>&nbsp;#exercise#</td>'
5285
                        .'</tr>'
5286
                        .'<tr>'
5287
                            .'<td>'.get_lang('StudentName').'</td>'
5288
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5289
                        .'</tr>'
5290
                        .'<tr>'
5291
                            .'<td>'.get_lang('StudentEmail').'</td>'
5292
                            .'<td>&nbsp;#mail#</td>'
5293
                        .'</tr>'
5294
                    .'</table>';
5295
        $open_question_list = null;
5296 View Code Duplication
        foreach ($question_list_answers as $item) {
5297
            $question    = $item['question'];
5298
            $answer      = $item['answer'];
5299
            $answer_type = $item['answer_type'];
5300
5301
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5302
                $open_question_list .=
5303
                    '<tr>'
5304
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5305
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5306
                    .'</tr>'
5307
                    .'<tr>'
5308
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5309
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5310
                    .'</tr>';
5311
            }
5312
        }
5313
5314
        if (!empty($open_question_list)) {
5315
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5316
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5317
            $msg .= $open_question_list;
5318
            $msg .= '</table><br />';
5319
5320
5321
            $msg1   = str_replace("#exercise#",    $this->exercise, $msg);
5322
            $msg    = str_replace("#firstName#",   $user_info['firstname'],$msg1);
5323
            $msg1   = str_replace("#lastName#",    $user_info['lastname'],$msg);
5324
            $msg    = str_replace("#mail#",        $user_info['email'],$msg1);
5325
            $msg    = str_replace("#course#",      $course_info['name'],$msg1);
5326
5327
            if ($origin != 'learnpath') {
5328
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5329
            }
5330
            $msg1 = str_replace("#url#", $url_email, $msg);
5331
            $mail_content = $msg1;
5332
            $subject = get_lang('OpenQuestionsAttempted');
5333
5334 View Code Duplication
            if (api_get_session_id()) {
5335
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5336
            } else {
5337
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5338
            }
5339
5340
            if (!empty($teachers)) {
5341
                foreach ($teachers as $user_id => $teacher_data) {
5342
                    MessageManager::send_message_simple(
5343
                        $user_id,
5344
                        $subject,
5345
                        $mail_content
5346
                    );
5347
                }
5348
            }
5349
        }
5350
    }
5351
5352
    function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5353
    {
5354
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5355
            return null;
5356
        }
5357
        // Email configuration settings
5358
        $courseCode     = api_get_course_id();
5359
        $course_info    = api_get_course_info($courseCode);
5360
5361
        $url_email = api_get_path(WEB_CODE_PATH)
5362
            . 'exercice/exercise_show.php?'
5363
            . api_get_cidreq()
5364
            . '&id_session='
5365
            . api_get_session_id()
5366
            . '&id='
5367
            . $exe_id
5368
            . '&action=qualify';
5369
        $user_info = api_get_user_info(api_get_user_id());
5370
5371
        $oral_question_list = null;
5372 View Code Duplication
        foreach ($question_list_answers as $item) {
5373
            $question    = $item['question'];
5374
            $answer      = $item['answer'];
5375
            $answer_type = $item['answer_type'];
5376
5377
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5378
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5379
                    .'<tr>'
5380
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5381
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5382
                    .'</tr>'
5383
                    .'<tr>'
5384
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5385
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5386
                    .'</tr></table>';
5387
            }
5388
        }
5389
5390
        if (!empty($oral_question_list)) {
5391
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5392
                    '.get_lang('AttemptDetails').' : <br /><br />'
5393
                    .'<table>'
5394
                        .'<tr>'
5395
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5396
                            .'<td>&nbsp;<b>#course#</b></td>'
5397
                        .'</tr>'
5398
                        .'<tr>'
5399
                            .'<td>'.get_lang('TestAttempted').'</td>'
5400
                            .'<td>&nbsp;#exercise#</td>'
5401
                        .'</tr>'
5402
                        .'<tr>'
5403
                            .'<td>'.get_lang('StudentName').'</td>'
5404
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5405
                        .'</tr>'
5406
                        .'<tr>'
5407
                            .'<td>'.get_lang('StudentEmail').'</td>'
5408
                            .'<td>&nbsp;#mail#</td>'
5409
                        .'</tr>'
5410
                    .'</table>';
5411
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5412
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5413
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5414
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5415
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5416
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5417
5418
            if ($origin != 'learnpath') {
5419
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5420
            }
5421
            $msg1 = str_replace("#url#", $url_email, $msg);
5422
            $mail_content = $msg1;
5423
            $subject = get_lang('OralQuestionsAttempted');
5424
5425 View Code Duplication
            if (api_get_session_id()) {
5426
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5427
            } else {
5428
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5429
            }
5430
5431
            if (!empty($teachers)) {
5432
                foreach ($teachers as $user_id => $teacher_data) {
5433
                    MessageManager::send_message_simple(
5434
                        $user_id,
5435
                        $subject,
5436
                        $mail_content
5437
                    );
5438
                }
5439
            }
5440
        }
5441
    }
5442
5443
    /**
5444
     * @param array $user_data result of api_get_user_info()
5445
     * @param null $start_date
5446
     * @param null $duration
5447
     * @param string $ip Optional. The user IP
5448
     * @return string
5449
     */
5450
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5451
    {
5452
        $array = array();
5453
5454
        if (!empty($user_data)) {
5455
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5456
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5457
            if (!empty($user_data['official_code'])) {
5458
                $array[] = array(
5459
                    'title' => get_lang('OfficialCode'),
5460
                    'content' => $user_data['official_code']
5461
                );
5462
            }
5463
        }
5464
        // Description can be very long and is generally meant to explain
5465
        //   rules *before* the exam. Leaving here to make display easier if
5466
        //   necessary
5467
        /*
5468
        if (!empty($this->description)) {
5469
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5470
        }
5471
        */
5472 View Code Duplication
        if (!empty($start_date)) {
5473
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5474
        }
5475
5476 View Code Duplication
        if (!empty($duration)) {
5477
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5478
        }
5479
5480 View Code Duplication
        if (!empty($ip)) {
5481
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5482
        }
5483
        $html  = '<div class="question-result">';
5484
        $html .= Display::page_header(
5485
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5486
        );
5487
        $html .= Display::description($array);
5488
        $html .="</div>";
5489
        return $html;
5490
    }
5491
5492
    /**
5493
     * Create a quiz from quiz data
5494
     * @param string  Title
5495
     * @param int     Time before it expires (in minutes)
5496
     * @param int     Type of exercise
5497
     * @param int     Whether it's randomly picked questions (1) or not (0)
5498
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5499
     * @param int     Whether the results are show to the user (0) or not (1)
5500
     * @param int     Maximum number of attempts (0 if no limit)
5501
     * @param int     Feedback type
5502
     * @todo this was function was added due the import exercise via CSV
5503
     * @return    int New exercise ID
5504
     */
5505
    public function createExercise(
5506
        $title,
5507
        $expired_time = 0,
5508
        $type = 2,
5509
        $random = 0,
5510
        $active = 1,
5511
        $results_disabled = 0,
5512
        $max_attempt = 0,
5513
        $feedback = 3,
5514
        $propagateNegative = 0
5515
    ) {
5516
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5517
        $type = intval($type);
5518
        $random = intval($random);
5519
        $active = intval($active);
5520
        $results_disabled = intval($results_disabled);
5521
        $max_attempt = intval($max_attempt);
5522
        $feedback = intval($feedback);
5523
        $expired_time = intval($expired_time);
5524
        $title = Database::escape_string($title);
5525
        $propagateNegative = intval($propagateNegative);
5526
        $sessionId = api_get_session_id();
5527
        $course_id = api_get_course_int_id();
5528
        // Save a new quiz
5529
        $sql = "INSERT INTO $tbl_quiz (
5530
                c_id,
5531
                title,
5532
                type,
5533
                random,
5534
                active,
5535
                results_disabled,
5536
                max_attempt,
5537
                start_time,
5538
                end_time,
5539
                feedback_type,
5540
                expired_time,
5541
                session_id,
5542
                propagate_neg
5543
            )
5544
            VALUES (
5545
                '$course_id',
5546
                '$title',
5547
                $type,
5548
                $random,
5549
                $active,
5550
                $results_disabled,
5551
                $max_attempt,
5552
                '',
5553
                '',
5554
                $feedback,
5555
                $expired_time,
5556
                $sessionId,
5557
                $propagateNegative
5558
            )";
5559
        Database::query($sql);
5560
        $quiz_id = Database::insert_id();
5561
5562
        if ($quiz_id) {
5563
5564
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5565
            Database::query($sql);
5566
        }
5567
5568
        return $quiz_id;
5569
    }
5570
5571
    function process_geometry()
5572
    {
5573
5574
    }
5575
5576
    /**
5577
     * Returns the exercise result
5578
     * @param 	int		attempt id
5579
     * @return 	float 	exercise result
5580
     */
5581
    public function get_exercise_result($exe_id)
5582
    {
5583
        $result = array();
5584
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5585
5586
        if (!empty($track_exercise_info)) {
5587
            $totalScore = 0;
5588
            $objExercise = new Exercise();
5589
            $objExercise->read($track_exercise_info['exe_exo_id']);
5590
            if (!empty($track_exercise_info['data_tracking'])) {
5591
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5592
            }
5593
            foreach ($question_list as $questionId) {
5594
                $question_result = $objExercise->manage_answer(
5595
                    $exe_id,
5596
                    $questionId,
5597
                    '',
5598
                    'exercise_show',
5599
                    array(),
5600
                    false,
5601
                    true,
5602
                    false,
5603
                    $objExercise->selectPropagateNeg()
5604
                );
5605
                $totalScore      += $question_result['score'];
5606
            }
5607
5608
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5609
                $totalScore = 0;
5610
            }
5611
            $result = array(
5612
                'score' => $totalScore,
5613
                'weight' => $track_exercise_info['exe_weighting']
5614
            );
5615
        }
5616
        return $result;
5617
    }
5618
5619
    /**
5620
     * Checks if the exercise is visible due a lot of conditions
5621
     * visibility, time limits, student attempts
5622
     * Return associative array
5623
     * value : true if execise visible
5624
     * message : HTML formated message
5625
     * rawMessage : text message
5626
     * @param int $lpId
5627
     * @param int $lpItemId
5628
     * @param int $lpItemViewId
5629
     * @param bool $filterByAdmin
5630
     * @return array
5631
     */
5632
    public function is_visible(
5633
        $lpId = 0,
5634
        $lpItemId = 0,
5635
        $lpItemViewId = 0,
5636
        $filterByAdmin = true
5637
    ) {
5638
        // 1. By default the exercise is visible
5639
        $isVisible = true;
5640
        $message = null;
5641
5642
        // 1.1 Admins and teachers can access to the exercise
5643
        if ($filterByAdmin) {
5644
            if (api_is_platform_admin() || api_is_course_admin()) {
5645
                return array('value' => true, 'message' => '');
5646
            }
5647
        }
5648
5649
        // Deleted exercise.
5650 View Code Duplication
        if ($this->active == -1) {
5651
            return array(
5652
                'value' => false,
5653
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5654
                'rawMessage' => get_lang('ExerciseNotFound')
5655
            );
5656
        }
5657
5658
        // Checking visibility in the item_property table.
5659
        $visibility = api_get_item_visibility(
5660
            api_get_course_info(),
5661
            TOOL_QUIZ,
5662
            $this->id,
5663
            api_get_session_id()
5664
        );
5665
5666
        if ($visibility == 0 || $visibility == 2) {
5667
            $this->active = 0;
5668
        }
5669
5670
        // 2. If the exercise is not active.
5671
        if (empty($lpId)) {
5672
            // 2.1 LP is OFF
5673 View Code Duplication
            if ($this->active == 0) {
5674
                return array(
5675
                    'value' => false,
5676
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5677
                    'rawMessage' => get_lang('ExerciseNotFound')
5678
                );
5679
            }
5680
        } else {
5681
            // 2.1 LP is loaded
5682
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5683
                return array(
5684
                    'value' => false,
5685
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5686
                    'rawMessage' => get_lang('ExerciseNotFound')
5687
                );
5688
            }
5689
        }
5690
5691
        //3. We check if the time limits are on
5692
        if ((!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00')
5693
            || (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00')) {
5694
            $limitTimeExists = true;
5695
        } else {
5696
            $limitTimeExists = false;
5697
        }
5698
5699
        if ($limitTimeExists) {
5700
            $timeNow = time();
5701
5702
            $existsStartDate = false;
5703
            $nowIsAfterStartDate = true;
5704
            $existsEndDate = false;
5705
            $nowIsBeforeEndDate = true;
5706
5707
5708
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
5709
                $existsStartDate = true;
5710
            }
5711
5712
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
5713
                $existsEndDate = true;
5714
            }
5715
5716
            // check if we are before-or-after end-or-start date
5717
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5718
                $nowIsAfterStartDate = false;
5719
                    }
5720
5721
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5722
                $nowIsBeforeEndDate = false;
5723
                }
5724
5725
            // lets check all cases
5726
            if ($existsStartDate && !$existsEndDate) {
5727
                // exists start date and dont exists end date
5728
                if ($nowIsAfterStartDate) {
5729
                    // after start date, no end date
5730
                    $isVisible = true;
5731
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5732
                        api_convert_and_format_date($this->start_time));
5733
                } else {
5734
                    // before start date, no end date
5735
                    $isVisible = false;
5736
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5737
                        api_convert_and_format_date($this->start_time));
5738
            }
5739
            } else if (!$existsStartDate && $existsEndDate) {
5740
                // doesnt exist start date, exists end date
5741
                if ($nowIsBeforeEndDate) {
5742
                    // before end date, no start date
5743
                    $isVisible = true;
5744
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5745
                        api_convert_and_format_date($this->end_time));
5746
                } else {
5747
                    // after end date, no start date
5748
                    $isVisible = false;
5749
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5750
                        api_convert_and_format_date($this->end_time));
5751
                }
5752
            } elseif ($existsStartDate && $existsEndDate) {
5753
                // exists start date and end date
5754
                if ($nowIsAfterStartDate) {
5755
                    if ($nowIsBeforeEndDate) {
5756
                        // after start date and before end date
5757
                        $isVisible = true;
5758
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5759
                    api_convert_and_format_date($this->start_time),
5760
                            api_convert_and_format_date($this->end_time));
5761 View Code Duplication
                    } else {
5762
                        // after start date and after end date
5763
                        $isVisible = false;
5764
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5765
                            api_convert_and_format_date($this->start_time),
5766
                            api_convert_and_format_date($this->end_time));
5767
                    }
5768 View Code Duplication
                } else {
5769
                    if ($nowIsBeforeEndDate) {
5770
                        // before start date and before end date
5771
                        $isVisible = false;
5772
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5773
                            api_convert_and_format_date($this->start_time),
5774
                            api_convert_and_format_date($this->end_time));
5775
                    }
5776
                    // case before start date and after end date is impossible
5777
                }
5778
            } elseif (!$existsStartDate && !$existsEndDate) {
5779
                // doesnt exist start date nor end date
5780
                $isVisible = true;
5781
                $message = "";
5782
            }
5783
        }
5784
5785
        // 4. We check if the student have attempts
5786
        $exerciseAttempts = $this->selectAttempts();
5787
5788
        if ($isVisible) {
5789
            if ($exerciseAttempts > 0) {
5790
5791
                $attemptCount = Event::get_attempt_count_not_finished(
5792
                    api_get_user_id(),
5793
                    $this->id,
5794
                    $lpId,
5795
                    $lpItemId,
5796
                    $lpItemViewId
5797
                );
5798
5799
                if ($attemptCount >= $exerciseAttempts) {
5800
                    $message = sprintf(
5801
                        get_lang('ReachedMaxAttempts'),
5802
                        $this->name,
5803
                        $exerciseAttempts
5804
                    );
5805
                    $isVisible = false;
5806
                }
5807
            }
5808
        }
5809
5810
        $rawMessage = "";
5811
        if (!empty($message)){
5812
            $rawMessage = $message;
5813
            $message = Display::return_message($message, 'warning', false);
5814
        }
5815
5816
        return array(
5817
            'value' => $isVisible,
5818
            'message' => $message,
5819
            'rawMessage' => $rawMessage
5820
        );
5821
    }
5822
5823
    public function added_in_lp()
5824
    {
5825
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5826
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5827
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5828
        $result = Database::query($sql);
5829
        if (Database::num_rows($result) > 0) {
5830
            return true;
5831
        }
5832
        return false;
5833
    }
5834
5835
    /**
5836
     * Returns an array with the media list
5837
     * @param array question list
5838
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5839
     * <code>
5840
     * array (size=2)
5841
     *  999 =>
5842
     *    array (size=3)
5843
     *      0 => int 7
5844
     *      1 => int 6
5845
     *      2 => int 3254
5846
     *  100 =>
5847
     *   array (size=1)
5848
     *      0 => int 5
5849
     *  </code>
5850
     * @return array
5851
     */
5852
    private function setMediaList($questionList)
5853
    {
5854
        $mediaList = array();
5855
        if (!empty($questionList)) {
5856
            foreach ($questionList as $questionId) {
5857
                $objQuestionTmp = Question::read($questionId, $this->course_id);
5858
5859
                // If a media question exists
5860
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
5861
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
5862
                } else {
5863
                    //Always the last item
5864
                    $mediaList[999][] = $objQuestionTmp->id;
5865
                }
5866
            }
5867
        }
5868
        $this->mediaList = $mediaList;
5869
    }
5870
5871
    /**
5872
     * Returns an array with this form
5873
     * @example
5874
     * <code>
5875
     * array (size=3)
5876
    999 =>
5877
    array (size=3)
5878
    0 => int 3422
5879
    1 => int 3423
5880
    2 => int 3424
5881
    100 =>
5882
    array (size=2)
5883
    0 => int 3469
5884
    1 => int 3470
5885
    101 =>
5886
    array (size=1)
5887
    0 => int 3482
5888
     * </code>
5889
     * The array inside the key 999 means the question list that belongs to the media id = 999,
5890
     * this case is special because 999 means "no media".
5891
     * @return array
5892
     */
5893
    public function getMediaList()
5894
    {
5895
        return $this->mediaList;
5896
    }
5897
5898
    /**
5899
     * Is media question activated?
5900
     * @return bool
5901
     */
5902
    public function mediaIsActivated()
5903
    {
5904
        $mediaQuestions = $this->getMediaList();
5905
        $active = false;
5906
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
5907
            $media_count = count($mediaQuestions);
5908
            if ($media_count > 1) {
5909
                return true;
5910
            } elseif ($media_count == 1) {
5911
                if (isset($mediaQuestions[999])) {
5912
                    return false;
5913
                } else {
5914
                    return true;
5915
                }
5916
            }
5917
        }
5918
5919
        return $active;
5920
    }
5921
5922
    /**
5923
     * Gets question list from the exercise
5924
     *
5925
     * @return array
5926
     */
5927
    public function getQuestionList()
5928
    {
5929
        return $this->questionList;
5930
    }
5931
5932
    /**
5933
     * Question list with medias compressed like this
5934
     * @example
5935
     * <code>
5936
     * array(
5937
     *      question_id_1,
5938
     *      question_id_2,
5939
     *      media_id, <- this media id contains question ids
5940
     *      question_id_3,
5941
     * )
5942
     * </code>
5943
     * @return array
5944
     */
5945
    public function getQuestionListWithMediasCompressed()
5946
    {
5947
        return $this->questionList;
5948
    }
5949
5950
    /**
5951
     * Question list with medias uncompressed like this
5952
     * @example
5953
     * <code>
5954
     * array(
5955
     *      question_id,
5956
     *      question_id,
5957
     *      question_id, <- belongs to a media id
5958
     *      question_id, <- belongs to a media id
5959
     *      question_id,
5960
     * )
5961
     * </code>
5962
     * @return array
5963
     */
5964
    public function getQuestionListWithMediasUncompressed()
5965
    {
5966
        return $this->questionListUncompressed;
5967
    }
5968
5969
    /**
5970
     * Sets the question list when the exercise->read() is executed
5971
     */
5972
    public function setQuestionList()
5973
    {
5974
        // Getting question list.
5975
        $questionList = $this->selectQuestionList(true);
5976
5977
        $this->setMediaList($questionList);
5978
5979
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
5980
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
5981
    }
5982
5983
    /**
5984
     *
5985
     * @params array question list
5986
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
5987
     *
5988
     **/
5989 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
5990
    {
5991
        $new_question_list = array();
5992
        if (!empty($question_list)) {
5993
            $media_questions = $this->getMediaList();
5994
5995
            $media_active = $this->mediaIsActivated($media_questions);
5996
5997
            if ($media_active) {
5998
                $counter = 1;
5999
                foreach ($question_list as $question_id) {
6000
                    $add_question = true;
6001
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6002
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6003
                            $add_question = false;
6004
                            if (!in_array($media_id, $new_question_list)) {
6005
                                $new_question_list[$counter] = $media_id;
6006
                                $counter++;
6007
                            }
6008
                            break;
6009
                        }
6010
                    }
6011
                    if ($add_question) {
6012
                        $new_question_list[$counter] = $question_id;
6013
                        $counter++;
6014
                    }
6015
                }
6016
                if ($expand_media_questions) {
6017
                    $media_key_list = array_keys($media_questions);
6018
                    foreach ($new_question_list as &$question_id) {
6019
                        if (in_array($question_id, $media_key_list)) {
6020
                            $question_id = $media_questions[$question_id];
6021
                        }
6022
                    }
6023
                    $new_question_list = array_flatten($new_question_list);
6024
                }
6025
            } else {
6026
                $new_question_list = $question_list;
6027
            }
6028
        }
6029
6030
        return $new_question_list;
6031
    }
6032
6033
    function get_validated_question_list()
6034
    {
6035
        $tabres = array();
6036
        $isRandomByCategory = $this->isRandomByCat();
6037
        if ($isRandomByCategory == 0) {
6038
            if ($this->isRandom()) {
6039
                $tabres = $this->selectRandomList();
6040
            } else {
6041
                $tabres = $this->selectQuestionList();
6042
            }
6043
        } else {
6044
            if ($this->isRandom()) {
6045
                // USE question categories
6046
                // get questions by category for this exercise
6047
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6048
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6049
                // value is the array of question id of this category
6050
                $questionList = array();
6051
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6052
                $isRandomByCategory = $this->selectRandomByCat();
6053
                // on tri les categories en fonction du terme entre [] en tete de la description de la categorie
6054
                /*
6055
                 * ex de catégories :
6056
                 * [biologie] Maitriser les mecanismes de base de la genetique
6057
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6058
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6059
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6060
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6061
                 * [chimie] Connaître les charges des particules
6062
                 * On veut dans l'ordre des groupes definis par le terme entre crochet au debut du titre de la categorie
6063
                */
6064
                // If test option is Grouped By Categories
6065
                if ($isRandomByCategory == 2) {
6066
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6067
                }
6068
                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...
6069
                    $number_of_random_question = $this->random;
6070
                    if ($this->random == -1) {
6071
                        $number_of_random_question = count($this->questionList);
6072
                    }
6073
                    $questionList = array_merge(
6074
                        $questionList,
6075
                        TestCategory::getNElementsFromArray(
6076
                            $tabquestion,
6077
                            $number_of_random_question
6078
                        )
6079
                    );
6080
                }
6081
                // shuffle the question list if test is not grouped by categories
6082
                if ($isRandomByCategory == 1) {
6083
                    shuffle($questionList); // or not
6084
                }
6085
                $tabres = $questionList;
6086
            } 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...
6087
                // Problem, random by category has been selected and
6088
                // we have no $this->isRandom number of question selected
6089
                // Should not happened
6090
            }
6091
        }
6092
        return $tabres;
6093
    }
6094
6095
    function get_question_list($expand_media_questions = false)
6096
    {
6097
        $question_list = $this->get_validated_question_list();
6098
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6099
        return $question_list;
6100
    }
6101
6102 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6103
    {
6104
        $new_question_list = array();
6105
        if (!empty($question_list)) {
6106
            $media_questions = $this->getMediaList();
6107
            $media_active = $this->mediaIsActivated($media_questions);
6108
6109
            if ($media_active) {
6110
                $counter = 1;
6111
                foreach ($question_list as $question_id) {
6112
                    $add_question = true;
6113
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6114
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6115
                            $add_question = false;
6116
                            if (!in_array($media_id, $new_question_list)) {
6117
                                $new_question_list[$counter] = $media_id;
6118
                                $counter++;
6119
                            }
6120
                            break;
6121
                        }
6122
                    }
6123
                    if ($add_question) {
6124
                        $new_question_list[$counter] = $question_id;
6125
                        $counter++;
6126
                    }
6127
                }
6128
                if ($expand_media_questions) {
6129
                    $media_key_list = array_keys($media_questions);
6130
                    foreach ($new_question_list as &$question_id) {
6131
                        if (in_array($question_id, $media_key_list)) {
6132
                            $question_id = $media_questions[$question_id];
6133
                        }
6134
                    }
6135
                    $new_question_list = array_flatten($new_question_list);
6136
                }
6137
            } else {
6138
                $new_question_list = $question_list;
6139
            }
6140
        }
6141
        return $new_question_list;
6142
    }
6143
6144
    /**
6145
     * @param int $exe_id
6146
     * @return array|mixed
6147
     */
6148
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6149
    {
6150
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6151
        $exe_id = intval($exe_id);
6152
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6153
        $result = Database::query($sql_track);
6154
        $new_array = array();
6155
        if (Database::num_rows($result) > 0 ) {
6156
            $new_array = Database::fetch_array($result, 'ASSOC');
6157
6158
            $new_array['duration'] = null;
6159
6160
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6161
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6162
6163
            if (!empty($start_date) && !empty($end_date)) {
6164
                $start_date = api_strtotime($start_date, 'UTC');
6165
                $end_date = api_strtotime($end_date, 'UTC');
6166
                if ($start_date && $end_date) {
6167
                    $mytime = $end_date- $start_date;
6168
                    $new_learnpath_item = new learnpathItem(null);
6169
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6170
                    $h = get_lang('h');
6171
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6172
                    $new_array['duration'] = $time_attemp;
6173
                }
6174
            }
6175
        }
6176
        return $new_array;
6177
    }
6178
6179
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6180
    {
6181
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6182
        $question_id = intval($question_id);
6183
        $exe_id = intval($exe_id);
6184
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6185
        if ($exercise_info) {
6186
6187
            if (empty($exercise_info['questions_to_check'])) {
6188
                if ($action == 'add') {
6189
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6190
                    $result = Database::query($sql);
6191
                }
6192
            } else {
6193
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6194
6195
                $remind_list_string = '';
6196
                if ($action == 'add') {
6197
                    if (!in_array($question_id, $remind_list)) {
6198
                        $remind_list[] = $question_id;
6199
                        if (!empty($remind_list)) {
6200
                            sort($remind_list);
6201
                            array_filter($remind_list);
6202
                        }
6203
                        $remind_list_string = implode(',', $remind_list);
6204
                    }
6205
                } elseif ($action == 'delete')  {
6206
                    if (!empty($remind_list)) {
6207
                        if (in_array($question_id, $remind_list)) {
6208
                            $remind_list = array_flip($remind_list);
6209
                            unset($remind_list[$question_id]);
6210
                            $remind_list = array_flip($remind_list);
6211
6212
                            if (!empty($remind_list)) {
6213
                                sort($remind_list);
6214
                                array_filter($remind_list);
6215
                                $remind_list_string = implode(',', $remind_list);
6216
                            }
6217
                        }
6218
                    }
6219
                }
6220
                $remind_list_string = Database::escape_string($remind_list_string);
6221
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6222
                Database::query($sql);
6223
            }
6224
        }
6225
    }
6226
6227
    public function fill_in_blank_answer_to_array($answer)
6228
    {
6229
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6230
        $teacher_answer_list = $teacher_answer_list[0];
6231
        return $teacher_answer_list;
6232
    }
6233
6234
    public function fill_in_blank_answer_to_string($answer)
6235
    {
6236
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6237
        $result = '';
6238
        if (!empty($teacher_answer_list)) {
6239
            $i = 0;
6240
            foreach ($teacher_answer_list as $teacher_item) {
6241
                $value = null;
6242
                //Cleaning student answer list
6243
                $value = strip_tags($teacher_item);
6244
                $value = api_substr($value, 1, api_strlen($value) - 2);
6245
                $value = explode('/', $value);
6246
                if (!empty($value[0])) {
6247
                    $value = trim($value[0]);
6248
                    $value = str_replace('&nbsp;', '', $value);
6249
                    $result .= $value;
6250
                }
6251
            }
6252
        }
6253
        return $result;
6254
    }
6255
6256
    function return_time_left_div()
6257
    {
6258
        $html = '<div id="clock_warning" style="display:none">';
6259
        $html .= Display::return_message(
6260
            get_lang('ReachedTimeLimit'),
6261
            'warning'
6262
        );
6263
        $html .= ' ';
6264
        $html .= sprintf(
6265
            get_lang('YouWillBeRedirectedInXSeconds'),
6266
            '<span id="counter_to_redirect" class="red_alert"></span>'
6267
        );
6268
        $html .= '</div>';
6269
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6270
        return $html;
6271
    }
6272
6273
    function get_count_question_list()
6274
    {
6275
        //Real question count
6276
        $question_count = 0;
6277
        $question_list = $this->get_question_list();
6278
        if (!empty($question_list)) {
6279
            $question_count = count($question_list);
6280
        }
6281
        return $question_count;
6282
    }
6283
6284 View Code Duplication
    function get_exercise_list_ordered()
6285
    {
6286
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6287
        $course_id = api_get_course_int_id();
6288
        $session_id = api_get_session_id();
6289
        $sql = "SELECT exercise_id, exercise_order
6290
                FROM $table_exercise_order
6291
                WHERE c_id = $course_id AND session_id = $session_id
6292
                ORDER BY exercise_order";
6293
        $result = Database::query($sql);
6294
        $list = array();
6295
        if (Database::num_rows($result)) {
6296
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6297
                $list[$row['exercise_order']] = $row['exercise_id'];
6298
            }
6299
        }
6300
        return $list;
6301
    }
6302
6303
    /**
6304
     * Get categories added in the exercise--category matrix
6305
     * @return bool
6306
     */
6307
    public function get_categories_in_exercise()
6308
    {
6309
        if (!$this->specialCategoryOrders) {
6310
            return false;
6311
        }
6312
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6313
        if (!empty($this->id)) {
6314
            $sql = "SELECT * FROM $table
6315
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6316
            $result = Database::query($sql);
6317
            $list = array();
6318
            if (Database::num_rows($result)) {
6319
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6320
                    $list[$row['category_id']] = $row;
6321
                }
6322
                return $list;
6323
            }
6324
        }
6325
        return false;
6326
    }
6327
6328
    /**
6329
     * @param null $order
6330
     * @return bool
6331
     */
6332
    public function get_categories_with_name_in_exercise($order = null)
6333
    {
6334
        if (!$this->specialCategoryOrders) {
6335
            return false;
6336
        }
6337
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6338
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6339
        $sql = "SELECT * FROM $table qc
6340
                INNER JOIN $table_category c
6341
                ON (category_id = c.iid)
6342
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6343
        if (!empty($order)) {
6344
            $sql .= "ORDER BY $order ";
6345
        }
6346
        $result = Database::query($sql);
6347
        if (Database::num_rows($result)) {
6348
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6349
                $list[$row['category_id']] = $row;
6350
            }
6351
            return $list;
6352
        }
6353
        return false;
6354
    }
6355
6356
    /**
6357
     * Get total number of question that will be parsed when using the category/exercise
6358
     */
6359
    public function getNumberQuestionExerciseCategory()
6360
    {
6361
        if (!$this->specialCategoryOrders) {
6362
            return false;
6363
        }
6364
6365
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6366
        if (!empty($this->id)) {
6367
            $sql = "SELECT SUM(count_questions) count_questions
6368
                    FROM $table
6369
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6370
            $result = Database::query($sql);
6371
            if (Database::num_rows($result)) {
6372
                $row = Database::fetch_array($result);
6373
                return $row['count_questions'];
6374
            }
6375
        }
6376
        return 0;
6377
    }
6378
6379
    /**
6380
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6381
     * @param array $categories
6382
     */
6383
    public function save_categories_in_exercise($categories)
6384
    {
6385
        if (!$this->specialCategoryOrders) {
6386
            return false;
6387
        }
6388
        if (!empty($categories) && !empty($this->id)) {
6389
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6390
            $sql = "DELETE FROM $table
6391
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6392
            Database::query($sql);
6393
            if (!empty($categories)) {
6394
                foreach ($categories as $category_id => $count_questions) {
6395
                    $params = array(
6396
                        'c_id' => $this->course_id,
6397
                        'exercise_id' => $this->id,
6398
                        'category_id' => $category_id,
6399
                        'count_questions' => $count_questions
6400
                    );
6401
                    Database::insert($table, $params);
6402
                }
6403
            }
6404
        }
6405
    }
6406
6407
    /**
6408
     * @param array $questionList
6409
     * @param int $currentQuestion
6410
     * @param array $conditions
6411
     * @param string $link
6412
     * @return string
6413
     */
6414
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6415
    {
6416
        $mediaQuestions = $this->getMediaList();
6417
6418
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6419
        $counter = 0;
6420
        $nextValue = 0;
6421
        $wasMedia = false;
6422
        $before = 0;
6423
        $counterNoMedias = 0;
6424
        foreach ($questionList as $questionId) {
6425
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6426
6427
            if (!empty($nextValue)) {
6428
                if ($wasMedia) {
6429
                    $nextValue = $nextValue - $before + 1;
6430
                }
6431
            }
6432
6433
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6434
                $fixedValue = $counterNoMedias;
6435
6436
                $html .= Display::progressPaginationBar(
6437
                    $nextValue,
6438
                    $mediaQuestions[$questionId],
6439
                    $currentQuestion,
6440
                    $fixedValue,
6441
                    $conditions,
6442
                    $link,
6443
                    true,
6444
                    true
6445
                );
6446
6447
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6448
                $before = count($questionList);
6449
                $wasMedia = true;
6450
                $nextValue += count($questionList);
6451
            } else {
6452
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6453
                $counter++;
6454
                $nextValue++;
6455
                $wasMedia = false;
6456
            }
6457
            $counterNoMedias++;
6458
        }
6459
        $html .= '</ul></div>';
6460
        return $html;
6461
    }
6462
6463
6464
    /**
6465
     *  Shows a list of numbers that represents the question to answer in a exercise
6466
     *
6467
     * @param array $categories
6468
     * @param int $current
6469
     * @param array $conditions
6470
     * @param string $link
6471
     * @return string
6472
     */
6473
    public function progressExercisePaginationBarWithCategories(
6474
        $categories,
6475
        $current,
6476
        $conditions = array(),
6477
        $link = null
6478
    ) {
6479
        $html = null;
6480
        $counterNoMedias = 0;
6481
        $nextValue = 0;
6482
        $wasMedia = false;
6483
        $before = 0;
6484
6485
        if (!empty($categories)) {
6486
            $selectionType = $this->getQuestionSelectionType();
6487
            $useRootAsCategoryTitle = false;
6488
6489
            // Grouping questions per parent category see BT#6540
6490
6491
            if (in_array(
6492
                $selectionType,
6493
                array(
6494
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6495
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6496
                )
6497
            )) {
6498
                $useRootAsCategoryTitle = true;
6499
            }
6500
6501
            // If the exercise is set to only show the titles of the categories
6502
            // at the root of the tree, then pre-order the categories tree by
6503
            // removing children and summing their questions into the parent
6504
            // categories
6505
6506
            if ($useRootAsCategoryTitle) {
6507
                // The new categories list starts empty
6508
                $newCategoryList = array();
6509
                foreach ($categories as $category) {
6510
                    $rootElement = $category['root'];
6511
6512
                    if (isset($category['parent_info'])) {
6513
                        $rootElement = $category['parent_info']['id'];
6514
                    }
6515
6516
                    //$rootElement = $category['id'];
6517
                    // If the current category's ancestor was never seen
6518
                    // before, then declare it and assign the current
6519
                    // category to it.
6520
                    if (!isset($newCategoryList[$rootElement])) {
6521
                        $newCategoryList[$rootElement] = $category;
6522
                    } else {
6523
                        // If it was already seen, then merge the previous with
6524
                        // the current category
6525
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6526
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6527
                        $newCategoryList[$rootElement] = $category;
6528
                    }
6529
                }
6530
                // Now use the newly built categories list, with only parents
6531
                $categories = $newCategoryList;
6532
            }
6533
6534
            foreach ($categories as $category) {
6535
                $questionList = $category['question_list'];
6536
                // Check if in this category there questions added in a media
6537
                $mediaQuestionId = $category['media_question'];
6538
                $isMedia = false;
6539
                $fixedValue = null;
6540
6541
                // Media exists!
6542
                if ($mediaQuestionId != 999) {
6543
                    $isMedia = true;
6544
                    $fixedValue = $counterNoMedias;
6545
                }
6546
6547
                //$categoryName = $category['path']; << show the path
6548
                $categoryName = $category['name'];
6549
6550
                if ($useRootAsCategoryTitle) {
6551
                    if (isset($category['parent_info'])) {
6552
                        $categoryName  = $category['parent_info']['title'];
6553
                    }
6554
                }
6555
                $html .= '<div class="row">';
6556
                $html .= '<div class="span2">'.$categoryName.'</div>';
6557
                $html .= '<div class="span8">';
6558
6559
                if (!empty($nextValue)) {
6560
                    if ($wasMedia) {
6561
                        $nextValue = $nextValue - $before + 1;
6562
                    }
6563
                }
6564
                $html .= Display::progressPaginationBar(
6565
                    $nextValue,
6566
                    $questionList,
6567
                    $current,
6568
                    $fixedValue,
6569
                    $conditions,
6570
                    $link,
6571
                    $isMedia,
6572
                    true
6573
                );
6574
                $html .= '</div>';
6575
                $html .= '</div>';
6576
6577
                if ($mediaQuestionId == 999) {
6578
                    $counterNoMedias += count($questionList);
6579
                } else {
6580
                    $counterNoMedias++;
6581
                }
6582
6583
                $nextValue += count($questionList);
6584
                $before = count($questionList);
6585
6586
                if ($mediaQuestionId != 999) {
6587
                    $wasMedia = true;
6588
                } else {
6589
                    $wasMedia = false;
6590
                }
6591
6592
            }
6593
        }
6594
        return $html;
6595
    }
6596
6597
    /**
6598
     * Renders a question list
6599
     *
6600
     * @param array $questionList (with media questions compressed)
6601
     * @param int $currentQuestion
6602
     * @param array $exerciseResult
6603
     * @param array $attemptList
6604
     * @param array $remindList
6605
     */
6606
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6607
    {
6608
        $mediaQuestions = $this->getMediaList();
6609
        $i = 0;
6610
6611
        // Normal question list render (medias compressed)
6612
        foreach ($questionList as $questionId) {
6613
            $i++;
6614
            // For sequential exercises
6615
            if ($this->type == ONE_PER_PAGE) {
6616
                // If it is not the right question, goes to the next loop iteration
6617
                if ($currentQuestion != $i) {
6618
                    continue;
6619
                } else {
6620
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6621
                        // if the user has already answered this question
6622
                        if (isset($exerciseResult[$questionId])) {
6623
                            Display::display_normal_message(get_lang('AlreadyAnswered'));
6624
                            break;
6625
                        }
6626
                    }
6627
                }
6628
            }
6629
6630
            // The $questionList contains the media id we check if this questionId is a media question type
6631
6632
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6633
6634
                // The question belongs to a media
6635
                $mediaQuestionList = $mediaQuestions[$questionId];
6636
                $objQuestionTmp = Question::read($questionId);
6637
6638
                $counter = 1;
6639
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6640
                    echo $objQuestionTmp->show_media_content();
6641
6642
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6643
6644
                    // Show questions that belongs to a media
6645
                    if (!empty($mediaQuestionList)) {
6646
                        // In order to parse media questions we use letters a, b, c, etc.
6647
                        $letterCounter = 97;
6648
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6649
                            $isLastQuestionInMedia = false;
6650
                            if ($counter == $countQuestionsInsideMedia) {
6651
                                $isLastQuestionInMedia = true;
6652
                            }
6653
                            $this->renderQuestion(
6654
                                $questionIdInsideMedia,
6655
                                $attemptList,
6656
                                $remindList,
6657
                                chr($letterCounter),
6658
                                $currentQuestion,
6659
                                $mediaQuestionList,
6660
                                $isLastQuestionInMedia,
6661
                                $questionList
6662
                            );
6663
                            $letterCounter++;
6664
                            $counter++;
6665
                        }
6666
                    }
6667
                } else {
6668
                    $this->renderQuestion(
6669
                        $questionId,
6670
                        $attemptList,
6671
                        $remindList,
6672
                        $i,
6673
                        $currentQuestion,
6674
                        null,
6675
                        null,
6676
                        $questionList
6677
                    );
6678
                    $i++;
6679
                }
6680
            } else {
6681
                // Normal question render.
6682
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6683
            }
6684
6685
            // For sequential exercises.
6686
            if ($this->type == ONE_PER_PAGE) {
6687
                // quits the loop
6688
                break;
6689
            }
6690
        }
6691
        // end foreach()
6692
6693
        if ($this->type == ALL_ON_ONE_PAGE) {
6694
            $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 6612. 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...
6695
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6696
        }
6697
    }
6698
6699
    /**
6700
     * @param int $questionId
6701
     * @param array $attemptList
6702
     * @param array $remindList
6703
     * @param int $i
6704
     * @param int $current_question
6705
     * @param array $questions_in_media
6706
     * @param bool $last_question_in_media
6707
     * @param array $realQuestionList
6708
     * @param bool $generateJS
6709
     * @return null
6710
     */
6711
    public function renderQuestion(
6712
        $questionId,
6713
        $attemptList,
6714
        $remindList,
6715
        $i,
6716
        $current_question,
6717
        $questions_in_media = array(),
6718
        $last_question_in_media = false,
6719
        $realQuestionList,
6720
        $generateJS = true
6721
    ) {
6722
6723
        // With this option on the question is loaded via AJAX
6724
        //$generateJS = true;
6725
        //$this->loadQuestionAJAX = true;
6726
6727
        if ($generateJS && $this->loadQuestionAJAX) {
6728
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId;
6729
            $params = array(
6730
                'questionId' => $questionId,
6731
                'attemptList'=> $attemptList,
6732
                'remindList' => $remindList,
6733
                'i' => $i,
6734
                'current_question' => $current_question,
6735
                'questions_in_media' => $questions_in_media,
6736
                'last_question_in_media' => $last_question_in_media
6737
            );
6738
            $params = json_encode($params);
6739
6740
            $script = '<script>
6741
            $(function(){
6742
                var params = '.$params.';
6743
                $.ajax({
6744
                    type: "GET",
6745
                    async: false,
6746
                    data: params,
6747
                    url: "'.$url.'",
6748
                    success: function(return_value) {
6749
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6750
                    }
6751
                });
6752
            });
6753
            </script>
6754
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6755
            echo $script;
6756
        } else {
6757
6758
            global $origin;
6759
            $question_obj = Question::read($questionId);
6760
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6761
6762
            $remind_highlight = null;
6763
6764
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6765
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6766
                $remind_highlight = 'no_remind_highlight';
6767
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6768
                    return null;
6769
                }
6770
            }
6771
6772
            $attributes = array('id' =>'remind_list['.$questionId.']');
6773
            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...
6774
                //$attributes['checked'] = 1;
6775
                //$remind_highlight = ' remind_highlight ';
6776
            }
6777
6778
            // Showing the question
6779
6780
            $exercise_actions  = null;
6781
6782
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6783
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6784
6785
            // Shows the question + possible answers
6786
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6787
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6788
6789
            // Button save and continue
6790 View Code Duplication
            switch ($this->type) {
6791
                case ONE_PER_PAGE:
6792
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6793
                    break;
6794
                case ALL_ON_ONE_PAGE:
6795
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
6796
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6797
                    $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
6798
                    break;
6799
            }
6800
6801
            if (!empty($questions_in_media)) {
6802
                $count_of_questions_inside_media = count($questions_in_media);
6803
                if ($count_of_questions_inside_media > 1) {
6804
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
6805
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6806
                    $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
6807
                }
6808
6809
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6810
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6811
                }
6812
            }
6813
6814
            // Checkbox review answers
6815
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6816
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6817
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6818
            }
6819
6820
            echo Display::div(' ', array('class'=>'clear'));
6821
6822
            $paginationCounter = null;
6823
            if ($this->type == ONE_PER_PAGE) {
6824
                if (empty($questions_in_media)) {
6825
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6826
                } else {
6827
                    if ($last_question_in_media) {
6828
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6829
                    }
6830
                }
6831
            }
6832
6833
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6834
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6835
            echo '</div>';
6836
        }
6837
    }
6838
6839
    /**
6840
     * Shows a question
6841
     * @param Question $objQuestionTmp
6842
     * @param bool $only_questions if true only show the questions, no exercise title
6843
     * @param bool $origin origin i.e = learnpath
6844
     * @param string $current_item current item from the list of questions
6845
     * @param bool $show_title
6846
     * @param bool $freeze
6847
     * @param array $user_choice
6848
     * @param bool $show_comment
6849
     * @param null $exercise_feedback
6850
     * @param bool $show_answers
6851
     * @param null $modelType
6852
     * @param bool $categoryMinusOne
6853
     * @return bool|null|string
6854
     */
6855
    public function showQuestion(
6856
        Question $objQuestionTmp,
6857
        $only_questions = false,
6858
        $origin = false,
6859
        $current_item = '',
6860
        $show_title = true,
6861
        $freeze = false,
6862
        $user_choice = array(),
6863
        $show_comment = false,
6864
        $exercise_feedback = null,
6865
        $show_answers = false,
6866
        $modelType = null,
6867
        $categoryMinusOne = true
6868
    ) {
6869
        // Text direction for the current language
6870
        //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
6871
        // Change false to true in the following line to enable answer hinting
6872
        $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
6873
        // Reads question information
6874
        if (!$objQuestionTmp) {
6875
            // Question not found
6876
            return false;
6877
        }
6878
6879
        $html = null;
6880
6881
        $questionId = $objQuestionTmp->id;
6882
6883
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
6884
            $show_comment = false;
6885
        }
6886
6887
        $answerType = $objQuestionTmp->selectType();
6888
        $pictureName = $objQuestionTmp->selectPicture();
6889
6890
        $s = null;
6891
        $form = new FormValidator('question');
6892
        $renderer = $form->defaultRenderer();
6893
        $form_template = '{content}';
6894
        $renderer->setFormTemplate($form_template);
6895
6896
        if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
6897
            // Question is not a hotspot
6898
            if (!$only_questions) {
6899
                $questionDescription = $objQuestionTmp->selectDescription();
6900
                if ($show_title) {
6901
                    $categoryName = TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id, null, true, $categoryMinusOne);
0 ignored issues
show
Bug introduced by
The method getCategoryNamesForQuestion() does not exist on TestCategory. Did you maybe mean getCategory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
6902
                    $html .= $categoryName;
6903
                    $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
6904
                    if (!empty($questionDescription)) {
6905
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6906
                    }
6907 View Code Duplication
                } else {
6908
                    $html .= '<div class="media">';
6909
                    $html .= '<div class="pull-left">';
6910
                    $html .= '<div class="media-object">';
6911
                    $html .= Display::div($current_item, array('class' => 'question_no_title'));
6912
                    $html .= '</div>';
6913
                    $html .= '</div>';
6914
                    $html .= '<div class="media-body">';
6915
                    if (!empty($questionDescription)) {
6916
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6917
                    }
6918
                    $html .= '</div>';
6919
                    $html .= '</div>';
6920
                }
6921
            }
6922
6923
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
6924
                return null;
6925
            }
6926
6927
            $html .= '<div class="question_options">';
6928
            // construction of the Answer object (also gets all answers details)
6929
            $objAnswerTmp = new Answer($questionId, null, $this);
6930
6931
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
6932
            $course_id = api_get_course_int_id();
6933
            $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
6934
6935
            // For "matching" type here, we need something a little bit special
6936
            // because the match between the suggestions and the answers cannot be
6937
            // done easily (suggestions and answers are in the same table), so we
6938
            // have to go through answers first (elems with "correct" value to 0).
6939
            $select_items = array();
6940
            //This will contain the number of answers on the left side. We call them
6941
            // suggestions here, for the sake of comprehensions, while the ones
6942
            // on the right side are called answers
6943
            $num_suggestions = 0;
6944
6945
            if ($answerType == MATCHING || $answerType == DRAGGABLE) {
6946 View Code Duplication
                if ($answerType == DRAGGABLE) {
6947
                    $s .= '<div class="ui-widget ui-helper-clearfix">
6948
                            <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
6949
                } else {
6950
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
6951
                    $s .= '<table class="data_table">';
6952
                }
6953
6954
                $j = 1; //iterate through answers
6955
                $letter = 'A'; //mark letters for each answer
6956
                $answer_matching = array();
6957
                $capital_letter = array();
6958
                //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
6959
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
6960
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
6961
                    $answer = $objAnswerTmp->selectAnswer($answerId);
6962
                    if ($answerCorrect == 0) {
6963
                        // options (A, B, C, ...) that will be put into the list-box
6964
                        // have the "correct" field set to 0 because they are answer
6965
                        $capital_letter[$j] = $letter;
6966
                        //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
6967
                        $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
6968
                        $j++;
6969
                        $letter++;
6970
                    }
6971
                }
6972
6973
                $i = 1;
6974
6975
                $select_items[0]['id'] = 0;
6976
                $select_items[0]['letter'] = '--';
6977
                $select_items[0]['answer'] = '';
6978
6979 View Code Duplication
                foreach ($answer_matching as $id => $value) {
6980
                    $select_items[$i]['id'] = $value['id'];
6981
                    $select_items[$i]['letter'] = $capital_letter[$id];
6982
                    $select_items[$i]['answer'] = $value['answer'];
6983
                    $i++;
6984
                }
6985
                $num_suggestions = ($nbrAnswers - $j) + 1;
6986
            } elseif ($answerType == FREE_ANSWER) {
6987
                $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
6988
                $toolBar = 'TestFreeAnswer';
6989
                if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
6990
                    $toolBar = 'TestFreeAnswerStrict';
6991
                }
6992
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
6993
                $form->setDefaults(array("choice[".$questionId."]" => $content));
6994
                $s .= $form->return_form();
0 ignored issues
show
Deprecated Code introduced by
The method FormValidator::return_form() has been deprecated with message: use returnForm()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
6995
            } elseif ($answerType == ORAL_EXPRESSION) {
6996
                // Add nanogong
6997 View Code Duplication
                if (api_get_setting('document.enable_nanogong') == 'true') {
6998
6999
                    //@todo pass this as a parameter
7000
                    global $exercise_stat_info, $exerciseId;
7001
7002
                    if (!empty($exercise_stat_info)) {
7003
                        $params = array(
7004
                            'exercise_id' => $exercise_stat_info['exe_exo_id'],
7005
                            'exe_id' => $exercise_stat_info['exe_id'],
7006
                            'question_id' => $questionId
7007
                        );
7008
                    } else {
7009
                        $params = array(
7010
                            'exercise_id' => $exerciseId,
7011
                            'exe_id' => 'temp_exe',
7012
                            'question_id' => $questionId
7013
                        );
7014
                    }
7015
7016
                    $nano = new Nanogong($params);
7017
                    $s .= $nano->show_button();
7018
                }
7019
7020
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => 'TestFreeAnswer'));
7021
                //$form->setDefaults(array("choice[".$questionId."]" => $content));
7022
                $s .= $form->return_form();
0 ignored issues
show
Deprecated Code introduced by
The method FormValidator::return_form() has been deprecated with message: use returnForm()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
7023
            }
7024
7025
            // Now navigate through the possible answers, using the max number of
7026
            // answers for the question as a limiter
7027
            $lines_count = 1; // a counter for matching-type answers
7028
7029
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7030
                $header = Display::tag('th', get_lang('Options'));
7031
                foreach ($objQuestionTmp->options as $item) {
0 ignored issues
show
Bug introduced by
The property options 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...
7032
                    $header .= Display::tag('th', $item);
7033
                }
7034
                if ($show_comment) {
7035
                    $header .= Display::tag('th', get_lang('Feedback'));
7036
                }
7037
                $s .= '<table class="data_table">';
7038
                $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7039
            }
7040
7041 View Code Duplication
            if ($show_comment) {
7042
                if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
7043
                    $header = Display::tag('th', get_lang('Options'));
7044
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
7045
                        $header .= Display::tag('th', get_lang('Feedback'));
7046
                    }
7047
                    $s .= '<table class="data_table">';
7048
                    $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7049
                }
7050
            }
7051
7052
            $matching_correct_answer = 0;
7053
            $user_choice_array = array();
7054
            if (!empty($user_choice)) {
7055
                foreach ($user_choice as $item) {
7056
                    $user_choice_array[] = $item['answer'];
7057
                }
7058
            }
7059
7060
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7061
                $answer = $objAnswerTmp->selectAnswer($answerId);
7062
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7063
                $comment = $objAnswerTmp->selectComment($answerId);
7064
7065
                //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
7066
                $numAnswer = $answerId;
7067
7068
                $attributes = array();
7069
                // Unique answer
7070
                if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
7071
7072
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7073 View Code Duplication
                    if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
7074
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7075
                    } else {
7076
                        $attributes = array('id' => $input_id);
7077
                    }
7078
7079
                    if ($debug_mark_answer) {
7080
                        if ($answerCorrect) {
7081
                            $attributes['checked'] = 1;
7082
                            $attributes['selected'] = 1;
7083
                        }
7084
                    }
7085
7086
                    $answer = Security::remove_XSS($answer);
7087
                    $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
7088
7089
                    $answer_input = null;
7090
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7091
                        $attributes['style'] = 'display:none';
7092
                        $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
7093
                    }
7094
7095
                    $answer_input .= '<label class="radio">';
7096
                    $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
7097
                    $answer_input .= $answer;
7098
                    $answer_input .= '</label>';
7099
7100
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7101
                        $answer_input .= "</div>";
7102
                    }
7103
7104
                    if ($show_comment) {
7105
                        $s .= '<tr><td>';
7106
                        $s .= $answer_input;
7107
                        $s .= '</td>';
7108
                        $s .= '<td>';
7109
                        $s .= $comment;
7110
                        $s .= '</td>';
7111
                        $s .= '</tr>';
7112
                    } else {
7113
                        $s .= $answer_input;
7114
                    }
7115
7116
                } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
7117
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7118
                    $answer = Security::remove_XSS($answer);
7119
7120
                    if (in_array($numAnswer, $user_choice_array)) {
7121
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7122
                    } else {
7123
                        $attributes = array('id' => $input_id);
7124
                    }
7125
7126
                    if ($debug_mark_answer) {
7127
                        if ($answerCorrect) {
7128
                            $attributes['checked'] = 1;
7129
                            $attributes['selected'] = 1;
7130
                        }
7131
                    }
7132
7133 View Code Duplication
                    if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
7134
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7135
7136
                        $answer_input = '<label class="checkbox">';
7137
                        $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
7138
                        $answer_input .= $answer;
7139
                        $answer_input .= '</label>';
7140
7141
                        if ($show_comment) {
7142
                            $s .= '<tr><td>';
7143
                            $s .= $answer_input;
7144
                            $s .= '</td>';
7145
                            $s .= '<td>';
7146
                            $s .= $comment;
7147
                            $s .= '</td>';
7148
                            $s .='</tr>';
7149
                        } else {
7150
                            $s .= $answer_input;
7151
                        }
7152
                    } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
7153
7154
                        $my_choice = array();
7155
                        if (!empty($user_choice_array)) {
7156
                            foreach ($user_choice_array as $item) {
7157
                                $item = explode(':', $item);
7158
                                $my_choice[$item[0]] = $item[1];
7159
                            }
7160
                        }
7161
7162
                        $s .='<tr>';
7163
                        $s .= Display::tag('td', $answer);
7164
7165
                        if (!empty($quiz_question_options)) {
7166
                            foreach ($quiz_question_options as $id => $item) {
7167
                                $id = $item['iid'];
7168
                                if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
7169
                                    $attributes = array('checked' => 1, 'selected' => 1);
7170
                                } else {
7171
                                    $attributes = array();
7172
                                }
7173
7174
                                if ($debug_mark_answer) {
7175
                                    if ($id == $answerCorrect) {
7176
                                        $attributes['checked'] = 1;
7177
                                        $attributes['selected'] = 1;
7178
                                    }
7179
                                }
7180
                                $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
7181
                            }
7182
                        }
7183
7184
                        if ($show_comment) {
7185
                            $s .= '<td>';
7186
                            $s .= $comment;
7187
                            $s .= '</td>';
7188
                        }
7189
                        $s.='</tr>';
7190
                    }
7191
7192 View Code Duplication
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
7193
7194
                    // multiple answers
7195
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7196
7197
                    if (in_array($numAnswer, $user_choice_array)) {
7198
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7199
                    } else {
7200
                        $attributes = array('id' => $input_id);
7201
                    }
7202
7203
                    if ($debug_mark_answer) {
7204
                        if ($answerCorrect) {
7205
                            $attributes['checked'] = 1;
7206
                            $attributes['selected'] = 1;
7207
                        }
7208
                    }
7209
7210
                    $answer = Security::remove_XSS($answer);
7211
                    $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7212
                    $answer_input .= '<label class="checkbox">';
7213
                    $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
7214
                    $answer_input .= $answer;
7215
                    $answer_input .= '</label>';
7216
7217
                    if ($show_comment) {
7218
                        $s.= '<tr>';
7219
                        $s .= '<td>';
7220
                        $s.= $answer_input;
7221
                        $s .= '</td>';
7222
                        $s .= '<td>';
7223
                        $s .= $comment;
7224
                        $s .= '</td>';
7225
                        $s.= '</tr>';
7226
                    } else {
7227
                        $s.= $answer_input;
7228
                    }
7229
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7230
                    $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7231
7232
                    $my_choice = array();
7233
                    if (!empty($user_choice_array)) {
7234
                        foreach ($user_choice_array as $item) {
7235
                            $item = explode(':', $item);
7236
                            $my_choice[$item[0]] = $item[1];
7237
                        }
7238
                    }
7239
                    $answer = Security::remove_XSS($answer);
7240
                    $s .='<tr>';
7241
                    $s .= Display::tag('td', $answer);
7242
7243
                    foreach ($objQuestionTmp->options as $key => $item) {
7244
                        if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
7245
                            $attributes = array('checked' => 1, 'selected' => 1);
7246
                        } else {
7247
                            $attributes = array();
7248
                        }
7249
7250
                        if ($debug_mark_answer) {
7251
                            if ($key == $answerCorrect) {
7252
                                $attributes['checked'] = 1;
7253
                                $attributes['selected'] = 1;
7254
                            }
7255
                        }
7256
                        $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
7257
                    }
7258
7259
                    if ($show_comment) {
7260
                        $s .= '<td>';
7261
                        $s .= $comment;
7262
                        $s .= '</td>';
7263
                    }
7264
                    $s.='</tr>';
7265
                } elseif ($answerType == FILL_IN_BLANKS) {
7266
                    list($answer) = explode('::', $answer);
7267
7268
                    //Correct answer
7269
                    api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
7270
7271
                    //Student's answezr
7272
                    if (isset($user_choice[0]['answer'])) {
7273
                        api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
7274
                        $student_answer_list = $student_answer_list[0];
7275
                    }
7276
7277
                    //If debug
7278
                    if ($debug_mark_answer) {
7279
                        $student_answer_list = $correct_answer_list[0];
7280
                    }
7281
7282
                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
7283
                        $correct_answer_list = $correct_answer_list[0];
7284
                        $i = 0;
7285
                        foreach ($correct_answer_list as $correct_item) {
7286
                            $value = null;
7287
                            if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
7288
7289
                                //Cleaning student answer list
7290
                                $value = strip_tags($student_answer_list[$i]);
7291
                                $value = api_substr($value, 1, api_strlen($value) - 2);
7292
                                $value = explode('/', $value);
7293
7294
                                if (!empty($value[0])) {
7295
                                    $value = str_replace('&nbsp;', '', trim($value[0]));
7296
                                }
7297
                                $correct_item = preg_quote($correct_item);
7298
                                $correct_item = api_preg_replace('|/|', '\/', $correct_item);   // to prevent error if there is a / in the text to find
7299
                                $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
7300
                            }
7301
                            $i++;
7302
                        }
7303
                    } else {
7304
                        $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
7305
                    }
7306
                    $s .= $answer;
7307
                } elseif ($answerType == MATCHING) {
7308
                    // matching type, showing suggestions and answers
7309
                    // TODO: replace $answerId by $numAnswer
7310
7311
                    if ($lines_count == 1) {
7312
                        $s .= $objAnswerTmp->getJs();
7313
                    }
7314
                    if ($answerCorrect != 0) {
7315
                        // only show elements to be answered (not the contents of
7316
                        // the select boxes, who are correct = 0)
7317
                        $s .= '<tr><td width="45%">';
7318
                        $parsed_answer = $answer;
7319
                        $windowId = $questionId.'_'.$lines_count;
7320
                        //left part questions
7321
                        $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
7322
                                    <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
7323
                                </div>
7324
                                </td>';
7325
7326
                        // middle part (matches selects)
7327
7328
                        $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
7329
                        $s .= '<div style="display:block">';
7330
7331
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
7332
                        $selectedValue = 0;
7333
                        // fills the list-box
7334
                        $item = 0;
7335 View Code Duplication
                        foreach ($select_items as $val) {
7336
                            // set $debug_mark_answer to true at public static function start to
7337
                            // show the correct answer with a suffix '-x'
7338
                            $selected = '';
7339
                            if ($debug_mark_answer) {
7340
                                if ($val['id'] == $answerCorrect) {
7341
                                    $selected = 'selected="selected"';
7342
                                    $selectedValue = $val['id'];
7343
                                }
7344
                            }
7345
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7346
                                $selected = 'selected="selected"';
7347
                                $selectedValue = $val['id'];
7348
                            }
7349
                            //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
7350
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7351
                            $item++;
7352
                        }
7353
7354 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7355
                            $s.= '<script>
7356
                                jsPlumb.ready(function() {
7357
                                    jsPlumb.connect({
7358
                                        source: "window_'.$windowId.'",
7359
                                        target: "window_'.$questionId.'_'.$selectedValue.'_answer",
7360
                                        endpoint:["Blank", { radius:15 }],
7361
                                        anchor:["RightMiddle","LeftMiddle"],
7362
                                        paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
7363
                                        connector: [connectorType, { curviness: curvinessValue } ],
7364
                                    })
7365
                                });
7366
                                </script>';
7367
                        }
7368
                        $s .= '</select></div></td>';
7369
7370
                        $s.='<td width="45%" valign="top" >';
7371
7372 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7373
                            $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
7374
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7375
                                  </div>';
7376
                        } else {
7377
                            $s.='&nbsp;';
7378
                        }
7379
7380
                        $s .= '</td>';
7381
                        $s .= '</tr>';
7382
                        $lines_count++;
7383
                        //if the left side of the "matching" has been completely
7384
                        // shown but the right side still has values to show...
7385
                        if (($lines_count - 1) == $num_suggestions) {
7386
                            // if it remains answers to shown at the right side
7387
                            while (isset($select_items[$lines_count])) {
7388
                                $s .= '<tr>
7389
                                      <td colspan="2"></td>
7390
                                      <td valign="top">';
7391
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7392
                                $s .= $select_items[$lines_count]['answer'];
7393
                                $s.="</td>
7394
                                </tr>";
7395
                                $lines_count++;
7396
                            } // end while()
7397
                        }  // end if()
7398
                        $matching_correct_answer++;
7399
                    }
7400
                } elseif ($answerType ==  DRAGGABLE) {
7401
                    // matching type, showing suggestions and answers
7402
                    // TODO: replace $answerId by $numAnswer
7403
7404
                    if ($answerCorrect != 0) {
7405
                        // only show elements to be answered (not the contents of
7406
                        // the select boxes, who are correct = 0)
7407
                        $s .= '<td>';
7408
                        $parsed_answer = $answer;
7409
                        $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
7410
7411
                        //left part questions
7412
                        $s .= '<li class="ui-state-default" id="'.$windowId.'">';
7413
                        $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
7414
                                   '.$parsed_answer.'
7415
                                </div>';
7416
7417
                        $s .= '<div style="display:none">';
7418
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
7419
                        $selectedValue = 0;
7420
                        // fills the list-box
7421
                        $item = 0;
7422 View Code Duplication
                        foreach ($select_items as $val) {
7423
                            // set $debug_mark_answer to true at function start to
7424
                            // show the correct answer with a suffix '-x'
7425
                            $selected = '';
7426
                            if ($debug_mark_answer) {
7427
                                if ($val['id'] == $answerCorrect) {
7428
                                    $selected = 'selected="selected"';
7429
                                    $selectedValue = $val['id'];
7430
                                }
7431
                            }
7432
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7433
                                $selected = 'selected="selected"';
7434
                                $selectedValue = $val['id'];
7435
                            }
7436
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7437
                            $item++;
7438
                        }
7439
                        $s .= '</select>';
7440
7441 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7442
                            $s.= '<script>
7443
                                $(function() {
7444
                                    deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
7445
                                });
7446
                                </script>';
7447
                        }
7448
7449 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7450
                            $s.= '<div id="window_'.$windowId.'_answer" class="">
7451
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7452
                                  </div>';
7453
                        } else {
7454
                            $s.='&nbsp;';
7455
                        }
7456
                        $lines_count++;
7457
                        //if the left side of the "matching" has been completely
7458
                        // shown but the right side still has values to show...
7459
7460 View Code Duplication
                        if (($lines_count - 1) == $num_suggestions) {
7461
                            // if it remains answers to shown at the right side
7462
                            while (isset($select_items[$lines_count])) {
7463
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7464
                                $s .= $select_items[$lines_count]['answer'];
7465
                                $lines_count++;
7466
                            }
7467
                        }
7468
                        $s .= '</div>';
7469
                        $matching_correct_answer++;
7470
                        $s .= '</li>';
7471
                    }
7472
                }
7473
            } // end for()
7474
7475
            if ($show_comment) {
7476
                $s .= '</table>';
7477
            } else {
7478
                if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
7479
                    $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7480
                    $s .= '</table>';
7481
                }
7482
            }
7483
7484
            if ($answerType == DRAGGABLE) {
7485
                $s .= '</ul><div class="clear"></div>';
7486
7487
                $counterAnswer = 1;
7488
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7489
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7490
                    $windowId = $questionId.'_'.$counterAnswer;
7491
                    if ($answerCorrect == 0) {
7492
                        $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
7493
                        $counterAnswer++;
7494
                    }
7495
                }
7496
            }
7497
7498
            if ($answerType == MATCHING) {
7499
                $s .= '</div>';
7500
            }
7501
7502
            $s .= '</div>';
7503
7504
            // destruction of the Answer object
7505
            unset($objAnswerTmp);
7506
7507
            // destruction of the Question object
7508
            unset($objQuestionTmp);
7509
7510
            $html .= $s;
7511
            return $html;
7512
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
7513
            // Question is a HOT_SPOT
7514
            //checking document/images visibility
7515 View Code Duplication
            if (api_is_platform_admin() || api_is_course_admin()) {
7516
                $course = api_get_course_info();
7517
                $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
7518
                if (is_numeric($doc_id)) {
7519
                    $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
7520
                    if (!$images_folder_visibility) {
7521
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
7522
                        Display::display_warning_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'));
7523
                    }
7524
                }
7525
            }
7526
            $questionName = $objQuestionTmp->selectTitle();
7527
            $questionDescription = $objQuestionTmp->selectDescription();
7528
7529
            if ($freeze) {
7530
                $s .= Display::img($objQuestionTmp->selectPicturePath());
0 ignored issues
show
Security Bug introduced by
It seems like $objQuestionTmp->selectPicturePath() targeting Question::selectPicturePath() can also be of type false; however, Display::img() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
7531
                $html .= $s;
7532
                return $html;
7533
            }
7534
7535
            // Get the answers, make a list
7536
            $objAnswerTmp = new Answer($questionId);
7537
7538
            // get answers of hotpost
7539
            $answers_hotspot = array();
7540
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7541
                //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
7542
                $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
7543
            }
7544
7545
            // display answers of hotpost order by id
7546
            $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
7547
            if (!empty($answers_hotspot)) {
7548
                ksort($answers_hotspot);
7549
                foreach ($answers_hotspot as $key => $value) {
7550
                    $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
7551
                }
7552
            }
7553
            $answer_list .= '</dl></div>';
7554
7555
            if ($answerType == HOT_SPOT_DELINEATION) {
7556
                $answer_list = '';
7557
                $swf_file = 'hotspot_delineation_user';
7558
                $swf_height = 405;
7559
            } else {
7560
                $swf_file = 'hotspot_user';
7561
                $swf_height = 436;
7562
            }
7563
7564
            if (!$only_questions) {
7565
                if ($show_title) {
7566
                    $html .=  TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id);
0 ignored issues
show
Bug introduced by
The method getCategoryNamesForQuestion() does not exist on TestCategory. Did you maybe mean getCategory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
7567
                    $html .=  '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
7568
                    $html .=  $questionDescription;
7569 View Code Duplication
                } else {
7570
                    $html .= '<div class="media">';
7571
                    $html .= '<div class="pull-left">';
7572
                    $html .= '<div class="media-object">';
7573
                    $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
7574
                    $html .= '</div>';
7575
                    $html .= '</div>';
7576
                    $html .= '<div class="media-body">';
7577
                    if (!empty($questionDescription)) {
7578
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7579
                    }
7580
                    $html .= '</div>';
7581
                    $html .= '</div>';
7582
                }
7583
                //@todo I need to the get the feedback type
7584
                $html .=  '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
7585
                $html .=  '<table class="exercise_questions">
7586
                           <tr>
7587
                            <td valign="top" colspan="2">';
7588
                $html .=  '</td></tr>';
7589
            }
7590
7591
            $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
7592
7593
            $s .= ' <script type="text/javascript" src="../plugin/hotspot/JavaScriptFlashGateway.js"></script>
7594
                    <script src="../plugin/hotspot/hotspot.js" type="text/javascript" ></script>
7595
                    <script type="text/javascript">
7596
                    <!--
7597
                    // Globals
7598
                    // Major version of Flash required
7599
                    var requiredMajorVersion = 7;
7600
                    // Minor version of Flash required
7601
                    var requiredMinorVersion = 0;
7602
                    // Minor version of Flash required
7603
                    var requiredRevision = 0;
7604
                    // the version of javascript supported
7605
                    var jsVersion = 1.0;
7606
                    // -->
7607
                    </script>
7608
                    <script language="VBScript" type="text/vbscript">
7609
                    <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
7610
                    Function VBGetSwfVer(i)
7611
                      on error resume next
7612
                      Dim swControl, swVersion
7613
                      swVersion = 0
7614
7615
                      set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
7616
                      if (IsObject(swControl)) then
7617
                        swVersion = swControl.GetVariable("$version")
7618
                      end if
7619
                      VBGetSwfVer = swVersion
7620
                    End Function
7621
                    // -->
7622
                    </script>
7623
7624
                    <script language="JavaScript1.1" type="text/javascript">
7625
                    <!-- // Detect Client Browser type
7626
                    var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
7627
                    var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
7628
                    var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
7629
                    jsVersion = 1.1;
7630
                    // JavaScript helper required to detect Flash Player PlugIn version information
7631
                    function JSGetSwfVer(i) {
7632
                        // NS/Opera version >= 3 check for Flash plugin in plugin array
7633
                        if (navigator.plugins != null && navigator.plugins.length > 0) {
7634
                            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
7635
                                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
7636
                                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
7637
                                descArray = flashDescription.split(" ");
7638
                                tempArrayMajor = descArray[2].split(".");
7639
                                versionMajor = tempArrayMajor[0];
7640
                                versionMinor = tempArrayMajor[1];
7641
                                if ( descArray[3] != "" ) {
7642
                                    tempArrayMinor = descArray[3].split("r");
7643
                                } else {
7644
                                    tempArrayMinor = descArray[4].split("r");
7645
                                }
7646
                                versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
7647
                                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
7648
                            } else {
7649
                                flashVer = -1;
7650
                            }
7651
                        }
7652
                        // MSN/WebTV 2.6 supports Flash 4
7653
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
7654
                        // WebTV 2.5 supports Flash 3
7655
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
7656
                        // older WebTV supports Flash 2
7657
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
7658
                        // Can\'t detect in all other cases
7659
                        else {
7660
                            flashVer = -1;
7661
                        }
7662
                        return flashVer;
7663
                    }
7664
                    // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
7665
7666
                    function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
7667
                        reqVer = parseFloat(reqMajorVer + "." + reqRevision);
7668
                        // loop backwards through the versions until we find the newest version
7669
                        for (i=25;i>0;i--) {
7670
                            if (isIE && isWin && !isOpera) {
7671
                                versionStr = VBGetSwfVer(i);
7672
                            } else {
7673
                                versionStr = JSGetSwfVer(i);
7674
                            }
7675
                            if (versionStr == -1 ) {
7676
                                return false;
7677
                            } else if (versionStr != 0) {
7678
                                if(isIE && isWin && !isOpera) {
7679
                                    tempArray         = versionStr.split(" ");
7680
                                    tempString        = tempArray[1];
7681
                                    versionArray      = tempString .split(",");
7682
                                } else {
7683
                                    versionArray      = versionStr.split(".");
7684
                                }
7685
                                versionMajor      = versionArray[0];
7686
                                versionMinor      = versionArray[1];
7687
                                versionRevision   = versionArray[2];
7688
7689
                                versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
7690
                                versionNum        = parseFloat(versionString);
7691
                                // is the major.revision >= requested major.revision AND the minor version >= requested minor
7692
                                if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
7693
                                    return true;
7694
                                } else {
7695
                                    return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
7696
                                }
7697
                            }
7698
                        }
7699
                    }
7700
                    // -->
7701
                    </script>';
7702
            $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
7703
                    <script>
7704
                        // Version check based upon the values entered above in "Globals"
7705
                        var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
7706
7707
                        // Check to see if the version meets the requirements for playback
7708
                        if (hasReqestedVersion) {  // if we\'ve detected an acceptable version
7709
                            var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
7710
                                        + \'<param name="wmode" value="transparent">\'
7711
                                        + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
7712
                                        + \'<\/object>\';
7713
                            document.write(oeTags);   // embed the Flash Content SWF when all tests are passed
7714
                        } else {  // flash is too old or we can\'t detect the plugin
7715
                            var alternateContent = "Error<br \/>"
7716
                                + "Hotspots requires Macromedia Flash 7.<br \/>"
7717
                                + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
7718
                            document.write(alternateContent);  // insert non-flash content
7719
                        }
7720
                    </script>
7721
                    </td>
7722
                    <td valign="top" align="left">'.$answer_list.'</td></tr>
7723
                    </table>
7724
            </td></tr>';
7725
            $html .= $s;
7726
            $html .= '</table>';
7727
            return $html;
7728
        }
7729
        return $nbrAnswers;
0 ignored issues
show
Bug introduced by
The variable $nbrAnswers seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
7730
    }
7731
7732
    /**
7733
     * @param int $exeId
7734
     * @return array
7735
     */
7736
    public function returnQuestionListByAttempt($exeId)
7737
    {
7738
        return $this->displayQuestionListByAttempt($exeId, false, true);
7739
    }
7740
7741
    /**
7742
     * Display the exercise results
7743
     * @param int  $exe_id
7744
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7745
     * @param bool $returnExerciseResult return array with exercise result info
7746
     * @return mixed
7747
     */
7748
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7749
    {
7750
        global $origin, $debug;
7751
7752
        //Getting attempt info
7753
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7754
7755
        //Getting question list
7756
        $question_list = array();
7757
        if (!empty($exercise_stat_info['data_tracking'])) {
7758
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7759
        } else {
7760
            //Try getting the question list only if save result is off
7761
            if ($saveUserResult == false) {
7762
                $question_list = $this->selectQuestionList();
7763
            }
7764
            error_log("Data tracking is empty! exe_id: $exe_id");
7765
        }
7766
7767
        $counter = 1;
7768
        $total_score = 0;
7769
        $total_weight = 0;
7770
7771
        $exercise_content = null;
7772
7773
        // Hide results
7774
        $show_results = false;
7775
        $show_only_score = false;
7776
7777
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7778
            $show_results = true;
7779
        }
7780
7781
        $showScoreOptions = [
7782
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7783
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7784
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7785
        ];
7786
7787
        if (in_array($this->results_disabled, $showScoreOptions)) {
7788
            $show_only_score = true;
7789
        }
7790
7791 View Code Duplication
        if ($show_results || $show_only_score) {
7792
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7793
            // Shows exercise header.
7794
            echo $this->show_exercise_result_header(
7795
                $user_info['complete_name'],
7796
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7797
                $exercise_stat_info['duration']
7798
            );
7799
        }
7800
7801
        // Display text when test is finished #4074 and for LP #4227
7802
        $end_of_message = $this->selectTextWhenFinished();
7803
        if (!empty($end_of_message)) {
7804
            Display::display_normal_message($end_of_message, false);
7805
            echo "<div class='clear'>&nbsp;</div>";
7806
        }
7807
7808
        $question_list_answers = array();
7809
        $media_list = array();
7810
        $category_list = array();
7811
        $tempParentId = null;
7812
        $mediaCounter = 0;
7813
7814
        $exerciseResultInfo = array();
7815
7816
        // Loop over all question to show results for each of them, one by one
7817
        if (!empty($question_list)) {
7818
            if ($debug) {
7819
                error_log('Looping question_list '.print_r($question_list, 1));
7820
            }
7821
7822
            foreach ($question_list as $questionId) {
7823
7824
                // Creates a temporary Question object
7825
                $objQuestionTmp = Question::read($questionId);
7826
7827
                // This variable commes from exercise_submit_modal.php
7828
                ob_start();
7829
                $hotspot_delineation_result = null;
7830
7831
                // We're inside *one* question. Go through each possible answer for this question
7832
                $result = $this->manageAnswers(
7833
                    $exercise_stat_info['exe_id'],
7834
                    $questionId,
7835
                    null,
7836
                    'exercise_result',
7837
                    array(),
7838
                    $saveUserResult,
7839
                    true,
7840
                    $show_results,
7841
                    $hotspot_delineation_result
7842
                );
7843
7844
                if (empty($result)) {
7845
                    continue;
7846
                }
7847
7848
                $total_score += $result['score'];
7849
                $total_weight += $result['weight'];
7850
7851
                $question_list_answers[] = array(
7852
                    'question' => $result['open_question'],
7853
                    'answer' => $result['open_answer'],
7854
                    'answer_type' => $result['answer_type']
7855
                );
7856
7857
                $my_total_score = $result['score'];
7858
                $my_total_weight = $result['weight'];
7859
7860
                // Category report
7861
                $category_was_added_for_this_test = false;
7862
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7863
7864
                $category_list = array();
7865
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7866
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7867
                        if (!isset($category_list[$category_id])) {
7868
                            $category_list[$category_id] = array();
7869
                            $category_list[$category_id]['score'] = 0;
7870
                            $category_list[$category_id]['total'] = 0;
7871
                        }
7872
                        $category_list[$category_id]['score'] += $my_total_score;
7873
                        $category_list[$category_id]['total'] += $my_total_weight;
7874
                        $category_was_added_for_this_test = true;
7875
                    }
7876
                }
7877
7878
                // No category for this question!
7879
                if ($category_was_added_for_this_test == false) {
7880
                    if (!isset($category_list['none'])) {
7881
                        $category_list['none'] = array();
7882
                        $category_list['none']['score'] = 0;
7883
                        $category_list['none']['total'] = 0;
7884
                    }
7885
7886
                    $category_list['none']['score'] += $my_total_score;
7887
                    $category_list['none']['total'] += $my_total_weight;
7888
                }
7889
7890
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7891
                    $my_total_score = 0;
7892
                }
7893
7894
                $comnt = null;
7895 View Code Duplication
                if ($show_results) {
7896
                    $comnt = get_comments($exe_id, $questionId);
7897
                    if (!empty($comnt)) {
7898
                        echo '<b>'.get_lang('Feedback').'</b>';
7899
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7900
                    }
7901
                }
7902
7903
                $score = array();
7904
                $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
7905
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
7906
                $score['score'] = $my_total_score;
7907
                $score['weight'] = $my_total_weight;
7908
                $score['comments'] = $comnt;
7909
7910
                $exerciseResultInfo[$questionId]['score'] = $score;
7911
                $exerciseResultInfo[$questionId]['details'] = $result;
7912
7913
                // If no results we hide the results
7914
                if ($show_results == false) {
7915
                    $score = array();
7916
                }
7917
                $contents = ob_get_clean();
7918
7919
                $question_content = '<div class="question_row">';
7920
7921
                if ($show_results) {
7922
7923
                    $show_media = false;
7924
                    $counterToShow = $counter;
7925
                    if ($objQuestionTmp->parent_id != 0) {
7926
7927
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
7928
                            $media_list[] = $objQuestionTmp->parent_id;
7929
                            $show_media = true;
7930
                        }
7931
                        if ($tempParentId == $objQuestionTmp->parent_id) {
7932
                            $mediaCounter++;
7933
                        } else {
7934
                            $mediaCounter = 0;
7935
                        }
7936
                        $counterToShow = chr(97 + $mediaCounter);
7937
                        $tempParentId = $objQuestionTmp->parent_id;
7938
                    }
7939
7940
                    // Shows question title an description.
7941
                    $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
7942
7943
                    // display question category, if any
7944
                    $question_content .= TestCategory::getCategoryNamesForQuestion($questionId, null, true, $this->categoryMinusOne);
0 ignored issues
show
Bug introduced by
The method getCategoryNamesForQuestion() does not exist on TestCategory. Did you maybe mean getCategory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
7945
                }
7946
                $counter++;
7947
7948
                $question_content .= $contents;
7949
                $question_content .= '</div>';
7950
7951
                $exercise_content .= $question_content;
7952
            } // end foreach() block that loops over all questions
7953
        }
7954
7955
        $total_score_text = null;
7956
7957
        if ($returnExerciseResult) {
7958
            return $exerciseResultInfo;
7959
        }
7960
7961
        if ($origin != 'learnpath') {
7962
            if ($show_results || $show_only_score) {
7963
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
7964
            }
7965
        }
7966
7967 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
7968
            //Adding total
7969
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
7970
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
7971
        }
7972
7973
        echo $total_score_text;
7974
        echo $exercise_content;
7975
7976
        if (!$show_only_score) {
7977
            echo $total_score_text;
7978
        }
7979
7980
        if ($saveUserResult) {
7981
7982
            // Tracking of results
7983
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
7984
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
7985
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
7986
7987 View Code Duplication
            if (api_is_allowed_to_session_edit()) {
7988
                update_event_exercise(
7989
                    $exercise_stat_info['exe_id'],
7990
                    $this->selectId(),
7991
                    $total_score,
7992
                    $total_weight,
7993
                    api_get_session_id(),
7994
                    $learnpath_id,
7995
                    $learnpath_item_id,
7996
                    $learnpath_item_view_id,
7997
                    $exercise_stat_info['exe_duration'],
7998
                    '',
7999
                    array()
8000
                );
8001
            }
8002
8003
            // Send notification.
8004
            if (!api_is_allowed_to_edit(null, true)) {
8005
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
8006
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
8007
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
8008
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
8009
            }
8010
        }
8011
    }
8012
8013
    /**
8014
     * Returns an HTML ribbon to show on top of the exercise result, with
8015
     * colouring depending on the success or failure of the student
8016
     * @param $score
8017
     * @param $weight
8018
     * @param bool $check_pass_percentage
8019
     * @return string
8020
     */
8021
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
8022
    {
8023
        $eventMessage = null;
8024
        $ribbon = '<div class="question_row">';
8025
        $ribbon .= '<div class="ribbon">';
8026
        if ($check_pass_percentage) {
8027
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
8028
            // Color the final test score if pass_percentage activated
8029
            $ribbon_total_success_or_error = "";
8030
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
8031
                if ($is_success) {
8032
                    $eventMessage = $this->getOnSuccessMessage();
8033
                    $ribbon_total_success_or_error = ' ribbon-total-success';
8034
                } else {
8035
                    $eventMessage = $this->getOnFailedMessage();
8036
                    $ribbon_total_success_or_error = ' ribbon-total-error';
8037
                }
8038
            }
8039
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
8040
        } else {
8041
            $ribbon .= '<div class="rib rib-total">';
8042
        }
8043
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
8044
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
8045
        $ribbon .= '</h3>';
8046
        $ribbon .= '</div>';
8047
8048
        if ($check_pass_percentage) {
8049
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
8050
        }
8051
        $ribbon .= '</div>';
8052
        $ribbon .= '</div>';
8053
8054
        $ribbon .= $eventMessage;
8055
8056
        return $ribbon;
8057
    }
8058
8059
    /**
8060
     * Returns an array of categories details for the questions of the current
8061
     * exercise.
8062
     * @return array
8063
     */
8064
    public function getQuestionWithCategories()
8065
    {
8066
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8067
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8068
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8069
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8070
        $sql = "SELECT DISTINCT cat.*
8071
                FROM $TBL_EXERCICE_QUESTION e
8072
                INNER JOIN $TBL_QUESTIONS q
8073
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8074
                INNER JOIN $categoryRelTable catRel
8075
                ON (catRel.question_id = e.question_id)
8076
                INNER JOIN $categoryTable cat
8077
                ON (cat.id = catRel.category_id)
8078
                WHERE
8079
                  e.c_id = {$this->course_id} AND
8080
                  e.exercice_id	= ".intval($this->id);
8081
8082
        $result = Database::query($sql);
8083
        $categoriesInExercise = array();
8084
        if (Database::num_rows($result)) {
8085
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8086
        }
8087
8088
        return $categoriesInExercise;
8089
    }
8090
8091
    /**
8092
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
8093
     */
8094
    public function get_max_score()
8095
    {
8096
        $out_max_score = 0;
8097
        // list of question's id !!! the array key start at 1 !!!
8098
        $questionList = $this->selectQuestionList(true);
8099
8100
        // test is randomQuestions - see field random of test
8101
        if ($this->random > 0 && $this->randomByCat == 0) {
8102
            $numberRandomQuestions = $this->random;
8103
            $questionScoreList = array();
8104
            for ($i = 1; $i <= count($questionList); $i++) {
8105
                $tmpobj_question = Question::read($questionList[$i]);
8106
                $questionScoreList[] = $tmpobj_question->weighting;
8107
            }
8108
            rsort($questionScoreList);
8109
            // add the first $numberRandomQuestions value of score array to get max_score
8110
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8111
                $out_max_score += $questionScoreList[$i];
8112
            }
8113
        } else if ($this->random > 0 && $this->randomByCat > 0) {
8114
            // test is random by category
8115
            // get the $numberRandomQuestions best score question of each category
8116
8117
            $numberRandomQuestions = $this->random;
8118
            $tab_categories_scores = array();
8119
            for ($i = 1; $i <= count($questionList); $i++) {
8120
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
8121
                if (!is_array($tab_categories_scores[$question_category_id])) {
8122
                    $tab_categories_scores[$question_category_id] = array();
8123
                }
8124
                $tmpobj_question = Question::read($questionList[$i]);
8125
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8126
            }
8127
8128
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8129
            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...
8130
                rsort($tab_scores);
8131
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8132
                    $out_max_score += $tab_scores[$i];
8133
                }
8134
            }
8135
        } else {
8136
            // standard test, just add each question score
8137
            foreach ($questionList as $questionId) {
8138
                $question = Question::read($questionId, $this->course_id);
8139
                $out_max_score += $question->weighting;
8140
            }
8141
        }
8142
8143
        return $out_max_score;
8144
    }
8145
8146
    /**
8147
    * @return string
8148
    */
8149
    public function get_formated_title()
8150
    {
8151
        return api_html_entity_decode($this->selectTitle());
8152
    }
8153
8154
    /**
8155
     * @param $in_title
8156
     * @return string
8157
     */
8158
    public static function get_formated_title_variable($in_title)
8159
    {
8160
        return api_html_entity_decode($in_title);
8161
    }
8162
8163
    /**
8164
     * @return string
8165
     */
8166
    public function format_title()
8167
    {
8168
        return api_htmlentities($this->title);
8169
    }
8170
8171
    /**
8172
     * @param $in_title
8173
     * @return string
8174
     */
8175
    public static function format_title_variable($in_title)
8176
    {
8177
        return api_htmlentities($in_title);
8178
    }
8179
8180
    /**
8181
     * @param int $courseId
8182
     * @param int $sessionId
8183
     * @return array exercises
8184
     */
8185 View Code Duplication
    public function getExercisesByCouseSession($courseId, $sessionId)
8186
    {
8187
        $courseId = intval($courseId);
8188
        $sessionId = intval($sessionId);
8189
8190
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8191
        $sql = "SELECT * FROM $tbl_quiz cq
8192
                WHERE
8193
                    cq.c_id = %s AND
8194
                    (cq.session_id = %s OR cq.session_id = 0) AND
8195
                    cq.active = 0
8196
                ORDER BY cq.id";
8197
        $sql = sprintf($sql, $courseId, $sessionId);
8198
8199
        $result = Database::query($sql);
8200
8201
        $rows = array();
8202
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8203
            $rows[] = $row;
8204
        }
8205
8206
        return $rows;
8207
    }
8208
8209
    /**
8210
     *
8211
     * @param int $courseId
8212
     * @param int $sessionId
8213
     * @param array $quizId
8214
     * @return array exercises
8215
     */
8216
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
8217
    {
8218
        if (empty($quizId)) {
8219
            return array();
8220
        }
8221
8222
        $sessionId = intval($sessionId);
8223
8224
        $ids = is_array($quizId) ? $quizId : array($quizId);
8225
        $ids = array_map('intval', $ids);
8226
        $ids = implode(',', $ids);
8227
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8228
        if ($sessionId != 0) {
8229
            $sql = "SELECT * FROM $track_exercises te
8230
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8231
              WHERE
8232
              te.id = %s AND
8233
              te.session_id = %s AND
8234
              cq.id IN (%s)
8235
              ORDER BY cq.id";
8236
8237
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8238
        } else {
8239
            $sql = "SELECT * FROM $track_exercises te
8240
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8241
              WHERE
8242
              te.id = %s AND
8243
              cq.id IN (%s)
8244
              ORDER BY cq.id";
8245
            $sql = sprintf($sql, $courseId, $ids);
8246
        }
8247
        $result = Database::query($sql);
8248
        $rows = array();
8249
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8250
            $rows[] = $row;
8251
        }
8252
8253
        return $rows;
8254
    }
8255
8256
    /**
8257
     * @param $exeId
8258
     * @param $exercise_stat_info
8259
     * @param $remindList
8260
     * @param $currentQuestion
8261
     * @return int|null
8262
     */
8263
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
8264
    {
8265
        $result = get_exercise_results_by_attempt($exeId, 'incomplete');
8266
8267
        if (isset($result[$exeId])) {
8268
            $result = $result[$exeId];
8269
        } else {
8270
            return null;
8271
        }
8272
8273
        $data_tracking  = $exercise_stat_info['data_tracking'];
8274
        $data_tracking  = explode(',', $data_tracking);
8275
8276
        // if this is the final question do nothing.
8277
        if ($currentQuestion == count($data_tracking)) {
8278
            return null;
8279
        }
8280
8281
        $currentQuestion = $currentQuestion - 1;
8282
8283
        if (!empty($result['question_list'])) {
8284
            $answeredQuestions = array();
8285
8286
            foreach ($result['question_list'] as $question) {
8287
                if (!empty($question['answer'])) {
8288
                    $answeredQuestions[] = $question['question_id'];
8289
                }
8290
            }
8291
8292
            // Checking answered questions
8293
8294
            $counterAnsweredQuestions = 0;
8295
            foreach ($data_tracking as $questionId) {
8296
                if (!in_array($questionId, $answeredQuestions)) {
8297
                    if ($currentQuestion != $counterAnsweredQuestions) {
8298
                        break;
8299
                    }
8300
                }
8301
                $counterAnsweredQuestions++;
8302
            }
8303
8304
            $counterRemindListQuestions = 0;
8305
            // Checking questions saved in the reminder list
8306
8307
            if (!empty($remindList)) {
8308
                foreach ($data_tracking as $questionId) {
8309
                    if (in_array($questionId, $remindList)) {
8310
                        // Skip the current question
8311
                        if ($currentQuestion != $counterRemindListQuestions) {
8312
                            break;
8313
                        }
8314
                    }
8315
                    $counterRemindListQuestions++;
8316
                }
8317
8318
                if ($counterRemindListQuestions < $currentQuestion) {
8319
                    return null;
8320
                }
8321
8322
                if (!empty($counterRemindListQuestions)) {
8323
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8324
                        return $counterAnsweredQuestions;
8325
                    } else {
8326
                        return $counterRemindListQuestions;
8327
                    }
8328
                }
8329
            }
8330
8331
            return $counterAnsweredQuestions;
8332
        }
8333
    }
8334
8335
    /**
8336
     * Gets the position of a questionId in the question list
8337
     * @param $questionId
8338
     * @return int
8339
     */
8340
    public function getPositionInCompressedQuestionList($questionId)
8341
    {
8342
        $questionList = $this->getQuestionListWithMediasCompressed();
8343
        $mediaQuestions = $this->getMediaList();
8344
        $position = 1;
8345
        foreach ($questionList as $id) {
8346
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8347
                $mediaQuestionList = $mediaQuestions[$id];
8348
                if (in_array($questionId, $mediaQuestionList)) {
8349
                    return $position;
8350
                } else {
8351
                    $position++;
8352
                }
8353
            } else {
8354
                if ($id == $questionId) {
8355
                    return $position;
8356
                } else {
8357
                    $position++;
8358
                }
8359
            }
8360
        }
8361
        return 1;
8362
    }
8363
}
8364