Completed
Push — 1.10.x ( 03922e...f55f3b )
by Yannick
666:41 queued 618:24
created

Exercise::get_validated_question_list()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 63
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 29
c 1
b 0
f 0
nc 7
nop 0
dl 0
loc 63
rs 6.8825

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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, marks 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
3858
                        $addCorrecWord = true;
3859
3860
                        if (
3861
                            Session::has('objExercise') &&
3862
                            Session::read('objExercise')->selectResultsDisabled() == EXERCISE_FEEDBACK_TYPE_EXAM
3863
                        ) {
3864
                            $addCorrecWord = false;
3865
                        }
3866
3867
                        if ($addCorrecWord) {
3868
                            // adds the correct word, followed by ] to close the blank
3869
                            $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>';
3870
                        }
3871
3872
                        $answer .= ']';
3873
3874
                        if (isset($realText[$i +1])) {
3875
                            $answer .= $realText[$i +1];
3876
                        }
3877
                    }
3878
                    break;
3879
                case FREE_ANSWER:
3880
                    if ($from_database) {
3881
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3882
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3883
                        $resq = Database::query($query);
3884
                        $data = Database::fetch_array($resq);
3885
3886
                        $choice = $data['answer'];
3887
                        $choice = str_replace('\r\n', '', $choice);
3888
                        $choice = stripslashes($choice);
3889
                        $questionScore = $data['marks'];
3890
3891
                        if ($questionScore == -1) {
3892
                            $totalScore+= 0;
3893
                        } else {
3894
                            $totalScore+= $questionScore;
3895
                        }
3896
                        if ($questionScore == '') {
3897
                            $questionScore = 0;
3898
                        }
3899
                        $arrques = $questionName;
3900
                        $arrans = $choice;
3901
                    } else {
3902
                        $studentChoice = $choice;
3903
                        if ($studentChoice) {
3904
                            //Fixing negative puntation see #2193
3905
                            $questionScore = 0;
3906
                            $totalScore += 0;
3907
                        }
3908
                    }
3909
                    break;
3910
                case ORAL_EXPRESSION:
3911
                    if ($from_database) {
3912
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3913
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3914
                        $resq   = Database::query($query);
3915
                        $choice = Database::result($resq,0,'answer');
3916
                        $choice = str_replace('\r\n', '', $choice);
3917
                        $choice = stripslashes($choice);
3918
                        $questionScore = Database::result($resq,0,"marks");
3919
                        if ($questionScore == -1) {
3920
                            $totalScore+=0;
3921
                        } else {
3922
                            $totalScore+=$questionScore;
3923
                        }
3924
                        $arrques = $questionName;
3925
                        $arrans  = $choice;
3926
                    } else {
3927
                        $studentChoice = $choice;
3928
                        if ($studentChoice) {
3929
                            //Fixing negative puntation see #2193
3930
                            $questionScore = 0;
3931
                            $totalScore += 0;
3932
                        }
3933
                    }
3934
                    break;
3935
                case DRAGGABLE:
3936
                    //no break
3937
                case MATCHING_DRAGGABLE:
3938
                    //no break
3939
                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...
3940
                    if ($from_database) {
3941
                        $sql = 'SELECT id, answer, id_auto
3942
                                FROM '.$table_ans.'
3943
                                WHERE
3944
                                    c_id = '.$course_id.' AND
3945
                                    question_id = "'.$questionId.'" AND
3946
                                    correct = 0';
3947
                        $res_answer = Database::query($sql);
3948
                        // Getting the real answer
3949
                        $real_list = array();
3950
                        while ($real_answer = Database::fetch_array($res_answer)) {
3951
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3952
                        }
3953
3954
                        $sql = 'SELECT id, answer, correct, id_auto, ponderation
3955
                                FROM '.$table_ans.'
3956
                                WHERE
3957
                                    c_id = '.$course_id.' AND
3958
                                    question_id="'.$questionId.'" AND
3959
                                    correct <> 0
3960
                                ORDER BY id_auto';
3961
                        $res_answers = Database::query($sql);
3962
3963
                        $questionScore = 0;
3964
3965
                        while ($a_answers = Database::fetch_array($res_answers)) {
3966
                            $i_answer_id = $a_answers['id']; //3
3967
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3968
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3969
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3970
3971
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3972
                                    WHERE
3973
                                        exe_id = '$exeId' AND
3974
                                        question_id = '$questionId' AND
3975
                                        position = '$i_answer_id_auto'";
3976
3977
                            $res_user_answer = Database::query($sql);
3978
3979
                            if (Database::num_rows($res_user_answer) > 0) {
3980
                                //  rich - good looking
3981
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3982
                            } else {
3983
                                $s_user_answer = 0;
3984
                            }
3985
3986
                            $i_answerWeighting = $a_answers['ponderation'];
3987
3988
                            $user_answer = '';
3989
                            if (!empty($s_user_answer)) {
3990
                                if ($answerType == DRAGGABLE) {
3991
                                    if ($s_user_answer == $i_answer_correct_answer) {
3992
                                        $questionScore += $i_answerWeighting;
3993
                                        $totalScore += $i_answerWeighting;
3994
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3995
                                    } else {
3996
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3997
                                    }
3998
                                } else {
3999
                                    if ($s_user_answer == $i_answer_correct_answer) {
4000
                                        $questionScore += $i_answerWeighting;
4001
                                        $totalScore += $i_answerWeighting;
4002
4003
                                        if (isset($real_list[$i_answer_id])) {
4004
                                            $user_answer = Display::span($real_list[$i_answer_id]);
4005
                                        }
4006
                                    } else {
4007
                                        $user_answer = Display::span(
4008
                                            $real_list[$s_user_answer],
4009
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4010
                                        );
4011
                                    }
4012
                                }
4013
                            } elseif ($answerType == DRAGGABLE) {
4014
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4015
                            }
4016
4017
                            if ($show_result) {
4018
                                if ($showTotalScoreAndUserChoices == true) {
4019
                                    $user_answer = '';
4020
                                }
4021
                                echo '<tr>';
4022
                                echo '<td>' . $s_answer_label . '</td>';
4023
                                echo '<td>' . $user_answer;
4024
4025
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4026
                                    if (isset($real_list[$i_answer_correct_answer]) && $showTotalScoreAndUserChoices == false) {
4027
                                        echo Display::span(
4028
                                            $real_list[$i_answer_correct_answer],
4029
                                            ['style' => 'color: #008000; font-weight: bold;']
4030
                                        );
4031
                                    }
4032
                                }
4033
                                echo '</td>';
4034
                                echo '</tr>';
4035
                            }
4036
                        }
4037
                        break(2); // break the switch and the "for" condition
4038
                    } else {
4039
                        if ($answerCorrect) {
4040
                            if (isset($choice[$answerAutoId]) &&
4041
                                $answerCorrect == $choice[$answerAutoId]
4042
                            ) {
4043
                                $questionScore += $answerWeighting;
4044
                                $totalScore += $answerWeighting;
4045
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4046
                            } else {
4047
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4048
                                    $user_answer = Display::span(
4049
                                        $answerMatching[$choice[$answerAutoId]],
4050
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4051
                                    );
4052
                                }
4053
                            }
4054
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4055
                        }
4056
                        break;
4057
                    }
4058
                case HOT_SPOT:
4059
                    if ($from_database) {
4060
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4061
                        $sql = "SELECT hotspot_correct
4062
                                FROM $TBL_TRACK_HOTSPOT
4063
                                WHERE
4064
                                    hotspot_exe_id = '".$exeId."' AND
4065
                                    hotspot_question_id= '".$questionId."' AND
4066
                                    hotspot_answer_id = ".intval($answerAutoId)."";
4067
                        $result = Database::query($sql);
4068
                        $studentChoice = Database::result($result, 0, "hotspot_correct");
4069
4070
                        if ($studentChoice) {
4071
                            $questionScore += $answerWeighting;
4072
                            $totalScore += $answerWeighting;
4073
                        }
4074
                    } else {
4075
                        if (!isset($choice[$answerAutoId])) {
4076
                            $choice[$answerAutoId] = 0;
4077
                        } else {
4078
                            $studentChoice = $choice[$answerAutoId];
4079
4080
                            $choiceIsValid = false;
4081
4082
                            if (!empty($studentChoice)) {
4083
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4084
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4085
                                $choicePoint = Geometry::decodePoint($studentChoice);
4086
4087
                                switch ($hotspotType) {
4088
                                    case 'square':
4089
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4090
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4091
                                        break;
4092
4093
                                    case 'circle':
4094
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4095
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4096
                                        break;
4097
4098
                                    case 'poly':
4099
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4100
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4101
                                        break;
4102
                                }
4103
                            }
4104
4105
                            $choice[$answerAutoId] = 0;
4106
                            if ($choiceIsValid) {
4107
                                $questionScore += $answerWeighting;
4108
                                $totalScore += $answerWeighting;
4109
                                $choice[$answerAutoId] = 1;
4110
                            }
4111
                        }
4112
                    }
4113
                    break;
4114
                // @todo never added to chamilo
4115
                //for hotspot with fixed order
4116
                case HOT_SPOT_ORDER :
4117
                    $studentChoice = $choice['order'][$answerId];
4118
                    if ($studentChoice == $answerId) {
4119
                        $questionScore  += $answerWeighting;
4120
                        $totalScore     += $answerWeighting;
4121
                        $studentChoice = true;
4122
                    } else {
4123
                        $studentChoice = false;
4124
                    }
4125
                    break;
4126
                // for hotspot with delineation
4127
                case HOT_SPOT_DELINEATION :
4128
                    if ($from_database) {
4129
                        // getting the user answer
4130
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4131
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4132
                                    FROM $TBL_TRACK_HOTSPOT
4133
                                    WHERE
4134
                                        hotspot_exe_id = '".$exeId."' AND
4135
                                        hotspot_question_id= '".$questionId."' AND
4136
                                        hotspot_answer_id='1'";
4137
                        //by default we take 1 because it's a delineation
4138
                        $resq = Database::query($query);
4139
                        $row = Database::fetch_array($resq,'ASSOC');
4140
4141
                        $choice = $row['hotspot_correct'];
4142
                        $user_answer = $row['hotspot_coordinate'];
4143
4144
                        // THIS is very important otherwise the poly_compile will throw an error!!
4145
                        // round-up the coordinates
4146
                        $coords = explode('/',$user_answer);
4147
                        $user_array = '';
4148 View Code Duplication
                        foreach ($coords as $coord) {
4149
                            list($x,$y) = explode(';',$coord);
4150
                            $user_array .= round($x).';'.round($y).'/';
4151
                        }
4152
                        $user_array = substr($user_array,0,-1);
4153
                    } else {
4154
                        if (!empty($studentChoice)) {
4155
                            $newquestionList[] = $questionId;
4156
                        }
4157
4158
                        if ($answerId === 1) {
4159
                            $studentChoice = $choice[$answerId];
4160
                            $questionScore += $answerWeighting;
4161
4162
                            if ($hotspot_delineation_result[1]==1) {
4163
                                $totalScore += $answerWeighting; //adding the total
4164
                            }
4165
                        }
4166
                    }
4167
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4168
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4169
                    break;
4170
            } // end switch Answertype
4171
4172
            if ($show_result) {
4173
                if ($debug) error_log('Showing questions $from '.$from);
4174
                if ($from == 'exercise_result') {
4175
                    // display answers (if not matching type, or if the answer is correct)
4176
                    if (
4177
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4178
                        $answerCorrect
4179
                    ) {
4180
                        if (
4181
                            in_array(
4182
                                $answerType,
4183
                                array(
4184
                                    UNIQUE_ANSWER,
4185
                                    UNIQUE_ANSWER_IMAGE,
4186
                                    UNIQUE_ANSWER_NO_OPTION,
4187
                                    MULTIPLE_ANSWER,
4188
                                    MULTIPLE_ANSWER_COMBINATION,
4189
                                    GLOBAL_MULTIPLE_ANSWER
4190
                                )
4191
                            )
4192
                        ) {
4193
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4194
                                $feedback_type,
4195
                                $answerType,
4196
                                $studentChoice,
4197
                                $answer,
4198
                                $answerComment,
4199
                                $answerCorrect,
4200
                                0,
4201
                                0,
4202
                                0,
4203
                                $results_disabled,
4204
                                $showTotalScoreAndUserChoices
4205
                            );
4206
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4207
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4208
                                $feedback_type,
4209
                                $answerType,
4210
                                $studentChoice,
4211
                                $answer,
4212
                                $answerComment,
4213
                                $answerCorrect,
4214
                                0,
4215
                                $questionId,
4216
                                0,
4217
                                $results_disabled,
4218
                                $showTotalScoreAndUserChoices
4219
                            );
4220
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4221
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4222
                                $feedback_type,
4223
                                $answerType,
4224
                                $studentChoice,
4225
                                $answer,
4226
                                $answerComment,
4227
                                $answerCorrect,
4228
                                0,
4229
                                0,
4230
                                0,
4231
                                $results_disabled,
4232
                                $showTotalScoreAndUserChoices
4233
                            );
4234
                        } elseif ($answerType == FILL_IN_BLANKS) {
4235
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4236
                                $feedback_type,
4237
                                $answer,
4238
                                0,
4239
                                0,
4240
                                $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...
4241
                                '',
4242
                                $showTotalScoreAndUserChoices
4243
                            );
4244
                        } elseif ($answerType == CALCULATED_ANSWER) {
4245
                            ExerciseShowFunctions::display_calculated_answer(
4246
                                $feedback_type,
4247
                                $answer,
4248
                                0,
4249
                                0,
4250
                                $results_disabled,
4251
                                $showTotalScoreAndUserChoices
4252
                            );
4253
                        } elseif ($answerType == FREE_ANSWER) {
4254
                            ExerciseShowFunctions::display_free_answer(
4255
                                $feedback_type,
4256
                                $choice,
4257
                                $exeId,
4258
                                $questionId,
4259
                                $questionScore,
4260
                                $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...
4261
                            );
4262
                        } elseif ($answerType == ORAL_EXPRESSION) {
4263
                            // to store the details of open questions in an array to be used in mail
4264
                            ExerciseShowFunctions::display_oral_expression_answer(
4265
                                $feedback_type,
4266
                                $choice,
4267
                                0,
4268
                                0,
4269
                                $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...
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::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...
4271
                            );
4272
                        } elseif ($answerType == HOT_SPOT) {
4273
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4274
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4275
                                    break;
4276
                                }
4277
                            }
4278
4279
                            ExerciseShowFunctions::display_hotspot_answer(
4280
                                $feedback_type,
4281
                                ++$correctAnswerId,
4282
                                $answer,
4283
                                $studentChoice,
4284
                                $answerComment,
4285
                                $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...
4286
                                $answerId,
4287
                                $showTotalScoreAndUserChoices
4288
                            );
4289
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4290
                            ExerciseShowFunctions::display_hotspot_order_answer(
4291
                                $feedback_type,
4292
                                $answerId,
4293
                                $answer,
4294
                                $studentChoice,
4295
                                $answerComment
4296
                            );
4297
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4298
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4299
4300
                            //round-up the coordinates
4301
                            $coords = explode('/',$user_answer);
4302
                            $user_array = '';
4303 View Code Duplication
                            foreach ($coords as $coord) {
4304
                                list($x,$y) = explode(';',$coord);
4305
                                $user_array .= round($x).';'.round($y).'/';
4306
                            }
4307
                            $user_array = substr($user_array,0,-1);
4308
4309 View Code Duplication
                            if ($next) {
4310
4311
                                $user_answer = $user_array;
4312
4313
                                // we compare only the delineation not the other points
4314
                                $answer_question = $_SESSION['hotspot_coord'][1];
4315
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4316
4317
                                //calculating the area
4318
                                $poly_user = convert_coordinates($user_answer, '/');
4319
                                $poly_answer = convert_coordinates($answer_question, '|');
4320
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4321
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4322
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4323
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4324
4325
                                $overlap = $poly_results['both'];
4326
                                $poly_answer_area = $poly_results['s1'];
4327
                                $poly_user_area = $poly_results['s2'];
4328
                                $missing = $poly_results['s1Only'];
4329
                                $excess = $poly_results['s2Only'];
4330
4331
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4332
                                // //this is an area in pixels
4333
                                if ($debug > 0) {
4334
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4335
                                }
4336
4337
                                if ($overlap < 1) {
4338
                                    //shortcut to avoid complicated calculations
4339
                                    $final_overlap = 0;
4340
                                    $final_missing = 100;
4341
                                    $final_excess = 100;
4342
                                } else {
4343
                                    // the final overlap is the percentage of the initial polygon
4344
                                    // that is overlapped by the user's polygon
4345
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4346
                                    if ($debug > 1) {
4347
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4348
                                    }
4349
                                    // the final missing area is the percentage of the initial polygon
4350
                                    // that is not overlapped by the user's polygon
4351
                                    $final_missing = 100 - $final_overlap;
4352
                                    if ($debug > 1) {
4353
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4354
                                    }
4355
                                    // the final excess area is the percentage of the initial polygon's size
4356
                                    // that is covered by the user's polygon outside of the initial polygon
4357
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4358
                                    if ($debug > 1) {
4359
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4360
                                    }
4361
                                }
4362
4363
                                //checking the destination parameters parsing the "@@"
4364
                                $destination_items= explode('@@', $answerDestination);
4365
                                $threadhold_total = $destination_items[0];
4366
                                $threadhold_items=explode(';',$threadhold_total);
4367
                                $threadhold1 = $threadhold_items[0]; // overlap
4368
                                $threadhold2 = $threadhold_items[1]; // excess
4369
                                $threadhold3 = $threadhold_items[2];	 //missing
4370
4371
                                // if is delineation
4372
                                if ($answerId===1) {
4373
                                    //setting colors
4374
                                    if ($final_overlap>=$threadhold1) {
4375
                                        $overlap_color=true; //echo 'a';
4376
                                    }
4377
                                    //echo $excess.'-'.$threadhold2;
4378
                                    if ($final_excess<=$threadhold2) {
4379
                                        $excess_color=true; //echo 'b';
4380
                                    }
4381
                                    //echo '--------'.$missing.'-'.$threadhold3;
4382
                                    if ($final_missing<=$threadhold3) {
4383
                                        $missing_color=true; //echo 'c';
4384
                                    }
4385
4386
                                    // if pass
4387
                                    if (
4388
                                        $final_overlap >= $threadhold1 &&
4389
                                        $final_missing <= $threadhold3 &&
4390
                                        $final_excess <= $threadhold2
4391
                                    ) {
4392
                                        $next=1; //go to the oars
4393
                                        $result_comment=get_lang('Acceptable');
4394
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4395
                                    } else {
4396
                                        $next=0;
4397
                                        $result_comment=get_lang('Unacceptable');
4398
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4399
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4400
                                        //checking the destination parameters parsing the "@@"
4401
                                        $destination_items= explode('@@', $answerDestination);
4402
                                    }
4403
                                } elseif($answerId>1) {
4404
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4405
                                        if ($debug>0) {
4406
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4407
                                        }
4408
                                        //type no error shouldn't be treated
4409
                                        $next = 1;
4410
                                        continue;
4411
                                    }
4412
                                    if ($debug>0) {
4413
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4414
                                    }
4415
                                    //check the intersection between the oar and the user
4416
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4417
                                    //echo 'official';print_r($x_list);print_r($y_list);
4418
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4419
                                    $inter= $result['success'];
4420
4421
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4422
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4423
4424
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4425
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4426
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4427
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4428
4429
                                    if ($overlap == false) {
4430
                                        //all good, no overlap
4431
                                        $next = 1;
4432
                                        continue;
4433
                                    } else {
4434
                                        if ($debug>0) {
4435
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4436
                                        }
4437
                                        $organs_at_risk_hit++;
4438
                                        //show the feedback
4439
                                        $next=0;
4440
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4441
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4442
4443
                                        $destination_items= explode('@@', $answerDestination);
4444
                                        $try_hotspot=$destination_items[1];
4445
                                        $lp_hotspot=$destination_items[2];
4446
                                        $select_question_hotspot=$destination_items[3];
4447
                                        $url_hotspot=$destination_items[4];
4448
                                    }
4449
                                }
4450
                            } else {	// the first delineation feedback
4451
                                if ($debug>0) {
4452
                                    error_log(__LINE__.' first',0);
4453
                                }
4454
                            }
4455
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4456
                            echo '<tr>';
4457
                            echo Display::tag('td', $answerMatching[$answerId]);
4458
                            echo Display::tag(
4459
                                'td',
4460
                                "$user_answer / " . Display::tag(
4461
                                    'strong',
4462
                                    $answerMatching[$answerCorrect],
4463
                                    ['style' => 'color: #008000; font-weight: bold;']
4464
                                )
4465
                            );
4466
                            echo '</tr>';
4467
                        }
4468
                    }
4469
                } else {
4470
                    if ($debug) error_log('Showing questions $from '.$from);
4471
4472
                    switch ($answerType) {
4473
                        case UNIQUE_ANSWER:
4474
                        case UNIQUE_ANSWER_IMAGE:
4475
                        case UNIQUE_ANSWER_NO_OPTION:
4476
                        case MULTIPLE_ANSWER:
4477
                        case GLOBAL_MULTIPLE_ANSWER :
4478 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4479
                            if ($answerId == 1) {
4480
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4481
                                    $feedback_type,
4482
                                    $answerType,
4483
                                    $studentChoice,
4484
                                    $answer,
4485
                                    $answerComment,
4486
                                    $answerCorrect,
4487
                                    $exeId,
4488
                                    $questionId,
4489
                                    $answerId,
4490
                                    $results_disabled,
4491
                                    $showTotalScoreAndUserChoices
4492
                                );
4493
                            } else {
4494
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4495
                                    $feedback_type,
4496
                                    $answerType,
4497
                                    $studentChoice,
4498
                                    $answer,
4499
                                    $answerComment,
4500
                                    $answerCorrect,
4501
                                    $exeId,
4502
                                    $questionId,
4503
                                    '',
4504
                                    $results_disabled,
4505
                                    $showTotalScoreAndUserChoices
4506
                                );
4507
                            }
4508
                            break;
4509 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4510
                            if ($answerId == 1) {
4511
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4512
                                    $feedback_type,
4513
                                    $answerType,
4514
                                    $studentChoice,
4515
                                    $answer,
4516
                                    $answerComment,
4517
                                    $answerCorrect,
4518
                                    $exeId,
4519
                                    $questionId,
4520
                                    $answerId,
4521
                                    $results_disabled,
4522
                                    $showTotalScoreAndUserChoices
4523
                                );
4524
                            } else {
4525
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4526
                                    $feedback_type,
4527
                                    $answerType,
4528
                                    $studentChoice,
4529
                                    $answer,
4530
                                    $answerComment,
4531
                                    $answerCorrect,
4532
                                    $exeId,
4533
                                    $questionId,
4534
                                    '',
4535
                                    $results_disabled,
4536
                                    $showTotalScoreAndUserChoices
4537
                                );
4538
                            }
4539
                            break;
4540 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4541
                            if ($answerId == 1) {
4542
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4543
                                    $feedback_type,
4544
                                    $answerType,
4545
                                    $studentChoice,
4546
                                    $answer,
4547
                                    $answerComment,
4548
                                    $answerCorrect,
4549
                                    $exeId,
4550
                                    $questionId,
4551
                                    $answerId,
4552
                                    $results_disabled,
4553
                                    $showTotalScoreAndUserChoices
4554
                                );
4555
                            } else {
4556
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4557
                                    $feedback_type,
4558
                                    $answerType,
4559
                                    $studentChoice,
4560
                                    $answer,
4561
                                    $answerComment,
4562
                                    $answerCorrect,
4563
                                    $exeId,
4564
                                    $questionId,
4565
                                    '',
4566
                                    $results_disabled,
4567
                                    $showTotalScoreAndUserChoices
4568
                                );
4569
                            }
4570
                            break;
4571
                        case FILL_IN_BLANKS:
4572
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4573
                                $feedback_type,
4574
                                $answer,
4575
                                $exeId,
4576
                                $questionId,
4577
                                $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...
4578
                                $str,
4579
                                $showTotalScoreAndUserChoices
4580
                            );
4581
                            break;
4582
                        case CALCULATED_ANSWER:
4583
                            ExerciseShowFunctions::display_calculated_answer(
4584
                                $feedback_type,
4585
                                $answer,
4586
                                $exeId,
4587
                                $questionId,
4588
                                $results_disabled,
4589
                                '',
4590
                                $showTotalScoreAndUserChoices
4591
                            );
4592
                            break;
4593
                        case FREE_ANSWER:
4594
                            echo ExerciseShowFunctions::display_free_answer(
4595
                                $feedback_type,
4596
                                $choice,
4597
                                $exeId,
4598
                                $questionId,
4599
                                $questionScore,
4600
                                $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...
4601
                            );
4602
                            break;
4603
                        case ORAL_EXPRESSION:
4604
                            echo '<tr>
4605
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4606
                                    $feedback_type,
4607
                                    $choice,
4608
                                    $exeId,
4609
                                    $questionId,
4610
                                    $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...
4611
                                    $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...
4612
                                ) . '</td>
4613
                                </tr>
4614
                                </table>';
4615
                            break;
4616
                        case HOT_SPOT:
4617
                            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...
4618
                                $feedback_type,
4619
                                $answerId,
4620
                                $answer,
4621
                                $studentChoice,
4622
                                $answerComment,
4623
                                $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...
4624
                                $answerId
4625
                            );
4626
                            break;
4627
                        case HOT_SPOT_DELINEATION:
4628
                            $user_answer = $user_array;
4629 View Code Duplication
                            if ($next) {
4630
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4631
                                // Save into db
4632
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4633
                                 * hotspot_user_id,
4634
                                 *  hotspot_course_code,
4635
                                 *  hotspot_exe_id,
4636
                                 *  hotspot_question_id,
4637
                                 *  hotspot_answer_id,
4638
                                 *  hotspot_correct,
4639
                                 *  hotspot_coordinate
4640
                                 *  )
4641
                                VALUES (
4642
                                 * '".Database::escape_string($_user['user_id'])."',
4643
                                 *  '".Database::escape_string($_course['id'])."',
4644
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4645
                                 *  '".Database::escape_string($answerId)."',
4646
                                 *  '".Database::escape_string($studentChoice)."',
4647
                                 *  '".Database::escape_string($user_array)."')";
4648
                                $result = Database::query($sql,__FILE__,__LINE__);
4649
                                 */
4650
                                $user_answer = $user_array;
4651
4652
                                // we compare only the delineation not the other points
4653
                                $answer_question = $_SESSION['hotspot_coord'][1];
4654
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4655
4656
                                //calculating the area
4657
                                $poly_user = convert_coordinates($user_answer, '/');
4658
                                $poly_answer = convert_coordinates($answer_question, '|');
4659
4660
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4661
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4662
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4663
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4664
4665
                                $overlap = $poly_results['both'];
4666
                                $poly_answer_area = $poly_results['s1'];
4667
                                $poly_user_area = $poly_results['s2'];
4668
                                $missing = $poly_results['s1Only'];
4669
                                $excess = $poly_results['s2Only'];
4670
4671
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
4672
                                if ($debug > 0) {
4673
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4674
                                }
4675
                                if ($overlap < 1) {
4676
                                    //shortcut to avoid complicated calculations
4677
                                    $final_overlap = 0;
4678
                                    $final_missing = 100;
4679
                                    $final_excess = 100;
4680
                                } else {
4681
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4682
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4683
                                    if ($debug > 1) {
4684
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4685
                                    }
4686
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4687
                                    $final_missing = 100 - $final_overlap;
4688
                                    if ($debug > 1) {
4689
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4690
                                    }
4691
                                    // 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
4692
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4693
                                    if ($debug > 1) {
4694
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4695
                                    }
4696
                                }
4697
4698
                                //checking the destination parameters parsing the "@@"
4699
                                $destination_items = explode('@@', $answerDestination);
4700
                                $threadhold_total = $destination_items[0];
4701
                                $threadhold_items = explode(';', $threadhold_total);
4702
                                $threadhold1 = $threadhold_items[0]; // overlap
4703
                                $threadhold2 = $threadhold_items[1]; // excess
4704
                                $threadhold3 = $threadhold_items[2];  //missing
4705
                                // if is delineation
4706
                                if ($answerId === 1) {
4707
                                    //setting colors
4708
                                    if ($final_overlap >= $threadhold1) {
4709
                                        $overlap_color = true; //echo 'a';
4710
                                    }
4711
                                    //echo $excess.'-'.$threadhold2;
4712
                                    if ($final_excess <= $threadhold2) {
4713
                                        $excess_color = true; //echo 'b';
4714
                                    }
4715
                                    //echo '--------'.$missing.'-'.$threadhold3;
4716
                                    if ($final_missing <= $threadhold3) {
4717
                                        $missing_color = true; //echo 'c';
4718
                                    }
4719
4720
                                    // if pass
4721
                                    if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
4722
                                        $next = 1; //go to the oars
4723
                                        $result_comment = get_lang('Acceptable');
4724
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4725
                                    } else {
4726
                                        $next = 0;
4727
                                        $result_comment = get_lang('Unacceptable');
4728
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4729
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4730
                                        //checking the destination parameters parsing the "@@"
4731
                                        $destination_items = explode('@@', $answerDestination);
4732
                                    }
4733
                                } elseif ($answerId > 1) {
4734
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4735
                                        if ($debug > 0) {
4736
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4737
                                        }
4738
                                        //type no error shouldn't be treated
4739
                                        $next = 1;
4740
                                        continue;
4741
                                    }
4742
                                    if ($debug > 0) {
4743
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4744
                                    }
4745
                                    //check the intersection between the oar and the user
4746
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4747
                                    //echo 'official';print_r($x_list);print_r($y_list);
4748
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4749
                                    $inter = $result['success'];
4750
4751
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4752
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4753
4754
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4755
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4756
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4757
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4758
4759
                                    if ($overlap == false) {
4760
                                        //all good, no overlap
4761
                                        $next = 1;
4762
                                        continue;
4763
                                    } else {
4764
                                        if ($debug > 0) {
4765
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4766
                                        }
4767
                                        $organs_at_risk_hit++;
4768
                                        //show the feedback
4769
                                        $next = 0;
4770
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4771
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4772
4773
                                        $destination_items = explode('@@', $answerDestination);
4774
                                        $try_hotspot = $destination_items[1];
4775
                                        $lp_hotspot = $destination_items[2];
4776
                                        $select_question_hotspot = $destination_items[3];
4777
                                        $url_hotspot=$destination_items[4];
4778
                                    }
4779
                                }
4780
                            } else {	// the first delineation feedback
4781
                                if ($debug > 0) {
4782
                                    error_log(__LINE__ . ' first', 0);
4783
                                }
4784
                            }
4785
                            break;
4786
                        case HOT_SPOT_ORDER:
4787
                            ExerciseShowFunctions::display_hotspot_order_answer(
4788
                                $feedback_type,
4789
                                $answerId,
4790
                                $answer,
4791
                                $studentChoice,
4792
                                $answerComment
4793
                            );
4794
                            break;
4795
                        case DRAGGABLE:
4796
                            //no break
4797
                        case MATCHING_DRAGGABLE:
4798
                            //no break
4799
                        case MATCHING:
4800
                            echo '<tr>';
4801
                            echo Display::tag('td', $answerMatching[$answerId]);
4802
                            echo Display::tag(
4803
                                'td',
4804
                                "$user_answer / " . Display::tag(
4805
                                    'strong',
4806
                                    $answerMatching[$answerCorrect],
4807
                                    ['style' => 'color: #008000; font-weight: bold;']
4808
                                )
4809
                            );
4810
                            echo '</tr>';
4811
4812
                            break;
4813
                    }
4814
                }
4815
            }
4816
            if ($debug) error_log(' ------ ');
4817
        } // end for that loops over all answers of the current question
4818
4819
        if ($debug) error_log('-- end answer loop --');
4820
4821
        $final_answer = true;
4822
4823
        foreach ($real_answers as $my_answer) {
4824
            if (!$my_answer) {
4825
                $final_answer = false;
4826
            }
4827
        }
4828
4829
        //we add the total score after dealing with the answers
4830
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4831
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4832
        ) {
4833
            if ($final_answer) {
4834
                //getting only the first score where we save the weight of all the question
4835
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4836
                $questionScore += $answerWeighting;
4837
                $totalScore += $answerWeighting;
4838
            }
4839
        }
4840
4841
        //Fixes multiple answer question in order to be exact
4842
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4843
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4844
            $diff = @array_diff($answer_correct_array, $real_answers);
4845
4846
            // All good answers or nothing works like exact
4847
4848
            $counter = 1;
4849
            $correct_answer = true;
4850
            foreach ($real_answers as $my_answer) {
4851
                if ($debug)
4852
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4853
                if ($my_answer != $answer_correct_array[$counter]) {
4854
                    $correct_answer = false;
4855
                    break;
4856
                }
4857
                $counter++;
4858
            }
4859
4860
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4861
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4862
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4863
4864
            if ($correct_answer == false) {
4865
                $questionScore = 0;
4866
            }
4867
4868
            // This makes the result non exact
4869
            if (!empty($diff)) {
4870
                $questionScore = 0;
4871
            }
4872
        }*/
4873
4874
        $extra_data = array(
4875
            'final_overlap' => $final_overlap,
4876
            'final_missing'=>$final_missing,
4877
            'final_excess'=> $final_excess,
4878
            'overlap_color' => $overlap_color,
4879
            'missing_color'=>$missing_color,
4880
            'excess_color'=> $excess_color,
4881
            'threadhold1'   => $threadhold1,
4882
            'threadhold2'=>$threadhold2,
4883
            'threadhold3'=> $threadhold3,
4884
        );
4885
        if ($from == 'exercise_result') {
4886
            // if answer is hotspot. To the difference of exercise_show.php,
4887
            //  we use the results from the session (from_db=0)
4888
            // TODO Change this, because it is wrong to show the user
4889
            //  some results that haven't been stored in the database yet
4890
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4891
4892
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4893
4894
                $my_exe_id = 0;
4895
                $from_database = 0;
4896
                if ($answerType == HOT_SPOT_DELINEATION) {
4897
                    if (0) {
4898
                        if ($overlap_color) {
4899
                            $overlap_color='green';
4900
                        } else {
4901
                            $overlap_color='red';
4902
                        }
4903
                        if ($missing_color) {
4904
                            $missing_color='green';
4905
                        } else {
4906
                            $missing_color='red';
4907
                        }
4908
                        if ($excess_color) {
4909
                            $excess_color='green';
4910
                        } else {
4911
                            $excess_color='red';
4912
                        }
4913
                        if (!is_numeric($final_overlap)) {
4914
                            $final_overlap = 0;
4915
                        }
4916
                        if (!is_numeric($final_missing)) {
4917
                            $final_missing = 0;
4918
                        }
4919
                        if (!is_numeric($final_excess)) {
4920
                            $final_excess = 0;
4921
                        }
4922
4923
                        if ($final_overlap>100) {
4924
                            $final_overlap = 100;
4925
                        }
4926
4927
                        $table_resume='<table class="data_table">
4928
                                <tr class="row_odd" >
4929
                                    <td></td>
4930
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4931
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4932
                                </tr>
4933
                                <tr class="row_even">
4934
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4935
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4936
                                    <td><div style="color:' . $overlap_color . '">'
4937
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4938
                                </tr>
4939
                                <tr>
4940
                                    <td><b>' . get_lang('Excess') . '</b></td>
4941
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4942
                                    <td><div style="color:' . $excess_color . '">'
4943
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4944
                                </tr>
4945
                                <tr class="row_even">
4946
                                    <td><b>' . get_lang('Missing') . '</b></td>
4947
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4948
                                    <td><div style="color:' . $missing_color . '">'
4949
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4950
                                </tr>
4951
                            </table>';
4952 View Code Duplication
                        if ($next == 0) {
4953
                            $try = $try_hotspot;
4954
                            $lp = $lp_hotspot;
4955
                            $destinationid = $select_question_hotspot;
4956
                            $url = $url_hotspot;
4957
                        } else {
4958
                            //show if no error
4959
                            //echo 'no error';
4960
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4961
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4962
                        }
4963
4964
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4965
                            <p style="text-align:center">';
4966
4967
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
4968
                        $message .= $table_resume;
4969
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
4970
                        if ($organs_at_risk_hit > 0) {
4971
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
4972
                        }
4973
                        $message .='<p>' . $comment . '</p>';
4974
                        echo $message;
4975
                    } else {
4976
                        echo $hotspot_delineation_result[0]; //prints message
4977
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
4978
                    }
4979
4980
                    //save the score attempts
4981
4982
                    if (1) {
4983
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
4984
                        $final_answer = $hotspot_delineation_result[1];
4985
                        if ($final_answer == 0) {
4986
                            $questionScore = 0;
4987
                        }
4988
                        // we always insert the answer_id 1 = delineation
4989
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
4990
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
4991
                        Event::saveExerciseAttemptHotspot(
4992
                            $exeId,
4993
                            $quesId,
4994
                            1,
4995
                            $hotspot_delineation_result[1],
4996
                            $exerciseResultCoordinates[$quesId]
4997
                        );
4998
                    } else {
4999
                        if ($final_answer==0) {
5000
                            $questionScore = 0;
5001
                            $answer=0;
5002
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5003 View Code Duplication
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5004
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5005
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,0,$val);
5006
                                }
5007
                            }
5008
                        } else {
5009
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5010 View Code Duplication
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5011
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5012
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,$choice[$idx],$val);
5013
                                }
5014
                            }
5015
                        }
5016
                    }
5017
                    $my_exe_id = $exeId;
5018
                }
5019
            }
5020
5021
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5022
                // We made an extra table for the answers
5023
5024
                if ($show_result) {
5025
                    $relPath = api_get_path(REL_PATH);
5026
                    //	if ($origin != 'learnpath') {
5027
                    echo '</table></td></tr>';
5028
                    echo "
5029
                        <tr>
5030
                            <td colspan=\"2\">
5031
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5032
5033
                                <div id=\"hotspot-solution-$questionId\"></div>
5034
5035
                                <script>
5036
                                    $(document).on('ready', function () {
5037
                                        new HotspotQuestion({
5038
                                            questionId: $questionId,
5039
                                            exerciseId: $exeId,
5040
                                            selector: '#hotspot-solution-$questionId',
5041
                                            for: 'solution',
5042
                                            relPath: '$relPath'
5043
                                        });
5044
                                    });
5045
5046
                                </script>
5047
                            </td>
5048
                        </tr>
5049
                    ";
5050
                    //	}
5051
                }
5052
            }
5053
5054
            //if ($origin != 'learnpath') {
5055
            if ($show_result) {
5056
                echo '</table>';
5057
            }
5058
            //	}
5059
        }
5060
        unset ($objAnswerTmp);
5061
5062
        $totalWeighting += $questionWeighting;
5063
        // Store results directly in the database
5064
        // For all in one page exercises, the results will be
5065
        // stored by exercise_results.php (using the session)
5066
5067
        if ($saved_results) {
5068
            if ($debug) error_log("Save question results $saved_results");
5069
            if ($debug) error_log(print_r($choice ,1 ));
5070
5071
            if (empty($choice)) {
5072
                $choice = 0;
5073
            }
5074
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5075
                if ($choice != 0) {
5076
                    $reply = array_keys($choice);
5077
                    for ($i = 0; $i < sizeof($reply); $i++) {
5078
                        $ans = $reply[$i];
5079
                        Event::saveQuestionAttempt(
5080
                            $questionScore,
5081
                            $ans . ':' . $choice[$ans],
5082
                            $quesId,
5083
                            $exeId,
5084
                            $i,
5085
                            $this->id
5086
                        );
5087
                        if ($debug) {
5088
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5089
                        }
5090
                    }
5091
                } else {
5092
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5093
                }
5094
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5095
                if ($choice != 0) {
5096
                    $reply = array_keys($choice);
5097
5098
                    if ($debug) {
5099
                        error_log("reply " . print_r($reply, 1) . "");
5100
                    }
5101 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5102
                        $ans = $reply[$i];
5103
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5104
                    }
5105
                } else {
5106
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5107
                }
5108
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5109
                if ($choice != 0) {
5110
                    $reply = array_keys($choice);
5111 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5112
                        $ans = $reply[$i];
5113
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5114
                    }
5115
                } else {
5116
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5117
                }
5118
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5119
                if (isset($matching)) {
5120
                    foreach ($matching as $j => $val) {
5121
                        Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
5122
                    }
5123
                }
5124
            } elseif ($answerType == FREE_ANSWER) {
5125
                $answer = $choice;
5126
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5127
            } elseif ($answerType == ORAL_EXPRESSION) {
5128
                $answer = $choice;
5129
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id, $nano);
5130
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5131
                $answer = $choice;
5132
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5133
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5134
            } elseif ($answerType == HOT_SPOT) {
5135
                $answer = [];
5136
5137
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5138
                    Database::delete(
5139
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5140
                        [
5141
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5142
                                $exeId,
5143
                                $questionId,
5144
                                api_get_course_int_id()
5145
                            ]
5146
                        ]
5147
                    );
5148
5149
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5150
                        $answer[] = $val;
5151
5152
                        Event::saveExerciseAttemptHotspot($exeId, $quesId, $idx, $choice[$idx], $val, false, $this->id);
5153
                    }
5154
                }
5155
5156
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5157
            } else {
5158
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5159
            }
5160
        }
5161
5162
        if ($propagate_neg == 0 && $questionScore < 0) {
5163
            $questionScore = 0;
5164
        }
5165
5166
        if ($saved_results) {
5167
            $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5168
            $sql = 'UPDATE ' . $stat_table . ' SET
5169
                        exe_result = exe_result + ' . floatval($questionScore) . '
5170
                    WHERE exe_id = ' . $exeId;
5171
            if ($debug) error_log($sql);
5172
            Database::query($sql);
5173
        }
5174
5175
        $return_array = array(
5176
            'score'         => $questionScore,
5177
            'weight'        => $questionWeighting,
5178
            'extra'         => $extra_data,
5179
            'open_question' => $arrques,
5180
            'open_answer'   => $arrans,
5181
            'answer_type'   => $answerType
5182
        );
5183
5184
        return $return_array;
5185
    }
5186
5187
    /**
5188
     * Sends a notification when a user ends an examn
5189
     *
5190
     */
5191
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id)
5192
    {
5193
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5194
            return null;
5195
        }
5196
        // Email configuration settings
5197
        $courseCode = api_get_course_id();
5198
        $courseInfo = api_get_course_info($courseCode);
5199
        $sessionId = api_get_session_id();
5200
5201
        if (empty($courseInfo)) {
5202
            return false;
5203
        }
5204
5205
        $url_email = api_get_path(WEB_CODE_PATH)
5206
            . 'exercice/exercise_show.php?'
5207
            . api_get_cidreq()
5208
            . '&id_session='
5209
            . $sessionId
5210
            . '&id='
5211
            . $exe_id
5212
            . '&action=qualify';
5213
        $user_info = api_get_user_info(api_get_user_id());
5214
5215
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5216
                    .get_lang('AttemptDetails').' : <br /><br />'.
5217
                    '<table>'
5218
                        .'<tr>'
5219
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5220
                            .'<td>&nbsp;<b>#course#</b></td>'
5221
                        .'</tr>'
5222
                        .'<tr>'
5223
                            .'<td>'.get_lang('TestAttempted').'</td>'
5224
                            .'<td>&nbsp;#exercise#</td>'
5225
                        .'</tr>'
5226
                        .'<tr>'
5227
                            .'<td>'.get_lang('StudentName').'</td>'
5228
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5229
                        .'</tr>'
5230
                        .'<tr>'
5231
                            .'<td>'.get_lang('StudentEmail').'</td>'
5232
                            .'<td>&nbsp;#email#</td>'
5233
                        .'</tr>'
5234
                    .'</table>';
5235
        $open_question_list = null;
5236
5237
        $msg = str_replace("#email#", $user_info['email'], $msg);
5238
        $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5239
        $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5240
        $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5241
        $msg = str_replace("#course#", $courseInfo['name'], $msg1);
5242
5243
        if ($origin != 'learnpath') {
5244
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5245
        }
5246
        $msg1 = str_replace("#url#", $url_email, $msg);
5247
        $mail_content = $msg1;
5248
        $subject = get_lang('ExerciseAttempted');
5249
5250
        if (!empty($sessionId)) {
5251
            $teachers = CourseManager::get_coach_list_from_course_code($courseCode, $sessionId);
5252
        } else {
5253
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5254
        }
5255
5256
        if (!empty($teachers)) {
5257
            foreach ($teachers as $user_id => $teacher_data) {
5258
                MessageManager::send_message_simple(
5259
                    $user_id,
5260
                    $subject,
5261
                    $mail_content
5262
                );
5263
            }
5264
        }
5265
    }
5266
5267
    /**
5268
     * Sends a notification when a user ends an examn
5269
     *
5270
     */
5271
    function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5272
    {
5273
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5274
            return null;
5275
        }
5276
        // Email configuration settings
5277
        $courseCode     = api_get_course_id();
5278
        $course_info    = api_get_course_info($courseCode);
5279
5280
        $url_email = api_get_path(WEB_CODE_PATH)
5281
            . 'exercice/exercise_show.php?'
5282
            . api_get_cidreq()
5283
            . '&id_session='
5284
            . api_get_session_id()
5285
            . '&id='
5286
            . $exe_id
5287
            . '&action=qualify';
5288
        $user_info = api_get_user_info(api_get_user_id());
5289
5290
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5291
                    .get_lang('AttemptDetails').' : <br /><br />'
5292
                    .'<table>'
5293
                        .'<tr>'
5294
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5295
                            .'<td>&nbsp;<b>#course#</b></td>'
5296
                        .'</tr>'
5297
                        .'<tr>'
5298
                            .'<td>'.get_lang('TestAttempted').'</td>'
5299
                            .'<td>&nbsp;#exercise#</td>'
5300
                        .'</tr>'
5301
                        .'<tr>'
5302
                            .'<td>'.get_lang('StudentName').'</td>'
5303
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5304
                        .'</tr>'
5305
                        .'<tr>'
5306
                            .'<td>'.get_lang('StudentEmail').'</td>'
5307
                            .'<td>&nbsp;#mail#</td>'
5308
                        .'</tr>'
5309
                    .'</table>';
5310
        $open_question_list = null;
5311 View Code Duplication
        foreach ($question_list_answers as $item) {
5312
            $question    = $item['question'];
5313
            $answer      = $item['answer'];
5314
            $answer_type = $item['answer_type'];
5315
5316
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5317
                $open_question_list .=
5318
                    '<tr>'
5319
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5320
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5321
                    .'</tr>'
5322
                    .'<tr>'
5323
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5324
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5325
                    .'</tr>';
5326
            }
5327
        }
5328
5329
        if (!empty($open_question_list)) {
5330
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5331
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5332
            $msg .= $open_question_list;
5333
            $msg .= '</table><br />';
5334
5335
5336
            $msg1   = str_replace("#exercise#",    $this->exercise, $msg);
5337
            $msg    = str_replace("#firstName#",   $user_info['firstname'],$msg1);
5338
            $msg1   = str_replace("#lastName#",    $user_info['lastname'],$msg);
5339
            $msg    = str_replace("#mail#",        $user_info['email'],$msg1);
5340
            $msg    = str_replace("#course#",      $course_info['name'],$msg1);
5341
5342
            if ($origin != 'learnpath') {
5343
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5344
            }
5345
            $msg1 = str_replace("#url#", $url_email, $msg);
5346
            $mail_content = $msg1;
5347
            $subject = get_lang('OpenQuestionsAttempted');
5348
5349 View Code Duplication
            if (api_get_session_id()) {
5350
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5351
            } else {
5352
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5353
            }
5354
5355
            if (!empty($teachers)) {
5356
                foreach ($teachers as $user_id => $teacher_data) {
5357
                    MessageManager::send_message_simple(
5358
                        $user_id,
5359
                        $subject,
5360
                        $mail_content
5361
                    );
5362
                }
5363
            }
5364
        }
5365
    }
5366
5367
    function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5368
    {
5369
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5370
            return null;
5371
        }
5372
        // Email configuration settings
5373
        $courseCode     = api_get_course_id();
5374
        $course_info    = api_get_course_info($courseCode);
5375
5376
        $url_email = api_get_path(WEB_CODE_PATH)
5377
            . 'exercice/exercise_show.php?'
5378
            . api_get_cidreq()
5379
            . '&id_session='
5380
            . api_get_session_id()
5381
            . '&id='
5382
            . $exe_id
5383
            . '&action=qualify';
5384
        $user_info = api_get_user_info(api_get_user_id());
5385
5386
        $oral_question_list = null;
5387 View Code Duplication
        foreach ($question_list_answers as $item) {
5388
            $question    = $item['question'];
5389
            $answer      = $item['answer'];
5390
            $answer_type = $item['answer_type'];
5391
5392
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5393
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5394
                    .'<tr>'
5395
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5396
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5397
                    .'</tr>'
5398
                    .'<tr>'
5399
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5400
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5401
                    .'</tr></table>';
5402
            }
5403
        }
5404
5405
        if (!empty($oral_question_list)) {
5406
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5407
                    '.get_lang('AttemptDetails').' : <br /><br />'
5408
                    .'<table>'
5409
                        .'<tr>'
5410
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5411
                            .'<td>&nbsp;<b>#course#</b></td>'
5412
                        .'</tr>'
5413
                        .'<tr>'
5414
                            .'<td>'.get_lang('TestAttempted').'</td>'
5415
                            .'<td>&nbsp;#exercise#</td>'
5416
                        .'</tr>'
5417
                        .'<tr>'
5418
                            .'<td>'.get_lang('StudentName').'</td>'
5419
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5420
                        .'</tr>'
5421
                        .'<tr>'
5422
                            .'<td>'.get_lang('StudentEmail').'</td>'
5423
                            .'<td>&nbsp;#mail#</td>'
5424
                        .'</tr>'
5425
                    .'</table>';
5426
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5427
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5428
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5429
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5430
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5431
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5432
5433
            if ($origin != 'learnpath') {
5434
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5435
            }
5436
            $msg1 = str_replace("#url#", $url_email, $msg);
5437
            $mail_content = $msg1;
5438
            $subject = get_lang('OralQuestionsAttempted');
5439
5440 View Code Duplication
            if (api_get_session_id()) {
5441
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5442
            } else {
5443
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5444
            }
5445
5446
            if (!empty($teachers)) {
5447
                foreach ($teachers as $user_id => $teacher_data) {
5448
                    MessageManager::send_message_simple(
5449
                        $user_id,
5450
                        $subject,
5451
                        $mail_content
5452
                    );
5453
                }
5454
            }
5455
        }
5456
    }
5457
5458
    /**
5459
     * @param array $user_data result of api_get_user_info()
5460
     * @param null $start_date
5461
     * @param null $duration
5462
     * @param string $ip Optional. The user IP
5463
     * @return string
5464
     */
5465
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5466
    {
5467
        $array = array();
5468
5469
        if (!empty($user_data)) {
5470
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5471
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5472
            if (!empty($user_data['official_code'])) {
5473
                $array[] = array(
5474
                    'title' => get_lang('OfficialCode'),
5475
                    'content' => $user_data['official_code']
5476
                );
5477
            }
5478
        }
5479
        // Description can be very long and is generally meant to explain
5480
        //   rules *before* the exam. Leaving here to make display easier if
5481
        //   necessary
5482
        /*
5483
        if (!empty($this->description)) {
5484
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5485
        }
5486
        */
5487 View Code Duplication
        if (!empty($start_date)) {
5488
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5489
        }
5490
5491 View Code Duplication
        if (!empty($duration)) {
5492
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5493
        }
5494
5495 View Code Duplication
        if (!empty($ip)) {
5496
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5497
        }
5498
        $html  = '<div class="question-result">';
5499
        $html .= Display::page_header(
5500
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5501
        );
5502
        $html .= Display::description($array);
5503
        $html .="</div>";
5504
        return $html;
5505
    }
5506
5507
    /**
5508
     * Create a quiz from quiz data
5509
     * @param string  Title
5510
     * @param int     Time before it expires (in minutes)
5511
     * @param int     Type of exercise
5512
     * @param int     Whether it's randomly picked questions (1) or not (0)
5513
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5514
     * @param int     Whether the results are show to the user (0) or not (1)
5515
     * @param int     Maximum number of attempts (0 if no limit)
5516
     * @param int     Feedback type
5517
     * @todo this was function was added due the import exercise via CSV
5518
     * @return    int New exercise ID
5519
     */
5520
    public function createExercise(
5521
        $title,
5522
        $expired_time = 0,
5523
        $type = 2,
5524
        $random = 0,
5525
        $active = 1,
5526
        $results_disabled = 0,
5527
        $max_attempt = 0,
5528
        $feedback = 3,
5529
        $propagateNegative = 0
5530
    ) {
5531
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5532
        $type = intval($type);
5533
        $random = intval($random);
5534
        $active = intval($active);
5535
        $results_disabled = intval($results_disabled);
5536
        $max_attempt = intval($max_attempt);
5537
        $feedback = intval($feedback);
5538
        $expired_time = intval($expired_time);
5539
        $title = Database::escape_string($title);
5540
        $propagateNegative = intval($propagateNegative);
5541
        $sessionId = api_get_session_id();
5542
        $course_id = api_get_course_int_id();
5543
        // Save a new quiz
5544
        $sql = "INSERT INTO $tbl_quiz (
5545
                c_id,
5546
                title,
5547
                type,
5548
                random,
5549
                active,
5550
                results_disabled,
5551
                max_attempt,
5552
                start_time,
5553
                end_time,
5554
                feedback_type,
5555
                expired_time,
5556
                session_id,
5557
                propagate_neg
5558
            )
5559
            VALUES (
5560
                '$course_id',
5561
                '$title',
5562
                $type,
5563
                $random,
5564
                $active,
5565
                $results_disabled,
5566
                $max_attempt,
5567
                '',
5568
                '',
5569
                $feedback,
5570
                $expired_time,
5571
                $sessionId,
5572
                $propagateNegative
5573
            )";
5574
        Database::query($sql);
5575
        $quiz_id = Database::insert_id();
5576
5577
        if ($quiz_id) {
5578
5579
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5580
            Database::query($sql);
5581
        }
5582
5583
        return $quiz_id;
5584
    }
5585
5586
    function process_geometry()
5587
    {
5588
5589
    }
5590
5591
    /**
5592
     * Returns the exercise result
5593
     * @param 	int		attempt id
5594
     * @return 	float 	exercise result
5595
     */
5596
    public function get_exercise_result($exe_id)
5597
    {
5598
        $result = array();
5599
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5600
5601
        if (!empty($track_exercise_info)) {
5602
            $totalScore = 0;
5603
            $objExercise = new Exercise();
5604
            $objExercise->read($track_exercise_info['exe_exo_id']);
5605
            if (!empty($track_exercise_info['data_tracking'])) {
5606
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5607
            }
5608
            foreach ($question_list as $questionId) {
5609
                $question_result = $objExercise->manage_answer(
5610
                    $exe_id,
5611
                    $questionId,
5612
                    '',
5613
                    'exercise_show',
5614
                    array(),
5615
                    false,
5616
                    true,
5617
                    false,
5618
                    $objExercise->selectPropagateNeg()
5619
                );
5620
                $totalScore      += $question_result['score'];
5621
            }
5622
5623
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5624
                $totalScore = 0;
5625
            }
5626
            $result = array(
5627
                'score' => $totalScore,
5628
                'weight' => $track_exercise_info['exe_weighting']
5629
            );
5630
        }
5631
        return $result;
5632
    }
5633
5634
    /**
5635
     * Checks if the exercise is visible due a lot of conditions
5636
     * visibility, time limits, student attempts
5637
     * Return associative array
5638
     * value : true if execise visible
5639
     * message : HTML formated message
5640
     * rawMessage : text message
5641
     * @param int $lpId
5642
     * @param int $lpItemId
5643
     * @param int $lpItemViewId
5644
     * @param bool $filterByAdmin
5645
     * @return array
5646
     */
5647
    public function is_visible(
5648
        $lpId = 0,
5649
        $lpItemId = 0,
5650
        $lpItemViewId = 0,
5651
        $filterByAdmin = true
5652
    ) {
5653
        // 1. By default the exercise is visible
5654
        $isVisible = true;
5655
        $message = null;
5656
5657
        // 1.1 Admins and teachers can access to the exercise
5658
        if ($filterByAdmin) {
5659
            if (api_is_platform_admin() || api_is_course_admin()) {
5660
                return array('value' => true, 'message' => '');
5661
            }
5662
        }
5663
5664
        // Deleted exercise.
5665 View Code Duplication
        if ($this->active == -1) {
5666
            return array(
5667
                'value' => false,
5668
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5669
                'rawMessage' => get_lang('ExerciseNotFound')
5670
            );
5671
        }
5672
5673
        // Checking visibility in the item_property table.
5674
        $visibility = api_get_item_visibility(
5675
            api_get_course_info(),
5676
            TOOL_QUIZ,
5677
            $this->id,
5678
            api_get_session_id()
5679
        );
5680
5681
        if ($visibility == 0 || $visibility == 2) {
5682
            $this->active = 0;
5683
        }
5684
5685
        // 2. If the exercise is not active.
5686
        if (empty($lpId)) {
5687
            // 2.1 LP is OFF
5688 View Code Duplication
            if ($this->active == 0) {
5689
                return array(
5690
                    'value' => false,
5691
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5692
                    'rawMessage' => get_lang('ExerciseNotFound')
5693
                );
5694
            }
5695
        } else {
5696
            // 2.1 LP is loaded
5697
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5698
                return array(
5699
                    'value' => false,
5700
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5701
                    'rawMessage' => get_lang('ExerciseNotFound')
5702
                );
5703
            }
5704
        }
5705
5706
        //3. We check if the time limits are on
5707
        if ((!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00')
5708
            || (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00')) {
5709
            $limitTimeExists = true;
5710
        } else {
5711
            $limitTimeExists = false;
5712
        }
5713
5714
        if ($limitTimeExists) {
5715
            $timeNow = time();
5716
5717
            $existsStartDate = false;
5718
            $nowIsAfterStartDate = true;
5719
            $existsEndDate = false;
5720
            $nowIsBeforeEndDate = true;
5721
5722
5723
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
5724
                $existsStartDate = true;
5725
            }
5726
5727
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
5728
                $existsEndDate = true;
5729
            }
5730
5731
            // check if we are before-or-after end-or-start date
5732
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5733
                $nowIsAfterStartDate = false;
5734
                    }
5735
5736
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5737
                $nowIsBeforeEndDate = false;
5738
                }
5739
5740
            // lets check all cases
5741
            if ($existsStartDate && !$existsEndDate) {
5742
                // exists start date and dont exists end date
5743
                if ($nowIsAfterStartDate) {
5744
                    // after start date, no end date
5745
                    $isVisible = true;
5746
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5747
                        api_convert_and_format_date($this->start_time));
5748
                } else {
5749
                    // before start date, no end date
5750
                    $isVisible = false;
5751
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5752
                        api_convert_and_format_date($this->start_time));
5753
            }
5754
            } else if (!$existsStartDate && $existsEndDate) {
5755
                // doesnt exist start date, exists end date
5756
                if ($nowIsBeforeEndDate) {
5757
                    // before end date, no start date
5758
                    $isVisible = true;
5759
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5760
                        api_convert_and_format_date($this->end_time));
5761
                } else {
5762
                    // after end date, no start date
5763
                    $isVisible = false;
5764
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5765
                        api_convert_and_format_date($this->end_time));
5766
                }
5767
            } elseif ($existsStartDate && $existsEndDate) {
5768
                // exists start date and end date
5769
                if ($nowIsAfterStartDate) {
5770
                    if ($nowIsBeforeEndDate) {
5771
                        // after start date and before end date
5772
                        $isVisible = true;
5773
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5774
                    api_convert_and_format_date($this->start_time),
5775
                            api_convert_and_format_date($this->end_time));
5776 View Code Duplication
                    } else {
5777
                        // after start date and after end date
5778
                        $isVisible = false;
5779
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5780
                            api_convert_and_format_date($this->start_time),
5781
                            api_convert_and_format_date($this->end_time));
5782
                    }
5783 View Code Duplication
                } else {
5784
                    if ($nowIsBeforeEndDate) {
5785
                        // before start date and before end date
5786
                        $isVisible = false;
5787
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5788
                            api_convert_and_format_date($this->start_time),
5789
                            api_convert_and_format_date($this->end_time));
5790
                    }
5791
                    // case before start date and after end date is impossible
5792
                }
5793
            } elseif (!$existsStartDate && !$existsEndDate) {
5794
                // doesnt exist start date nor end date
5795
                $isVisible = true;
5796
                $message = "";
5797
            }
5798
        }
5799
5800
        // 4. We check if the student have attempts
5801
        $exerciseAttempts = $this->selectAttempts();
5802
5803
        if ($isVisible) {
5804
            if ($exerciseAttempts > 0) {
5805
5806
                $attemptCount = Event::get_attempt_count_not_finished(
5807
                    api_get_user_id(),
5808
                    $this->id,
5809
                    $lpId,
5810
                    $lpItemId,
5811
                    $lpItemViewId
5812
                );
5813
5814
                if ($attemptCount >= $exerciseAttempts) {
5815
                    $message = sprintf(
5816
                        get_lang('ReachedMaxAttempts'),
5817
                        $this->name,
5818
                        $exerciseAttempts
5819
                    );
5820
                    $isVisible = false;
5821
                }
5822
            }
5823
        }
5824
5825
        $rawMessage = "";
5826
        if (!empty($message)){
5827
            $rawMessage = $message;
5828
            $message = Display::return_message($message, 'warning', false);
5829
        }
5830
5831
        return array(
5832
            'value' => $isVisible,
5833
            'message' => $message,
5834
            'rawMessage' => $rawMessage
5835
        );
5836
    }
5837
5838
    public function added_in_lp()
5839
    {
5840
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5841
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5842
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5843
        $result = Database::query($sql);
5844
        if (Database::num_rows($result) > 0) {
5845
            return true;
5846
        }
5847
        return false;
5848
    }
5849
5850
    /**
5851
     * Returns an array with the media list
5852
     * @param array question list
5853
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5854
     * <code>
5855
     * array (size=2)
5856
     *  999 =>
5857
     *    array (size=3)
5858
     *      0 => int 7
5859
     *      1 => int 6
5860
     *      2 => int 3254
5861
     *  100 =>
5862
     *   array (size=1)
5863
     *      0 => int 5
5864
     *  </code>
5865
     * @return array
5866
     */
5867
    private function setMediaList($questionList)
5868
    {
5869
        $mediaList = array();
5870
        if (!empty($questionList)) {
5871
            foreach ($questionList as $questionId) {
5872
                $objQuestionTmp = Question::read($questionId, $this->course_id);
5873
5874
                // If a media question exists
5875
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
5876
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
5877
                } else {
5878
                    //Always the last item
5879
                    $mediaList[999][] = $objQuestionTmp->id;
5880
                }
5881
            }
5882
        }
5883
        $this->mediaList = $mediaList;
5884
    }
5885
5886
    /**
5887
     * Returns an array with this form
5888
     * @example
5889
     * <code>
5890
     * array (size=3)
5891
    999 =>
5892
    array (size=3)
5893
    0 => int 3422
5894
    1 => int 3423
5895
    2 => int 3424
5896
    100 =>
5897
    array (size=2)
5898
    0 => int 3469
5899
    1 => int 3470
5900
    101 =>
5901
    array (size=1)
5902
    0 => int 3482
5903
     * </code>
5904
     * The array inside the key 999 means the question list that belongs to the media id = 999,
5905
     * this case is special because 999 means "no media".
5906
     * @return array
5907
     */
5908
    public function getMediaList()
5909
    {
5910
        return $this->mediaList;
5911
    }
5912
5913
    /**
5914
     * Is media question activated?
5915
     * @return bool
5916
     */
5917
    public function mediaIsActivated()
5918
    {
5919
        $mediaQuestions = $this->getMediaList();
5920
        $active = false;
5921
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
5922
            $media_count = count($mediaQuestions);
5923
            if ($media_count > 1) {
5924
                return true;
5925
            } elseif ($media_count == 1) {
5926
                if (isset($mediaQuestions[999])) {
5927
                    return false;
5928
                } else {
5929
                    return true;
5930
                }
5931
            }
5932
        }
5933
5934
        return $active;
5935
    }
5936
5937
    /**
5938
     * Gets question list from the exercise
5939
     *
5940
     * @return array
5941
     */
5942
    public function getQuestionList()
5943
    {
5944
        return $this->questionList;
5945
    }
5946
5947
    /**
5948
     * Question list with medias compressed like this
5949
     * @example
5950
     * <code>
5951
     * array(
5952
     *      question_id_1,
5953
     *      question_id_2,
5954
     *      media_id, <- this media id contains question ids
5955
     *      question_id_3,
5956
     * )
5957
     * </code>
5958
     * @return array
5959
     */
5960
    public function getQuestionListWithMediasCompressed()
5961
    {
5962
        return $this->questionList;
5963
    }
5964
5965
    /**
5966
     * Question list with medias uncompressed like this
5967
     * @example
5968
     * <code>
5969
     * array(
5970
     *      question_id,
5971
     *      question_id,
5972
     *      question_id, <- belongs to a media id
5973
     *      question_id, <- belongs to a media id
5974
     *      question_id,
5975
     * )
5976
     * </code>
5977
     * @return array
5978
     */
5979
    public function getQuestionListWithMediasUncompressed()
5980
    {
5981
        return $this->questionListUncompressed;
5982
    }
5983
5984
    /**
5985
     * Sets the question list when the exercise->read() is executed
5986
     */
5987
    public function setQuestionList()
5988
    {
5989
        // Getting question list.
5990
        $questionList = $this->selectQuestionList(true);
5991
5992
        $this->setMediaList($questionList);
5993
5994
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
5995
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
5996
    }
5997
5998
    /**
5999
     *
6000
     * @params array question list
6001
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
6002
     *
6003
     **/
6004 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
6005
    {
6006
        $new_question_list = array();
6007
        if (!empty($question_list)) {
6008
            $media_questions = $this->getMediaList();
6009
6010
            $media_active = $this->mediaIsActivated($media_questions);
6011
6012
            if ($media_active) {
6013
                $counter = 1;
6014
                foreach ($question_list as $question_id) {
6015
                    $add_question = true;
6016
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6017
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6018
                            $add_question = false;
6019
                            if (!in_array($media_id, $new_question_list)) {
6020
                                $new_question_list[$counter] = $media_id;
6021
                                $counter++;
6022
                            }
6023
                            break;
6024
                        }
6025
                    }
6026
                    if ($add_question) {
6027
                        $new_question_list[$counter] = $question_id;
6028
                        $counter++;
6029
                    }
6030
                }
6031
                if ($expand_media_questions) {
6032
                    $media_key_list = array_keys($media_questions);
6033
                    foreach ($new_question_list as &$question_id) {
6034
                        if (in_array($question_id, $media_key_list)) {
6035
                            $question_id = $media_questions[$question_id];
6036
                        }
6037
                    }
6038
                    $new_question_list = array_flatten($new_question_list);
6039
                }
6040
            } else {
6041
                $new_question_list = $question_list;
6042
            }
6043
        }
6044
6045
        return $new_question_list;
6046
    }
6047
6048
    function get_validated_question_list()
6049
    {
6050
        $tabres = array();
6051
        $isRandomByCategory = $this->isRandomByCat();
6052
        if ($isRandomByCategory == 0) {
6053
            if ($this->isRandom()) {
6054
                $tabres = $this->selectRandomList();
6055
            } else {
6056
                $tabres = $this->selectQuestionList();
6057
            }
6058
        } else {
6059
            if ($this->isRandom()) {
6060
                // USE question categories
6061
                // get questions by category for this exercise
6062
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6063
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6064
                // value is the array of question id of this category
6065
                $questionList = array();
6066
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6067
                $isRandomByCategory = $this->selectRandomByCat();
6068
                // We sort categories based on the term between [] in the head
6069
                // of the category's description
6070
                /*
6071
                 * examples of categories :
6072
                 * [biologie] Maitriser les mecanismes de base de la genetique
6073
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6074
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6075
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6076
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6077
                 * [chimie] Connaître les charges des particules
6078
                 * We want that in the order of the groups defined by the term
6079
                 * between brackets at the beginning of the category title
6080
                */
6081
                // If test option is Grouped By Categories
6082
                if ($isRandomByCategory == 2) {
6083
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6084
                }
6085
                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...
6086
                    $number_of_random_question = $this->random;
6087
                    if ($this->random == -1) {
6088
                        $number_of_random_question = count($this->questionList);
6089
                    }
6090
                    $questionList = array_merge(
6091
                        $questionList,
6092
                        TestCategory::getNElementsFromArray(
6093
                            $tabquestion,
6094
                            $number_of_random_question
6095
                        )
6096
                    );
6097
                }
6098
                // shuffle the question list if test is not grouped by categories
6099
                if ($isRandomByCategory == 1) {
6100
                    shuffle($questionList); // or not
6101
                }
6102
                $tabres = $questionList;
6103
            } 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...
6104
                // Problem, random by category has been selected and
6105
                // we have no $this->isRandom number of question selected
6106
                // Should not happened
6107
            }
6108
        }
6109
        return $tabres;
6110
    }
6111
6112
    function get_question_list($expand_media_questions = false)
6113
    {
6114
        $question_list = $this->get_validated_question_list();
6115
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6116
        return $question_list;
6117
    }
6118
6119 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6120
    {
6121
        $new_question_list = array();
6122
        if (!empty($question_list)) {
6123
            $media_questions = $this->getMediaList();
6124
            $media_active = $this->mediaIsActivated($media_questions);
6125
6126
            if ($media_active) {
6127
                $counter = 1;
6128
                foreach ($question_list as $question_id) {
6129
                    $add_question = true;
6130
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6131
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6132
                            $add_question = false;
6133
                            if (!in_array($media_id, $new_question_list)) {
6134
                                $new_question_list[$counter] = $media_id;
6135
                                $counter++;
6136
                            }
6137
                            break;
6138
                        }
6139
                    }
6140
                    if ($add_question) {
6141
                        $new_question_list[$counter] = $question_id;
6142
                        $counter++;
6143
                    }
6144
                }
6145
                if ($expand_media_questions) {
6146
                    $media_key_list = array_keys($media_questions);
6147
                    foreach ($new_question_list as &$question_id) {
6148
                        if (in_array($question_id, $media_key_list)) {
6149
                            $question_id = $media_questions[$question_id];
6150
                        }
6151
                    }
6152
                    $new_question_list = array_flatten($new_question_list);
6153
                }
6154
            } else {
6155
                $new_question_list = $question_list;
6156
            }
6157
        }
6158
        return $new_question_list;
6159
    }
6160
6161
    /**
6162
     * @param int $exe_id
6163
     * @return array|mixed
6164
     */
6165
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6166
    {
6167
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6168
        $exe_id = intval($exe_id);
6169
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6170
        $result = Database::query($sql_track);
6171
        $new_array = array();
6172
        if (Database::num_rows($result) > 0 ) {
6173
            $new_array = Database::fetch_array($result, 'ASSOC');
6174
6175
            $new_array['duration'] = null;
6176
6177
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6178
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6179
6180
            if (!empty($start_date) && !empty($end_date)) {
6181
                $start_date = api_strtotime($start_date, 'UTC');
6182
                $end_date = api_strtotime($end_date, 'UTC');
6183
                if ($start_date && $end_date) {
6184
                    $mytime = $end_date- $start_date;
6185
                    $new_learnpath_item = new learnpathItem(null);
6186
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6187
                    $h = get_lang('h');
6188
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6189
                    $new_array['duration'] = $time_attemp;
6190
                }
6191
            }
6192
        }
6193
        return $new_array;
6194
    }
6195
6196
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6197
    {
6198
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6199
        $question_id = intval($question_id);
6200
        $exe_id = intval($exe_id);
6201
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6202
        if ($exercise_info) {
6203
6204
            if (empty($exercise_info['questions_to_check'])) {
6205
                if ($action == 'add') {
6206
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6207
                    $result = Database::query($sql);
6208
                }
6209
            } else {
6210
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6211
6212
                $remind_list_string = '';
6213
                if ($action == 'add') {
6214
                    if (!in_array($question_id, $remind_list)) {
6215
                        $remind_list[] = $question_id;
6216
                        if (!empty($remind_list)) {
6217
                            sort($remind_list);
6218
                            array_filter($remind_list);
6219
                        }
6220
                        $remind_list_string = implode(',', $remind_list);
6221
                    }
6222
                } elseif ($action == 'delete')  {
6223
                    if (!empty($remind_list)) {
6224
                        if (in_array($question_id, $remind_list)) {
6225
                            $remind_list = array_flip($remind_list);
6226
                            unset($remind_list[$question_id]);
6227
                            $remind_list = array_flip($remind_list);
6228
6229
                            if (!empty($remind_list)) {
6230
                                sort($remind_list);
6231
                                array_filter($remind_list);
6232
                                $remind_list_string = implode(',', $remind_list);
6233
                            }
6234
                        }
6235
                    }
6236
                }
6237
                $remind_list_string = Database::escape_string($remind_list_string);
6238
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6239
                Database::query($sql);
6240
            }
6241
        }
6242
    }
6243
6244
    public function fill_in_blank_answer_to_array($answer)
6245
    {
6246
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6247
        $teacher_answer_list = $teacher_answer_list[0];
6248
        return $teacher_answer_list;
6249
    }
6250
6251
    public function fill_in_blank_answer_to_string($answer)
6252
    {
6253
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6254
        $result = '';
6255
        if (!empty($teacher_answer_list)) {
6256
            $i = 0;
6257
            foreach ($teacher_answer_list as $teacher_item) {
6258
                $value = null;
6259
                //Cleaning student answer list
6260
                $value = strip_tags($teacher_item);
6261
                $value = api_substr($value, 1, api_strlen($value) - 2);
6262
                $value = explode('/', $value);
6263
                if (!empty($value[0])) {
6264
                    $value = trim($value[0]);
6265
                    $value = str_replace('&nbsp;', '', $value);
6266
                    $result .= $value;
6267
                }
6268
            }
6269
        }
6270
        return $result;
6271
    }
6272
6273
    function return_time_left_div()
6274
    {
6275
        $html = '<div id="clock_warning" style="display:none">';
6276
        $html .= Display::return_message(
6277
            get_lang('ReachedTimeLimit'),
6278
            'warning'
6279
        );
6280
        $html .= ' ';
6281
        $html .= sprintf(
6282
            get_lang('YouWillBeRedirectedInXSeconds'),
6283
            '<span id="counter_to_redirect" class="red_alert"></span>'
6284
        );
6285
        $html .= '</div>';
6286
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6287
        return $html;
6288
    }
6289
6290
    function get_count_question_list()
6291
    {
6292
        //Real question count
6293
        $question_count = 0;
6294
        $question_list = $this->get_question_list();
6295
        if (!empty($question_list)) {
6296
            $question_count = count($question_list);
6297
        }
6298
        return $question_count;
6299
    }
6300
6301 View Code Duplication
    function get_exercise_list_ordered()
6302
    {
6303
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6304
        $course_id = api_get_course_int_id();
6305
        $session_id = api_get_session_id();
6306
        $sql = "SELECT exercise_id, exercise_order
6307
                FROM $table_exercise_order
6308
                WHERE c_id = $course_id AND session_id = $session_id
6309
                ORDER BY exercise_order";
6310
        $result = Database::query($sql);
6311
        $list = array();
6312
        if (Database::num_rows($result)) {
6313
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6314
                $list[$row['exercise_order']] = $row['exercise_id'];
6315
            }
6316
        }
6317
        return $list;
6318
    }
6319
6320
    /**
6321
     * Get categories added in the exercise--category matrix
6322
     * @return bool
6323
     */
6324
    public function get_categories_in_exercise()
6325
    {
6326
        if (!$this->specialCategoryOrders) {
6327
            return false;
6328
        }
6329
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6330
        if (!empty($this->id)) {
6331
            $sql = "SELECT * FROM $table
6332
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6333
            $result = Database::query($sql);
6334
            $list = array();
6335
            if (Database::num_rows($result)) {
6336
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6337
                    $list[$row['category_id']] = $row;
6338
                }
6339
                return $list;
6340
            }
6341
        }
6342
        return false;
6343
    }
6344
6345
    /**
6346
     * @param null $order
6347
     * @return bool
6348
     */
6349
    public function get_categories_with_name_in_exercise($order = null)
6350
    {
6351
        if (!$this->specialCategoryOrders) {
6352
            return false;
6353
        }
6354
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6355
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6356
        $sql = "SELECT * FROM $table qc
6357
                INNER JOIN $table_category c
6358
                ON (category_id = c.iid)
6359
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6360
        if (!empty($order)) {
6361
            $sql .= "ORDER BY $order ";
6362
        }
6363
        $result = Database::query($sql);
6364
        if (Database::num_rows($result)) {
6365
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6366
                $list[$row['category_id']] = $row;
6367
            }
6368
            return $list;
6369
        }
6370
        return false;
6371
    }
6372
6373
    /**
6374
     * Get total number of question that will be parsed when using the category/exercise
6375
     */
6376
    public function getNumberQuestionExerciseCategory()
6377
    {
6378
        if (!$this->specialCategoryOrders) {
6379
            return false;
6380
        }
6381
6382
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6383
        if (!empty($this->id)) {
6384
            $sql = "SELECT SUM(count_questions) count_questions
6385
                    FROM $table
6386
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6387
            $result = Database::query($sql);
6388
            if (Database::num_rows($result)) {
6389
                $row = Database::fetch_array($result);
6390
                return $row['count_questions'];
6391
            }
6392
        }
6393
        return 0;
6394
    }
6395
6396
    /**
6397
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6398
     * @param array $categories
6399
     */
6400
    public function save_categories_in_exercise($categories)
6401
    {
6402
        if (!$this->specialCategoryOrders) {
6403
            return false;
6404
        }
6405
        if (!empty($categories) && !empty($this->id)) {
6406
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6407
            $sql = "DELETE FROM $table
6408
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6409
            Database::query($sql);
6410
            if (!empty($categories)) {
6411
                foreach ($categories as $category_id => $count_questions) {
6412
                    $params = array(
6413
                        'c_id' => $this->course_id,
6414
                        'exercise_id' => $this->id,
6415
                        'category_id' => $category_id,
6416
                        'count_questions' => $count_questions
6417
                    );
6418
                    Database::insert($table, $params);
6419
                }
6420
            }
6421
        }
6422
    }
6423
6424
    /**
6425
     * @param array $questionList
6426
     * @param int $currentQuestion
6427
     * @param array $conditions
6428
     * @param string $link
6429
     * @return string
6430
     */
6431
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6432
    {
6433
        $mediaQuestions = $this->getMediaList();
6434
6435
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6436
        $counter = 0;
6437
        $nextValue = 0;
6438
        $wasMedia = false;
6439
        $before = 0;
6440
        $counterNoMedias = 0;
6441
        foreach ($questionList as $questionId) {
6442
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6443
6444
            if (!empty($nextValue)) {
6445
                if ($wasMedia) {
6446
                    $nextValue = $nextValue - $before + 1;
6447
                }
6448
            }
6449
6450
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6451
                $fixedValue = $counterNoMedias;
6452
6453
                $html .= Display::progressPaginationBar(
6454
                    $nextValue,
6455
                    $mediaQuestions[$questionId],
6456
                    $currentQuestion,
6457
                    $fixedValue,
6458
                    $conditions,
6459
                    $link,
6460
                    true,
6461
                    true
6462
                );
6463
6464
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6465
                $before = count($questionList);
6466
                $wasMedia = true;
6467
                $nextValue += count($questionList);
6468
            } else {
6469
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6470
                $counter++;
6471
                $nextValue++;
6472
                $wasMedia = false;
6473
            }
6474
            $counterNoMedias++;
6475
        }
6476
        $html .= '</ul></div>';
6477
        return $html;
6478
    }
6479
6480
6481
    /**
6482
     *  Shows a list of numbers that represents the question to answer in a exercise
6483
     *
6484
     * @param array $categories
6485
     * @param int $current
6486
     * @param array $conditions
6487
     * @param string $link
6488
     * @return string
6489
     */
6490
    public function progressExercisePaginationBarWithCategories(
6491
        $categories,
6492
        $current,
6493
        $conditions = array(),
6494
        $link = null
6495
    ) {
6496
        $html = null;
6497
        $counterNoMedias = 0;
6498
        $nextValue = 0;
6499
        $wasMedia = false;
6500
        $before = 0;
6501
6502
        if (!empty($categories)) {
6503
            $selectionType = $this->getQuestionSelectionType();
6504
            $useRootAsCategoryTitle = false;
6505
6506
            // Grouping questions per parent category see BT#6540
6507
6508
            if (in_array(
6509
                $selectionType,
6510
                array(
6511
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6512
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6513
                )
6514
            )) {
6515
                $useRootAsCategoryTitle = true;
6516
            }
6517
6518
            // If the exercise is set to only show the titles of the categories
6519
            // at the root of the tree, then pre-order the categories tree by
6520
            // removing children and summing their questions into the parent
6521
            // categories
6522
6523
            if ($useRootAsCategoryTitle) {
6524
                // The new categories list starts empty
6525
                $newCategoryList = array();
6526
                foreach ($categories as $category) {
6527
                    $rootElement = $category['root'];
6528
6529
                    if (isset($category['parent_info'])) {
6530
                        $rootElement = $category['parent_info']['id'];
6531
                    }
6532
6533
                    //$rootElement = $category['id'];
6534
                    // If the current category's ancestor was never seen
6535
                    // before, then declare it and assign the current
6536
                    // category to it.
6537
                    if (!isset($newCategoryList[$rootElement])) {
6538
                        $newCategoryList[$rootElement] = $category;
6539
                    } else {
6540
                        // If it was already seen, then merge the previous with
6541
                        // the current category
6542
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6543
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6544
                        $newCategoryList[$rootElement] = $category;
6545
                    }
6546
                }
6547
                // Now use the newly built categories list, with only parents
6548
                $categories = $newCategoryList;
6549
            }
6550
6551
            foreach ($categories as $category) {
6552
                $questionList = $category['question_list'];
6553
                // Check if in this category there questions added in a media
6554
                $mediaQuestionId = $category['media_question'];
6555
                $isMedia = false;
6556
                $fixedValue = null;
6557
6558
                // Media exists!
6559
                if ($mediaQuestionId != 999) {
6560
                    $isMedia = true;
6561
                    $fixedValue = $counterNoMedias;
6562
                }
6563
6564
                //$categoryName = $category['path']; << show the path
6565
                $categoryName = $category['name'];
6566
6567
                if ($useRootAsCategoryTitle) {
6568
                    if (isset($category['parent_info'])) {
6569
                        $categoryName  = $category['parent_info']['title'];
6570
                    }
6571
                }
6572
                $html .= '<div class="row">';
6573
                $html .= '<div class="span2">'.$categoryName.'</div>';
6574
                $html .= '<div class="span8">';
6575
6576
                if (!empty($nextValue)) {
6577
                    if ($wasMedia) {
6578
                        $nextValue = $nextValue - $before + 1;
6579
                    }
6580
                }
6581
                $html .= Display::progressPaginationBar(
6582
                    $nextValue,
6583
                    $questionList,
6584
                    $current,
6585
                    $fixedValue,
6586
                    $conditions,
6587
                    $link,
6588
                    $isMedia,
6589
                    true
6590
                );
6591
                $html .= '</div>';
6592
                $html .= '</div>';
6593
6594
                if ($mediaQuestionId == 999) {
6595
                    $counterNoMedias += count($questionList);
6596
                } else {
6597
                    $counterNoMedias++;
6598
                }
6599
6600
                $nextValue += count($questionList);
6601
                $before = count($questionList);
6602
6603
                if ($mediaQuestionId != 999) {
6604
                    $wasMedia = true;
6605
                } else {
6606
                    $wasMedia = false;
6607
                }
6608
6609
            }
6610
        }
6611
        return $html;
6612
    }
6613
6614
    /**
6615
     * Renders a question list
6616
     *
6617
     * @param array $questionList (with media questions compressed)
6618
     * @param int $currentQuestion
6619
     * @param array $exerciseResult
6620
     * @param array $attemptList
6621
     * @param array $remindList
6622
     */
6623
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6624
    {
6625
        $mediaQuestions = $this->getMediaList();
6626
        $i = 0;
6627
6628
        // Normal question list render (medias compressed)
6629
        foreach ($questionList as $questionId) {
6630
            $i++;
6631
            // For sequential exercises
6632
            if ($this->type == ONE_PER_PAGE) {
6633
                // If it is not the right question, goes to the next loop iteration
6634
                if ($currentQuestion != $i) {
6635
                    continue;
6636
                } else {
6637
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6638
                        // if the user has already answered this question
6639
                        if (isset($exerciseResult[$questionId])) {
6640
                            Display::display_normal_message(get_lang('AlreadyAnswered'));
6641
                            break;
6642
                        }
6643
                    }
6644
                }
6645
            }
6646
6647
            // The $questionList contains the media id we check if this questionId is a media question type
6648
6649
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6650
6651
                // The question belongs to a media
6652
                $mediaQuestionList = $mediaQuestions[$questionId];
6653
                $objQuestionTmp = Question::read($questionId);
6654
6655
                $counter = 1;
6656
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6657
                    echo $objQuestionTmp->show_media_content();
6658
6659
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6660
6661
                    // Show questions that belongs to a media
6662
                    if (!empty($mediaQuestionList)) {
6663
                        // In order to parse media questions we use letters a, b, c, etc.
6664
                        $letterCounter = 97;
6665
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6666
                            $isLastQuestionInMedia = false;
6667
                            if ($counter == $countQuestionsInsideMedia) {
6668
                                $isLastQuestionInMedia = true;
6669
                            }
6670
                            $this->renderQuestion(
6671
                                $questionIdInsideMedia,
6672
                                $attemptList,
6673
                                $remindList,
6674
                                chr($letterCounter),
6675
                                $currentQuestion,
6676
                                $mediaQuestionList,
6677
                                $isLastQuestionInMedia,
6678
                                $questionList
6679
                            );
6680
                            $letterCounter++;
6681
                            $counter++;
6682
                        }
6683
                    }
6684
                } else {
6685
                    $this->renderQuestion(
6686
                        $questionId,
6687
                        $attemptList,
6688
                        $remindList,
6689
                        $i,
6690
                        $currentQuestion,
6691
                        null,
6692
                        null,
6693
                        $questionList
6694
                    );
6695
                    $i++;
6696
                }
6697
            } else {
6698
                // Normal question render.
6699
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6700
            }
6701
6702
            // For sequential exercises.
6703
            if ($this->type == ONE_PER_PAGE) {
6704
                // quits the loop
6705
                break;
6706
            }
6707
        }
6708
        // end foreach()
6709
6710
        if ($this->type == ALL_ON_ONE_PAGE) {
6711
            $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 6629. 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...
6712
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6713
        }
6714
    }
6715
6716
    /**
6717
     * @param int $questionId
6718
     * @param array $attemptList
6719
     * @param array $remindList
6720
     * @param int $i
6721
     * @param int $current_question
6722
     * @param array $questions_in_media
6723
     * @param bool $last_question_in_media
6724
     * @param array $realQuestionList
6725
     * @param bool $generateJS
6726
     * @return null
6727
     */
6728
    public function renderQuestion(
6729
        $questionId,
6730
        $attemptList,
6731
        $remindList,
6732
        $i,
6733
        $current_question,
6734
        $questions_in_media = array(),
6735
        $last_question_in_media = false,
6736
        $realQuestionList,
6737
        $generateJS = true
6738
    ) {
6739
6740
        // With this option on the question is loaded via AJAX
6741
        //$generateJS = true;
6742
        //$this->loadQuestionAJAX = true;
6743
6744
        if ($generateJS && $this->loadQuestionAJAX) {
6745
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId;
6746
            $params = array(
6747
                'questionId' => $questionId,
6748
                'attemptList'=> $attemptList,
6749
                'remindList' => $remindList,
6750
                'i' => $i,
6751
                'current_question' => $current_question,
6752
                'questions_in_media' => $questions_in_media,
6753
                'last_question_in_media' => $last_question_in_media
6754
            );
6755
            $params = json_encode($params);
6756
6757
            $script = '<script>
6758
            $(function(){
6759
                var params = '.$params.';
6760
                $.ajax({
6761
                    type: "GET",
6762
                    async: false,
6763
                    data: params,
6764
                    url: "'.$url.'",
6765
                    success: function(return_value) {
6766
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6767
                    }
6768
                });
6769
            });
6770
            </script>
6771
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6772
            echo $script;
6773
        } else {
6774
6775
            global $origin;
6776
            $question_obj = Question::read($questionId);
6777
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6778
6779
            $remind_highlight = null;
6780
6781
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6782
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6783
                $remind_highlight = 'no_remind_highlight';
6784
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6785
                    return null;
6786
                }
6787
            }
6788
6789
            $attributes = array('id' =>'remind_list['.$questionId.']');
6790
            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...
6791
                //$attributes['checked'] = 1;
6792
                //$remind_highlight = ' remind_highlight ';
6793
            }
6794
6795
            // Showing the question
6796
6797
            $exercise_actions  = null;
6798
6799
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6800
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6801
6802
            // Shows the question + possible answers
6803
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6804
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6805
6806
            // Button save and continue
6807 View Code Duplication
            switch ($this->type) {
6808
                case ONE_PER_PAGE:
6809
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6810
                    break;
6811
                case ALL_ON_ONE_PAGE:
6812
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
6813
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6814
                    $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
6815
                    break;
6816
            }
6817
6818
            if (!empty($questions_in_media)) {
6819
                $count_of_questions_inside_media = count($questions_in_media);
6820
                if ($count_of_questions_inside_media > 1) {
6821
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
6822
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6823
                    $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
6824
                }
6825
6826
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6827
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6828
                }
6829
            }
6830
6831
            // Checkbox review answers
6832
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6833
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6834
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6835
            }
6836
6837
            echo Display::div(' ', array('class'=>'clear'));
6838
6839
            $paginationCounter = null;
6840
            if ($this->type == ONE_PER_PAGE) {
6841
                if (empty($questions_in_media)) {
6842
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6843
                } else {
6844
                    if ($last_question_in_media) {
6845
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6846
                    }
6847
                }
6848
            }
6849
6850
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6851
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6852
            echo '</div>';
6853
        }
6854
    }
6855
6856
    /**
6857
     * Shows a question
6858
     * @param Question $objQuestionTmp
6859
     * @param bool $only_questions if true only show the questions, no exercise title
6860
     * @param bool $origin origin i.e = learnpath
6861
     * @param string $current_item current item from the list of questions
6862
     * @param bool $show_title
6863
     * @param bool $freeze
6864
     * @param array $user_choice
6865
     * @param bool $show_comment
6866
     * @param null $exercise_feedback
6867
     * @param bool $show_answers
6868
     * @param null $modelType
6869
     * @param bool $categoryMinusOne
6870
     * @return bool|null|string
6871
     */
6872
    public function showQuestion(
6873
        Question $objQuestionTmp,
6874
        $only_questions = false,
6875
        $origin = false,
6876
        $current_item = '',
6877
        $show_title = true,
6878
        $freeze = false,
6879
        $user_choice = array(),
6880
        $show_comment = false,
6881
        $exercise_feedback = null,
6882
        $show_answers = false,
6883
        $modelType = null,
6884
        $categoryMinusOne = true
6885
    ) {
6886
        // Text direction for the current language
6887
        //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
6888
        // Change false to true in the following line to enable answer hinting
6889
        $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
6890
        // Reads question information
6891
        if (!$objQuestionTmp) {
6892
            // Question not found
6893
            return false;
6894
        }
6895
6896
        $html = null;
6897
6898
        $questionId = $objQuestionTmp->id;
6899
6900
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
6901
            $show_comment = false;
6902
        }
6903
6904
        $answerType = $objQuestionTmp->selectType();
6905
        $pictureName = $objQuestionTmp->selectPicture();
6906
6907
        $s = null;
6908
        $form = new FormValidator('question');
6909
        $renderer = $form->defaultRenderer();
6910
        $form_template = '{content}';
6911
        $renderer->setFormTemplate($form_template);
6912
6913
        if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
6914
            // Question is not a hotspot
6915
            if (!$only_questions) {
6916
                $questionDescription = $objQuestionTmp->selectDescription();
6917
                if ($show_title) {
6918
                    $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...
6919
                    $html .= $categoryName;
6920
                    $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
6921
                    if (!empty($questionDescription)) {
6922
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6923
                    }
6924 View Code Duplication
                } else {
6925
                    $html .= '<div class="media">';
6926
                    $html .= '<div class="pull-left">';
6927
                    $html .= '<div class="media-object">';
6928
                    $html .= Display::div($current_item, array('class' => 'question_no_title'));
6929
                    $html .= '</div>';
6930
                    $html .= '</div>';
6931
                    $html .= '<div class="media-body">';
6932
                    if (!empty($questionDescription)) {
6933
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6934
                    }
6935
                    $html .= '</div>';
6936
                    $html .= '</div>';
6937
                }
6938
            }
6939
6940
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
6941
                return null;
6942
            }
6943
6944
            $html .= '<div class="question_options">';
6945
            // construction of the Answer object (also gets all answers details)
6946
            $objAnswerTmp = new Answer($questionId, null, $this);
6947
6948
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
6949
            $course_id = api_get_course_int_id();
6950
            $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
6951
6952
            // For "matching" type here, we need something a little bit special
6953
            // because the match between the suggestions and the answers cannot be
6954
            // done easily (suggestions and answers are in the same table), so we
6955
            // have to go through answers first (elems with "correct" value to 0).
6956
            $select_items = array();
6957
            //This will contain the number of answers on the left side. We call them
6958
            // suggestions here, for the sake of comprehensions, while the ones
6959
            // on the right side are called answers
6960
            $num_suggestions = 0;
6961
6962
            if ($answerType == MATCHING || $answerType == DRAGGABLE) {
6963 View Code Duplication
                if ($answerType == DRAGGABLE) {
6964
                    $s .= '<div class="ui-widget ui-helper-clearfix">
6965
                            <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
6966
                } else {
6967
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
6968
                    $s .= '<table class="data_table">';
6969
                }
6970
6971
                $j = 1; //iterate through answers
6972
                $letter = 'A'; //mark letters for each answer
6973
                $answer_matching = array();
6974
                $capital_letter = array();
6975
                //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
6976
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
6977
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
6978
                    $answer = $objAnswerTmp->selectAnswer($answerId);
6979
                    if ($answerCorrect == 0) {
6980
                        // options (A, B, C, ...) that will be put into the list-box
6981
                        // have the "correct" field set to 0 because they are answer
6982
                        $capital_letter[$j] = $letter;
6983
                        //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
6984
                        $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
6985
                        $j++;
6986
                        $letter++;
6987
                    }
6988
                }
6989
6990
                $i = 1;
6991
6992
                $select_items[0]['id'] = 0;
6993
                $select_items[0]['letter'] = '--';
6994
                $select_items[0]['answer'] = '';
6995
6996 View Code Duplication
                foreach ($answer_matching as $id => $value) {
6997
                    $select_items[$i]['id'] = $value['id'];
6998
                    $select_items[$i]['letter'] = $capital_letter[$id];
6999
                    $select_items[$i]['answer'] = $value['answer'];
7000
                    $i++;
7001
                }
7002
                $num_suggestions = ($nbrAnswers - $j) + 1;
7003
            } elseif ($answerType == FREE_ANSWER) {
7004
                $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
7005
                $toolBar = 'TestFreeAnswer';
7006
                if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
7007
                    $toolBar = 'TestFreeAnswerStrict';
7008
                }
7009
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
7010
                $form->setDefaults(array("choice[".$questionId."]" => $content));
7011
                $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...
7012
            } elseif ($answerType == ORAL_EXPRESSION) {
7013
                // Add nanogong
7014 View Code Duplication
                if (api_get_setting('document.enable_nanogong') == 'true') {
7015
7016
                    //@todo pass this as a parameter
7017
                    global $exercise_stat_info, $exerciseId;
7018
7019
                    if (!empty($exercise_stat_info)) {
7020
                        $params = array(
7021
                            'exercise_id' => $exercise_stat_info['exe_exo_id'],
7022
                            'exe_id' => $exercise_stat_info['exe_id'],
7023
                            'question_id' => $questionId
7024
                        );
7025
                    } else {
7026
                        $params = array(
7027
                            'exercise_id' => $exerciseId,
7028
                            'exe_id' => 'temp_exe',
7029
                            'question_id' => $questionId
7030
                        );
7031
                    }
7032
7033
                    $nano = new Nanogong($params);
7034
                    $s .= $nano->show_button();
7035
                }
7036
7037
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => 'TestFreeAnswer'));
7038
                //$form->setDefaults(array("choice[".$questionId."]" => $content));
7039
                $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...
7040
            }
7041
7042
            // Now navigate through the possible answers, using the max number of
7043
            // answers for the question as a limiter
7044
            $lines_count = 1; // a counter for matching-type answers
7045
7046
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7047
                $header = Display::tag('th', get_lang('Options'));
7048
                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...
7049
                    $header .= Display::tag('th', $item);
7050
                }
7051
                if ($show_comment) {
7052
                    $header .= Display::tag('th', get_lang('Feedback'));
7053
                }
7054
                $s .= '<table class="data_table">';
7055
                $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7056
            }
7057
7058 View Code Duplication
            if ($show_comment) {
7059
                if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
7060
                    $header = Display::tag('th', get_lang('Options'));
7061
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
7062
                        $header .= Display::tag('th', get_lang('Feedback'));
7063
                    }
7064
                    $s .= '<table class="data_table">';
7065
                    $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7066
                }
7067
            }
7068
7069
            $matching_correct_answer = 0;
7070
            $user_choice_array = array();
7071
            if (!empty($user_choice)) {
7072
                foreach ($user_choice as $item) {
7073
                    $user_choice_array[] = $item['answer'];
7074
                }
7075
            }
7076
7077
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7078
                $answer = $objAnswerTmp->selectAnswer($answerId);
7079
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7080
                $comment = $objAnswerTmp->selectComment($answerId);
7081
7082
                //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
7083
                $numAnswer = $answerId;
7084
7085
                $attributes = array();
7086
                // Unique answer
7087
                if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
7088
7089
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7090 View Code Duplication
                    if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
7091
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7092
                    } else {
7093
                        $attributes = array('id' => $input_id);
7094
                    }
7095
7096
                    if ($debug_mark_answer) {
7097
                        if ($answerCorrect) {
7098
                            $attributes['checked'] = 1;
7099
                            $attributes['selected'] = 1;
7100
                        }
7101
                    }
7102
7103
                    $answer = Security::remove_XSS($answer);
7104
                    $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
7105
7106
                    $answer_input = null;
7107
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7108
                        $attributes['style'] = 'display:none';
7109
                        $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
7110
                    }
7111
7112
                    $answer_input .= '<label class="radio">';
7113
                    $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
7114
                    $answer_input .= $answer;
7115
                    $answer_input .= '</label>';
7116
7117
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7118
                        $answer_input .= "</div>";
7119
                    }
7120
7121
                    if ($show_comment) {
7122
                        $s .= '<tr><td>';
7123
                        $s .= $answer_input;
7124
                        $s .= '</td>';
7125
                        $s .= '<td>';
7126
                        $s .= $comment;
7127
                        $s .= '</td>';
7128
                        $s .= '</tr>';
7129
                    } else {
7130
                        $s .= $answer_input;
7131
                    }
7132
7133
                } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
7134
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7135
                    $answer = Security::remove_XSS($answer);
7136
7137
                    if (in_array($numAnswer, $user_choice_array)) {
7138
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7139
                    } else {
7140
                        $attributes = array('id' => $input_id);
7141
                    }
7142
7143
                    if ($debug_mark_answer) {
7144
                        if ($answerCorrect) {
7145
                            $attributes['checked'] = 1;
7146
                            $attributes['selected'] = 1;
7147
                        }
7148
                    }
7149
7150 View Code Duplication
                    if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
7151
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7152
7153
                        $answer_input = '<label class="checkbox">';
7154
                        $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
7155
                        $answer_input .= $answer;
7156
                        $answer_input .= '</label>';
7157
7158
                        if ($show_comment) {
7159
                            $s .= '<tr><td>';
7160
                            $s .= $answer_input;
7161
                            $s .= '</td>';
7162
                            $s .= '<td>';
7163
                            $s .= $comment;
7164
                            $s .= '</td>';
7165
                            $s .='</tr>';
7166
                        } else {
7167
                            $s .= $answer_input;
7168
                        }
7169
                    } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
7170
7171
                        $my_choice = array();
7172
                        if (!empty($user_choice_array)) {
7173
                            foreach ($user_choice_array as $item) {
7174
                                $item = explode(':', $item);
7175
                                $my_choice[$item[0]] = $item[1];
7176
                            }
7177
                        }
7178
7179
                        $s .='<tr>';
7180
                        $s .= Display::tag('td', $answer);
7181
7182
                        if (!empty($quiz_question_options)) {
7183
                            foreach ($quiz_question_options as $id => $item) {
7184
                                $id = $item['iid'];
7185
                                if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
7186
                                    $attributes = array('checked' => 1, 'selected' => 1);
7187
                                } else {
7188
                                    $attributes = array();
7189
                                }
7190
7191
                                if ($debug_mark_answer) {
7192
                                    if ($id == $answerCorrect) {
7193
                                        $attributes['checked'] = 1;
7194
                                        $attributes['selected'] = 1;
7195
                                    }
7196
                                }
7197
                                $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
7198
                            }
7199
                        }
7200
7201
                        if ($show_comment) {
7202
                            $s .= '<td>';
7203
                            $s .= $comment;
7204
                            $s .= '</td>';
7205
                        }
7206
                        $s.='</tr>';
7207
                    }
7208
7209 View Code Duplication
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
7210
7211
                    // multiple answers
7212
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7213
7214
                    if (in_array($numAnswer, $user_choice_array)) {
7215
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7216
                    } else {
7217
                        $attributes = array('id' => $input_id);
7218
                    }
7219
7220
                    if ($debug_mark_answer) {
7221
                        if ($answerCorrect) {
7222
                            $attributes['checked'] = 1;
7223
                            $attributes['selected'] = 1;
7224
                        }
7225
                    }
7226
7227
                    $answer = Security::remove_XSS($answer);
7228
                    $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7229
                    $answer_input .= '<label class="checkbox">';
7230
                    $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
7231
                    $answer_input .= $answer;
7232
                    $answer_input .= '</label>';
7233
7234
                    if ($show_comment) {
7235
                        $s.= '<tr>';
7236
                        $s .= '<td>';
7237
                        $s.= $answer_input;
7238
                        $s .= '</td>';
7239
                        $s .= '<td>';
7240
                        $s .= $comment;
7241
                        $s .= '</td>';
7242
                        $s.= '</tr>';
7243
                    } else {
7244
                        $s.= $answer_input;
7245
                    }
7246
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7247
                    $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7248
7249
                    $my_choice = array();
7250
                    if (!empty($user_choice_array)) {
7251
                        foreach ($user_choice_array as $item) {
7252
                            $item = explode(':', $item);
7253
                            $my_choice[$item[0]] = $item[1];
7254
                        }
7255
                    }
7256
                    $answer = Security::remove_XSS($answer);
7257
                    $s .='<tr>';
7258
                    $s .= Display::tag('td', $answer);
7259
7260
                    foreach ($objQuestionTmp->options as $key => $item) {
7261
                        if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
7262
                            $attributes = array('checked' => 1, 'selected' => 1);
7263
                        } else {
7264
                            $attributes = array();
7265
                        }
7266
7267
                        if ($debug_mark_answer) {
7268
                            if ($key == $answerCorrect) {
7269
                                $attributes['checked'] = 1;
7270
                                $attributes['selected'] = 1;
7271
                            }
7272
                        }
7273
                        $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
7274
                    }
7275
7276
                    if ($show_comment) {
7277
                        $s .= '<td>';
7278
                        $s .= $comment;
7279
                        $s .= '</td>';
7280
                    }
7281
                    $s.='</tr>';
7282
                } elseif ($answerType == FILL_IN_BLANKS) {
7283
                    list($answer) = explode('::', $answer);
7284
7285
                    //Correct answer
7286
                    api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
7287
7288
                    //Student's answezr
7289
                    if (isset($user_choice[0]['answer'])) {
7290
                        api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
7291
                        $student_answer_list = $student_answer_list[0];
7292
                    }
7293
7294
                    //If debug
7295
                    if ($debug_mark_answer) {
7296
                        $student_answer_list = $correct_answer_list[0];
7297
                    }
7298
7299
                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
7300
                        $correct_answer_list = $correct_answer_list[0];
7301
                        $i = 0;
7302
                        foreach ($correct_answer_list as $correct_item) {
7303
                            $value = null;
7304
                            if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
7305
7306
                                //Cleaning student answer list
7307
                                $value = strip_tags($student_answer_list[$i]);
7308
                                $value = api_substr($value, 1, api_strlen($value) - 2);
7309
                                $value = explode('/', $value);
7310
7311
                                if (!empty($value[0])) {
7312
                                    $value = str_replace('&nbsp;', '', trim($value[0]));
7313
                                }
7314
                                $correct_item = preg_quote($correct_item);
7315
                                $correct_item = api_preg_replace('|/|', '\/', $correct_item);   // to prevent error if there is a / in the text to find
7316
                                $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
7317
                            }
7318
                            $i++;
7319
                        }
7320
                    } else {
7321
                        $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
7322
                    }
7323
                    $s .= $answer;
7324
                } elseif ($answerType == MATCHING) {
7325
                    // matching type, showing suggestions and answers
7326
                    // TODO: replace $answerId by $numAnswer
7327
7328
                    if ($lines_count == 1) {
7329
                        $s .= $objAnswerTmp->getJs();
7330
                    }
7331
                    if ($answerCorrect != 0) {
7332
                        // only show elements to be answered (not the contents of
7333
                        // the select boxes, who are correct = 0)
7334
                        $s .= '<tr><td width="45%">';
7335
                        $parsed_answer = $answer;
7336
                        $windowId = $questionId.'_'.$lines_count;
7337
                        //left part questions
7338
                        $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
7339
                                    <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
7340
                                </div>
7341
                                </td>';
7342
7343
                        // middle part (matches selects)
7344
7345
                        $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
7346
                        $s .= '<div style="display:block">';
7347
7348
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
7349
                        $selectedValue = 0;
7350
                        // fills the list-box
7351
                        $item = 0;
7352 View Code Duplication
                        foreach ($select_items as $val) {
7353
                            // set $debug_mark_answer to true at public static function start to
7354
                            // show the correct answer with a suffix '-x'
7355
                            $selected = '';
7356
                            if ($debug_mark_answer) {
7357
                                if ($val['id'] == $answerCorrect) {
7358
                                    $selected = 'selected="selected"';
7359
                                    $selectedValue = $val['id'];
7360
                                }
7361
                            }
7362
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7363
                                $selected = 'selected="selected"';
7364
                                $selectedValue = $val['id'];
7365
                            }
7366
                            //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
7367
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7368
                            $item++;
7369
                        }
7370
7371 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7372
                            $s.= '<script>
7373
                                jsPlumb.ready(function() {
7374
                                    jsPlumb.connect({
7375
                                        source: "window_'.$windowId.'",
7376
                                        target: "window_'.$questionId.'_'.$selectedValue.'_answer",
7377
                                        endpoint:["Blank", { radius:15 }],
7378
                                        anchor:["RightMiddle","LeftMiddle"],
7379
                                        paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
7380
                                        connector: [connectorType, { curviness: curvinessValue } ],
7381
                                    })
7382
                                });
7383
                                </script>';
7384
                        }
7385
                        $s .= '</select></div></td>';
7386
7387
                        $s.='<td width="45%" valign="top" >';
7388
7389 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7390
                            $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
7391
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7392
                                  </div>';
7393
                        } else {
7394
                            $s.='&nbsp;';
7395
                        }
7396
7397
                        $s .= '</td>';
7398
                        $s .= '</tr>';
7399
                        $lines_count++;
7400
                        //if the left side of the "matching" has been completely
7401
                        // shown but the right side still has values to show...
7402
                        if (($lines_count - 1) == $num_suggestions) {
7403
                            // if it remains answers to shown at the right side
7404
                            while (isset($select_items[$lines_count])) {
7405
                                $s .= '<tr>
7406
                                      <td colspan="2"></td>
7407
                                      <td valign="top">';
7408
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7409
                                $s .= $select_items[$lines_count]['answer'];
7410
                                $s.="</td>
7411
                                </tr>";
7412
                                $lines_count++;
7413
                            } // end while()
7414
                        }  // end if()
7415
                        $matching_correct_answer++;
7416
                    }
7417
                } elseif ($answerType ==  DRAGGABLE) {
7418
                    // matching type, showing suggestions and answers
7419
                    // TODO: replace $answerId by $numAnswer
7420
7421
                    if ($answerCorrect != 0) {
7422
                        // only show elements to be answered (not the contents of
7423
                        // the select boxes, who are correct = 0)
7424
                        $s .= '<td>';
7425
                        $parsed_answer = $answer;
7426
                        $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
7427
7428
                        //left part questions
7429
                        $s .= '<li class="ui-state-default" id="'.$windowId.'">';
7430
                        $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
7431
                                   '.$parsed_answer.'
7432
                                </div>';
7433
7434
                        $s .= '<div style="display:none">';
7435
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
7436
                        $selectedValue = 0;
7437
                        // fills the list-box
7438
                        $item = 0;
7439 View Code Duplication
                        foreach ($select_items as $val) {
7440
                            // set $debug_mark_answer to true at function start to
7441
                            // show the correct answer with a suffix '-x'
7442
                            $selected = '';
7443
                            if ($debug_mark_answer) {
7444
                                if ($val['id'] == $answerCorrect) {
7445
                                    $selected = 'selected="selected"';
7446
                                    $selectedValue = $val['id'];
7447
                                }
7448
                            }
7449
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7450
                                $selected = 'selected="selected"';
7451
                                $selectedValue = $val['id'];
7452
                            }
7453
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7454
                            $item++;
7455
                        }
7456
                        $s .= '</select>';
7457
7458 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7459
                            $s.= '<script>
7460
                                $(function() {
7461
                                    deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
7462
                                });
7463
                                </script>';
7464
                        }
7465
7466 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7467
                            $s.= '<div id="window_'.$windowId.'_answer" class="">
7468
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7469
                                  </div>';
7470
                        } else {
7471
                            $s.='&nbsp;';
7472
                        }
7473
                        $lines_count++;
7474
                        //if the left side of the "matching" has been completely
7475
                        // shown but the right side still has values to show...
7476
7477 View Code Duplication
                        if (($lines_count - 1) == $num_suggestions) {
7478
                            // if it remains answers to shown at the right side
7479
                            while (isset($select_items[$lines_count])) {
7480
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7481
                                $s .= $select_items[$lines_count]['answer'];
7482
                                $lines_count++;
7483
                            }
7484
                        }
7485
                        $s .= '</div>';
7486
                        $matching_correct_answer++;
7487
                        $s .= '</li>';
7488
                    }
7489
                }
7490
            } // end for()
7491
7492
            if ($show_comment) {
7493
                $s .= '</table>';
7494
            } else {
7495
                if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
7496
                    $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7497
                    $s .= '</table>';
7498
                }
7499
            }
7500
7501
            if ($answerType == DRAGGABLE) {
7502
                $s .= '</ul><div class="clear"></div>';
7503
7504
                $counterAnswer = 1;
7505
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7506
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7507
                    $windowId = $questionId.'_'.$counterAnswer;
7508
                    if ($answerCorrect == 0) {
7509
                        $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
7510
                        $counterAnswer++;
7511
                    }
7512
                }
7513
            }
7514
7515
            if ($answerType == MATCHING) {
7516
                $s .= '</div>';
7517
            }
7518
7519
            $s .= '</div>';
7520
7521
            // destruction of the Answer object
7522
            unset($objAnswerTmp);
7523
7524
            // destruction of the Question object
7525
            unset($objQuestionTmp);
7526
7527
            $html .= $s;
7528
            return $html;
7529
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
7530
            // Question is a HOT_SPOT
7531
            //checking document/images visibility
7532 View Code Duplication
            if (api_is_platform_admin() || api_is_course_admin()) {
7533
                $course = api_get_course_info();
7534
                $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
7535
                if (is_numeric($doc_id)) {
7536
                    $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
7537
                    if (!$images_folder_visibility) {
7538
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
7539
                        Display::display_warning_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'));
7540
                    }
7541
                }
7542
            }
7543
            $questionName = $objQuestionTmp->selectTitle();
7544
            $questionDescription = $objQuestionTmp->selectDescription();
7545
7546
            if ($freeze) {
7547
                $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...
7548
                $html .= $s;
7549
                return $html;
7550
            }
7551
7552
            // Get the answers, make a list
7553
            $objAnswerTmp = new Answer($questionId);
7554
7555
            // get answers of hotpost
7556
            $answers_hotspot = array();
7557
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7558
                //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
7559
                $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
7560
            }
7561
7562
            // display answers of hotpost order by id
7563
            $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
7564
            if (!empty($answers_hotspot)) {
7565
                ksort($answers_hotspot);
7566
                foreach ($answers_hotspot as $key => $value) {
7567
                    $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
7568
                }
7569
            }
7570
            $answer_list .= '</dl></div>';
7571
7572
            if ($answerType == HOT_SPOT_DELINEATION) {
7573
                $answer_list = '';
7574
                $swf_file = 'hotspot_delineation_user';
7575
                $swf_height = 405;
7576
            } else {
7577
                $swf_file = 'hotspot_user';
7578
                $swf_height = 436;
7579
            }
7580
7581
            if (!$only_questions) {
7582
                if ($show_title) {
7583
                    $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...
7584
                    $html .=  '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
7585
                    $html .=  $questionDescription;
7586 View Code Duplication
                } else {
7587
                    $html .= '<div class="media">';
7588
                    $html .= '<div class="pull-left">';
7589
                    $html .= '<div class="media-object">';
7590
                    $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
7591
                    $html .= '</div>';
7592
                    $html .= '</div>';
7593
                    $html .= '<div class="media-body">';
7594
                    if (!empty($questionDescription)) {
7595
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7596
                    }
7597
                    $html .= '</div>';
7598
                    $html .= '</div>';
7599
                }
7600
                //@todo I need to the get the feedback type
7601
                $html .=  '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
7602
                $html .=  '<table class="exercise_questions">
7603
                           <tr>
7604
                            <td valign="top" colspan="2">';
7605
                $html .=  '</td></tr>';
7606
            }
7607
7608
            $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
7609
7610
            $s .= ' <script type="text/javascript" src="../plugin/hotspot/JavaScriptFlashGateway.js"></script>
7611
                    <script src="../plugin/hotspot/hotspot.js" type="text/javascript" ></script>
7612
                    <script type="text/javascript">
7613
                    <!--
7614
                    // Globals
7615
                    // Major version of Flash required
7616
                    var requiredMajorVersion = 7;
7617
                    // Minor version of Flash required
7618
                    var requiredMinorVersion = 0;
7619
                    // Minor version of Flash required
7620
                    var requiredRevision = 0;
7621
                    // the version of javascript supported
7622
                    var jsVersion = 1.0;
7623
                    // -->
7624
                    </script>
7625
                    <script language="VBScript" type="text/vbscript">
7626
                    <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
7627
                    Function VBGetSwfVer(i)
7628
                      on error resume next
7629
                      Dim swControl, swVersion
7630
                      swVersion = 0
7631
7632
                      set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
7633
                      if (IsObject(swControl)) then
7634
                        swVersion = swControl.GetVariable("$version")
7635
                      end if
7636
                      VBGetSwfVer = swVersion
7637
                    End Function
7638
                    // -->
7639
                    </script>
7640
7641
                    <script language="JavaScript1.1" type="text/javascript">
7642
                    <!-- // Detect Client Browser type
7643
                    var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
7644
                    var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
7645
                    var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
7646
                    jsVersion = 1.1;
7647
                    // JavaScript helper required to detect Flash Player PlugIn version information
7648
                    function JSGetSwfVer(i) {
7649
                        // NS/Opera version >= 3 check for Flash plugin in plugin array
7650
                        if (navigator.plugins != null && navigator.plugins.length > 0) {
7651
                            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
7652
                                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
7653
                                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
7654
                                descArray = flashDescription.split(" ");
7655
                                tempArrayMajor = descArray[2].split(".");
7656
                                versionMajor = tempArrayMajor[0];
7657
                                versionMinor = tempArrayMajor[1];
7658
                                if ( descArray[3] != "" ) {
7659
                                    tempArrayMinor = descArray[3].split("r");
7660
                                } else {
7661
                                    tempArrayMinor = descArray[4].split("r");
7662
                                }
7663
                                versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
7664
                                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
7665
                            } else {
7666
                                flashVer = -1;
7667
                            }
7668
                        }
7669
                        // MSN/WebTV 2.6 supports Flash 4
7670
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
7671
                        // WebTV 2.5 supports Flash 3
7672
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
7673
                        // older WebTV supports Flash 2
7674
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
7675
                        // Can\'t detect in all other cases
7676
                        else {
7677
                            flashVer = -1;
7678
                        }
7679
                        return flashVer;
7680
                    }
7681
                    // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
7682
7683
                    function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
7684
                        reqVer = parseFloat(reqMajorVer + "." + reqRevision);
7685
                        // loop backwards through the versions until we find the newest version
7686
                        for (i=25;i>0;i--) {
7687
                            if (isIE && isWin && !isOpera) {
7688
                                versionStr = VBGetSwfVer(i);
7689
                            } else {
7690
                                versionStr = JSGetSwfVer(i);
7691
                            }
7692
                            if (versionStr == -1 ) {
7693
                                return false;
7694
                            } else if (versionStr != 0) {
7695
                                if(isIE && isWin && !isOpera) {
7696
                                    tempArray         = versionStr.split(" ");
7697
                                    tempString        = tempArray[1];
7698
                                    versionArray      = tempString .split(",");
7699
                                } else {
7700
                                    versionArray      = versionStr.split(".");
7701
                                }
7702
                                versionMajor      = versionArray[0];
7703
                                versionMinor      = versionArray[1];
7704
                                versionRevision   = versionArray[2];
7705
7706
                                versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
7707
                                versionNum        = parseFloat(versionString);
7708
                                // is the major.revision >= requested major.revision AND the minor version >= requested minor
7709
                                if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
7710
                                    return true;
7711
                                } else {
7712
                                    return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
7713
                                }
7714
                            }
7715
                        }
7716
                    }
7717
                    // -->
7718
                    </script>';
7719
            $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
7720
                    <script>
7721
                        // Version check based upon the values entered above in "Globals"
7722
                        var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
7723
7724
                        // Check to see if the version meets the requirements for playback
7725
                        if (hasReqestedVersion) {  // if we\'ve detected an acceptable version
7726
                            var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
7727
                                        + \'<param name="wmode" value="transparent">\'
7728
                                        + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
7729
                                        + \'<\/object>\';
7730
                            document.write(oeTags);   // embed the Flash Content SWF when all tests are passed
7731
                        } else {  // flash is too old or we can\'t detect the plugin
7732
                            var alternateContent = "Error<br \/>"
7733
                                + "Hotspots requires Macromedia Flash 7.<br \/>"
7734
                                + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
7735
                            document.write(alternateContent);  // insert non-flash content
7736
                        }
7737
                    </script>
7738
                    </td>
7739
                    <td valign="top" align="left">'.$answer_list.'</td></tr>
7740
                    </table>
7741
            </td></tr>';
7742
            $html .= $s;
7743
            $html .= '</table>';
7744
            return $html;
7745
        }
7746
        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...
7747
    }
7748
7749
    /**
7750
     * @param int $exeId
7751
     * @return array
7752
     */
7753
    public function returnQuestionListByAttempt($exeId)
7754
    {
7755
        return $this->displayQuestionListByAttempt($exeId, false, true);
7756
    }
7757
7758
    /**
7759
     * Display the exercise results
7760
     * @param int  $exe_id
7761
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7762
     * @param bool $returnExerciseResult return array with exercise result info
7763
     * @return mixed
7764
     */
7765
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7766
    {
7767
        global $origin, $debug;
7768
7769
        //Getting attempt info
7770
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7771
7772
        //Getting question list
7773
        $question_list = array();
7774
        if (!empty($exercise_stat_info['data_tracking'])) {
7775
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7776
        } else {
7777
            //Try getting the question list only if save result is off
7778
            if ($saveUserResult == false) {
7779
                $question_list = $this->selectQuestionList();
7780
            }
7781
            error_log("Data tracking is empty! exe_id: $exe_id");
7782
        }
7783
7784
        $counter = 1;
7785
        $total_score = 0;
7786
        $total_weight = 0;
7787
7788
        $exercise_content = null;
7789
7790
        // Hide results
7791
        $show_results = false;
7792
        $show_only_score = false;
7793
7794
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7795
            $show_results = true;
7796
        }
7797
7798
        $showScoreOptions = [
7799
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7800
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7801
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7802
        ];
7803
7804
        if (in_array($this->results_disabled, $showScoreOptions)) {
7805
            $show_only_score = true;
7806
        }
7807
7808 View Code Duplication
        if ($show_results || $show_only_score) {
7809
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7810
            // Shows exercise header.
7811
            echo $this->show_exercise_result_header(
7812
                $user_info['complete_name'],
7813
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7814
                $exercise_stat_info['duration']
7815
            );
7816
        }
7817
7818
        // Display text when test is finished #4074 and for LP #4227
7819
        $end_of_message = $this->selectTextWhenFinished();
7820
        if (!empty($end_of_message)) {
7821
            Display::display_normal_message($end_of_message, false);
7822
            echo "<div class='clear'>&nbsp;</div>";
7823
        }
7824
7825
        $question_list_answers = array();
7826
        $media_list = array();
7827
        $category_list = array();
7828
        $tempParentId = null;
7829
        $mediaCounter = 0;
7830
7831
        $exerciseResultInfo = array();
7832
7833
        // Loop over all question to show results for each of them, one by one
7834
        if (!empty($question_list)) {
7835
            if ($debug) {
7836
                error_log('Looping question_list '.print_r($question_list, 1));
7837
            }
7838
7839
            foreach ($question_list as $questionId) {
7840
7841
                // Creates a temporary Question object
7842
                $objQuestionTmp = Question::read($questionId);
7843
7844
                // This variable commes from exercise_submit_modal.php
7845
                ob_start();
7846
                $hotspot_delineation_result = null;
7847
7848
                // We're inside *one* question. Go through each possible answer for this question
7849
                $result = $this->manageAnswers(
7850
                    $exercise_stat_info['exe_id'],
7851
                    $questionId,
7852
                    null,
7853
                    'exercise_result',
7854
                    array(),
7855
                    $saveUserResult,
7856
                    true,
7857
                    $show_results,
7858
                    $hotspot_delineation_result
7859
                );
7860
7861
                if (empty($result)) {
7862
                    continue;
7863
                }
7864
7865
                $total_score += $result['score'];
7866
                $total_weight += $result['weight'];
7867
7868
                $question_list_answers[] = array(
7869
                    'question' => $result['open_question'],
7870
                    'answer' => $result['open_answer'],
7871
                    'answer_type' => $result['answer_type']
7872
                );
7873
7874
                $my_total_score = $result['score'];
7875
                $my_total_weight = $result['weight'];
7876
7877
                // Category report
7878
                $category_was_added_for_this_test = false;
7879
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7880
7881
                $category_list = array();
7882
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7883
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7884
                        if (!isset($category_list[$category_id])) {
7885
                            $category_list[$category_id] = array();
7886
                            $category_list[$category_id]['score'] = 0;
7887
                            $category_list[$category_id]['total'] = 0;
7888
                        }
7889
                        $category_list[$category_id]['score'] += $my_total_score;
7890
                        $category_list[$category_id]['total'] += $my_total_weight;
7891
                        $category_was_added_for_this_test = true;
7892
                    }
7893
                }
7894
7895
                // No category for this question!
7896
                if ($category_was_added_for_this_test == false) {
7897
                    if (!isset($category_list['none'])) {
7898
                        $category_list['none'] = array();
7899
                        $category_list['none']['score'] = 0;
7900
                        $category_list['none']['total'] = 0;
7901
                    }
7902
7903
                    $category_list['none']['score'] += $my_total_score;
7904
                    $category_list['none']['total'] += $my_total_weight;
7905
                }
7906
7907
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7908
                    $my_total_score = 0;
7909
                }
7910
7911
                $comnt = null;
7912 View Code Duplication
                if ($show_results) {
7913
                    $comnt = get_comments($exe_id, $questionId);
7914
                    if (!empty($comnt)) {
7915
                        echo '<b>'.get_lang('Feedback').'</b>';
7916
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7917
                    }
7918
                }
7919
7920
                $score = array();
7921
                $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
7922
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
7923
                $score['score'] = $my_total_score;
7924
                $score['weight'] = $my_total_weight;
7925
                $score['comments'] = $comnt;
7926
7927
                $exerciseResultInfo[$questionId]['score'] = $score;
7928
                $exerciseResultInfo[$questionId]['details'] = $result;
7929
7930
                // If no results we hide the results
7931
                if ($show_results == false) {
7932
                    $score = array();
7933
                }
7934
                $contents = ob_get_clean();
7935
7936
                $question_content = '<div class="question_row">';
7937
7938
                if ($show_results) {
7939
7940
                    $show_media = false;
7941
                    $counterToShow = $counter;
7942
                    if ($objQuestionTmp->parent_id != 0) {
7943
7944
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
7945
                            $media_list[] = $objQuestionTmp->parent_id;
7946
                            $show_media = true;
7947
                        }
7948
                        if ($tempParentId == $objQuestionTmp->parent_id) {
7949
                            $mediaCounter++;
7950
                        } else {
7951
                            $mediaCounter = 0;
7952
                        }
7953
                        $counterToShow = chr(97 + $mediaCounter);
7954
                        $tempParentId = $objQuestionTmp->parent_id;
7955
                    }
7956
7957
                    // Shows question title an description.
7958
                    $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
7959
7960
                    // display question category, if any
7961
                    $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...
7962
                }
7963
                $counter++;
7964
7965
                $question_content .= $contents;
7966
                $question_content .= '</div>';
7967
7968
                $exercise_content .= $question_content;
7969
            } // end foreach() block that loops over all questions
7970
        }
7971
7972
        $total_score_text = null;
7973
7974
        if ($returnExerciseResult) {
7975
            return $exerciseResultInfo;
7976
        }
7977
7978
        if ($origin != 'learnpath') {
7979
            if ($show_results || $show_only_score) {
7980
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
7981
            }
7982
        }
7983
7984 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
7985
            //Adding total
7986
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
7987
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
7988
        }
7989
7990
        echo $total_score_text;
7991
        echo $exercise_content;
7992
7993
        if (!$show_only_score) {
7994
            echo $total_score_text;
7995
        }
7996
7997
        if ($saveUserResult) {
7998
7999
            // Tracking of results
8000
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
8001
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
8002
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
8003
8004 View Code Duplication
            if (api_is_allowed_to_session_edit()) {
8005
                update_event_exercise(
8006
                    $exercise_stat_info['exe_id'],
8007
                    $this->selectId(),
8008
                    $total_score,
8009
                    $total_weight,
8010
                    api_get_session_id(),
8011
                    $learnpath_id,
8012
                    $learnpath_item_id,
8013
                    $learnpath_item_view_id,
8014
                    $exercise_stat_info['exe_duration'],
8015
                    '',
8016
                    array()
8017
                );
8018
            }
8019
8020
            // Send notification.
8021
            if (!api_is_allowed_to_edit(null, true)) {
8022
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
8023
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
8024
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
8025
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
8026
            }
8027
        }
8028
    }
8029
8030
    /**
8031
     * Returns an HTML ribbon to show on top of the exercise result, with
8032
     * colouring depending on the success or failure of the student
8033
     * @param $score
8034
     * @param $weight
8035
     * @param bool $check_pass_percentage
8036
     * @return string
8037
     */
8038
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
8039
    {
8040
        $eventMessage = null;
8041
        $ribbon = '<div class="question_row">';
8042
        $ribbon .= '<div class="ribbon">';
8043
        if ($check_pass_percentage) {
8044
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
8045
            // Color the final test score if pass_percentage activated
8046
            $ribbon_total_success_or_error = "";
8047
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
8048
                if ($is_success) {
8049
                    $eventMessage = $this->getOnSuccessMessage();
8050
                    $ribbon_total_success_or_error = ' ribbon-total-success';
8051
                } else {
8052
                    $eventMessage = $this->getOnFailedMessage();
8053
                    $ribbon_total_success_or_error = ' ribbon-total-error';
8054
                }
8055
            }
8056
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
8057
        } else {
8058
            $ribbon .= '<div class="rib rib-total">';
8059
        }
8060
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
8061
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
8062
        $ribbon .= '</h3>';
8063
        $ribbon .= '</div>';
8064
8065
        if ($check_pass_percentage) {
8066
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
8067
        }
8068
        $ribbon .= '</div>';
8069
        $ribbon .= '</div>';
8070
8071
        $ribbon .= $eventMessage;
8072
8073
        return $ribbon;
8074
    }
8075
8076
    /**
8077
     * Returns an array of categories details for the questions of the current
8078
     * exercise.
8079
     * @return array
8080
     */
8081
    public function getQuestionWithCategories()
8082
    {
8083
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8084
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8085
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8086
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8087
        $sql = "SELECT DISTINCT cat.*
8088
                FROM $TBL_EXERCICE_QUESTION e
8089
                INNER JOIN $TBL_QUESTIONS q
8090
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8091
                INNER JOIN $categoryRelTable catRel
8092
                ON (catRel.question_id = e.question_id)
8093
                INNER JOIN $categoryTable cat
8094
                ON (cat.id = catRel.category_id)
8095
                WHERE
8096
                  e.c_id = {$this->course_id} AND
8097
                  e.exercice_id	= ".intval($this->id);
8098
8099
        $result = Database::query($sql);
8100
        $categoriesInExercise = array();
8101
        if (Database::num_rows($result)) {
8102
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8103
        }
8104
8105
        return $categoriesInExercise;
8106
    }
8107
8108
    /**
8109
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
8110
     */
8111
    public function get_max_score()
8112
    {
8113
        $out_max_score = 0;
8114
        // list of question's id !!! the array key start at 1 !!!
8115
        $questionList = $this->selectQuestionList(true);
8116
8117
        // test is randomQuestions - see field random of test
8118
        if ($this->random > 0 && $this->randomByCat == 0) {
8119
            $numberRandomQuestions = $this->random;
8120
            $questionScoreList = array();
8121
            for ($i = 1; $i <= count($questionList); $i++) {
8122
                $tmpobj_question = Question::read($questionList[$i]);
8123
                $questionScoreList[] = $tmpobj_question->weighting;
8124
            }
8125
            rsort($questionScoreList);
8126
            // add the first $numberRandomQuestions value of score array to get max_score
8127
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8128
                $out_max_score += $questionScoreList[$i];
8129
            }
8130
        } else if ($this->random > 0 && $this->randomByCat > 0) {
8131
            // test is random by category
8132
            // get the $numberRandomQuestions best score question of each category
8133
8134
            $numberRandomQuestions = $this->random;
8135
            $tab_categories_scores = array();
8136
            for ($i = 1; $i <= count($questionList); $i++) {
8137
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
8138
                if (!is_array($tab_categories_scores[$question_category_id])) {
8139
                    $tab_categories_scores[$question_category_id] = array();
8140
                }
8141
                $tmpobj_question = Question::read($questionList[$i]);
8142
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8143
            }
8144
8145
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8146
            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...
8147
                rsort($tab_scores);
8148
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8149
                    $out_max_score += $tab_scores[$i];
8150
                }
8151
            }
8152
        } else {
8153
            // standard test, just add each question score
8154
            foreach ($questionList as $questionId) {
8155
                $question = Question::read($questionId, $this->course_id);
8156
                $out_max_score += $question->weighting;
8157
            }
8158
        }
8159
8160
        return $out_max_score;
8161
    }
8162
8163
    /**
8164
    * @return string
8165
    */
8166
    public function get_formated_title()
8167
    {
8168
        return api_html_entity_decode($this->selectTitle());
8169
    }
8170
8171
    /**
8172
     * @param $in_title
8173
     * @return string
8174
     */
8175
    public static function get_formated_title_variable($in_title)
8176
    {
8177
        return api_html_entity_decode($in_title);
8178
    }
8179
8180
    /**
8181
     * @return string
8182
     */
8183
    public function format_title()
8184
    {
8185
        return api_htmlentities($this->title);
8186
    }
8187
8188
    /**
8189
     * @param $in_title
8190
     * @return string
8191
     */
8192
    public static function format_title_variable($in_title)
8193
    {
8194
        return api_htmlentities($in_title);
8195
    }
8196
8197
    /**
8198
     * @param int $courseId
8199
     * @param int $sessionId
8200
     * @return array exercises
8201
     */
8202 View Code Duplication
    public function getExercisesByCouseSession($courseId, $sessionId)
8203
    {
8204
        $courseId = intval($courseId);
8205
        $sessionId = intval($sessionId);
8206
8207
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8208
        $sql = "SELECT * FROM $tbl_quiz cq
8209
                WHERE
8210
                    cq.c_id = %s AND
8211
                    (cq.session_id = %s OR cq.session_id = 0) AND
8212
                    cq.active = 0
8213
                ORDER BY cq.id";
8214
        $sql = sprintf($sql, $courseId, $sessionId);
8215
8216
        $result = Database::query($sql);
8217
8218
        $rows = array();
8219
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8220
            $rows[] = $row;
8221
        }
8222
8223
        return $rows;
8224
    }
8225
8226
    /**
8227
     *
8228
     * @param int $courseId
8229
     * @param int $sessionId
8230
     * @param array $quizId
8231
     * @return array exercises
8232
     */
8233
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
8234
    {
8235
        if (empty($quizId)) {
8236
            return array();
8237
        }
8238
8239
        $sessionId = intval($sessionId);
8240
8241
        $ids = is_array($quizId) ? $quizId : array($quizId);
8242
        $ids = array_map('intval', $ids);
8243
        $ids = implode(',', $ids);
8244
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8245
        if ($sessionId != 0) {
8246
            $sql = "SELECT * FROM $track_exercises te
8247
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8248
              WHERE
8249
              te.id = %s AND
8250
              te.session_id = %s AND
8251
              cq.id IN (%s)
8252
              ORDER BY cq.id";
8253
8254
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8255
        } else {
8256
            $sql = "SELECT * FROM $track_exercises te
8257
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8258
              WHERE
8259
              te.id = %s AND
8260
              cq.id IN (%s)
8261
              ORDER BY cq.id";
8262
            $sql = sprintf($sql, $courseId, $ids);
8263
        }
8264
        $result = Database::query($sql);
8265
        $rows = array();
8266
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8267
            $rows[] = $row;
8268
        }
8269
8270
        return $rows;
8271
    }
8272
8273
    /**
8274
     * @param $exeId
8275
     * @param $exercise_stat_info
8276
     * @param $remindList
8277
     * @param $currentQuestion
8278
     * @return int|null
8279
     */
8280
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
8281
    {
8282
        $result = get_exercise_results_by_attempt($exeId, 'incomplete');
8283
8284
        if (isset($result[$exeId])) {
8285
            $result = $result[$exeId];
8286
        } else {
8287
            return null;
8288
        }
8289
8290
        $data_tracking  = $exercise_stat_info['data_tracking'];
8291
        $data_tracking  = explode(',', $data_tracking);
8292
8293
        // if this is the final question do nothing.
8294
        if ($currentQuestion == count($data_tracking)) {
8295
            return null;
8296
        }
8297
8298
        $currentQuestion = $currentQuestion - 1;
8299
8300
        if (!empty($result['question_list'])) {
8301
            $answeredQuestions = array();
8302
8303
            foreach ($result['question_list'] as $question) {
8304
                if (!empty($question['answer'])) {
8305
                    $answeredQuestions[] = $question['question_id'];
8306
                }
8307
            }
8308
8309
            // Checking answered questions
8310
8311
            $counterAnsweredQuestions = 0;
8312
            foreach ($data_tracking as $questionId) {
8313
                if (!in_array($questionId, $answeredQuestions)) {
8314
                    if ($currentQuestion != $counterAnsweredQuestions) {
8315
                        break;
8316
                    }
8317
                }
8318
                $counterAnsweredQuestions++;
8319
            }
8320
8321
            $counterRemindListQuestions = 0;
8322
            // Checking questions saved in the reminder list
8323
8324
            if (!empty($remindList)) {
8325
                foreach ($data_tracking as $questionId) {
8326
                    if (in_array($questionId, $remindList)) {
8327
                        // Skip the current question
8328
                        if ($currentQuestion != $counterRemindListQuestions) {
8329
                            break;
8330
                        }
8331
                    }
8332
                    $counterRemindListQuestions++;
8333
                }
8334
8335
                if ($counterRemindListQuestions < $currentQuestion) {
8336
                    return null;
8337
                }
8338
8339
                if (!empty($counterRemindListQuestions)) {
8340
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8341
                        return $counterAnsweredQuestions;
8342
                    } else {
8343
                        return $counterRemindListQuestions;
8344
                    }
8345
                }
8346
            }
8347
8348
            return $counterAnsweredQuestions;
8349
        }
8350
    }
8351
8352
    /**
8353
     * Gets the position of a questionId in the question list
8354
     * @param $questionId
8355
     * @return int
8356
     */
8357
    public function getPositionInCompressedQuestionList($questionId)
8358
    {
8359
        $questionList = $this->getQuestionListWithMediasCompressed();
8360
        $mediaQuestions = $this->getMediaList();
8361
        $position = 1;
8362
        foreach ($questionList as $id) {
8363
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8364
                $mediaQuestionList = $mediaQuestions[$id];
8365
                if (in_array($questionId, $mediaQuestionList)) {
8366
                    return $position;
8367
                } else {
8368
                    $position++;
8369
                }
8370
            } else {
8371
                if ($id == $questionId) {
8372
                    return $position;
8373
                } else {
8374
                    $position++;
8375
                }
8376
            }
8377
        }
8378
        return 1;
8379
    }
8380
}
8381